Skip to content

Commit

Permalink
feat(cli): 自动install组件包,支持pnpm,npm,yarn
Browse files Browse the repository at this point in the history
  • Loading branch information
roymondchen authored and jia000 committed Aug 17, 2022
1 parent 2cde4bb commit c19afda
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 19 deletions.
8 changes: 7 additions & 1 deletion packages/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ export interface EntryFile {
componentFileAffix: string;
}

export interface NpmConfig {
registry?: string;
client?: 'npm' | 'yarn' | 'pnpm';
}

export interface UserConfig {
source: string;
temp: string;
packages: Record<string, any>;
packages: (string | Record<string, string>)[];
componentFileAffix: string;
cleanTemp: boolean;
npmConfig?: NpmConfig;
}
117 changes: 99 additions & 18 deletions packages/cli/src/utils/resolveAppPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { execSync } from 'child_process';
import path from 'path';
import { exit } from 'process';

import chalk from 'chalk';
import fs from 'fs-extra';
import * as recast from 'recast';

import type App from '../Core';
import { Entry, EntryType, PackageType } from '../types';
import { Entry, EntryType, NpmConfig, PackageType } from '../types';

interface TypeAssertion {
type: string;
Expand All @@ -26,10 +27,16 @@ export const resolveAppPackages = (app: App) => {
const valueMap: Record<string, string> = {};
const pluginMap: Record<string, string> = {};

Object.entries(app.options.packages).forEach(([key, packagePath]) => {
installPackage(packagePath, app.options.source);
const dependencies: Record<string, string> = {};

const indexPath = require.resolve(packagePath);
const setPackages = (cwd: string, packagePath: string, key?: string) => {
const { name: moduleName } = splitNameVersion(packagePath);

if (!moduleName) throw Error('packages中包含非法配置');

const indexPath = execSync(`node -e "console.log(require.resolve('${moduleName}'))"`, { cwd })
.toString()
.replace('\n', '');
const indexCode = fs.readFileSync(indexPath, { encoding: 'utf-8', flag: 'r' });
const ast = recast.parse(indexCode, { parser: require('recast/parsers/typescript') });
const result = typeAssertion({ ast, indexPath });
Expand All @@ -41,12 +48,12 @@ export const resolveAppPackages = (app: App) => {
if (entry.value) valueMap[key] = entry.value;
};

if (result.type === PackageType.COMPONENT) {
if (result.type === PackageType.COMPONENT && key) {
// 组件
setItem(key, parseEntry({ ast, package: packagePath, indexPath }));
} else if (result.type === PackageType.PLUGIN) {
setItem(key, parseEntry({ ast, package: moduleName, indexPath }));
} else if (result.type === PackageType.PLUGIN && key) {
// 插件
pluginMap[key] = packagePath;
pluginMap[key] = moduleName;
} else if (result.type === PackageType.COMPONENT_PACKAGE) {
// 组件&插件包
result.imports.forEach((i) => {
Expand All @@ -66,6 +73,52 @@ export const resolveAppPackages = (app: App) => {
}
});
}
};

const getDependencies = (packagePath: string) => {
if (fs.existsSync(packagePath)) return;
const { name: moduleName, version } = splitNameVersion(packagePath);
if (!moduleName) return;
dependencies[moduleName] = version;
};

app.options.packages.forEach((item) => {
if (typeof item === 'object') {
Object.entries(item).forEach(([, packagePath]) => {
getDependencies(packagePath);
});
} else {
getDependencies(item);
}
});

if (Object.keys(dependencies).length) {
const packageFile = path.join(app.options.source, 'package.json');
const packageBakFile = path.join(app.options.source, 'package.json.bak');
if (fs.existsSync(packageFile)) {
fs.copyFileSync(packageFile, packageBakFile);
}

try {
npmInstall(dependencies, app.options.source, app.options.npmConfig);
} catch (e) {
console.error(e);
}

if (fs.existsSync(packageBakFile)) {
fs.unlinkSync(packageFile);
fs.renameSync(packageBakFile, packageFile);
}
}

app.options.packages.forEach((item) => {
if (typeof item === 'object') {
Object.entries(item).forEach(([key, packagePath]) => {
setPackages(app.options.source, packagePath, key);
});
} else {
setPackages(app.options.source, item);
}
});

return {
Expand All @@ -77,16 +130,27 @@ export const resolveAppPackages = (app: App) => {
};
};

const installPackage = function (module: string, cwd: string) {
try {
// window下需要将路径中\转换成/
execSync(`node -e "require.resolve('${module.replace(/\\/g, '/')}')"`, { stdio: 'ignore' });
} catch (e) {
execSync(`npm install ${module}`, {
stdio: 'inherit',
cwd,
});
}
const npmInstall = function (dependencies: Record<string, string>, cwd: string, npmConfig: NpmConfig = {}) {
const { client = 'npm', registry = 'https://registry.npmjs.org/' } = npmConfig;
const install = {
npm: 'install',
yarn: 'add',
pnpm: 'add',
}[client];

const packages = Object.entries(dependencies)
.map(([name, version]) => `${name}@${version}`)
.join(' ');

const command = `${client} ${install} ${packages} --registry ${registry}`;

console.log(chalk.blue(cwd));
console.log(chalk.blue(command));

execSync(command, {
stdio: 'inherit',
cwd,
});
};

/**
Expand Down Expand Up @@ -362,3 +426,20 @@ const getASTTokenByTraverse = ({ ast, indexPath }: { ast: any; indexPath: string
exportDefaultToken,
};
};

const splitNameVersion = function (str: string) {
if (typeof str !== 'string') {
return {};
}
const packStr = String.prototype.trim.call(str);
const ret = packStr.match(/((^|@).+)@(.+)/);
let name = packStr;
let version = 'latest';
if (ret && ret[3] !== '') {
({ 1: name, 3: version } = ret);
}
return {
name,
version,
};
};

0 comments on commit c19afda

Please sign in to comment.