UNPKG

13.8 kBPlain TextView Raw
1import Commander from './commander';
2import Hook from './hook';
3import Binp from './universal-pkg/binp';
4import fs from 'fs';
5import inquirer from 'inquirer';
6import logger from './logger';
7import osenv from 'osenv';
8import path from 'path';
9import Table from 'easy-table';
10import spawn from 'cross-spawn';
11import loadPlugins from './plugin/loadPlugins';
12import loadUniversalPlugin from './plugin/loadUniversalPlugin';
13import loadDevkits from './devkit/loadDevkits';
14import getCommandLine from './devkit/commandOptions';
15import {
16 FEFLOW_ROOT,
17 FEFLOW_BIN,
18 FEFLOW_LIB,
19 UNIVERSAL_PKG_JSON,
20 UNIVERSAL_MODULES
21} from '../shared/constant';
22import { safeDump, parseYaml } from '../shared/yaml';
23import packageJson from '../shared/packageJson';
24import { getRegistryUrl, install } from '../shared/npm';
25import chalk from 'chalk';
26import semver from 'semver';
27import commandLineUsage from 'command-line-usage';
28import { UniversalPkg } from './universal-pkg/dep/pkg';
29const pkg = require('../../package.json');
30
31export default class Feflow {
32 public args: any;
33 public cmd: any;
34 public projectConfig: any;
35 public projectPath: any;
36 public version: string;
37 public logger: any;
38 public commander: any;
39 public hook: any;
40 public root: any;
41 public rootPkg: any;
42 public universalPkgPath: string;
43 public universalModules: string;
44 public config: any;
45 public configPath: any;
46 public bin: string;
47 public lib: string;
48 public universalPkg: UniversalPkg;
49
50 constructor(args: any) {
51 args = args || {};
52 const root = path.join(osenv.home(), FEFLOW_ROOT);
53 const configPath = path.join(root, '.feflowrc.yml');
54 this.root = root;
55 const bin = path.join(root, FEFLOW_BIN);
56 const lib = path.join(root, FEFLOW_LIB);
57 this.bin = bin;
58 this.lib = lib;
59 this.rootPkg = path.join(root, 'package.json');
60 this.universalPkgPath = path.join(root, UNIVERSAL_PKG_JSON);
61 this.universalModules = path.join(root, UNIVERSAL_MODULES);
62 this.args = args;
63 this.version = pkg.version;
64 this.config = parseYaml(configPath);
65 this.configPath = configPath;
66 this.commander = new Commander();
67 this.hook = new Hook();
68 this.logger = logger({
69 debug: Boolean(args.debug),
70 silent: Boolean(args.silent)
71 });
72 this.universalPkg = new UniversalPkg(this.universalPkgPath);
73 }
74
75 async init(cmd: string) {
76 if (cmd === 'config') {
77 await this.initClient();
78 await this.loadNative();
79 } else {
80 await this.initClient();
81 await this.initPackageManager();
82 const disableCheck =
83 !this.args['disable-check'] && !(this.config.disableCheck === 'true');
84 if (disableCheck) {
85 await this.checkCliUpdate();
86 await this.checkUpdate();
87 // await this.checkUniversalPluginAndUpdate();
88 }
89 await this.loadNative();
90 await this.loadInternalPlugins();
91 await loadPlugins(this);
92 await loadUniversalPlugin(this);
93 await loadDevkits(this);
94 }
95 }
96
97 initClient() {
98 const { root, rootPkg } = this;
99
100 return new Promise<any>((resolve, reject) => {
101 if (fs.existsSync(root) && fs.statSync(root).isFile()) {
102 fs.unlinkSync(root);
103 }
104
105 if (!fs.existsSync(root)) {
106 fs.mkdirSync(root);
107 }
108
109 if (!fs.existsSync(rootPkg)) {
110 fs.writeFileSync(
111 rootPkg,
112 JSON.stringify(
113 {
114 name: 'feflow-home',
115 version: '0.0.0',
116 private: true
117 },
118 null,
119 2
120 )
121 );
122 }
123 this.initBinPath();
124 resolve();
125 });
126 }
127
128 private initBinPath() {
129 const { bin } = this;
130
131 if (fs.existsSync(bin) && fs.statSync(bin).isFile()) {
132 fs.unlinkSync(bin);
133 }
134
135 if (!fs.existsSync(bin)) {
136 fs.mkdirSync(bin);
137 }
138 new Binp().register(bin);
139 }
140
141 initPackageManager() {
142 const { root, logger } = this;
143
144 return new Promise<any>((resolve, reject) => {
145 if (!this.config || !this.config.packageManager) {
146 const isInstalled = (packageName: string) => {
147 try {
148 const ret = spawn.sync(packageName, ['-v'], { stdio: 'ignore' });
149 if (ret.status !== 0) {
150 return false;
151 }
152 return true;
153 } catch (err) {
154 return false;
155 }
156 };
157
158 const packageManagers = [
159 {
160 name: 'npm',
161 installed: isInstalled('npm')
162 },
163 {
164 name: 'tnpm',
165 installed: isInstalled('tnpm')
166 },
167 {
168 name: 'cnpm',
169 installed: isInstalled('cnpm')
170 },
171 {
172 name: 'yarn',
173 installed: isInstalled('yarn')
174 }
175 ];
176
177 const installedPackageManagers = packageManagers.filter(
178 (packageManager) => packageManager.installed
179 );
180
181 if (installedPackageManagers.length === 0) {
182 const notify = 'You must installed a package manager';
183 console.error(notify);
184 } else {
185 const options = installedPackageManagers.map(
186 (installedPackageManager: any) => {
187 return installedPackageManager.name;
188 }
189 );
190 inquirer
191 .prompt([
192 {
193 type: 'list',
194 name: 'packageManager',
195 message: 'Please select one package manager',
196 choices: options
197 }
198 ])
199 .then((answer: any) => {
200 const configPath = path.join(root, '.feflowrc.yml');
201 safeDump(answer, configPath);
202 this.config = parseYaml(configPath);
203 resolve();
204 });
205 }
206 return;
207 } else {
208 logger.debug('Use packageManager is: ', this.config.packageManager);
209 }
210 resolve();
211 });
212 }
213
214 checkUpdate() {
215 const { root, rootPkg, config, logger } = this;
216 if (!config) {
217 return;
218 }
219
220 const table = new Table();
221 const packageManager = config.packageManager;
222 return Promise.all(
223 this.getInstalledPlugins().map(async (name: any) => {
224 const pluginPath = path.join(
225 root,
226 'node_modules',
227 name,
228 'package.json'
229 );
230 const content: any = fs.readFileSync(pluginPath);
231 const pkg: any = JSON.parse(content);
232 const localVersion = pkg.version;
233 const registryUrl = await getRegistryUrl(packageManager);
234 const latestVersion: any = await packageJson(name, registryUrl).catch(
235 (err) => {
236 logger.debug('Check plugin update error', err);
237 }
238 );
239
240 if (latestVersion && semver.gt(latestVersion, localVersion)) {
241 table.cell('Name', name);
242 table.cell(
243 'Version',
244 localVersion === latestVersion
245 ? localVersion
246 : localVersion + ' -> ' + latestVersion
247 );
248 table.cell('Tag', 'latest');
249 table.cell('Update', localVersion === latestVersion ? 'N' : 'Y');
250 table.newRow();
251
252 return {
253 name,
254 latestVersion
255 };
256 } else {
257 logger.debug('All plugins is in latest version');
258 }
259 })
260 ).then((plugins: any) => {
261 plugins = plugins.filter((plugin: any) => {
262 return plugin && plugin.name;
263 });
264 if (plugins.length) {
265 this.logger.info(
266 'It will update your local templates or plugins, this will take few minutes'
267 );
268 console.log(table.toString());
269
270 this.updatePluginsVersion(rootPkg, plugins);
271
272 const needUpdatePlugins: any = [];
273 plugins.map((plugin: any) => {
274 needUpdatePlugins.push(plugin.name);
275 });
276
277 return install(
278 packageManager,
279 root,
280 packageManager === 'yarn' ? 'add' : 'install',
281 needUpdatePlugins,
282 false,
283 true
284 ).then(() => {
285 this.logger.info('Plugin update success');
286 });
287 }
288 });
289 }
290
291 updatePluginsVersion(packagePath: string, plugins: any) {
292 const obj = require(packagePath);
293
294 plugins.map((plugin: any) => {
295 obj.dependencies[plugin.name] = plugin.latestVersion;
296 });
297
298 fs.writeFileSync(packagePath, JSON.stringify(obj, null, 4));
299 }
300
301 getInstalledPlugins() {
302 const { root, rootPkg } = this;
303
304 let plugins: any = [];
305 const exist = fs.existsSync(rootPkg);
306 const pluginDir = path.join(root, 'node_modules');
307
308 if (!exist) {
309 plugins = [];
310 } else {
311 const content: any = fs.readFileSync(rootPkg);
312
313 let json: any;
314
315 try {
316 json = JSON.parse(content);
317 const deps = json.dependencies || json.devDependencies || {};
318
319 plugins = Object.keys(deps);
320 } catch (ex) {
321 plugins = [];
322 }
323 }
324 return plugins.filter((name: any) => {
325 if (
326 !/^feflow-plugin-|^@[^/]+\/feflow-plugin-|generator-|^@[^/]+\/generator-/.test(
327 name
328 )
329 ) {
330 return false;
331 }
332 const pathFn = path.join(pluginDir, name);
333 return fs.existsSync(pathFn);
334 });
335 }
336
337 loadNative() {
338 return new Promise<any>((resolve, reject) => {
339 const nativePath = path.join(__dirname, './native');
340 fs.readdirSync(nativePath)
341 .filter((file) => {
342 return file.endsWith('.js');
343 })
344 .map((file) => {
345 require(path.join(__dirname, './native', file))(this);
346 });
347 resolve();
348 });
349 }
350
351 loadInternalPlugins() {
352 ['@feflow/feflow-plugin-devtool'].map((name: string) => {
353 try {
354 this.logger.debug('Plugin loaded: %s', chalk.magenta(name));
355 return require(name)(this);
356 } catch (err) {
357 this.logger.error(
358 { err: err },
359 'Plugin load failed: %s',
360 chalk.magenta(name)
361 );
362 }
363 });
364 }
365
366 call(name: any, ctx: any) {
367 const args = ctx.args;
368 if (args.h || args.help) {
369 return this.showCommandOptionDescription(name, ctx);
370 }
371 return new Promise<any>((resolve, reject) => {
372 const cmd = this.commander.get(name);
373 if (cmd) {
374 cmd.call(this, ctx);
375 resolve();
376 } else {
377 reject(
378 new Error('Command `' + name + '` has not been registered yet!')
379 );
380 }
381 });
382 }
383
384 async updateCli(packageManager: string) {
385 return new Promise((resolve, reject) => {
386 const args =
387 packageManager === 'yarn'
388 ? ['global', 'add', '@feflow/cli@latest', '--extract']
389 : [
390 'install',
391 '@feflow/cli@latest',
392 '--color=always',
393 '--save',
394 '--save-exact',
395 '--loglevel',
396 'error',
397 '-g'
398 ];
399
400 const child = spawn(packageManager, args, { stdio: 'inherit' });
401 child.on('close', (code) => {
402 if (code !== 0) {
403 reject({
404 command: `${packageManager} ${args.join(' ')}`
405 });
406 return;
407 }
408 resolve();
409 });
410 });
411 }
412
413 async checkCliUpdate() {
414 const { args, version, config, configPath } = this;
415 if (!config) {
416 return;
417 }
418 const packageManager = config.packageManager;
419 const autoUpdate = args['auto-update'] || config.autoUpdate === 'true';
420 if (
421 config.lastUpdateCheck &&
422 +new Date() - parseInt(config.lastUpdateCheck, 10) <= 1000 * 3600 * 24
423 ) {
424 return;
425 }
426 const registryUrl = await getRegistryUrl(packageManager);
427 const latestVersion: any = await packageJson(
428 '@feflow/cli',
429 registryUrl
430 ).catch(() => {
431 this.logger.warn(
432 `Network error, can't reach ${registryUrl}, CLI give up verison check.`
433 );
434 });
435
436 this.logger.debug(`Auto update: ${autoUpdate}`);
437 if (latestVersion && semver.gt(latestVersion, version)) {
438 this.logger.debug(
439 `Find new version, current version: ${version}, latest version: ${autoUpdate}`
440 );
441 if (autoUpdate) {
442 this.logger.debug(
443 `Auto update version from ${version} to ${latestVersion}`
444 );
445 return await this.updateCli(packageManager);
446 }
447 const askIfUpdateCli = [
448 {
449 type: 'confirm',
450 name: 'ifUpdate',
451 message: `${chalk.yellow(
452 `@feflow/cli's latest version is ${chalk.green(
453 `${latestVersion}`
454 )}, but your version is ${chalk.red(
455 `${version}`
456 )}, Do you want to update it?`
457 )}`,
458 default: true
459 }
460 ];
461 const answer = await inquirer.prompt(askIfUpdateCli);
462 if (answer.ifUpdate) {
463 await this.updateCli(packageManager);
464 } else {
465 safeDump(
466 {
467 ...config,
468 lastUpdateCheck: +new Date()
469 },
470 configPath
471 );
472 }
473 } else {
474 this.logger.debug(`Current version is already latest.`);
475 }
476 }
477
478 async showCommandOptionDescription(cmd: any, ctx: any): Promise<any> {
479 const registriedCommand = ctx.commander.get(cmd);
480 let commandLine: object[] = [];
481
482 if (registriedCommand && registriedCommand.options) {
483 commandLine = getCommandLine(
484 registriedCommand.options,
485 registriedCommand.desc,
486 cmd
487 );
488 }
489
490 if (cmd === 'help') {
491 return registriedCommand.call(this, ctx);
492 }
493 if (commandLine.length == 0) {
494 ctx.logger.warn(`Current command dosen't have help message`);
495 return;
496 }
497
498 let sections = [];
499
500 sections.push(...commandLine);
501 const usage = commandLineUsage(sections);
502
503 console.log(usage);
504 }
505}
506
\No newline at end of file