UNPKG

7.84 kBPlain TextView Raw
1import path from 'path';
2import glob from 'glob';
3import Report from '@feflow/report';
4import commandLineUsage from 'command-line-usage';
5
6import Commander from './commander';
7import Hook from './hook';
8import Binp from './universal-pkg/binp';
9import logger from './logger';
10import loadPlugins from './plugin/loadPlugins';
11import loadUniversalPlugin from './plugin/loadUniversalPlugin';
12import loadDevkits from './devkit/loadDevkits';
13import getCommandLine from './devkit/commandOptions';
14import {
15 FEFLOW_HOME,
16 FEFLOW_BIN,
17 FEFLOW_LIB,
18 UNIVERSAL_PKG_JSON,
19 UNIVERSAL_MODULES,
20 HOOK_TYPE_ON_COMMAND_REGISTERED,
21 LOG_FILE
22} from '../shared/constant';
23import { safeDump, parseYaml } from '../shared/yaml';
24import { FefError } from '../shared/fefError';
25import { setServerUrl } from '../shared/git';
26import { UniversalPkg } from './universal-pkg/dep/pkg';
27import CommandPicker, {
28 LOAD_UNIVERSAL_PLUGIN,
29 LOAD_PLUGIN,
30 LOAD_DEVKIT,
31 LOAD_ALL
32} from './command-picker';
33import { checkUpdate } from './resident';
34import {
35 mkdirAsync,
36 statAsync,
37 unlinkAsync,
38 writeFileAsync,
39 readFileAsync
40} from '../shared/fs';
41import { isInstalledPM } from '../shared/npm';
42
43const pkg = require('../../package.json');
44
45export default class Feflow {
46 public args: any;
47 public cmd: any;
48 public projectConfig: any;
49 public projectPath: any;
50 public version: string;
51 public logger: any;
52 public loggerPath: any;
53 public commander: any;
54 public hook: any;
55 public root: any;
56 public rootPkg: any;
57 public universalPkgPath: string;
58 public universalModules: string;
59 public config: any;
60 public configPath: any;
61 public bin: string;
62 public lib: string;
63 public universalPkg: UniversalPkg;
64 public reporter: any;
65 public commandPick: CommandPicker | null;
66 public fefError: FefError;
67
68 constructor(args: any) {
69 args = args || {};
70 const configPath = path.join(FEFLOW_HOME, '.feflowrc.yml');
71 this.root = FEFLOW_HOME;
72 this.bin = path.join(FEFLOW_HOME, FEFLOW_BIN);
73 this.lib = path.join(FEFLOW_HOME, FEFLOW_LIB);
74 this.rootPkg = path.join(FEFLOW_HOME, 'package.json');
75 this.loggerPath = path.join(FEFLOW_HOME, LOG_FILE);
76 this.universalPkgPath = path.join(FEFLOW_HOME, UNIVERSAL_PKG_JSON);
77 this.universalModules = path.join(FEFLOW_HOME, UNIVERSAL_MODULES);
78 this.args = args;
79 this.version = pkg.version;
80 this.config = parseYaml(configPath);
81 setServerUrl(this.config?.serverUrl);
82 this.configPath = configPath;
83 this.hook = new Hook();
84 this.commander = new Commander((cmdName: string) => {
85 this.hook.emit(HOOK_TYPE_ON_COMMAND_REGISTERED, cmdName);
86 });
87 this.logger = logger({
88 debug: Boolean(args.debug),
89 silent: Boolean(args.silent)
90 });
91 this.reporter = new Report(this);
92 this.universalPkg = new UniversalPkg(this.universalPkgPath);
93 this.commandPick = null;
94 this.fefError = new FefError(this);
95 }
96
97 async init(cmd: string | undefined) {
98 this.reporter.init(cmd);
99
100 await Promise.all([
101 this.initClient(),
102 this.initPackageManager(),
103 this.initBinPath()
104 ]);
105
106 const disableCheck =
107 this.args['disable-check'] || this.config?.disableCheck;
108 if (!disableCheck) {
109 checkUpdate(this);
110 }
111
112 this.commandPick = new CommandPicker(this, cmd);
113
114 if (this.commandPick.isAvailable()) {
115 // should hit the cache in most cases
116 this.logger.debug('find cmd in cache');
117 this.commandPick.pickCommand();
118 await this.loadCommands(LOAD_DEVKIT);
119 } else {
120 // if not, load plugin/devkit/native in need
121 this.logger.debug('not find cmd in cache');
122 await this.loadCommands(this.commandPick.getLoadOrder());
123 // make sure the command has at least one funtion, otherwise replace to help command
124 this.commandPick.checkCommand();
125 }
126 }
127
128 async initClient() {
129 const { rootPkg } = this;
130 let pkgInfo = null;
131 try {
132 await statAsync(rootPkg);
133 pkgInfo = await readFileAsync(rootPkg);
134 } catch (e) {}
135 if (!pkgInfo) {
136 await writeFileAsync(
137 rootPkg,
138 JSON.stringify(
139 {
140 name: 'feflow-home',
141 version: '0.0.0',
142 private: true
143 },
144 null,
145 2
146 )
147 );
148 }
149 }
150
151 async initBinPath() {
152 const { bin } = this;
153 try {
154 const stats = await statAsync(bin);
155 if (!stats.isDirectory()) {
156 await unlinkAsync(bin);
157 }
158 } catch (e) {
159 await mkdirAsync(bin);
160 }
161 new Binp().register(bin);
162 }
163
164 initPackageManager() {
165 const { root, logger } = this;
166 return new Promise<any>((resolve, reject) => {
167 if (!this.config?.packageManager) {
168 const packageManagers = ['npm', 'tnpm', 'yarn', 'cnpm'];
169 const defaultPackageManager = packageManagers.find(packageManager =>
170 isInstalledPM(packageManager)
171 );
172 if (!defaultPackageManager) {
173 // 无包管理器直接结束
174 logger.error('You must installed a package manager');
175 return;
176 } else {
177 const configPath = path.join(root, '.feflowrc.yml');
178 safeDump(
179 Object.assign({}, parseYaml(configPath), {
180 packageManager: defaultPackageManager
181 }),
182 configPath
183 );
184 this.config = parseYaml(configPath);
185 }
186 } else {
187 logger.debug('Use packageManager is: ', this.config.packageManager);
188 }
189 resolve();
190 });
191 }
192
193 loadNative() {
194 return new Promise<any>((resolve, reject) => {
195 const nativePath = path.join(__dirname, './native/*.js');
196 // fs.readdirSync(nativePath)
197 glob.sync(nativePath).forEach((file: string) => {
198 require(file)(this);
199 });
200 resolve();
201 });
202 }
203
204 async loadCommands(orderType: number) {
205 this.logger.debug('load order: ', orderType);
206 if ((orderType & LOAD_ALL) === LOAD_ALL) {
207 await Promise.all([
208 this.loadNative(),
209 loadUniversalPlugin(this),
210 loadPlugins(this),
211 loadDevkits(this)
212 ]);
213 return;
214 }
215 if ((orderType & LOAD_PLUGIN) === LOAD_PLUGIN) {
216 await loadPlugins(this);
217 }
218 if ((orderType & LOAD_UNIVERSAL_PLUGIN) === LOAD_UNIVERSAL_PLUGIN) {
219 await loadUniversalPlugin(this);
220 }
221 if ((orderType & LOAD_DEVKIT) === LOAD_DEVKIT) {
222 await loadDevkits(this);
223 }
224 }
225
226 loadInternalPlugins() {
227 const devToolPlugin = '@feflow/feflow-plugin-devtool';
228 try {
229 this.logger.debug('Plugin loaded: %s', devToolPlugin);
230 return require(devToolPlugin)(this);
231 } catch (err) {
232 this.fefError.printError({
233 error: err,
234 msg: 'internal plugin load failed: %s'
235 });
236 }
237 }
238
239 async call(name: string | undefined, ctx: any) {
240 if (this.args.help && name) {
241 await this.showCommandOptionDescription(name, ctx);
242 }
243 const cmd = this.commander.get(name);
244 if (cmd) {
245 this.logger.name = cmd.pluginName;
246 await cmd.runFn.call(this, ctx);
247 } else {
248 this.logger.debug(`Command ' ${name} ' has not been registered yet!`);
249 }
250 }
251
252 async showCommandOptionDescription(cmd: string, ctx: any): Promise<any> {
253 const registeredCommand = ctx.commander.get(cmd);
254 let commandLine: object[] = [];
255
256 if (registeredCommand && registeredCommand.options) {
257 commandLine = getCommandLine(
258 registeredCommand.options,
259 registeredCommand.desc,
260 cmd
261 );
262 }
263 // 有副作用,暂无好方法改造
264 if (cmd === 'help') {
265 registeredCommand.runFn.call(this, ctx);
266 return true;
267 }
268 if (commandLine.length == 0) {
269 return false;
270 }
271
272 const sections = [];
273 sections.push(...commandLine);
274 const usage = commandLineUsage(sections);
275 console.log(usage);
276 return true;
277 }
278}
279
\No newline at end of file