UNPKG

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