1 | const os = require('os');
|
2 | const path = require('path');
|
3 | const semver = require('semver');
|
4 |
|
5 | const httpClient = require('@src/clients/http-client');
|
6 | const {
|
7 | validateRequiredOption,
|
8 | validateOptionString,
|
9 | validateOptionRules,
|
10 | } = require('@src/commands/option-validator');
|
11 | const AppConfig = require('@src/model/app-config');
|
12 | const CONSTANTS = require('@src/utils/constants');
|
13 | const Messenger = require('@src/view/messenger');
|
14 | const metricClient = require('@src/utils/metrics');
|
15 | const profileHelper = require('@src/utils/profile-helper');
|
16 |
|
17 |
|
18 | const packageJson = require('@root/package.json');
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | class AbstractCommand {
|
24 | constructor(optionModel) {
|
25 |
|
26 | this._optionModel = optionModel || require('@src/commands/option-model');
|
27 | }
|
28 |
|
29 | name() {
|
30 | throw new Error('Unimplemented abstract function: name()!');
|
31 | }
|
32 |
|
33 | description() {
|
34 | throw new Error('Unimplemented abstract function: description()!');
|
35 | }
|
36 |
|
37 | requiredOptions() {
|
38 | return [];
|
39 | }
|
40 |
|
41 | optionalOptions() {
|
42 | return [];
|
43 | }
|
44 |
|
45 | handle() {}
|
46 |
|
47 | exit(statusCode) {
|
48 | Messenger.getInstance().dispose();
|
49 | process.exit(statusCode || 0);
|
50 | }
|
51 |
|
52 | createCommand() {
|
53 | new Messenger({});
|
54 | return (commander) => {
|
55 | try {
|
56 |
|
57 | const commanderCopy = commander
|
58 | .command(this.name())
|
59 | .description(this.description());
|
60 |
|
61 |
|
62 | this._registerOptions(commanderCopy);
|
63 |
|
64 |
|
65 | this._registerAction(commanderCopy);
|
66 | } catch (err) {
|
67 | Messenger.getInstance().fatal(err);
|
68 | this.exit(1);
|
69 | }
|
70 | };
|
71 | }
|
72 |
|
73 | _registerAction(commander) {
|
74 |
|
75 | commander.action((...args) => new Promise((resolve) => {
|
76 | const commandInstance = args[0];
|
77 | const remaining = args[1];
|
78 |
|
79 |
|
80 | Messenger.getInstance().doDebug = commandInstance.debug;
|
81 |
|
82 |
|
83 | metricClient.startAction(commandInstance._name, 'command');
|
84 |
|
85 |
|
86 | this._remindsIfNewVersion(commandInstance.debug, process.env.ASK_SKIP_NEW_VERSION_REMINDER, () => {
|
87 | try {
|
88 | this._validateOptions(commandInstance);
|
89 |
|
90 | |
91 |
|
92 |
|
93 |
|
94 |
|
95 | if (commandInstance._name !== 'configure'
|
96 | && profileHelper.runtimeProfile() !== CONSTANTS.PLACEHOLDER.ENVIRONMENT_VAR.PROFILE_NAME) {
|
97 | this._initiateAppConfig();
|
98 | }
|
99 | } catch (err) {
|
100 | Messenger.getInstance().error(err);
|
101 | resolve();
|
102 | this.exit(1);
|
103 | return;
|
104 | }
|
105 |
|
106 | this.handle(commandInstance, (error) => {
|
107 | metricClient.sendData(error).then(() => {
|
108 | resolve();
|
109 | this.exit(error ? 1 : 0);
|
110 | });
|
111 | }, remaining);
|
112 | });
|
113 | }));
|
114 | }
|
115 |
|
116 | _registerOptions(commander) {
|
117 | const requiredOptions = this.requiredOptions();
|
118 | if (requiredOptions && requiredOptions.length) {
|
119 | for (const optionId of requiredOptions) {
|
120 | commander = this._registerOption(commander, optionId, true);
|
121 | }
|
122 | }
|
123 |
|
124 | const optionalOptions = this.optionalOptions();
|
125 | if (optionalOptions && optionalOptions.length) {
|
126 | for (const optionId of optionalOptions) {
|
127 | commander = this._registerOption(commander, optionId, false);
|
128 | }
|
129 | }
|
130 |
|
131 | return commander;
|
132 | }
|
133 |
|
134 | _registerOption(commander, optionId, required) {
|
135 | const optionModel = this._optionModel[optionId];
|
136 |
|
137 |
|
138 | if (!optionModel) {
|
139 | throw new Error(`Unrecognized option ID: ${optionId}`);
|
140 | }
|
141 |
|
142 | return commander.option(
|
143 | AbstractCommand.buildOptionString(optionModel),
|
144 | `${required ? '[REQUIRED]' : '[OPTIONAL]'} ${optionModel.description}`
|
145 | );
|
146 | }
|
147 |
|
148 | _validateOptions(cmd) {
|
149 | const requiredOptions = this.requiredOptions();
|
150 | if (requiredOptions && requiredOptions.length) {
|
151 | for (const optionId of requiredOptions) {
|
152 | this._validateOption(cmd, optionId, true);
|
153 | }
|
154 | }
|
155 |
|
156 | const optionalOptions = this.optionalOptions();
|
157 | if (optionalOptions && optionalOptions.length) {
|
158 | for (const optionId of optionalOptions) {
|
159 | this._validateOption(cmd, optionId, false);
|
160 | }
|
161 | }
|
162 | }
|
163 |
|
164 | _validateOption(cmd, optionId, required) {
|
165 | const optionModel = this._optionModel[optionId];
|
166 | const optionKey = AbstractCommand.parseOptionKey(optionModel.name);
|
167 | try {
|
168 | if (required) {
|
169 | validateRequiredOption(cmd, optionKey);
|
170 | }
|
171 |
|
172 | if (cmd[optionKey]) {
|
173 |
|
174 | if (optionModel.stringInput === 'REQUIRED') {
|
175 | validateOptionString(cmd, optionKey);
|
176 | }
|
177 |
|
178 | validateOptionRules(cmd, optionKey, optionModel.rule);
|
179 | }
|
180 | } catch (err) {
|
181 | throw (`Please provide valid input for option: ${optionModel.name}. ${err}`);
|
182 | }
|
183 | }
|
184 |
|
185 | _remindsIfNewVersion(doDebug, skip, callback) {
|
186 | if (skip) return callback();
|
187 |
|
188 | httpClient.request({
|
189 | url: `${CONSTANTS.NPM_REGISTRY_URL_BASE}/${CONSTANTS.APPLICATION_NAME}/latest`,
|
190 | method: CONSTANTS.HTTP_REQUEST.VERB.GET
|
191 | }, 'GET_NPM_REGISTRY', doDebug, (err, response) => {
|
192 | if (err) {
|
193 | Messenger.getInstance().error(`Failed to get the latest version for ${CONSTANTS.APPLICATION_NAME} from NPM registry.\n${err}\n`);
|
194 | } else {
|
195 | const BANNER_WITH_HASH = '##########################################################################';
|
196 | const latestVersion = JSON.parse(response.body).version;
|
197 | if (packageJson.version !== latestVersion) {
|
198 | if (semver.major(packageJson.version) < semver.major(latestVersion)) {
|
199 | Messenger.getInstance().info(`\
|
200 | ${BANNER_WITH_HASH}
|
201 | [Info]: New MAJOR version (v${latestVersion}) of ${CONSTANTS.APPLICATION_NAME} is available now. Current version v${packageJson.version}.
|
202 | It is recommended to use the latest version. Please update using "npm upgrade -g ${CONSTANTS.APPLICATION_NAME}".
|
203 | ${BANNER_WITH_HASH}\n`);
|
204 | } else if (
|
205 | semver.major(packageJson.version) === semver.major(latestVersion)
|
206 | && semver.minor(packageJson.version) < semver.minor(latestVersion)
|
207 | ) {
|
208 | Messenger.getInstance().info(`\
|
209 | ${BANNER_WITH_HASH}
|
210 | [Info]: New MINOR version (v${latestVersion}) of ${CONSTANTS.APPLICATION_NAME} is available now. Current version v${packageJson.version}.
|
211 | It is recommended to use the latest version. Please update using "npm upgrade -g ${CONSTANTS.APPLICATION_NAME}".
|
212 | ${BANNER_WITH_HASH}\n`);
|
213 | }
|
214 | }
|
215 | }
|
216 | callback();
|
217 | });
|
218 | }
|
219 |
|
220 | _initiateAppConfig() {
|
221 | const configFilePath = path.join(os.homedir(), CONSTANTS.FILE_PATH.ASK.HIDDEN_FOLDER, CONSTANTS.FILE_PATH.ASK.PROFILE_FILE);
|
222 | new AppConfig(configFilePath);
|
223 | }
|
224 |
|
225 | |
226 |
|
227 |
|
228 |
|
229 | static buildOptionString(optionModel) {
|
230 | const optionStringArray = [];
|
231 |
|
232 | if (optionModel.alias) {
|
233 | optionStringArray.push(`-${optionModel.alias},`);
|
234 | }
|
235 |
|
236 | optionStringArray.push(`--${optionModel.name}`);
|
237 |
|
238 | if (optionModel.stringInput === 'REQUIRED') {
|
239 | optionStringArray.push(`<${optionModel.name}>`);
|
240 | } else if (optionModel.stringInput === 'OPTIONAL') {
|
241 | optionStringArray.push(`[${optionModel.name}]`);
|
242 | }
|
243 |
|
244 | return optionStringArray.join(' ');
|
245 | }
|
246 |
|
247 | |
248 |
|
249 |
|
250 |
|
251 |
|
252 | static parseOptionKey(name) {
|
253 | const arr = name.split('-');
|
254 |
|
255 | return arr.slice(1).reduce((end, element) => end + element.charAt(0).toUpperCase() + element.slice(1), arr[0]);
|
256 | }
|
257 | }
|
258 |
|
259 | module.exports = {
|
260 | AbstractCommand
|
261 | };
|