UNPKG

@feflow/cli

Version:
259 lines (239 loc) 8.21 kB
import path from 'path'; import glob from 'glob'; import Report from '@feflow/report'; import commandLineUsage from 'command-line-usage'; import minimist from 'minimist'; import Commander from './commander'; import Hook from './hook'; import createLogger, { Logger } from './logger'; import CommandPicker, { LOAD_ALL, LOAD_DEVKIT, LOAD_PLUGIN, LOAD_UNIVERSAL_PLUGIN } from './command-picker'; import { checkUpdate } from './resident'; import loadPlugins from './plugin/load-plugins'; import loadUniversalPlugin from './plugin/load-universal-plugin'; import loadDevkits from './devkit/load-devkits'; import getCommandLine from './devkit/command-options'; import Binp from './universal-pkg/binp'; import { UniversalPkg } from './universal-pkg/dep/pkg'; import { FEFLOW_BIN, FEFLOW_HOME, FEFLOW_LIB, HOOK_TYPE_ON_COMMAND_REGISTERED, LOG_FILE, UNIVERSAL_MODULES, UNIVERSAL_PKG_JSON, } from '../shared/constant'; import { parseYaml, safeDump } from '../shared/yaml'; import { FefError } from '../shared/fef-error'; import { setServerUrl } from '../shared/git'; import { mkdirAsync, readFileAsync, statAsync, unlinkAsync, writeFileAsync } from '../shared/fs'; import { isInstalledPM } from '../shared/npm'; import { FeflowConfig, isValidConfig } from '../shared/type-predicates'; import pkgJson from '../../package.json'; export default class Feflow { public args: minimist.ParsedArgs; public root: string; public rootPkg: string; public configPath: string; public bin: string; public lib: string; public loggerPath: string; public universalPkgPath: string; public universalModules: string; public version: string; public logger: Logger; public commander: Commander; public hook: Hook; public config?: Partial<FeflowConfig>; public universalPkg: UniversalPkg; public reporter: Report; public commandPick: CommandPicker | null; public fefError: FefError; public cmd?: string; public projectPath?: string; public projectConfig?: object; public pkgConfig?: { name: string; }; constructor(args: minimist.ParsedArgs) { this.root = FEFLOW_HOME; this.configPath = path.join(FEFLOW_HOME, '.feflowrc.yml'); this.bin = path.join(FEFLOW_HOME, FEFLOW_BIN); this.lib = path.join(FEFLOW_HOME, FEFLOW_LIB); this.rootPkg = path.join(FEFLOW_HOME, 'package.json'); this.loggerPath = path.join(FEFLOW_HOME, LOG_FILE); this.universalPkgPath = path.join(FEFLOW_HOME, UNIVERSAL_PKG_JSON); this.universalModules = path.join(FEFLOW_HOME, UNIVERSAL_MODULES); this.args = args; this.version = pkgJson.version; const config = parseYaml(this.configPath); isValidConfig(config) && (this.config = config) && setServerUrl(config.serverUrl); this.hook = new Hook(); this.commander = new Commander((cmdName: string) => { this.hook.emit(HOOK_TYPE_ON_COMMAND_REGISTERED, cmdName); }); this.logger = createLogger({ name: 'feflow-cli', debug: Boolean(args.debug), silent: Boolean(args.silent), }); this.reporter = new Report(this); this.universalPkg = new UniversalPkg(this.universalPkgPath); this.commandPick = null; this.fefError = new FefError(this); } async init(cmdName?: string) { this.reporter.init(cmdName); await Promise.all([this.initClient(), this.initPackageManager(), this.initBinPath()]); const disableCheck = Boolean(this.args['disable-check']) || Boolean(this.config?.disableCheck); if (!disableCheck) { // 使用 try catch 避免检查更新出错中断整体流程 try { await checkUpdate(this); } catch (error) { this.logger.error('check update error: ', error); } } this.commandPick = new CommandPicker(this, cmdName); if (this.commandPick.isAvailable()) { // should hit the cache in most cases this.logger.debug('find cmd in cache'); await this.commandPick.pickCommand(); await this.loadCommands(LOAD_DEVKIT); } else { // if not, load plugin/devkit/native in need this.logger.debug('not find cmd in cache'); await this.loadCommands(LOAD_ALL); // make sure the command has at least one function, otherwise replace to help command await this.commandPick.checkCommand(); } } async initClient() { const { rootPkg } = this; let pkgInfo = null; try { await statAsync(rootPkg); pkgInfo = await readFileAsync(rootPkg); } catch (e) {} if (!pkgInfo) { await writeFileAsync( rootPkg, JSON.stringify( { name: 'feflow-home', version: '0.0.0', private: true, }, null, 2, ), ); } } async initBinPath() { const { bin } = this; try { const stats = await statAsync(bin); if (!stats.isDirectory()) { await unlinkAsync(bin); } } catch (e) { await mkdirAsync(bin); } new Binp().register(bin); } initPackageManager() { const { root, logger } = this; return new Promise<void>((resolve, reject) => { if (!this.config?.packageManager) { const packageManagers = ['npm', 'tnpm', 'yarn', 'cnpm']; const defaultPackageManager = packageManagers.find(packageManager => isInstalledPM(packageManager)); if (!defaultPackageManager) { // 无包管理器直接结束 logger.error('You must installed a package manager'); return reject(); } const configPath = path.join(root, '.feflowrc.yml'); const config = Object.assign({}, parseYaml(configPath), { packageManager: defaultPackageManager, }); safeDump(config, configPath); this.config = config; } else { logger.debug('Use packageManager is: ', this.config.packageManager); } resolve(); }); } loadNative() { const nativePath = path.join(__dirname, './native/*.js'); glob.sync(nativePath).forEach((file: string) => { (async () => { const nativeCommandEntry = await import(file); nativeCommandEntry.default(this); })(); }); } async loadCommands(orderType: number) { this.logger.debug('load order: ', orderType); if ((orderType & LOAD_ALL) === LOAD_ALL) { this.loadNative(); loadUniversalPlugin(this); await loadPlugins(this); loadDevkits(this); return; } if ((orderType & LOAD_PLUGIN) === LOAD_PLUGIN) { await loadPlugins(this); } if ((orderType & LOAD_UNIVERSAL_PLUGIN) === LOAD_UNIVERSAL_PLUGIN) { loadUniversalPlugin(this); } if ((orderType & LOAD_DEVKIT) === LOAD_DEVKIT) { loadDevkits(this); } } async loadInternalPlugins() { const devToolPlugin = '@feflow/feflow-plugin-devtool'; try { this.logger.debug('Plugin loaded: %s', devToolPlugin); const devToolPluginEntry = await import(devToolPlugin); return devToolPluginEntry.default(this); } catch (err) { this.fefError.printError({ error: err, msg: 'internal plugin load failed: %s', }); } } async invoke(cmdName: string | undefined, ctx: Feflow) { if (this.args.help && cmdName && cmdName !== 'help') { await this.showCommandOptionDescription(cmdName, ctx); } const cmd = this.commander.get(cmdName); if (cmd) { this.logger.name = cmd.pluginName; await cmd.runFn.call(this, ctx); } else { this.logger.debug(`Command ' ${cmdName} ' has not been registered yet!`); } } async showCommandOptionDescription(cmdName: string, ctx: Feflow) { const registeredCommand = ctx.commander.get(cmdName); let commandLine: object[] = []; if (registeredCommand?.options) { commandLine = getCommandLine(registeredCommand.options, registeredCommand.desc, cmdName); } // 有副作用,暂无好方法改造 if (cmdName === 'help' && registeredCommand) { return registeredCommand.runFn.call(this, ctx); } if (commandLine.length === 0) { return; } const sections = []; sections.push(...commandLine); const usage = commandLineUsage(sections); console.info(usage); } }