UNPKG

4.76 kBJavaScriptView Raw
1// Libraries
2const yargs = require('yargs');
3
4// Gigster
5const {_} = require('@gigster/gig-utils');
6const {Command, PluginManager} = require('@gigster/gig-models');
7const {
8 AnalyticsService,
9 ConfigService,
10 LogService,
11 NetworkService,
12 UpdateService,
13} = require('@gigster/gig-services');
14
15/**
16 * The base wrapper for the CLI that handles reading the plugin config, loading
17 * plugins dynamically, and executing the cli commands.
18 */
19class CLI {
20
21 constructor({name, version}) {
22 this.name = name;
23 this.version = version;
24 this.pluginManager = new PluginManager();
25 ConfigService.version = version;
26 }
27
28 /**
29 * Parses the plugins to extract all commands. From the commands, we can
30 * then extract out the specific flags and positional args using yargs.
31 */
32 parseArgs() {
33 const {commands} = this.pluginManager;
34 const createCommand = (args, command) => {
35 return args.command({
36 command: command.name,
37 aliases: command.aliases,
38 description: command.description,
39 builder: command.builder,
40 handler: this.createHandler({command}),
41 });
42 };
43
44 return _.tap(commands.reduce(createCommand, yargs), (args) => {
45 // NOTE(mark): In order to set the proper name, we need to override the parsed
46 // name from yargs.
47 args.$0 = this.name; // eslint-disable-line no-param-reassign
48 args.options(Command.globalFlags);
49 });
50 }
51
52 /**
53 * Creates a handler for the command that runs when this command is executed
54 * by the user of the cli.
55 */
56 createHandler({command}) {
57 return (argv) => {
58 try {
59 const [name, ...args] = argv._;
60 const context = {name, args, flags: argv};
61
62 argv.resolve(command.execute(context));
63 } catch (error) {
64 argv.reject(error);
65 }
66 };
67 }
68
69 async runHandlers({input, args}) {
70 const showHelp = _.includes(input, '--help');
71 const showVersion = _.includes(input, '--version');
72 const positionalArgs = _.filter(input, arg => !arg.startsWith('-'));
73
74 // If the --help or --version flags are passed in, we let yargs handle them.
75 if (!showHelp && !showVersion && positionalArgs.length === 0) {
76 return args.showHelp();
77 }
78
79 return new Promise((resolve, reject) => {
80 // We build a context object to pass to argv to use with the `handler`.
81 const context = {resolve, reject};
82
83 args
84 .exitProcess(false)
85 .fail((message, error) => reject(error))
86 .parse(input, context, (error, argv, output) => {
87 if (error) {
88 return reject(error);
89 }
90
91 if (output) {
92 console.log(output); // eslint-disable-line no-console
93 }
94
95 return resolve();
96 });
97 });
98 }
99
100 /**
101 * Sets the CLI network mode: offline / online.
102 */
103 async initializeNetworkMode() {
104 ConfigService.online = await NetworkService.isInternetAvailable();
105
106 if (!ConfigService.online) {
107 LogService.info('Your computer seems to be offline.');
108 LogService.notice('To use the CLI offline, you will need to ensure all the used packages are cached.');
109 LogService.info('Proceeding with offline mode.');
110 }
111 }
112
113 /**
114 * Parses args and flags to determine which command to call from which plugin.
115 */
116 async execute({input}) {
117 const event = `CLI`;
118 return AnalyticsService.time(event, {}, async () => {
119 // Set the log level based on --verbose.
120 const verbose = _.includes(input, '--verbose');
121 LogService.setVerbose(verbose);
122
123 // Set the CLI network mode: offline / online.
124 await this.initializeNetworkMode();
125
126 // Check for update and update both CLI and plugins if necessary.
127 if (ConfigService.isOnline) {
128 await this.checkAndUpdate();
129 }
130
131 // Construct the Command objects through the plugin -> handler interface.
132 this.pluginManager.parseCommands();
133
134 // Parse the input and args to determine the dynamic set of commands.
135 const args = this.parseArgs();
136
137 // Extract the flags and positional args. Currently, this is what's
138 // actually calling the plugins.
139 await this.runHandlers({input, args});
140 });
141 }
142
143 async checkAndUpdate() {
144 const {autoupdate, isDevelopmentOrCI} = ConfigService;
145
146 if (!autoupdate || isDevelopmentOrCI) {
147 // We want to fetch the plugins in case they aren't downloaded already.
148 this.pluginManager.fetchPlugins();
149 return;
150 }
151
152 // Update and restart the CLI if an update is available.
153 // Exits current process and spawns updated version of the CLI.
154 await UpdateService.updateAndRestartCli();
155
156 // Update the plugins
157 await this.pluginManager.updateAndFetchPlugins();
158 }
159
160}
161
162module.exports = CLI;