@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suitegeezus/suitecloud-cli",
3
- "version": "3.1.4",
3
+ "version": "3.1.6-0",
4
4
  "license": "UPL-1.0",
5
5
  "description": "SuiteCloud CLI for Node.js",
6
6
  "keywords": [
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": false,
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": false,
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
  );
@@ -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
- const validationErrors = commandOptionsValidator.validate({
97
- commandOptions: this._commandMetadata.options,
98
- arguments: params,
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: SUITE_SCRIPTS_FOLDER,
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
- this._dumpDebugFile(debugFilePath, undefined, 'FIRST');
148
- this._dumpDebugFile(debugFilePath, 'beforeExecuting', beforeExecutingOptions, runInInteractiveMode);
149
- const beforeExecutingOutput = await commandUserExtension.beforeExecuting(beforeExecutingOptions);
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
- if (options.hasOwnProperty(optionId) && args.hasOwnProperty(optionId)) {
249
- optionValues[optionId] = args[optionId];
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 datetime = new Date().toISOString().replace(/[:.]/g, '-');
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
- + lineBreak + NodeTranslationService.getMessage(ERRORS.SEE_PROJECT_STRUCTURE, INFO.PROJECT_STRUCTURE);
206
- throw new CLIException(errorMessage);
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
  };