@cyberismo/cli 0.0.17 → 0.0.19

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/dist/index.js CHANGED
@@ -11,7 +11,8 @@
11
11
  details. You should have received a copy of the GNU Affero General Public
12
12
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
13
  */
14
- import { readFile } from 'node:fs/promises';
14
+ import { constants, existsSync } from 'node:fs';
15
+ import { access, lstat, readFile } from 'node:fs/promises';
15
16
  import { resolve } from 'node:path';
16
17
  import { Argument, Command, Option } from 'commander';
17
18
  import confirm from '@inquirer/confirm';
@@ -100,61 +101,6 @@ const program = new Command();
100
101
  // Ensure that all names have the same guideline.
101
102
  const nameGuideline = 'Name can contain letters (a-z|A-Z), spaces, underscores or hyphens.';
102
103
  const pathGuideline = 'Path to the project root. Mandatory if not running inside a project tree.';
103
- const additionalHelpForCreate = `Sub-command help:
104
- create attachment <cardKey> <filename>, where
105
- <cardKey> is card key of a card to have the attachment,
106
- <filename> is attachment filename.
107
-
108
- create card <template> [cardKey], where
109
- <template> Template to use. You can list the templates in a project with "show templates" command.
110
- [cardKey] Parent card's card key. If defined, new card will be created as a child card to that card.
111
-
112
- create cardType <name> <workflow>, where
113
- <name> Name for cardType. ${nameGuideline}
114
- <workflow> Workflow for the card type. You can list workflows in a project with "show workflows" command.
115
-
116
- create fieldType <name> <dataType>, where
117
- <name> Name for fieldType. ${nameGuideline}
118
- <dataType> Type of field. You can list field types in a project with "show fieldTypes" command.
119
-
120
- create graphModel <name>, where
121
- <name> Name for graph model. ${nameGuideline}
122
-
123
- create graphView <name>, where
124
- <name> Name for graph view. ${nameGuideline}
125
-
126
- create label <cardKey> <labelName>, where
127
- <cardKey> Card key of the label
128
- <labelName> Name for the new label
129
-
130
- create link <source> <destination> <linkType> [description], where
131
- <source> Source card key of the link
132
- <destination> Destination card key of the link
133
- <linkType> Link type to create
134
- [description] Link description
135
-
136
- create linkType <name>, where
137
- <name> Name for linkType. ${nameGuideline}
138
-
139
- create project <name> <prefix> <path>, where
140
- <name> Name of the project.
141
- <prefix> Prefix for the project.
142
- <path> Path where to create the project
143
-
144
- create report <name>, where
145
- <name> Name for report. ${nameGuideline}
146
-
147
- create template <name> [content], where
148
- <name> Name for template. ${nameGuideline}
149
- [content] If empty, template is created with default values. Template content must conform to schema "templateSchema.json"
150
-
151
- create workflow <name> [content], where
152
- <name> Name for workflow. ${nameGuideline}
153
- [content] If empty, workflow is created with default values. Workflow content must conform to schema "workflowSchema.json"
154
-
155
- create <resourceName> [content], where
156
- <resourceName> Name of the resource (e.g. <prefix>/<type>/<identifier>)
157
- [content] If empty, resource is created with default values. Content must conform to its resource schema.`;
158
104
  const additionalHelpForRemove = `Sub-command help:
159
105
  remove attachment <cardKey> <filename>, where
160
106
  <cardKey> is card key of the owning card,
@@ -211,6 +157,17 @@ const additionalHelpForUpdate = `Sub-command help:
211
157
  const contextOption = new Option('-c, --context [context]', 'Context to run the logic programs in.')
212
158
  .choices(validContexts)
213
159
  .default('app');
160
+ const pathOption = new Option('-p, --project-path <path>', pathGuideline);
161
+ // Custom Command class with pathOption pre-configured
162
+ class CommandWithPath extends Command {
163
+ createCommand(name) {
164
+ return new CommandWithPath(name);
165
+ }
166
+ constructor(name) {
167
+ super(name);
168
+ this.addOption(pathOption);
169
+ }
170
+ }
214
171
  // Main CLI program.
215
172
  program
216
173
  .name('cyberismo')
@@ -220,7 +177,8 @@ program
220
177
  .addOption(new Option('-L, --log-level <level>', 'Set the log level')
221
178
  .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])
222
179
  .default('fatal'));
223
- const addCmd = program.command('add').description('Add items to the project');
180
+ const addCmd = new CommandWithPath('add').description('Add items to the project');
181
+ program.addCommand(addCmd);
224
182
  // Add card to a template
225
183
  addCmd
226
184
  .command('card')
@@ -228,7 +186,6 @@ addCmd
228
186
  .argument('<template>', 'Template for a new card. \nYou can list the templates in a project with "show templates" command.')
229
187
  .argument('<cardType>', 'Card type to use for the new card. \nYou can list the card types in a project with "show cardTypes" command.')
230
188
  .argument('[cardKey]', "Parent card's card key")
231
- .option('-p, --project-path [path]', `${pathGuideline}`)
232
189
  .option('-r, --repeat <quantity>', 'Add multiple cards to a template')
233
190
  .action(async (template, cardType, cardKey, options) => {
234
191
  const result = await commandHandler.command(Cmd.add, ['card', template, cardType, cardKey], Object.assign({}, options, program.opts()));
@@ -238,7 +195,6 @@ addCmd
238
195
  .command('hub')
239
196
  .description('Add a hub to the project')
240
197
  .argument('<location>', 'Hub URL. Default hub can be added by using "default"')
241
- .option('-p, --project-path [path]', `${pathGuideline}`)
242
198
  .action(async (location, options) => {
243
199
  if (location === 'default') {
244
200
  location = DEFAULT_HUB;
@@ -246,15 +202,13 @@ addCmd
246
202
  const result = await commandHandler.command(Cmd.add, ['hub', location], Object.assign({}, options, program.opts()));
247
203
  handleResponse(result);
248
204
  });
249
- const calculate = program
250
- .command('calc')
251
- .description('Used for running logic programs');
205
+ const calculate = new CommandWithPath('calc').description('Used for running logic programs');
206
+ program.addCommand(calculate);
252
207
  calculate
253
208
  .command('generate')
254
209
  .description('Generate a logic program')
255
210
  .argument('<destination>', 'Path to an output file. Command writes the logic program to this file.')
256
211
  .argument('[query]', 'Query to run')
257
- .option('-p, --project-path [path]', `${pathGuideline}`)
258
212
  .action(async (destination, query, options) => {
259
213
  const result = await commandHandler.command(Cmd.calc, ['generate', destination, query], Object.assign({}, options, program.opts()));
260
214
  handleResponse(result);
@@ -264,83 +218,118 @@ calculate
264
218
  .description('Run a logic program')
265
219
  .argument('<filePath>', 'Path to the logic program')
266
220
  .addOption(contextOption)
267
- .option('-p, --project-path [path]', `${pathGuideline}`)
268
221
  .action(async (filePath, options) => {
269
222
  const result = await commandHandler.command(Cmd.calc, ['run', filePath], Object.assign({}, options, program.opts()));
270
223
  handleResponse(result);
271
224
  });
272
- program
273
- .command('create')
274
- .description('Create cards, resources and other project items')
275
- .argument('<type>', `types to create: '${Parser.listTargets('create').join("', '")}', or resource name (e.g. <prefix>/<type>/<identifier>)`, Parser.parseCreateTypes)
276
- .argument('[target]', 'Name to create, or in some operations cardKey to create data to a specific card. See below')
277
- .argument('[parameter1]', 'Depends on context; see below for specific remove operation')
278
- .argument('[parameter2]', 'Depends on context; see below for specific remove operation')
279
- .argument('[parameter3]', 'Depends on context; see below for specific remove operation')
280
- .addHelpText('after', additionalHelpForCreate)
281
- .option('-p, --project-path [path]', `${pathGuideline}`)
225
+ const createCmd = new CommandWithPath('create').description('Create cards, resources and other project items');
226
+ program.addCommand(createCmd);
227
+ // Create attachment subcommand
228
+ createCmd
229
+ .command('attachment')
230
+ .description('Create an attachment for a card')
231
+ .argument('<cardKey>', 'Card key of the card to attach to')
232
+ .argument('<filename>', 'Path to the file to attach')
233
+ .action(async (cardKey, filename, options) => {
234
+ const result = await commandHandler.command(Cmd.create, ['attachment', cardKey, filename], Object.assign({}, options, program.opts()));
235
+ handleResponse(result);
236
+ });
237
+ // Create card subcommand
238
+ createCmd
239
+ .command('card')
240
+ .description('Create a card from a template')
241
+ .argument('<template>', 'Template to use. You can list templates with "show templates" command')
242
+ .argument('[parentCardKey]', "Parent card's card key. If defined, new card will be created as a child")
243
+ .action(async (template, parentCardKey, options) => {
244
+ const result = await commandHandler.command(Cmd.create, ['card', template, parentCardKey].filter(Boolean), Object.assign({}, options, program.opts()));
245
+ handleResponse(result);
246
+ });
247
+ // Create cardType subcommand
248
+ createCmd
249
+ .command('cardType')
250
+ .description('Create a new card type')
251
+ .argument('<name>', `Name for card type. ${nameGuideline}`)
252
+ .argument('<workflow>', 'Workflow for the card type. You can list workflows with "show workflows" command')
253
+ .action(async (name, workflow, options) => {
254
+ const result = await commandHandler.command(Cmd.create, ['cardType', name, workflow], Object.assign({}, options, program.opts()));
255
+ handleResponse(result);
256
+ });
257
+ // Create fieldType subcommand
258
+ createCmd
259
+ .command('fieldType')
260
+ .description('Create a new field type')
261
+ .argument('<name>', `Name for field type. ${nameGuideline}`)
262
+ .argument('<dataType>', 'Type of field. You can list field types with "show fieldTypes" command')
263
+ .action(async (name, dataType, options) => {
264
+ const result = await commandHandler.command(Cmd.create, ['fieldType', name, dataType], Object.assign({}, options, program.opts()));
265
+ handleResponse(result);
266
+ });
267
+ // Create graphModel subcommand
268
+ createCmd
269
+ .command('graphModel')
270
+ .description('Create a new graph model')
271
+ .argument('<name>', `Name for graph model. ${nameGuideline}`)
272
+ .action(async (name, options) => {
273
+ const result = await commandHandler.command(Cmd.create, ['graphModel', name], Object.assign({}, options, program.opts()));
274
+ handleResponse(result);
275
+ });
276
+ // Create graphView subcommand
277
+ createCmd
278
+ .command('graphView')
279
+ .description('Create a new graph view')
280
+ .argument('<name>', `Name for graph view. ${nameGuideline}`)
281
+ .action(async (name, options) => {
282
+ const result = await commandHandler.command(Cmd.create, ['graphView', name], Object.assign({}, options, program.opts()));
283
+ handleResponse(result);
284
+ });
285
+ // Create label subcommand
286
+ createCmd
287
+ .command('label')
288
+ .description('Create a label on a card')
289
+ .argument('<cardKey>', 'Card key')
290
+ .argument('<labelName>', 'Name for the new label')
291
+ .action(async (cardKey, labelName, options) => {
292
+ const result = await commandHandler.command(Cmd.create, ['label', cardKey, labelName], Object.assign({}, options, program.opts()));
293
+ handleResponse(result);
294
+ });
295
+ // Create link subcommand
296
+ createCmd
297
+ .command('link')
298
+ .description('Create a link between two cards')
299
+ .argument('<source>', 'Source card key')
300
+ .argument('<destination>', 'Destination card key')
301
+ .argument('<linkType>', 'Link type to create')
302
+ .argument('[description]', 'Optional link description')
303
+ .action(async (source, destination, linkType, description, options) => {
304
+ const result = await commandHandler.command(Cmd.create, ['link', source, destination, linkType, description].filter(Boolean), Object.assign({}, options, program.opts()));
305
+ handleResponse(result);
306
+ });
307
+ // Create linkType subcommand
308
+ createCmd
309
+ .command('linkType')
310
+ .description('Create a new link type')
311
+ .argument('<name>', `Name for link type. ${nameGuideline}`)
312
+ .action(async (name, options) => {
313
+ const result = await commandHandler.command(Cmd.create, ['linkType', name], Object.assign({}, options, program.opts()));
314
+ handleResponse(result);
315
+ });
316
+ // Create project subcommand
317
+ createCmd
318
+ .command('project')
319
+ .description('Create a new project')
320
+ .argument('<name>', 'Project name')
321
+ .argument('<prefix>', 'Project prefix')
322
+ .argument('<path>', 'Path where to create the project')
323
+ .argument('[category]', 'Project category (optional)')
324
+ .argument('[description]', 'Project description (optional)')
282
325
  .option('-s, --skipModuleImport', 'Skip importing modules when creating a project')
283
- .action(async (type, target, parameter1, parameter2, parameter3, options) => {
284
- if (!type) {
285
- program.error(`missing required argument <type>`);
286
- }
287
- const resourceName = type.split('/').length === 3;
288
- function nameOfFirstArgument(type) {
289
- if (type === 'attachment' || type === 'label')
290
- return 'cardKey';
291
- if (type === 'card')
292
- return 'template';
293
- if (type === 'link')
294
- return 'source';
295
- return 'name';
296
- }
297
- function nameOfSecondArgument(type) {
298
- if (type === 'attachment')
299
- return 'fileName';
300
- if (type === 'cardType')
301
- return 'workflow';
302
- if (type === 'fieldType')
303
- return 'dataType';
304
- if (type === 'label')
305
- return 'labelName';
306
- if (type === 'link')
307
- return 'destination';
308
- if (type === 'project')
309
- return 'prefix';
310
- return type;
311
- }
312
- if (!target && !resourceName) {
313
- program.error(`missing required argument <${nameOfFirstArgument(type)}>`);
314
- }
315
- if (!resourceName &&
316
- !parameter1 &&
317
- type !== 'card' &&
318
- type !== 'graphModel' &&
319
- type !== 'graphView' &&
320
- type !== 'linkType' &&
321
- type !== 'report' &&
322
- type !== 'template' &&
323
- type !== 'workflow') {
324
- program.error(`missing required argument <${nameOfSecondArgument(type)}>`);
325
- }
326
- if (resourceName &&
327
- (type.includes('cardTypes') || type.includes('fieldTypes')) &&
328
- !target) {
329
- program.error(`missing required argument <${nameOfSecondArgument(type)}>`);
330
- }
331
- if (type === 'project') {
332
- if (!parameter2) {
333
- program.error(`missing required argument <path>`);
334
- }
335
- // Project path must be set to 'options' when creating a project.
336
- options.projectPath = parameter2;
337
- }
326
+ .action(async (name, prefix, path, category, description, options) => {
327
+ // Project path must be set to 'options' when creating a project
328
+ options.projectPath = path;
338
329
  const commandOptions = Object.assign({}, options, program.opts());
339
- const result = await commandHandler.command(Cmd.create, [type, target, parameter1, parameter2, parameter3], commandOptions);
340
- // Post-handling after creating a new project.
341
- if (type === 'project' &&
342
- !commandOptions.skipModuleImport &&
343
- result.statusCode === 200) {
330
+ const result = await commandHandler.command(Cmd.create, ['project', name, prefix, path, category || '', description || ''], commandOptions);
331
+ // Post-handling after creating a new project
332
+ if (!commandOptions.skipModuleImport && result.statusCode === 200) {
344
333
  try {
345
334
  // add default hub
346
335
  await commandHandler.command(Cmd.add, ['hub', DEFAULT_HUB], commandOptions);
@@ -372,24 +361,61 @@ program
372
361
  }
373
362
  handleResponse(result);
374
363
  });
364
+ // Create report subcommand
365
+ createCmd
366
+ .command('report')
367
+ .description('Create a new report')
368
+ .argument('<name>', `Name for report. ${nameGuideline}`)
369
+ .action(async (name, options) => {
370
+ const result = await commandHandler.command(Cmd.create, ['report', name], Object.assign({}, options, program.opts()));
371
+ handleResponse(result);
372
+ });
373
+ // Create template subcommand
374
+ createCmd
375
+ .command('template')
376
+ .description('Create a new template')
377
+ .argument('<name>', `Name for template. ${nameGuideline}`)
378
+ .argument('[content]', 'Template content. If empty, template is created with default values. Must conform to templateSchema.json')
379
+ .action(async (name, content, options) => {
380
+ const result = await commandHandler.command(Cmd.create, ['template', name, content].filter(Boolean), Object.assign({}, options, program.opts()));
381
+ handleResponse(result);
382
+ });
383
+ // Create workflow subcommand
384
+ createCmd
385
+ .command('workflow')
386
+ .description('Create a new workflow')
387
+ .argument('<name>', `Name for workflow. ${nameGuideline}`)
388
+ .argument('[content]', 'Workflow content. If empty, workflow is created with default values. Must conform to workflowSchema.json')
389
+ .action(async (name, content, options) => {
390
+ const result = await commandHandler.command(Cmd.create, ['workflow', name, content].filter(Boolean), Object.assign({}, options, program.opts()));
391
+ handleResponse(result);
392
+ });
393
+ // Create new resource subcommand
394
+ createCmd
395
+ .command('resource')
396
+ .description('Create a new resource')
397
+ .argument('<resourceName>', 'Resource name (e.g. <prefix>/<type>/<identifier>)')
398
+ .argument('[content]', 'Resource content. If empty, resource is created with default values. Must conform to its resource schema')
399
+ .action(async (resourceName, content, options) => {
400
+ const result = await commandHandler.command(Cmd.create, [resourceName, content].filter(Boolean), Object.assign({}, options, program.opts()));
401
+ handleResponse(result);
402
+ });
375
403
  // Edit command
376
- program
377
- .command('edit')
404
+ const editCmd = new CommandWithPath('edit')
378
405
  .description('Edit a card')
379
- .argument('<cardKey>', 'Card key of card')
380
- .option('-p, --project-path [path]', `${pathGuideline}`)
381
- .action(async (cardKey, options) => {
406
+ .argument('<cardKey>', 'Card key of card');
407
+ program.addCommand(editCmd);
408
+ editCmd.action(async (cardKey, options) => {
382
409
  const result = await commandHandler.command(Cmd.edit, [cardKey], Object.assign({}, options, program.opts()));
383
410
  handleResponse(result);
384
411
  });
385
412
  // Export command
386
- program
387
- .command('export')
388
- .description('Export a project or a card')
413
+ const exportCmd = new CommandWithPath('export').description('Export a project or a card');
414
+ program.addCommand(exportCmd);
415
+ exportCmd
389
416
  .addArgument(new Argument('<format>', 'Export format').choices(Object.values(ExportFormats)))
390
417
  .argument('<output>', 'Output path')
391
418
  .argument('[cardKey]', 'Export a specific card by card key. If omitted, exports the whole site.')
392
- .option('-p, --project-path [path]', `${pathGuideline}`)
393
419
  .option('-r, --recursive', 'Export cards under the specified card recursively')
394
420
  .option('-t, --title [title]', 'Title of the exported document(pdf export only)')
395
421
  .option('-n, --name [name]', 'Name of the exported document(pdf export only)')
@@ -436,22 +462,17 @@ program
436
462
  const result = await commandHandler.command(Cmd.export, [format, output, cardKey], Object.assign({}, options, program.opts()));
437
463
  handleResponse(result);
438
464
  });
439
- const fetchCmd = program
440
- .command('fetch')
441
- .description('Retrieve external data to local file system.')
442
- .option('-p, --project-path [path]', `${pathGuideline}`);
465
+ const fetchCmd = new CommandWithPath('fetch').description('Retrieve external data to local file system.');
466
+ program.addCommand(fetchCmd);
443
467
  fetchCmd
444
468
  .command('hubs')
445
469
  .description('Retrieves module lists from hubs')
446
- .option('-p, --project-path [path]', `${pathGuideline}`)
447
470
  .action(async (options) => {
448
471
  const result = await commandHandler.command(Cmd.fetch, ['hubs'], Object.assign({}, options, program.opts()));
449
472
  handleResponse(result);
450
473
  });
451
- const importCmd = program
452
- .command('import')
453
- .description('Import modules and data into the project')
454
- .option('-p, --project-path [path]', `${pathGuideline}`);
474
+ const importCmd = new CommandWithPath('import').description('Import modules and data into the project');
475
+ program.addCommand(importCmd);
455
476
  // Import module
456
477
  importCmd
457
478
  .command('module')
@@ -459,7 +480,6 @@ importCmd
459
480
  .argument('[source]', 'Path to import from or module name. If omitted, shows interactive selection')
460
481
  .argument('[branch]', 'When using git URL defines the branch. Default: main')
461
482
  .argument('[useCredentials]', 'When using git URL uses credentials for cloning. Default: false')
462
- .option('-p, --project-path [path]', `${pathGuideline}`)
463
483
  .action(async (source, branch, useCredentials, options) => {
464
484
  let resolvedSource = source;
465
485
  let resolvedBranch = branch;
@@ -525,20 +545,68 @@ importCmd
525
545
  .command('csv')
526
546
  .description('Imports cards from a csv file')
527
547
  .argument('<csvFile>', 'File to import from')
528
- .argument('[cardKey]', 'Card key of the parent. If defined, cards are created as children of this card')
529
- .option('-p, --project-path [path]', `${pathGuideline}`)
548
+ .argument('[cardKey]', 'Parent card key. If defined, cards are created as children of this card')
530
549
  .action(async (csvFile, cardKey, options) => {
531
550
  const result = await commandHandler.command(Cmd.import, ['csv', csvFile, cardKey], Object.assign({}, options, program.opts()));
532
551
  handleResponse(result);
533
552
  });
534
- // Move command
553
+ // Migrate command
535
554
  program
536
- .command('move')
555
+ .command('migrate')
556
+ .description('Migrate project schema to a newer version.')
557
+ .argument('[version]', 'Target schema version. If not provided, migrates to the latest version. Can only migrate one version at a time when specified.')
558
+ .option('-p, --project-path [path]', `${pathGuideline}`)
559
+ .option('-b, --backup <directory>', 'Create a backup before migration in the specified directory. Directory must exist.')
560
+ .option('-t, --timeout <minutes>', 'Timeout for migration in minutes (default: 2 minutes)', '2')
561
+ .action(async (version, options) => {
562
+ if (version) {
563
+ const versionNumber = parseInt(version);
564
+ if (isNaN(versionNumber)) {
565
+ console.error(`Error: migration version is not a number: '${version}'`);
566
+ process.exit(1);
567
+ }
568
+ if (versionNumber <= 0) {
569
+ console.error(`Error: migration version must be above zero: '${version}'`);
570
+ process.exit(1);
571
+ }
572
+ if (options.backup) {
573
+ options.backup = resolve(options.backup.toString().trim());
574
+ if (!existsSync(options.backup)) {
575
+ console.error(`Error: Backup directory does not exist: ${options.backup}`);
576
+ process.exit(1);
577
+ }
578
+ try {
579
+ await access(options.backup, constants.W_OK);
580
+ }
581
+ catch {
582
+ console.error(`Error: Cannot write to backup directory: ${options.backup}`);
583
+ process.exit(1);
584
+ }
585
+ if (!(await lstat(options.backup)).isDirectory()) {
586
+ console.error(`Error: Backup directory is a file: ${options.backup}`);
587
+ process.exit(1);
588
+ }
589
+ }
590
+ }
591
+ if (options.timeout !== undefined) {
592
+ const timeoutMinutes = Number(options.timeout);
593
+ if (isNaN(timeoutMinutes) || timeoutMinutes <= 0) {
594
+ console.error(`Error: Timeout must be a positive number`);
595
+ process.exit(1);
596
+ }
597
+ // Convert minutes to milliseconds
598
+ options.timeout = timeoutMinutes * 60 * 1000;
599
+ }
600
+ const result = await commandHandler.command(Cmd.migrate, [version], Object.assign({}, options, program.opts()));
601
+ handleResponse(result);
602
+ });
603
+ // Move command
604
+ const moveCmd = new CommandWithPath('move')
537
605
  .description('Moves a card from root to under another card, from under another card to root, or from under a one card to another.')
538
606
  .argument('[source]', 'Source Card key that needs to be moved')
539
- .argument('[destination]', 'Destination Card key where "source" is moved to. If moving to root, use "root"')
540
- .option('-p, --project-path [path]', `${pathGuideline}`)
541
- .action(async (source, destination, options) => {
607
+ .argument('[destination]', 'Destination Card key where "source" is moved to. If moving to root, use "root"');
608
+ program.addCommand(moveCmd);
609
+ moveCmd.action(async (source, destination, options) => {
542
610
  const result = await commandHandler.command(Cmd.move, [source, destination], Object.assign({}, options, program.opts()));
543
611
  handleResponse(result);
544
612
  });
@@ -549,16 +617,13 @@ program
549
617
  .action(async (dir) => {
550
618
  await previewSite(dir || '.', true);
551
619
  });
552
- const rank = program
553
- .command('rank')
554
- .description('Manage card ranking and ordering')
555
- .option('-p, --project-path [path]', `${pathGuideline}`);
620
+ const rank = new CommandWithPath('rank').description('Manage card ranking and ordering');
621
+ program.addCommand(rank);
556
622
  rank
557
623
  .command('card')
558
624
  .description('Set the rank of a card. Ranks define the order in which cards are shown.')
559
625
  .argument('<cardKey>', 'Card key of the card to be moved')
560
626
  .argument('<afterCardKey>', 'Card key of the card that the card should be after. Use "first" to rank the card first.')
561
- .option('-p, --project-path [path]', `${pathGuideline}`)
562
627
  .action(async (cardKey, afterCardKey, options) => {
563
628
  const result = await commandHandler.command(Cmd.rank, ['card', cardKey, afterCardKey], Object.assign({}, options, program.opts()));
564
629
  handleResponse(result);
@@ -566,22 +631,20 @@ rank
566
631
  rank
567
632
  .command('rebalance')
568
633
  .description('Rebalance the rank of all cards in the project. Can be also used, if ranks do not exist')
569
- .argument('[parentCardKey]', 'if null, rebalance the whole project, otherwise rebalance only the direct children of the card key')
570
- .option('-p, --project-path [path]', `${pathGuideline}`)
634
+ .argument('[parentCardKey]', 'if null, rebalance the whole project, otherwise rebalance only the direct children')
571
635
  .action(async (cardKey, options) => {
572
636
  const result = await commandHandler.command(Cmd.rank, ['rebalance', cardKey], Object.assign({}, options, program.opts()));
573
637
  handleResponse(result);
574
638
  });
575
639
  // Remove command
576
- program
577
- .command('remove')
578
- .description('Remove cards, resources and other project items')
640
+ const removeCmd = new CommandWithPath('remove').description('Remove cards, resources and other project items');
641
+ program.addCommand(removeCmd);
642
+ removeCmd
579
643
  .argument('<type>', `removable types: '${Parser.listTargets('remove').join("', '")}', or resource name (e.g. <prefix>/<type>/<identifier>)`, Parser.parseRemoveTypes)
580
644
  .argument('[parameter1]', 'Depends on context; see below for specific remove operation')
581
645
  .argument('[parameter2]', 'Depends on context; see below for specific remove operation')
582
646
  .argument('[parameter3]', 'Depends on context; see below for specific remove operation')
583
647
  .addHelpText('after', additionalHelpForRemove)
584
- .option('-p, --project-path [path]', `${pathGuideline}`)
585
648
  .action(async (type, parameter1, parameter2, parameter3, options) => {
586
649
  if (type) {
587
650
  if (!parameter1) {
@@ -620,36 +683,33 @@ program
620
683
  }
621
684
  });
622
685
  // Rename command
623
- program
624
- .command('rename')
686
+ const renameCmd = new CommandWithPath('rename')
625
687
  .description('Change project prefix and rename all the content with the new prefix')
626
- .argument('<to>', 'New project prefix')
627
- .option('-p, --project-path [path]', `${pathGuideline}`)
628
- .action(async (to, options) => {
688
+ .argument('<to>', 'New project prefix');
689
+ program.addCommand(renameCmd);
690
+ renameCmd.action(async (to, options) => {
629
691
  const result = await commandHandler.command(Cmd.rename, [to], Object.assign({}, options, program.opts()));
630
692
  handleResponse(result);
631
693
  });
632
694
  // Report command
633
- program
634
- .command('report')
695
+ const reportCmd = new CommandWithPath('report')
635
696
  .description('Runs a report')
636
697
  .argument('<parameters>', 'Path to parameters file. This file defines which report to run and what parameters to use.')
637
698
  .argument('[output]', 'Optional output file; if omitted output will be directed to stdout')
638
- .addOption(contextOption)
639
- .option('-p, --project-path [path]', `${pathGuideline}`)
640
- .action(async (parameters, output, options) => {
699
+ .addOption(contextOption);
700
+ program.addCommand(reportCmd);
701
+ reportCmd.action(async (parameters, output, options) => {
641
702
  const result = await commandHandler.command(Cmd.report, [parameters, output], Object.assign({}, options, program.opts()));
642
703
  handleResponse(result);
643
704
  });
644
705
  // Show command
645
- program
646
- .command('show')
647
- .description('Shows details from a project')
706
+ const showCmd = new CommandWithPath('show').description('Shows details from a project');
707
+ program.addCommand(showCmd);
708
+ showCmd
648
709
  .argument('<type>', `details can be seen from: ${Parser.listTargets('show').join(', ')}`, Parser.parseShowTypes)
649
710
  .argument('[typeDetail]', 'additional information about the requested type; for example a card key')
650
711
  .option('-d --details', 'Certain types (such as cards) can have additional details')
651
712
  .option('-a --showAll', 'Show all modules, irregardless if it has been imported or not. Only with "show importableModules"')
652
- .option('-p, --project-path [path]', `${pathGuideline}`)
653
713
  .option('-u --show-use', 'Show where resource is used. Only used with resources, otherwise will be ignored.')
654
714
  .action(async (type, typeDetail, options) => {
655
715
  if (type !== '') {
@@ -666,48 +726,43 @@ program
666
726
  }
667
727
  });
668
728
  // Transition command
669
- program
670
- .command('transition')
729
+ const transitionCmd = new CommandWithPath('transition')
671
730
  .description('Transition a card to the specified state')
672
731
  .argument('<cardKey>', 'card key of a card')
673
- .argument('<transition>', 'Workflow state transition that is done.\nYou can list the workflows in a project with "show workflows" command.\nYou can see the available transitions with "show workflow <name>" command.')
674
- .option('-p, --project-path [path]', `${pathGuideline}`)
675
- .action(async (cardKey, transition, options) => {
732
+ .argument('<transition>', 'Workflow state transition that is done.\nYou can list the workflows in a project with "show workflows" command.\nYou can see the available transitions with "show workflow <name>" command.');
733
+ program.addCommand(transitionCmd);
734
+ transitionCmd.action(async (cardKey, transition, options) => {
676
735
  const result = await commandHandler.command(Cmd.transition, [cardKey, transition], Object.assign({}, options, program.opts()));
677
736
  handleResponse(result);
678
737
  });
679
738
  // Update command
680
- program
681
- .command('update')
682
- .description('Update resource details')
739
+ const updateCmd = new CommandWithPath('update').description('Update resource details');
740
+ program.addCommand(updateCmd);
741
+ updateCmd
683
742
  .argument('<resourceName>', 'Resource name')
684
743
  .argument('<operation>', 'Type of change, either "add", "change", "rank" or "remove" ')
685
744
  .argument('<key>', 'Detail to be changed')
686
745
  .argument('<value>', 'Value for a detail')
687
746
  .argument('[newValue]', 'When using "change" define new value for detail.\nWhen using "remove" provide optional replacement value for removed value')
688
- .option('-m, --mapping-file [path]', 'Path to JSON file containing workflow state mapping (only used when changing workflow)')
689
- .option('-p, --project-path [path]', `${pathGuideline}`)
747
+ .option('-m, --mapping-file <path>', 'Path to JSON file containing workflow state mapping (only used when changing workflow)')
690
748
  .addHelpText('after', additionalHelpForUpdate)
691
749
  .action(async (resourceName, operation, key, value, newValue, options) => {
692
750
  const result = await commandHandler.command(Cmd.update, [resourceName, operation, key, value, newValue], Object.assign({}, options, program.opts()));
693
751
  handleResponse(result);
694
752
  });
695
753
  // Updates all modules, or specific named module in the project.
696
- program
697
- .command('update-modules')
754
+ const updateModulesCmd = new CommandWithPath('update-modules')
698
755
  .description('Updates to latest versions either all modules or a specific module')
699
- .argument('[moduleName]', 'Module name')
700
- .option('-p, --project-path [path]', `${pathGuideline}`)
701
- .action(async (moduleName, options) => {
756
+ .argument('[moduleName]', 'Module name');
757
+ program.addCommand(updateModulesCmd);
758
+ updateModulesCmd.action(async (moduleName, options) => {
702
759
  const result = await commandHandler.command(Cmd.updateModules, [moduleName], Object.assign({}, options, program.opts()), credentials());
703
760
  handleResponse(result);
704
761
  });
705
762
  // Validate command
706
- program
707
- .command('validate')
708
- .description('Validate project structure')
709
- .option('-p, --project-path [path]', `${pathGuideline}`)
710
- .action(async (options) => {
763
+ const validateCmd = new CommandWithPath('validate').description('Validate project structure');
764
+ program.addCommand(validateCmd);
765
+ validateCmd.action(async (options) => {
711
766
  const result = await commandHandler.command(Cmd.validate, [], Object.assign({}, options, program.opts()));
712
767
  handleResponse(result);
713
768
  });
@@ -716,12 +771,11 @@ program
716
771
  // There is 10 sec timeout on the prompt. If user does not reply, then
717
772
  // it is assumed that validation errors do not matter and application
718
773
  // start is resumed.
719
- program
720
- .command('app')
774
+ const appCmd = new CommandWithPath('app')
721
775
  .description('Starts the cyberismo app, accessible with a web browser at http://localhost:3000')
722
- .option('-w, --watch-resource-changes', 'Project watches changes in .cards folder resources')
723
- .option('-p, --project-path [path]', `${pathGuideline}`)
724
- .action(async (options) => {
776
+ .option('-w, --watch-resource-changes', 'Project watches changes in .cards folder resources');
777
+ program.addCommand(appCmd);
778
+ appCmd.action(async (options) => {
725
779
  // validate project
726
780
  const result = await commandHandler.command(Cmd.validate, [], Object.assign({}, options, program.opts()));
727
781
  if (!result.message) {