@suitegeezus/suitecloud-cli 3.1.4 → 3.1.6-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/CLI.js +13 -3
- package/src/commands/Command.js +15 -7
- package/src/commands/account/setupci/AccountSetupCiAction.js +1 -1
- package/src/commands/file/list/ListFilesInputHandler.js +19 -8
- package/src/core/CommandActionExecutor.js +43 -16
- package/src/core/CommandRegistrationService.js +2 -1
- package/src/services/ProjectInfoService.js +6 -3
package/package.json
CHANGED
package/src/CLI.js
CHANGED
|
@@ -107,7 +107,7 @@ module.exports = class CLI {
|
|
|
107
107
|
_validateInteractive() {
|
|
108
108
|
let additionalAllowed = 0;
|
|
109
109
|
process.argv.forEach((arg)=>{
|
|
110
|
-
if( /\b(authid|project|config|debug)\b/.test(arg) ) additionalAllowed += 2;
|
|
110
|
+
if( /\b(authid|project|config|debug|skiphooks|folder)\b/.test(arg) ) additionalAllowed += 2;
|
|
111
111
|
});
|
|
112
112
|
if (process.argv.length > (4 + additionalAllowed) ) {
|
|
113
113
|
// There are more options apart from -i or --interactive
|
|
@@ -148,7 +148,7 @@ module.exports = class CLI {
|
|
|
148
148
|
"name": "customflag",
|
|
149
149
|
"option": "customflag",
|
|
150
150
|
"description": "A custom boolean that will be passed into the hooks. Has no effect unless you implement the logic in a hook",
|
|
151
|
-
"mandatory":
|
|
151
|
+
"mandatory": true,
|
|
152
152
|
"type": "FLAG",
|
|
153
153
|
"usage": "",
|
|
154
154
|
"defaultOption": false,
|
|
@@ -168,11 +168,21 @@ module.exports = class CLI {
|
|
|
168
168
|
"name": "debug",
|
|
169
169
|
"option": "debug",
|
|
170
170
|
"description": "Directory to dump debug data for debugging. Creates a unique {command}.{datetime}.json file per run",
|
|
171
|
-
"mandatory":
|
|
171
|
+
"mandatory": true,
|
|
172
172
|
"type": "SINGLE",
|
|
173
173
|
"usage": "\"./debug-output\"",
|
|
174
174
|
"defaultOption": false,
|
|
175
175
|
"disableInIntegrationMode": false
|
|
176
|
+
},
|
|
177
|
+
"skiphooks": {
|
|
178
|
+
"name": "skiphooks",
|
|
179
|
+
"option": "skiphooks",
|
|
180
|
+
"description": "Skip hook execution. Values: 'pre' (skip beforeExecuting), 'post' (skip onCompleted/onError), 'all' (skip all hooks)",
|
|
181
|
+
"mandatory": true,
|
|
182
|
+
"type": "SINGLE",
|
|
183
|
+
"usage": "\"pre|post|all|none\"",
|
|
184
|
+
"defaultOption": "none",
|
|
185
|
+
"disableInIntegrationMode": false
|
|
176
186
|
}
|
|
177
187
|
}
|
|
178
188
|
);
|
package/src/commands/Command.js
CHANGED
|
@@ -24,7 +24,8 @@ const NEVER_PARAMS = {
|
|
|
24
24
|
'NOCONFIG': 'noconfig',
|
|
25
25
|
'CUSTOMFLAG': 'customflag',
|
|
26
26
|
'CUSTOMOPTIONS': 'customoptions',
|
|
27
|
-
'DEBUG': 'debug'
|
|
27
|
+
'DEBUG': 'debug',
|
|
28
|
+
'SKIPHOOKS': 'skiphooks'
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
/** @type {import('./Command')} */
|
|
@@ -71,7 +72,7 @@ class Command {
|
|
|
71
72
|
|
|
72
73
|
const preExec = await this._action.preExecute(execParams);
|
|
73
74
|
|
|
74
|
-
this._validateActionParameters(preExec);
|
|
75
|
+
this._validateActionParameters(preExec, Object.values(NEVER_PARAMS));
|
|
75
76
|
|
|
76
77
|
const exec = await this._action.execute(preExec);
|
|
77
78
|
const actionResult = await this._action.postExecute(exec);
|
|
@@ -91,12 +92,19 @@ class Command {
|
|
|
91
92
|
})
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
_validateActionParameters(params) {
|
|
95
|
+
_validateActionParameters(params, ignores=[]) {
|
|
95
96
|
const commandOptionsValidator = new CommandOptionsValidator();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
|
|
98
|
+
// remove NEVER_PARAMS from this validation as they don't get passed down to the exec layer
|
|
99
|
+
const options = ignores.reduce((acc,ignore)=> {
|
|
100
|
+
delete acc.commandOptions[ignore];
|
|
101
|
+
return acc;
|
|
102
|
+
},{
|
|
103
|
+
commandOptions: this._commandMetadata.options,
|
|
104
|
+
arguments: params,
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
const validationErrors = commandOptionsValidator.validate(options);
|
|
100
108
|
if (validationErrors.length > 0) {
|
|
101
109
|
throwValidationException(validationErrors, this._runInInteractiveMode, this._commandMetadata);
|
|
102
110
|
}
|
|
@@ -21,7 +21,7 @@ module.exports = class AccountSetupCiAction extends BaseAction {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
preExecute(params) {
|
|
24
|
-
this._projectInfoService.checkWorkingDirectoryContainsValidProject(this._commandMetadata.name);
|
|
24
|
+
this._projectInfoService.checkWorkingDirectoryContainsValidProject(this._commandMetadata.name, false);
|
|
25
25
|
|
|
26
26
|
if (params[OPTIONS.ACCOUNT]) {
|
|
27
27
|
params[OPTIONS.ACCOUNT] = params[OPTIONS.ACCOUNT].toUpperCase();
|
|
@@ -35,20 +35,31 @@ module.exports = class ListFilesInputHandler extends BaseInputHandler {
|
|
|
35
35
|
if (accountFileCabinetFolders.length === 0) {
|
|
36
36
|
throw NodeTranslationService.getMessage(ERRORS.NO_FOLDERS_FOUND);
|
|
37
37
|
}
|
|
38
|
-
const fileCabinetFolders = accountFileCabinetFolders.map((folderPath) => {
|
|
39
|
-
return {
|
|
40
|
-
name: folderPath,
|
|
41
|
-
value: folderPath,
|
|
42
|
-
};
|
|
43
|
-
});
|
|
44
38
|
|
|
39
|
+
const fileCabinetFolders = new Set();
|
|
40
|
+
fileCabinetFolders.add(SUITE_SCRIPTS_FOLDER);
|
|
41
|
+
let defaultValue = SUITE_SCRIPTS_FOLDER;
|
|
42
|
+
accountFileCabinetFolders.forEach((folderPath) => {
|
|
43
|
+
// only return folder that match the filter
|
|
44
|
+
if( folderPath === params.folder ) defaultValue = folderPath;
|
|
45
|
+
if(
|
|
46
|
+
(typeof params.folder === 'string' && folderPath.includes(params.folder)) ||
|
|
47
|
+
typeof params.folder !== 'string'
|
|
48
|
+
){
|
|
49
|
+
return fileCabinetFolders.add({
|
|
50
|
+
name: folderPath,
|
|
51
|
+
value: folderPath,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
if( fileCabinetFolders.has(defaultValue)) fileCabinetFolders.delete(defaultValue);
|
|
45
56
|
return prompt([
|
|
46
57
|
{
|
|
47
58
|
type: CommandUtils.INQUIRER_TYPES.LIST,
|
|
48
59
|
name: this._commandMetadata.options.folder.name,
|
|
49
60
|
message: NodeTranslationService.getMessage(SELECT_FOLDER),
|
|
50
|
-
default:
|
|
51
|
-
choices: fileCabinetFolders,
|
|
61
|
+
default: defaultValue,
|
|
62
|
+
choices: [...fileCabinetFolders],
|
|
52
63
|
},
|
|
53
64
|
]);
|
|
54
65
|
}
|
|
@@ -52,6 +52,7 @@ module.exports = class CommandActionExecutor {
|
|
|
52
52
|
let commandUserExtension;
|
|
53
53
|
const commandName = context.commandName;
|
|
54
54
|
const debugFilePath = this._getDebugFilePath(context.arguments.debug, commandName);
|
|
55
|
+
const skipHooks = context.arguments.skiphooks;
|
|
55
56
|
try {
|
|
56
57
|
const commandMetadata = this._commandsMetadataService.getCommandMetadataByName(commandName);
|
|
57
58
|
if (context.arguments.config) {
|
|
@@ -68,6 +69,7 @@ module.exports = class CommandActionExecutor {
|
|
|
68
69
|
const runInInteractiveMode = context.runInInteractiveMode;
|
|
69
70
|
const commandArguments = this._extractOptionValuesFromArguments(commandMetadata.options, context.arguments);
|
|
70
71
|
|
|
72
|
+
|
|
71
73
|
// need the
|
|
72
74
|
const projectFolder = this._cliConfigurationService.getProjectFolder(commandName, commandArguments?.project);
|
|
73
75
|
const projectPath = this._cliConfigurationService.getProjectPath(projectFolder);
|
|
@@ -144,9 +146,12 @@ module.exports = class CommandActionExecutor {
|
|
|
144
146
|
process.env[ENV_VARS.SUITECLOUD_PROJECT_ROOT] = this._executionPath;
|
|
145
147
|
// this might modified but we need the user's hooks to take advantage of their current values
|
|
146
148
|
process.env[ENV_VARS.SUITECLOUD_AUTHID] = authId;
|
|
147
|
-
|
|
148
|
-
this._dumpDebugFile(debugFilePath, '
|
|
149
|
-
|
|
149
|
+
const skipPre = skipHooks === 'pre' || skipHooks === 'all';
|
|
150
|
+
this._dumpDebugFile(debugFilePath, undefined, 'FIRST', runInInteractiveMode, skipPre);
|
|
151
|
+
this._dumpDebugFile(debugFilePath, 'beforeExecuting', beforeExecutingOptions, runInInteractiveMode, skipPre);
|
|
152
|
+
const beforeExecutingOutput = skipPre
|
|
153
|
+
? { arguments: beforeExecutingOptions.arguments }
|
|
154
|
+
: await commandUserExtension.beforeExecuting(beforeExecutingOptions);
|
|
150
155
|
const overriddenArguments = beforeExecutingOutput.arguments;
|
|
151
156
|
|
|
152
157
|
// do not allow this argument to be overwritten (i.e. change it back)
|
|
@@ -156,7 +161,7 @@ module.exports = class CommandActionExecutor {
|
|
|
156
161
|
// update the authid as well (not recommended but possible) -- should this be in the isSetupRequired check?
|
|
157
162
|
// EXPERIMENTAL: ALLOW UPDATE OF THE AUTHID AT THIS TIME
|
|
158
163
|
if (commandMetadata.isSetupRequired)
|
|
159
|
-
authId = beforeExecutingOutput?.arguments?.authid || authId;
|
|
164
|
+
authId = beforeExecutingOutput?.['arguments']?.authid || authId;
|
|
160
165
|
|
|
161
166
|
if (commandMetadata.isSetupRequired && !context.arguments[AUTHORIZATION_PROPERTIES_KEYS.SKIP_AUHTORIZATION_CHECK]) {
|
|
162
167
|
// check if reauthz is needed to show proper message before continuing with the execution
|
|
@@ -173,6 +178,7 @@ module.exports = class CommandActionExecutor {
|
|
|
173
178
|
// command execution
|
|
174
179
|
// src/commands/Command.js, run(inputParams) => execution flow for all commands
|
|
175
180
|
const actionResult = await command.run(overriddenArguments);
|
|
181
|
+
const skipPost = skipHooks === 'post' || skipHooks === 'all';
|
|
176
182
|
|
|
177
183
|
if (context.runInInteractiveMode) {
|
|
178
184
|
// generate non-interactive equivalent
|
|
@@ -182,22 +188,23 @@ module.exports = class CommandActionExecutor {
|
|
|
182
188
|
|
|
183
189
|
if (actionResult.isSuccess() && commandUserExtension.onCompleted) {
|
|
184
190
|
// run onCompleted(output) from suitecloud.config.js
|
|
185
|
-
this._dumpDebugFile(debugFilePath, 'onCompleted', actionResult, runInInteractiveMode);
|
|
186
|
-
commandUserExtension.onCompleted(actionResult);
|
|
191
|
+
this._dumpDebugFile(debugFilePath, 'onCompleted', actionResult, runInInteractiveMode, skipPost);
|
|
192
|
+
if (!skipPost) commandUserExtension.onCompleted(actionResult);
|
|
187
193
|
} else if (!actionResult.isSuccess() && commandUserExtension.onError) {
|
|
188
194
|
// run onError(error) from suitecloud.config.js
|
|
189
195
|
const errorData = ActionResultUtils.getErrorMessagesString(actionResult);
|
|
190
|
-
this._dumpDebugFile(debugFilePath, 'onError', errorData, runInInteractiveMode);
|
|
191
|
-
commandUserExtension.onError(errorData);
|
|
196
|
+
this._dumpDebugFile(debugFilePath, 'onError', errorData, runInInteractiveMode, skipPost);
|
|
197
|
+
if (!skipPost) commandUserExtension.onError(errorData);
|
|
192
198
|
}
|
|
193
199
|
return actionResult;
|
|
194
200
|
|
|
195
201
|
} catch (error) {
|
|
196
202
|
let errorMessage = this._logGenericError(error);
|
|
203
|
+
const skipPostHooksCatch = skipHooks === 'post' || skipHooks === 'all';
|
|
197
204
|
if (commandUserExtension && commandUserExtension.onError) {
|
|
198
205
|
// run onError(error) from suitecloud.config.js
|
|
199
|
-
this._dumpDebugFile(debugFilePath, 'onError', error, context.runInInteractiveMode);
|
|
200
|
-
commandUserExtension.onError(error);
|
|
206
|
+
this._dumpDebugFile(debugFilePath, 'onError', error, context.runInInteractiveMode, skipPostHooksCatch);
|
|
207
|
+
if (!skipPostHooksCatch) commandUserExtension.onError(error);
|
|
201
208
|
}
|
|
202
209
|
return ActionResult.Builder.withErrors(Array.isArray(errorMessage) ? errorMessage : [errorMessage]).build();
|
|
203
210
|
}
|
|
@@ -245,8 +252,27 @@ module.exports = class CommandActionExecutor {
|
|
|
245
252
|
_extractOptionValuesFromArguments(options, args) {
|
|
246
253
|
const optionValues = {};
|
|
247
254
|
for (const optionId in options) {
|
|
248
|
-
|
|
249
|
-
|
|
255
|
+
const optionMeta = options[optionId];
|
|
256
|
+
// apply defaults first
|
|
257
|
+
if (options.hasOwnProperty(optionId)){
|
|
258
|
+
if(optionMeta?.mandatory) {
|
|
259
|
+
switch(true){
|
|
260
|
+
case optionMeta?.type !== 'FLAG' && typeof optionMeta?.defaultOption === "boolean":
|
|
261
|
+
// don't set a value for boolean unless it is a FLAG type
|
|
262
|
+
break;
|
|
263
|
+
case (optionMeta?.type === "FLAG"):
|
|
264
|
+
optionValues[optionId] = Boolean(optionMeta.defaultOption)
|
|
265
|
+
break;
|
|
266
|
+
case !["undefined"].includes( typeof optionMeta?.defaultOption) && optionMeta?.type !== 'FLAG':
|
|
267
|
+
optionValues[optionId] = optionMeta.defaultOption;
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if(args.hasOwnProperty(optionId)) {
|
|
274
|
+
optionValues[optionId] = args[optionId];
|
|
275
|
+
}
|
|
250
276
|
}
|
|
251
277
|
}
|
|
252
278
|
|
|
@@ -297,8 +323,7 @@ module.exports = class CommandActionExecutor {
|
|
|
297
323
|
_getDebugFilePath(debugDir, commandName) {
|
|
298
324
|
if (!debugDir) return null;
|
|
299
325
|
const sanitizedCommandName = commandName.replace(/:/g, '-');
|
|
300
|
-
const
|
|
301
|
-
const filename = `${sanitizedCommandName}.${datetime}.json`;
|
|
326
|
+
const filename = `${sanitizedCommandName}.json`;
|
|
302
327
|
return path.join(debugDir, filename);
|
|
303
328
|
}
|
|
304
329
|
|
|
@@ -308,9 +333,10 @@ module.exports = class CommandActionExecutor {
|
|
|
308
333
|
* @param hookName
|
|
309
334
|
* @param {'FIRST'|'LAST'|*} data
|
|
310
335
|
* @param {boolean} runInInteractiveMode
|
|
336
|
+
* @param {boolean} skipped
|
|
311
337
|
* @private
|
|
312
338
|
*/
|
|
313
|
-
_dumpDebugFile(debugFilePath, hookName, data, runInInteractiveMode = false) {
|
|
339
|
+
_dumpDebugFile(debugFilePath, hookName, data, runInInteractiveMode = false, skipped=false) {
|
|
314
340
|
if (!debugFilePath || !data ) return;
|
|
315
341
|
if (data === 'FIRST') {
|
|
316
342
|
fs.writeFileSync(debugFilePath, '[]');
|
|
@@ -323,9 +349,10 @@ module.exports = class CommandActionExecutor {
|
|
|
323
349
|
.forEach(key => { envVars[key] = process.env[key]; });
|
|
324
350
|
const entry = {
|
|
325
351
|
hook: hookName,
|
|
352
|
+
skipped,
|
|
326
353
|
timestamp: new Date().toISOString(),
|
|
327
354
|
env: envVars,
|
|
328
|
-
data: data
|
|
355
|
+
data: data,
|
|
329
356
|
};
|
|
330
357
|
|
|
331
358
|
// Print to console in interactive mode
|
|
@@ -65,11 +65,12 @@ module.exports = class CommandRegistrationService {
|
|
|
65
65
|
|
|
66
66
|
_addInteractiveCommandOptions(commandSetup,options){
|
|
67
67
|
const filteredOptions = Object.entries(options).filter(([key,o])=>{
|
|
68
|
-
return ['authid','project','config','debug'].includes(o.name);
|
|
68
|
+
return ['authid','project','config','debug','skiphooks','folder'].includes(o.name);
|
|
69
69
|
});
|
|
70
70
|
filteredOptions.push(['interactive',{
|
|
71
71
|
"name": "interactive",
|
|
72
72
|
"option": "interactive",
|
|
73
|
+
"alias": "i",
|
|
73
74
|
"description": "Be interactive",
|
|
74
75
|
"mandatory": true,
|
|
75
76
|
"type": "FLAG",
|
|
@@ -19,6 +19,7 @@ const NodeTranslationService = require('./NodeTranslationService');
|
|
|
19
19
|
const xml2js = require('xml2js');
|
|
20
20
|
const assert = require('assert');
|
|
21
21
|
const { lineBreak } = require('../loggers/LoggerOsConstants');
|
|
22
|
+
const NodeConsoleLogger = require('../loggers/NodeConsoleLogger');
|
|
22
23
|
|
|
23
24
|
const MANIFEST_TAG_XML_PATH = '/manifest';
|
|
24
25
|
const PROJECT_TYPE_ATTRIBUTE = 'projecttype';
|
|
@@ -199,11 +200,13 @@ module.exports = class ProjectInfoService {
|
|
|
199
200
|
return this.isAccountCustomizationProject() || this.isSuiteAppProject();
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
checkWorkingDirectoryContainsValidProject(commandName) {
|
|
203
|
+
checkWorkingDirectoryContainsValidProject(commandName, doThrow=true) {
|
|
203
204
|
if (!FileUtils.exists(path.join(this._projectFolder, FILES.MANIFEST_XML))) {
|
|
204
205
|
const errorMessage = NodeTranslationService.getMessage(ERRORS.NOT_PROJECT_FOLDER, FILES.MANIFEST_XML, this._projectFolder, commandName)
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
if( doThrow) throw new CLIException(
|
|
207
|
+
errorMessage + lineBreak + NodeTranslationService.getMessage(ERRORS.SEE_PROJECT_STRUCTURE, INFO.PROJECT_STRUCTURE)
|
|
208
|
+
);
|
|
209
|
+
NodeConsoleLogger.warning(errorMessage);
|
|
207
210
|
}
|
|
208
211
|
}
|
|
209
212
|
};
|