@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/src/index.ts CHANGED
@@ -12,7 +12,8 @@
12
12
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
13
  */
14
14
 
15
- import { readFile } from 'node:fs/promises';
15
+ import { constants, existsSync } from 'node:fs';
16
+ import { access, lstat, readFile } from 'node:fs/promises';
16
17
  import { resolve } from 'node:path';
17
18
 
18
19
  import { Argument, Command, Option } from 'commander';
@@ -138,62 +139,6 @@ const nameGuideline =
138
139
  const pathGuideline =
139
140
  'Path to the project root. Mandatory if not running inside a project tree.';
140
141
 
141
- const additionalHelpForCreate = `Sub-command help:
142
- create attachment <cardKey> <filename>, where
143
- <cardKey> is card key of a card to have the attachment,
144
- <filename> is attachment filename.
145
-
146
- create card <template> [cardKey], where
147
- <template> Template to use. You can list the templates in a project with "show templates" command.
148
- [cardKey] Parent card's card key. If defined, new card will be created as a child card to that card.
149
-
150
- create cardType <name> <workflow>, where
151
- <name> Name for cardType. ${nameGuideline}
152
- <workflow> Workflow for the card type. You can list workflows in a project with "show workflows" command.
153
-
154
- create fieldType <name> <dataType>, where
155
- <name> Name for fieldType. ${nameGuideline}
156
- <dataType> Type of field. You can list field types in a project with "show fieldTypes" command.
157
-
158
- create graphModel <name>, where
159
- <name> Name for graph model. ${nameGuideline}
160
-
161
- create graphView <name>, where
162
- <name> Name for graph view. ${nameGuideline}
163
-
164
- create label <cardKey> <labelName>, where
165
- <cardKey> Card key of the label
166
- <labelName> Name for the new label
167
-
168
- create link <source> <destination> <linkType> [description], where
169
- <source> Source card key of the link
170
- <destination> Destination card key of the link
171
- <linkType> Link type to create
172
- [description] Link description
173
-
174
- create linkType <name>, where
175
- <name> Name for linkType. ${nameGuideline}
176
-
177
- create project <name> <prefix> <path>, where
178
- <name> Name of the project.
179
- <prefix> Prefix for the project.
180
- <path> Path where to create the project
181
-
182
- create report <name>, where
183
- <name> Name for report. ${nameGuideline}
184
-
185
- create template <name> [content], where
186
- <name> Name for template. ${nameGuideline}
187
- [content] If empty, template is created with default values. Template content must conform to schema "templateSchema.json"
188
-
189
- create workflow <name> [content], where
190
- <name> Name for workflow. ${nameGuideline}
191
- [content] If empty, workflow is created with default values. Workflow content must conform to schema "workflowSchema.json"
192
-
193
- create <resourceName> [content], where
194
- <resourceName> Name of the resource (e.g. <prefix>/<type>/<identifier>)
195
- [content] If empty, resource is created with default values. Content must conform to its resource schema.`;
196
-
197
142
  const additionalHelpForRemove = `Sub-command help:
198
143
  remove attachment <cardKey> <filename>, where
199
144
  <cardKey> is card key of the owning card,
@@ -256,6 +201,20 @@ const contextOption = new Option(
256
201
  .choices(validContexts)
257
202
  .default('app');
258
203
 
204
+ const pathOption = new Option('-p, --project-path <path>', pathGuideline);
205
+
206
+ // Custom Command class with pathOption pre-configured
207
+ class CommandWithPath extends Command {
208
+ createCommand(name?: string): CommandWithPath {
209
+ return new CommandWithPath(name);
210
+ }
211
+
212
+ constructor(name?: string) {
213
+ super(name);
214
+ this.addOption(pathOption);
215
+ }
216
+ }
217
+
259
218
  // Main CLI program.
260
219
  program
261
220
  .name('cyberismo')
@@ -272,7 +231,10 @@ program
272
231
  .default('fatal'),
273
232
  );
274
233
 
275
- const addCmd = program.command('add').description('Add items to the project');
234
+ const addCmd = new CommandWithPath('add').description(
235
+ 'Add items to the project',
236
+ );
237
+ program.addCommand(addCmd);
276
238
 
277
239
  // Add card to a template
278
240
  addCmd
@@ -287,7 +249,6 @@ addCmd
287
249
  'Card type to use for the new card. \nYou can list the card types in a project with "show cardTypes" command.',
288
250
  )
289
251
  .argument('[cardKey]', "Parent card's card key")
290
- .option('-p, --project-path [path]', `${pathGuideline}`)
291
252
  .option('-r, --repeat <quantity>', 'Add multiple cards to a template')
292
253
  .action(
293
254
  async (
@@ -312,7 +273,6 @@ addCmd
312
273
  '<location>',
313
274
  'Hub URL. Default hub can be added by using "default"',
314
275
  )
315
- .option('-p, --project-path [path]', `${pathGuideline}`)
316
276
  .action(async (location: string, options: CommandOptions<'add'>) => {
317
277
  if (location === 'default') {
318
278
  location = DEFAULT_HUB;
@@ -325,9 +285,10 @@ addCmd
325
285
  handleResponse(result);
326
286
  });
327
287
 
328
- const calculate = program
329
- .command('calc')
330
- .description('Used for running logic programs');
288
+ const calculate = new CommandWithPath('calc').description(
289
+ 'Used for running logic programs',
290
+ );
291
+ program.addCommand(calculate);
331
292
 
332
293
  calculate
333
294
  .command('generate')
@@ -337,7 +298,6 @@ calculate
337
298
  'Path to an output file. Command writes the logic program to this file.',
338
299
  )
339
300
  .argument('[query]', 'Query to run')
340
- .option('-p, --project-path [path]', `${pathGuideline}`)
341
301
  .action(
342
302
  async (
343
303
  destination: string,
@@ -358,7 +318,6 @@ calculate
358
318
  .description('Run a logic program')
359
319
  .argument('<filePath>', 'Path to the logic program')
360
320
  .addOption(contextOption)
361
- .option('-p, --project-path [path]', `${pathGuideline}`)
362
321
  .action(async (filePath: string, options: CommandOptions<'calc'>) => {
363
322
  const result = await commandHandler.command(
364
323
  Cmd.calc,
@@ -368,120 +327,231 @@ calculate
368
327
  handleResponse(result);
369
328
  });
370
329
 
371
- program
372
- .command('create')
373
- .description('Create cards, resources and other project items')
374
- .argument(
375
- '<type>',
376
- `types to create: '${Parser.listTargets('create').join("', '")}', or resource name (e.g. <prefix>/<type>/<identifier>)`,
377
- Parser.parseCreateTypes,
378
- )
330
+ const createCmd = new CommandWithPath('create').description(
331
+ 'Create cards, resources and other project items',
332
+ );
333
+ program.addCommand(createCmd);
334
+
335
+ // Create attachment subcommand
336
+ createCmd
337
+ .command('attachment')
338
+ .description('Create an attachment for a card')
339
+ .argument('<cardKey>', 'Card key of the card to attach to')
340
+ .argument('<filename>', 'Path to the file to attach')
341
+ .action(
342
+ async (
343
+ cardKey: string,
344
+ filename: string,
345
+ options: CommandOptions<'create'>,
346
+ ) => {
347
+ const result = await commandHandler.command(
348
+ Cmd.create,
349
+ ['attachment', cardKey, filename],
350
+ Object.assign({}, options, program.opts()),
351
+ );
352
+ handleResponse(result);
353
+ },
354
+ );
355
+
356
+ // Create card subcommand
357
+ createCmd
358
+ .command('card')
359
+ .description('Create a card from a template')
379
360
  .argument(
380
- '[target]',
381
- 'Name to create, or in some operations cardKey to create data to a specific card. See below',
361
+ '<template>',
362
+ 'Template to use. You can list templates with "show templates" command',
382
363
  )
383
364
  .argument(
384
- '[parameter1]',
385
- 'Depends on context; see below for specific remove operation',
365
+ '[parentCardKey]',
366
+ "Parent card's card key. If defined, new card will be created as a child",
386
367
  )
368
+ .action(
369
+ async (
370
+ template: string,
371
+ parentCardKey: string | undefined,
372
+ options: CommandOptions<'create'>,
373
+ ) => {
374
+ const result = await commandHandler.command(
375
+ Cmd.create,
376
+ ['card', template, parentCardKey].filter(Boolean) as string[],
377
+ Object.assign({}, options, program.opts()),
378
+ );
379
+ handleResponse(result);
380
+ },
381
+ );
382
+
383
+ // Create cardType subcommand
384
+ createCmd
385
+ .command('cardType')
386
+ .description('Create a new card type')
387
+ .argument('<name>', `Name for card type. ${nameGuideline}`)
387
388
  .argument(
388
- '[parameter2]',
389
- 'Depends on context; see below for specific remove operation',
389
+ '<workflow>',
390
+ 'Workflow for the card type. You can list workflows with "show workflows" command',
390
391
  )
392
+ .action(
393
+ async (
394
+ name: string,
395
+ workflow: string,
396
+ options: CommandOptions<'create'>,
397
+ ) => {
398
+ const result = await commandHandler.command(
399
+ Cmd.create,
400
+ ['cardType', name, workflow],
401
+ Object.assign({}, options, program.opts()),
402
+ );
403
+ handleResponse(result);
404
+ },
405
+ );
406
+
407
+ // Create fieldType subcommand
408
+ createCmd
409
+ .command('fieldType')
410
+ .description('Create a new field type')
411
+ .argument('<name>', `Name for field type. ${nameGuideline}`)
391
412
  .argument(
392
- '[parameter3]',
393
- 'Depends on context; see below for specific remove operation',
394
- )
395
- .addHelpText('after', additionalHelpForCreate)
396
- .option('-p, --project-path [path]', `${pathGuideline}`)
397
- .option(
398
- '-s, --skipModuleImport',
399
- 'Skip importing modules when creating a project',
413
+ '<dataType>',
414
+ 'Type of field. You can list field types with "show fieldTypes" command',
400
415
  )
401
416
  .action(
402
417
  async (
403
- type: string,
404
- target: string,
405
- parameter1: string,
406
- parameter2: string,
407
- parameter3: string,
418
+ name: string,
419
+ dataType: string,
408
420
  options: CommandOptions<'create'>,
409
421
  ) => {
410
- if (!type) {
411
- program.error(`missing required argument <type>`);
412
- }
413
-
414
- const resourceName: boolean = type.split('/').length === 3;
422
+ const result = await commandHandler.command(
423
+ Cmd.create,
424
+ ['fieldType', name, dataType],
425
+ Object.assign({}, options, program.opts()),
426
+ );
427
+ handleResponse(result);
428
+ },
429
+ );
415
430
 
416
- function nameOfFirstArgument(type: string) {
417
- if (type === 'attachment' || type === 'label') return 'cardKey';
418
- if (type === 'card') return 'template';
419
- if (type === 'link') return 'source';
420
- return 'name';
421
- }
431
+ // Create graphModel subcommand
432
+ createCmd
433
+ .command('graphModel')
434
+ .description('Create a new graph model')
435
+ .argument('<name>', `Name for graph model. ${nameGuideline}`)
436
+ .action(async (name: string, options: CommandOptions<'create'>) => {
437
+ const result = await commandHandler.command(
438
+ Cmd.create,
439
+ ['graphModel', name],
440
+ Object.assign({}, options, program.opts()),
441
+ );
442
+ handleResponse(result);
443
+ });
422
444
 
423
- function nameOfSecondArgument(type: string) {
424
- if (type === 'attachment') return 'fileName';
425
- if (type === 'cardType') return 'workflow';
426
- if (type === 'fieldType') return 'dataType';
427
- if (type === 'label') return 'labelName';
428
- if (type === 'link') return 'destination';
429
- if (type === 'project') return 'prefix';
430
- return type;
431
- }
445
+ // Create graphView subcommand
446
+ createCmd
447
+ .command('graphView')
448
+ .description('Create a new graph view')
449
+ .argument('<name>', `Name for graph view. ${nameGuideline}`)
450
+ .action(async (name: string, options: CommandOptions<'create'>) => {
451
+ const result = await commandHandler.command(
452
+ Cmd.create,
453
+ ['graphView', name],
454
+ Object.assign({}, options, program.opts()),
455
+ );
456
+ handleResponse(result);
457
+ });
432
458
 
433
- if (!target && !resourceName) {
434
- program.error(
435
- `missing required argument <${nameOfFirstArgument(type)}>`,
436
- );
437
- }
459
+ // Create label subcommand
460
+ createCmd
461
+ .command('label')
462
+ .description('Create a label on a card')
463
+ .argument('<cardKey>', 'Card key')
464
+ .argument('<labelName>', 'Name for the new label')
465
+ .action(
466
+ async (
467
+ cardKey: string,
468
+ labelName: string,
469
+ options: CommandOptions<'create'>,
470
+ ) => {
471
+ const result = await commandHandler.command(
472
+ Cmd.create,
473
+ ['label', cardKey, labelName],
474
+ Object.assign({}, options, program.opts()),
475
+ );
476
+ handleResponse(result);
477
+ },
478
+ );
438
479
 
439
- if (
440
- !resourceName &&
441
- !parameter1 &&
442
- type !== 'card' &&
443
- type !== 'graphModel' &&
444
- type !== 'graphView' &&
445
- type !== 'linkType' &&
446
- type !== 'report' &&
447
- type !== 'template' &&
448
- type !== 'workflow'
449
- ) {
450
- program.error(
451
- `missing required argument <${nameOfSecondArgument(type)}>`,
452
- );
453
- }
480
+ // Create link subcommand
481
+ createCmd
482
+ .command('link')
483
+ .description('Create a link between two cards')
484
+ .argument('<source>', 'Source card key')
485
+ .argument('<destination>', 'Destination card key')
486
+ .argument('<linkType>', 'Link type to create')
487
+ .argument('[description]', 'Optional link description')
488
+ .action(
489
+ async (
490
+ source: string,
491
+ destination: string,
492
+ linkType: string,
493
+ description: string | undefined,
494
+ options: CommandOptions<'create'>,
495
+ ) => {
496
+ const result = await commandHandler.command(
497
+ Cmd.create,
498
+ ['link', source, destination, linkType, description].filter(
499
+ Boolean,
500
+ ) as string[],
501
+ Object.assign({}, options, program.opts()),
502
+ );
503
+ handleResponse(result);
504
+ },
505
+ );
454
506
 
455
- if (
456
- resourceName &&
457
- (type.includes('cardTypes') || type.includes('fieldTypes')) &&
458
- !target
459
- ) {
460
- program.error(
461
- `missing required argument <${nameOfSecondArgument(type)}>`,
462
- );
463
- }
507
+ // Create linkType subcommand
508
+ createCmd
509
+ .command('linkType')
510
+ .description('Create a new link type')
511
+ .argument('<name>', `Name for link type. ${nameGuideline}`)
512
+ .action(async (name: string, options: CommandOptions<'create'>) => {
513
+ const result = await commandHandler.command(
514
+ Cmd.create,
515
+ ['linkType', name],
516
+ Object.assign({}, options, program.opts()),
517
+ );
518
+ handleResponse(result);
519
+ });
464
520
 
465
- if (type === 'project') {
466
- if (!parameter2) {
467
- program.error(`missing required argument <path>`);
468
- }
469
- // Project path must be set to 'options' when creating a project.
470
- options.projectPath = parameter2;
471
- }
521
+ // Create project subcommand
522
+ createCmd
523
+ .command('project')
524
+ .description('Create a new project')
525
+ .argument('<name>', 'Project name')
526
+ .argument('<prefix>', 'Project prefix')
527
+ .argument('<path>', 'Path where to create the project')
528
+ .argument('[category]', 'Project category (optional)')
529
+ .argument('[description]', 'Project description (optional)')
530
+ .option(
531
+ '-s, --skipModuleImport',
532
+ 'Skip importing modules when creating a project',
533
+ )
534
+ .action(
535
+ async (
536
+ name: string,
537
+ prefix: string,
538
+ path: string,
539
+ category: string | undefined,
540
+ description: string | undefined,
541
+ options: CommandOptions<'create'>,
542
+ ) => {
543
+ // Project path must be set to 'options' when creating a project
544
+ options.projectPath = path;
472
545
  const commandOptions = Object.assign({}, options, program.opts());
546
+
473
547
  const result = await commandHandler.command(
474
548
  Cmd.create,
475
- [type, target, parameter1, parameter2, parameter3],
549
+ ['project', name, prefix, path, category || '', description || ''],
476
550
  commandOptions,
477
551
  );
478
552
 
479
- // Post-handling after creating a new project.
480
- if (
481
- type === 'project' &&
482
- !commandOptions.skipModuleImport &&
483
- result.statusCode === 200
484
- ) {
553
+ // Post-handling after creating a new project
554
+ if (!commandOptions.skipModuleImport && result.statusCode === 200) {
485
555
  try {
486
556
  // add default hub
487
557
  await commandHandler.command(
@@ -528,25 +598,115 @@ program
528
598
  },
529
599
  );
530
600
 
531
- // Edit command
532
- program
533
- .command('edit')
534
- .description('Edit a card')
535
- .argument('<cardKey>', 'Card key of card')
536
- .option('-p, --project-path [path]', `${pathGuideline}`)
537
- .action(async (cardKey: string, options: CommandOptions<'edit'>) => {
601
+ // Create report subcommand
602
+ createCmd
603
+ .command('report')
604
+ .description('Create a new report')
605
+ .argument('<name>', `Name for report. ${nameGuideline}`)
606
+ .action(async (name: string, options: CommandOptions<'create'>) => {
538
607
  const result = await commandHandler.command(
539
- Cmd.edit,
540
- [cardKey],
608
+ Cmd.create,
609
+ ['report', name],
541
610
  Object.assign({}, options, program.opts()),
542
611
  );
543
612
  handleResponse(result);
544
613
  });
545
614
 
615
+ // Create template subcommand
616
+ createCmd
617
+ .command('template')
618
+ .description('Create a new template')
619
+ .argument('<name>', `Name for template. ${nameGuideline}`)
620
+ .argument(
621
+ '[content]',
622
+ 'Template content. If empty, template is created with default values. Must conform to templateSchema.json',
623
+ )
624
+ .action(
625
+ async (
626
+ name: string,
627
+ content: string | undefined,
628
+ options: CommandOptions<'create'>,
629
+ ) => {
630
+ const result = await commandHandler.command(
631
+ Cmd.create,
632
+ ['template', name, content].filter(Boolean) as string[],
633
+ Object.assign({}, options, program.opts()),
634
+ );
635
+ handleResponse(result);
636
+ },
637
+ );
638
+
639
+ // Create workflow subcommand
640
+ createCmd
641
+ .command('workflow')
642
+ .description('Create a new workflow')
643
+ .argument('<name>', `Name for workflow. ${nameGuideline}`)
644
+ .argument(
645
+ '[content]',
646
+ 'Workflow content. If empty, workflow is created with default values. Must conform to workflowSchema.json',
647
+ )
648
+ .action(
649
+ async (
650
+ name: string,
651
+ content: string | undefined,
652
+ options: CommandOptions<'create'>,
653
+ ) => {
654
+ const result = await commandHandler.command(
655
+ Cmd.create,
656
+ ['workflow', name, content].filter(Boolean) as string[],
657
+ Object.assign({}, options, program.opts()),
658
+ );
659
+ handleResponse(result);
660
+ },
661
+ );
662
+
663
+ // Create new resource subcommand
664
+ createCmd
665
+ .command('resource')
666
+ .description('Create a new resource')
667
+ .argument(
668
+ '<resourceName>',
669
+ 'Resource name (e.g. <prefix>/<type>/<identifier>)',
670
+ )
671
+ .argument(
672
+ '[content]',
673
+ 'Resource content. If empty, resource is created with default values. Must conform to its resource schema',
674
+ )
675
+ .action(
676
+ async (
677
+ resourceName: string,
678
+ content: string | undefined,
679
+ options: CommandOptions<'create'>,
680
+ ) => {
681
+ const result = await commandHandler.command(
682
+ Cmd.create,
683
+ [resourceName, content].filter(Boolean) as string[],
684
+ Object.assign({}, options, program.opts()),
685
+ );
686
+ handleResponse(result);
687
+ },
688
+ );
689
+
690
+ // Edit command
691
+ const editCmd = new CommandWithPath('edit')
692
+ .description('Edit a card')
693
+ .argument('<cardKey>', 'Card key of card');
694
+ program.addCommand(editCmd);
695
+ editCmd.action(async (cardKey: string, options: CommandOptions<'edit'>) => {
696
+ const result = await commandHandler.command(
697
+ Cmd.edit,
698
+ [cardKey],
699
+ Object.assign({}, options, program.opts()),
700
+ );
701
+ handleResponse(result);
702
+ });
703
+
546
704
  // Export command
547
- program
548
- .command('export')
549
- .description('Export a project or a card')
705
+ const exportCmd = new CommandWithPath('export').description(
706
+ 'Export a project or a card',
707
+ );
708
+ program.addCommand(exportCmd);
709
+ exportCmd
550
710
  .addArgument(
551
711
  new Argument('<format>', 'Export format').choices(
552
712
  Object.values(ExportFormats),
@@ -557,7 +717,6 @@ program
557
717
  '[cardKey]',
558
718
  'Export a specific card by card key. If omitted, exports the whole site.',
559
719
  )
560
- .option('-p, --project-path [path]', `${pathGuideline}`)
561
720
  .option(
562
721
  '-r, --recursive',
563
722
  'Export cards under the specified card recursively',
@@ -637,15 +796,14 @@ program
637
796
  },
638
797
  );
639
798
 
640
- const fetchCmd = program
641
- .command('fetch')
642
- .description('Retrieve external data to local file system.')
643
- .option('-p, --project-path [path]', `${pathGuideline}`);
799
+ const fetchCmd = new CommandWithPath('fetch').description(
800
+ 'Retrieve external data to local file system.',
801
+ );
802
+ program.addCommand(fetchCmd);
644
803
 
645
804
  fetchCmd
646
805
  .command('hubs')
647
806
  .description('Retrieves module lists from hubs')
648
- .option('-p, --project-path [path]', `${pathGuideline}`)
649
807
  .action(async (options: CommandOptions<'fetch'>) => {
650
808
  const result = await commandHandler.command(
651
809
  Cmd.fetch,
@@ -655,10 +813,10 @@ fetchCmd
655
813
  handleResponse(result);
656
814
  });
657
815
 
658
- const importCmd = program
659
- .command('import')
660
- .description('Import modules and data into the project')
661
- .option('-p, --project-path [path]', `${pathGuideline}`);
816
+ const importCmd = new CommandWithPath('import').description(
817
+ 'Import modules and data into the project',
818
+ );
819
+ program.addCommand(importCmd);
662
820
 
663
821
  // Import module
664
822
  importCmd
@@ -675,7 +833,6 @@ importCmd
675
833
  '[useCredentials]',
676
834
  'When using git URL uses credentials for cloning. Default: false',
677
835
  )
678
- .option('-p, --project-path [path]', `${pathGuideline}`)
679
836
  .action(
680
837
  async (
681
838
  source: string,
@@ -763,9 +920,8 @@ importCmd
763
920
  .argument('<csvFile>', 'File to import from')
764
921
  .argument(
765
922
  '[cardKey]',
766
- 'Card key of the parent. If defined, cards are created as children of this card',
923
+ 'Parent card key. If defined, cards are created as children of this card',
767
924
  )
768
- .option('-p, --project-path [path]', `${pathGuideline}`)
769
925
  .action(
770
926
  async (
771
927
  csvFile: string,
@@ -781,9 +937,80 @@ importCmd
781
937
  },
782
938
  );
783
939
 
784
- // Move command
940
+ // Migrate command
785
941
  program
786
- .command('move')
942
+ .command('migrate')
943
+ .description('Migrate project schema to a newer version.')
944
+ .argument(
945
+ '[version]',
946
+ 'Target schema version. If not provided, migrates to the latest version. Can only migrate one version at a time when specified.',
947
+ )
948
+ .option('-p, --project-path [path]', `${pathGuideline}`)
949
+ .option(
950
+ '-b, --backup <directory>',
951
+ 'Create a backup before migration in the specified directory. Directory must exist.',
952
+ )
953
+ .option(
954
+ '-t, --timeout <minutes>',
955
+ 'Timeout for migration in minutes (default: 2 minutes)',
956
+ '2',
957
+ )
958
+ .action(async (version: string, options: CommandOptions<'migrate'>) => {
959
+ if (version) {
960
+ const versionNumber = parseInt(version);
961
+ if (isNaN(versionNumber)) {
962
+ console.error(`Error: migration version is not a number: '${version}'`);
963
+ process.exit(1);
964
+ }
965
+ if (versionNumber <= 0) {
966
+ console.error(
967
+ `Error: migration version must be above zero: '${version}'`,
968
+ );
969
+ process.exit(1);
970
+ }
971
+ if (options.backup) {
972
+ options.backup = resolve(options.backup.toString().trim());
973
+ if (!existsSync(options.backup)) {
974
+ console.error(
975
+ `Error: Backup directory does not exist: ${options.backup}`,
976
+ );
977
+ process.exit(1);
978
+ }
979
+ try {
980
+ await access(options.backup, constants.W_OK);
981
+ } catch {
982
+ console.error(
983
+ `Error: Cannot write to backup directory: ${options.backup}`,
984
+ );
985
+ process.exit(1);
986
+ }
987
+ if (!(await lstat(options.backup)).isDirectory()) {
988
+ console.error(`Error: Backup directory is a file: ${options.backup}`);
989
+ process.exit(1);
990
+ }
991
+ }
992
+ }
993
+
994
+ if (options.timeout !== undefined) {
995
+ const timeoutMinutes = Number(options.timeout);
996
+ if (isNaN(timeoutMinutes) || timeoutMinutes <= 0) {
997
+ console.error(`Error: Timeout must be a positive number`);
998
+ process.exit(1);
999
+ }
1000
+ // Convert minutes to milliseconds
1001
+ options.timeout = timeoutMinutes * 60 * 1000;
1002
+ }
1003
+
1004
+ const result = await commandHandler.command(
1005
+ Cmd.migrate,
1006
+ [version],
1007
+ Object.assign({}, options, program.opts()),
1008
+ );
1009
+ handleResponse(result);
1010
+ });
1011
+
1012
+ // Move command
1013
+ const moveCmd = new CommandWithPath('move')
787
1014
  .description(
788
1015
  'Moves a card from root to under another card, from under another card to root, or from under a one card to another.',
789
1016
  )
@@ -791,22 +1018,22 @@ program
791
1018
  .argument(
792
1019
  '[destination]',
793
1020
  'Destination Card key where "source" is moved to. If moving to root, use "root"',
794
- )
795
- .option('-p, --project-path [path]', `${pathGuideline}`)
796
- .action(
797
- async (
798
- source: string,
799
- destination: string,
800
- options: CommandOptions<'move'>,
801
- ) => {
802
- const result = await commandHandler.command(
803
- Cmd.move,
804
- [source, destination],
805
- Object.assign({}, options, program.opts()),
806
- );
807
- handleResponse(result);
808
- },
809
1021
  );
1022
+ program.addCommand(moveCmd);
1023
+ moveCmd.action(
1024
+ async (
1025
+ source: string,
1026
+ destination: string,
1027
+ options: CommandOptions<'move'>,
1028
+ ) => {
1029
+ const result = await commandHandler.command(
1030
+ Cmd.move,
1031
+ [source, destination],
1032
+ Object.assign({}, options, program.opts()),
1033
+ );
1034
+ handleResponse(result);
1035
+ },
1036
+ );
810
1037
 
811
1038
  program
812
1039
  .command('preview')
@@ -819,10 +1046,10 @@ program
819
1046
  await previewSite(dir || '.', true);
820
1047
  });
821
1048
 
822
- const rank = program
823
- .command('rank')
824
- .description('Manage card ranking and ordering')
825
- .option('-p, --project-path [path]', `${pathGuideline}`);
1049
+ const rank = new CommandWithPath('rank').description(
1050
+ 'Manage card ranking and ordering',
1051
+ );
1052
+ program.addCommand(rank);
826
1053
 
827
1054
  rank
828
1055
  .command('card')
@@ -834,7 +1061,6 @@ rank
834
1061
  '<afterCardKey>',
835
1062
  'Card key of the card that the card should be after. Use "first" to rank the card first.',
836
1063
  )
837
- .option('-p, --project-path [path]', `${pathGuideline}`)
838
1064
  .action(
839
1065
  async (
840
1066
  cardKey: string,
@@ -857,9 +1083,8 @@ rank
857
1083
  )
858
1084
  .argument(
859
1085
  '[parentCardKey]',
860
- 'if null, rebalance the whole project, otherwise rebalance only the direct children of the card key',
1086
+ 'if null, rebalance the whole project, otherwise rebalance only the direct children',
861
1087
  )
862
- .option('-p, --project-path [path]', `${pathGuideline}`)
863
1088
  .action(async (cardKey: string, options: CommandOptions<'rank'>) => {
864
1089
  const result = await commandHandler.command(
865
1090
  Cmd.rank,
@@ -870,9 +1095,11 @@ rank
870
1095
  });
871
1096
 
872
1097
  // Remove command
873
- program
874
- .command('remove')
875
- .description('Remove cards, resources and other project items')
1098
+ const removeCmd = new CommandWithPath('remove').description(
1099
+ 'Remove cards, resources and other project items',
1100
+ );
1101
+ program.addCommand(removeCmd);
1102
+ removeCmd
876
1103
  .argument(
877
1104
  '<type>',
878
1105
  `removable types: '${Parser.listTargets('remove').join("', '")}', or resource name (e.g. <prefix>/<type>/<identifier>)`,
@@ -891,7 +1118,6 @@ program
891
1118
  'Depends on context; see below for specific remove operation',
892
1119
  )
893
1120
  .addHelpText('after', additionalHelpForRemove)
894
- .option('-p, --project-path [path]', `${pathGuideline}`)
895
1121
  .action(
896
1122
  async (
897
1123
  type: string,
@@ -942,25 +1168,23 @@ program
942
1168
  );
943
1169
 
944
1170
  // Rename command
945
- program
946
- .command('rename')
1171
+ const renameCmd = new CommandWithPath('rename')
947
1172
  .description(
948
1173
  'Change project prefix and rename all the content with the new prefix',
949
1174
  )
950
- .argument('<to>', 'New project prefix')
951
- .option('-p, --project-path [path]', `${pathGuideline}`)
952
- .action(async (to: string, options: CommandOptions<'rename'>) => {
953
- const result = await commandHandler.command(
954
- Cmd.rename,
955
- [to],
956
- Object.assign({}, options, program.opts()),
957
- );
958
- handleResponse(result);
959
- });
1175
+ .argument('<to>', 'New project prefix');
1176
+ program.addCommand(renameCmd);
1177
+ renameCmd.action(async (to: string, options: CommandOptions<'rename'>) => {
1178
+ const result = await commandHandler.command(
1179
+ Cmd.rename,
1180
+ [to],
1181
+ Object.assign({}, options, program.opts()),
1182
+ );
1183
+ handleResponse(result);
1184
+ });
960
1185
 
961
1186
  // Report command
962
- program
963
- .command('report')
1187
+ const reportCmd = new CommandWithPath('report')
964
1188
  .description('Runs a report')
965
1189
  .argument(
966
1190
  '<parameters>',
@@ -970,27 +1194,29 @@ program
970
1194
  '[output]',
971
1195
  'Optional output file; if omitted output will be directed to stdout',
972
1196
  )
973
- .addOption(contextOption)
974
- .option('-p, --project-path [path]', `${pathGuideline}`)
975
- .action(
976
- async (
977
- parameters: string,
978
- output: string,
979
- options: CommandOptions<'report'>,
980
- ) => {
981
- const result = await commandHandler.command(
982
- Cmd.report,
983
- [parameters, output],
984
- Object.assign({}, options, program.opts()),
985
- );
986
- handleResponse(result);
987
- },
988
- );
1197
+ .addOption(contextOption);
1198
+ program.addCommand(reportCmd);
1199
+ reportCmd.action(
1200
+ async (
1201
+ parameters: string,
1202
+ output: string,
1203
+ options: CommandOptions<'report'>,
1204
+ ) => {
1205
+ const result = await commandHandler.command(
1206
+ Cmd.report,
1207
+ [parameters, output],
1208
+ Object.assign({}, options, program.opts()),
1209
+ );
1210
+ handleResponse(result);
1211
+ },
1212
+ );
989
1213
 
990
1214
  // Show command
991
- program
992
- .command('show')
993
- .description('Shows details from a project')
1215
+ const showCmd = new CommandWithPath('show').description(
1216
+ 'Shows details from a project',
1217
+ );
1218
+ program.addCommand(showCmd);
1219
+ showCmd
994
1220
  .argument(
995
1221
  '<type>',
996
1222
  `details can be seen from: ${Parser.listTargets('show').join(', ')}`,
@@ -1008,7 +1234,6 @@ program
1008
1234
  '-a --showAll',
1009
1235
  'Show all modules, irregardless if it has been imported or not. Only with "show importableModules"',
1010
1236
  )
1011
- .option('-p, --project-path [path]', `${pathGuideline}`)
1012
1237
  .option(
1013
1238
  '-u --show-use',
1014
1239
  'Show where resource is used. Only used with resources, otherwise will be ignored.',
@@ -1035,34 +1260,35 @@ program
1035
1260
  });
1036
1261
 
1037
1262
  // Transition command
1038
- program
1039
- .command('transition')
1263
+ const transitionCmd = new CommandWithPath('transition')
1040
1264
  .description('Transition a card to the specified state')
1041
1265
  .argument('<cardKey>', 'card key of a card')
1042
1266
  .argument(
1043
1267
  '<transition>',
1044
1268
  '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.',
1045
- )
1046
- .option('-p, --project-path [path]', `${pathGuideline}`)
1047
- .action(
1048
- async (
1049
- cardKey: string,
1050
- transition: string,
1051
- options: CommandOptions<'transition'>,
1052
- ) => {
1053
- const result = await commandHandler.command(
1054
- Cmd.transition,
1055
- [cardKey, transition],
1056
- Object.assign({}, options, program.opts()),
1057
- );
1058
- handleResponse(result);
1059
- },
1060
1269
  );
1270
+ program.addCommand(transitionCmd);
1271
+ transitionCmd.action(
1272
+ async (
1273
+ cardKey: string,
1274
+ transition: string,
1275
+ options: CommandOptions<'transition'>,
1276
+ ) => {
1277
+ const result = await commandHandler.command(
1278
+ Cmd.transition,
1279
+ [cardKey, transition],
1280
+ Object.assign({}, options, program.opts()),
1281
+ );
1282
+ handleResponse(result);
1283
+ },
1284
+ );
1061
1285
 
1062
1286
  // Update command
1063
- program
1064
- .command('update')
1065
- .description('Update resource details')
1287
+ const updateCmd = new CommandWithPath('update').description(
1288
+ 'Update resource details',
1289
+ );
1290
+ program.addCommand(updateCmd);
1291
+ updateCmd
1066
1292
  .argument('<resourceName>', 'Resource name')
1067
1293
  .argument(
1068
1294
  '<operation>',
@@ -1075,10 +1301,9 @@ program
1075
1301
  'When using "change" define new value for detail.\nWhen using "remove" provide optional replacement value for removed value',
1076
1302
  )
1077
1303
  .option(
1078
- '-m, --mapping-file [path]',
1304
+ '-m, --mapping-file <path>',
1079
1305
  'Path to JSON file containing workflow state mapping (only used when changing workflow)',
1080
1306
  )
1081
- .option('-p, --project-path [path]', `${pathGuideline}`)
1082
1307
  .addHelpText('after', additionalHelpForUpdate)
1083
1308
  .action(
1084
1309
  async (
@@ -1099,14 +1324,14 @@ program
1099
1324
  );
1100
1325
 
1101
1326
  // Updates all modules, or specific named module in the project.
1102
- program
1103
- .command('update-modules')
1327
+ const updateModulesCmd = new CommandWithPath('update-modules')
1104
1328
  .description(
1105
1329
  'Updates to latest versions either all modules or a specific module',
1106
1330
  )
1107
- .argument('[moduleName]', 'Module name')
1108
- .option('-p, --project-path [path]', `${pathGuideline}`)
1109
- .action(async (moduleName, options: CommandOptions<'updateModules'>) => {
1331
+ .argument('[moduleName]', 'Module name');
1332
+ program.addCommand(updateModulesCmd);
1333
+ updateModulesCmd.action(
1334
+ async (moduleName, options: CommandOptions<'updateModules'>) => {
1110
1335
  const result = await commandHandler.command(
1111
1336
  Cmd.updateModules,
1112
1337
  [moduleName],
@@ -1114,66 +1339,66 @@ program
1114
1339
  credentials(),
1115
1340
  );
1116
1341
  handleResponse(result);
1117
- });
1342
+ },
1343
+ );
1118
1344
 
1119
1345
  // Validate command
1120
- program
1121
- .command('validate')
1122
- .description('Validate project structure')
1123
- .option('-p, --project-path [path]', `${pathGuideline}`)
1124
- .action(async (options: CommandOptions<'validate'>) => {
1125
- const result = await commandHandler.command(
1126
- Cmd.validate,
1127
- [],
1128
- Object.assign({}, options, program.opts()),
1129
- );
1130
- handleResponse(result);
1131
- });
1346
+ const validateCmd = new CommandWithPath('validate').description(
1347
+ 'Validate project structure',
1348
+ );
1349
+ program.addCommand(validateCmd);
1350
+ validateCmd.action(async (options: CommandOptions<'validate'>) => {
1351
+ const result = await commandHandler.command(
1352
+ Cmd.validate,
1353
+ [],
1354
+ Object.assign({}, options, program.opts()),
1355
+ );
1356
+ handleResponse(result);
1357
+ });
1132
1358
 
1133
1359
  // Start app command.
1134
1360
  // If there are validation errors, user is prompted to continue or not.
1135
1361
  // There is 10 sec timeout on the prompt. If user does not reply, then
1136
1362
  // it is assumed that validation errors do not matter and application
1137
1363
  // start is resumed.
1138
- program
1139
- .command('app')
1364
+ const appCmd = new CommandWithPath('app')
1140
1365
  .description(
1141
1366
  'Starts the cyberismo app, accessible with a web browser at http://localhost:3000',
1142
1367
  )
1143
1368
  .option(
1144
1369
  '-w, --watch-resource-changes',
1145
1370
  'Project watches changes in .cards folder resources',
1146
- )
1147
- .option('-p, --project-path [path]', `${pathGuideline}`)
1148
- .action(async (options: CommandOptions<'start'>) => {
1149
- // validate project
1150
- const result = await commandHandler.command(
1151
- Cmd.validate,
1152
- [],
1153
- Object.assign({}, options, program.opts()),
1154
- );
1155
- if (!result.message) {
1156
- program.error('Expected validation result, but got none');
1371
+ );
1372
+ program.addCommand(appCmd);
1373
+ appCmd.action(async (options: CommandOptions<'start'>) => {
1374
+ // validate project
1375
+ const result = await commandHandler.command(
1376
+ Cmd.validate,
1377
+ [],
1378
+ Object.assign({}, options, program.opts()),
1379
+ );
1380
+ if (!result.message) {
1381
+ program.error('Expected validation result, but got none');
1382
+ return;
1383
+ }
1384
+ if (result.message !== 'Project structure validated') {
1385
+ truncateMessage(result.message).forEach((item) => console.error(item));
1386
+ console.error('\n'); // The output looks nicer with one extra row.
1387
+ result.message = '';
1388
+ const userConfirmation = await confirm(
1389
+ {
1390
+ message: 'There are validation errors. Do you want to continue?',
1391
+ },
1392
+ { signal: AbortSignal.timeout(10000), clearPromptOnDone: true },
1393
+ ).catch((error) => {
1394
+ return error.name === 'AbortPromptError';
1395
+ });
1396
+ if (!userConfirmation) {
1397
+ handleResponse(result);
1157
1398
  return;
1158
1399
  }
1159
- if (result.message !== 'Project structure validated') {
1160
- truncateMessage(result.message).forEach((item) => console.error(item));
1161
- console.error('\n'); // The output looks nicer with one extra row.
1162
- result.message = '';
1163
- const userConfirmation = await confirm(
1164
- {
1165
- message: 'There are validation errors. Do you want to continue?',
1166
- },
1167
- { signal: AbortSignal.timeout(10000), clearPromptOnDone: true },
1168
- ).catch((error) => {
1169
- return error.name === 'AbortPromptError';
1170
- });
1171
- if (!userConfirmation) {
1172
- handleResponse(result);
1173
- return;
1174
- }
1175
- }
1176
- await startServer(await commandHandler.getProjectPath(options.projectPath));
1177
- });
1400
+ }
1401
+ await startServer(await commandHandler.getProjectPath(options.projectPath));
1402
+ });
1178
1403
 
1179
1404
  export default program;