@objectstack/cli 3.0.6 → 3.0.8

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.
Files changed (140) hide show
  1. package/.turbo/turbo-build.log +2 -26
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +98 -54
  4. package/bin/run-dev.js +5 -0
  5. package/bin/run.js +5 -0
  6. package/dist/bin.d.ts +11 -0
  7. package/dist/bin.d.ts.map +1 -0
  8. package/dist/bin.js +12 -3767
  9. package/dist/bin.js.map +1 -0
  10. package/dist/commands/codemod/v2-to-v3.d.ts +10 -0
  11. package/dist/commands/codemod/v2-to-v3.d.ts.map +1 -0
  12. package/dist/commands/codemod/v2-to-v3.js +145 -0
  13. package/dist/commands/codemod/v2-to-v3.js.map +1 -0
  14. package/dist/commands/compile.d.ts +13 -0
  15. package/dist/commands/compile.d.ts.map +1 -0
  16. package/dist/commands/compile.js +91 -0
  17. package/dist/commands/compile.js.map +1 -0
  18. package/dist/commands/create.d.ts +91 -0
  19. package/dist/commands/create.d.ts.map +1 -0
  20. package/dist/commands/create.js +259 -0
  21. package/dist/commands/create.js.map +1 -0
  22. package/dist/commands/dev.d.ts +14 -0
  23. package/dist/commands/dev.d.ts.map +1 -0
  24. package/dist/commands/dev.js +67 -0
  25. package/dist/commands/dev.js.map +1 -0
  26. package/dist/commands/diff.d.ts +16 -0
  27. package/dist/commands/diff.d.ts.map +1 -0
  28. package/dist/commands/diff.js +239 -0
  29. package/dist/commands/diff.js.map +1 -0
  30. package/dist/commands/doctor.d.ts +10 -0
  31. package/dist/commands/doctor.d.ts.map +1 -0
  32. package/dist/commands/doctor.js +532 -0
  33. package/dist/commands/doctor.js.map +1 -0
  34. package/dist/commands/explain.d.ts +12 -0
  35. package/dist/commands/explain.d.ts.map +1 -0
  36. package/dist/commands/explain.js +368 -0
  37. package/dist/commands/explain.js.map +1 -0
  38. package/dist/commands/generate.d.ts +17 -0
  39. package/dist/commands/generate.d.ts.map +1 -0
  40. package/dist/commands/generate.js +833 -0
  41. package/dist/commands/generate.js.map +1 -0
  42. package/dist/commands/info.d.ts +12 -0
  43. package/dist/commands/info.d.ts.map +1 -0
  44. package/dist/commands/info.js +100 -0
  45. package/dist/commands/info.js.map +1 -0
  46. package/dist/commands/init.d.ts +22 -0
  47. package/dist/commands/init.d.ts.map +1 -0
  48. package/dist/commands/init.js +295 -0
  49. package/dist/commands/init.js.map +1 -0
  50. package/dist/commands/lint.d.ts +13 -0
  51. package/dist/commands/lint.d.ts.map +1 -0
  52. package/dist/commands/lint.js +255 -0
  53. package/dist/commands/lint.js.map +1 -0
  54. package/dist/commands/plugin/add.d.ts +22 -0
  55. package/dist/commands/plugin/add.d.ts.map +1 -0
  56. package/dist/commands/plugin/add.js +93 -0
  57. package/dist/commands/plugin/add.js.map +1 -0
  58. package/dist/commands/plugin/info.d.ts +10 -0
  59. package/dist/commands/plugin/info.d.ts.map +1 -0
  60. package/dist/commands/plugin/info.js +65 -0
  61. package/dist/commands/plugin/info.js.map +1 -0
  62. package/dist/commands/plugin/list.d.ts +13 -0
  63. package/dist/commands/plugin/list.d.ts.map +1 -0
  64. package/dist/commands/plugin/list.js +78 -0
  65. package/dist/commands/plugin/list.js.map +1 -0
  66. package/dist/commands/plugin/remove.d.ts +20 -0
  67. package/dist/commands/plugin/remove.d.ts.map +1 -0
  68. package/dist/commands/plugin/remove.js +79 -0
  69. package/dist/commands/plugin/remove.js.map +1 -0
  70. package/dist/commands/serve.d.ts +15 -0
  71. package/dist/commands/serve.d.ts.map +1 -0
  72. package/dist/commands/serve.js +286 -0
  73. package/dist/commands/serve.js.map +1 -0
  74. package/dist/commands/studio.d.ts +19 -0
  75. package/dist/commands/studio.d.ts.map +1 -0
  76. package/dist/commands/studio.js +43 -0
  77. package/dist/commands/studio.js.map +1 -0
  78. package/dist/commands/test.d.ts +13 -0
  79. package/dist/commands/test.d.ts.map +1 -0
  80. package/dist/commands/test.js +120 -0
  81. package/dist/commands/test.js.map +1 -0
  82. package/dist/commands/validate.d.ts +13 -0
  83. package/dist/commands/validate.d.ts.map +1 -0
  84. package/dist/commands/validate.js +115 -0
  85. package/dist/commands/validate.js.map +1 -0
  86. package/dist/index.d.ts +15 -114
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +20 -2805
  89. package/dist/index.js.map +1 -0
  90. package/dist/utils/config.d.ts +21 -0
  91. package/dist/utils/config.d.ts.map +1 -0
  92. package/dist/utils/config.js +66 -0
  93. package/dist/utils/config.js.map +1 -0
  94. package/dist/utils/format.d.ts +52 -0
  95. package/dist/utils/format.d.ts.map +1 -0
  96. package/dist/utils/format.js +202 -0
  97. package/dist/utils/format.js.map +1 -0
  98. package/dist/utils/plugin-helpers.d.ts +14 -0
  99. package/dist/utils/plugin-helpers.d.ts.map +1 -0
  100. package/dist/utils/plugin-helpers.js +40 -0
  101. package/dist/utils/plugin-helpers.js.map +1 -0
  102. package/dist/utils/studio.d.ts +58 -0
  103. package/dist/utils/studio.d.ts.map +1 -0
  104. package/dist/utils/studio.js +288 -0
  105. package/dist/utils/studio.js.map +1 -0
  106. package/package.json +33 -15
  107. package/src/bin.ts +11 -104
  108. package/src/commands/{codemod.ts → codemod/v2-to-v3.ts} +21 -28
  109. package/src/commands/compile.ts +31 -22
  110. package/src/commands/create.ts +29 -19
  111. package/src/commands/dev.ts +21 -10
  112. package/src/commands/diff.ts +28 -19
  113. package/src/commands/doctor.ts +17 -10
  114. package/src/commands/explain.ts +20 -10
  115. package/src/commands/generate.ts +81 -90
  116. package/src/commands/info.ts +20 -11
  117. package/src/commands/init.ts +32 -20
  118. package/src/commands/lint.ts +24 -14
  119. package/src/commands/plugin/add.ts +112 -0
  120. package/src/commands/plugin/info.ts +79 -0
  121. package/src/commands/plugin/list.ts +93 -0
  122. package/src/commands/plugin/remove.ts +97 -0
  123. package/src/commands/serve.ts +30 -20
  124. package/src/commands/studio.ts +21 -11
  125. package/src/commands/test.ts +21 -10
  126. package/src/commands/validate.ts +32 -22
  127. package/src/index.ts +20 -12
  128. package/src/utils/plugin-helpers.ts +37 -0
  129. package/src/utils/studio.ts +0 -1
  130. package/test/commands.test.ts +76 -37
  131. package/test/plugin-commands.test.ts +42 -160
  132. package/test/plugin.test.ts +19 -23
  133. package/tsconfig.build.json +18 -0
  134. package/bin/objectstack.js +0 -2
  135. package/dist/chunk-T2YN4AB7.js +0 -249
  136. package/dist/chunk-XNACYTC5.js +0 -251
  137. package/dist/config-FOXDQ5F7.js +0 -10
  138. package/dist/config-GBR54FKL.js +0 -11
  139. package/src/commands/plugin.ts +0 -372
  140. package/src/utils/plugin-commands.ts +0 -163
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
- import { Command } from 'commander';
3
+ import { Args, Command, Flags } from '@oclif/core';
4
4
  import chalk from 'chalk';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
@@ -305,14 +305,7 @@ function generateTypesFromConfig(config: Record<string, unknown>): string {
305
305
 
306
306
  // ─── Command ────────────────────────────────────────────────────────
307
307
 
308
- const generateMetadataCommand = new Command('metadata')
309
- .alias('m')
310
- .description('Generate metadata scaffold (object, view, action, flow, agent, dashboard, app)')
311
- .argument('<type>', 'Metadata type to generate')
312
- .argument('<name>', 'Name for the metadata (use kebab-case)')
313
- .option('-d, --dir <directory>', 'Target directory (overrides default)')
314
- .option('--dry-run', 'Show what would be created without writing files')
315
- .action(async (type: string, name: string, options) => {
308
+ async function runMetadataGeneration(type: string, name: string, flags: { dir?: string; dryRun?: boolean }): Promise<void> {
316
309
  printHeader('Generate');
317
310
 
318
311
  const generator = GENERATORS[type];
@@ -330,7 +323,7 @@ const generateMetadataCommand = new Command('metadata')
330
323
  process.exit(1);
331
324
  }
332
325
 
333
- const dir = options.dir || generator.defaultDir;
326
+ const dir = flags.dir || generator.defaultDir;
334
327
  const fileName = `${toSnakeCase(name)}.ts`;
335
328
  const filePath = path.join(process.cwd(), dir, fileName);
336
329
 
@@ -339,7 +332,7 @@ const generateMetadataCommand = new Command('metadata')
339
332
  console.log(` ${chalk.dim('File:')} ${chalk.white(path.join(dir, fileName))}`);
340
333
  console.log('');
341
334
 
342
- if (options.dryRun) {
335
+ if (flags.dryRun) {
343
336
  printInfo('Dry run — no files written');
344
337
  console.log('');
345
338
  console.log(chalk.dim(' Content:'));
@@ -395,14 +388,9 @@ const generateMetadataCommand = new Command('metadata')
395
388
  printError(error.message || String(error));
396
389
  process.exit(1);
397
390
  }
398
- });
399
-
400
- const generateTypesCommand = new Command('types')
401
- .description('Generate TypeScript type definitions from ObjectStack configuration')
402
- .argument('[config]', 'Configuration file path')
403
- .option('-o, --output <file>', 'Output file path', 'src/types/objectstack.d.ts')
404
- .option('--dry-run', 'Show what would be generated without writing files')
405
- .action(async (configPath, options) => {
391
+ }
392
+
393
+ async function runTypesGeneration(configPath: string | undefined, flags: { output: string; dryRun?: boolean }): Promise<void> {
406
394
  printHeader('Generate Types');
407
395
 
408
396
  try {
@@ -411,12 +399,12 @@ const generateTypesCommand = new Command('types')
411
399
  const { config, absolutePath } = await loadConfig(configPath);
412
400
 
413
401
  console.log(` ${chalk.dim('Config:')} ${chalk.white(absolutePath)}`);
414
- console.log(` ${chalk.dim('Output:')} ${chalk.white(options.output)}`);
402
+ console.log(` ${chalk.dim('Output:')} ${chalk.white(flags.output)}`);
415
403
  console.log('');
416
404
 
417
405
  const content = generateTypesFromConfig(config as Record<string, unknown>);
418
406
 
419
- if (options.dryRun) {
407
+ if (flags.dryRun) {
420
408
  printInfo('Dry run — no files written');
421
409
  console.log('');
422
410
  for (const line of content.split('\n')) {
@@ -426,20 +414,20 @@ const generateTypesCommand = new Command('types')
426
414
  return;
427
415
  }
428
416
 
429
- const outPath = path.resolve(process.cwd(), options.output);
417
+ const outPath = path.resolve(process.cwd(), flags.output);
430
418
  const outDir = path.dirname(outPath);
431
419
  if (!fs.existsSync(outDir)) {
432
420
  fs.mkdirSync(outDir, { recursive: true });
433
421
  }
434
422
  fs.writeFileSync(outPath, content);
435
- printSuccess(`Generated types at ${options.output}`);
423
+ printSuccess(`Generated types at ${flags.output}`);
436
424
  console.log('');
437
425
 
438
426
  } catch (error: any) {
439
427
  printError(error.message || String(error));
440
428
  process.exit(1);
441
429
  }
442
- });
430
+ }
443
431
 
444
432
  // ─── Client SDK Generator ───────────────────────────────────────────
445
433
 
@@ -541,12 +529,7 @@ function generateClientFromConfig(config: Record<string, unknown>): string {
541
529
  return lines.join('\n') + '\n';
542
530
  }
543
531
 
544
- const generateClientCommand = new Command('client')
545
- .description('Generate a type-safe client SDK from ObjectStack configuration')
546
- .argument('[config]', 'Configuration file path')
547
- .option('-o, --output <file>', 'Output file path', 'src/client/objectstack-client.ts')
548
- .option('--dry-run', 'Show output without writing')
549
- .action(async (configPath, options) => {
532
+ async function runClientGeneration(configPath: string | undefined, flags: { output: string; dryRun?: boolean }): Promise<void> {
550
533
  printHeader('Generate Client SDK');
551
534
 
552
535
  try {
@@ -556,13 +539,13 @@ const generateClientCommand = new Command('client')
556
539
  const { config, absolutePath } = await loadConfig(configPath);
557
540
 
558
541
  console.log(` ${chalk.dim('Config:')} ${chalk.white(absolutePath)}`);
559
- console.log(` ${chalk.dim('Output:')} ${chalk.white(options.output)}`);
542
+ console.log(` ${chalk.dim('Output:')} ${chalk.white(flags.output)}`);
560
543
  console.log('');
561
544
 
562
545
  printStep('Generating client SDK...');
563
546
  const content = generateClientFromConfig(config as Record<string, unknown>);
564
547
 
565
- if (options.dryRun) {
548
+ if (flags.dryRun) {
566
549
  printInfo('Dry run — no files written');
567
550
  console.log('');
568
551
  for (const line of content.split('\n')) {
@@ -572,20 +555,20 @@ const generateClientCommand = new Command('client')
572
555
  return;
573
556
  }
574
557
 
575
- const outPath = path.resolve(process.cwd(), options.output);
558
+ const outPath = path.resolve(process.cwd(), flags.output);
576
559
  const outDir = path.dirname(outPath);
577
560
  if (!fs.existsSync(outDir)) {
578
561
  fs.mkdirSync(outDir, { recursive: true });
579
562
  }
580
563
  fs.writeFileSync(outPath, content);
581
- printSuccess(`Generated client SDK at ${options.output} (${timer.display()})`);
564
+ printSuccess(`Generated client SDK at ${flags.output} (${timer.display()})`);
582
565
  console.log('');
583
566
 
584
567
  } catch (error: any) {
585
568
  printError(error.message || String(error));
586
569
  process.exit(1);
587
570
  }
588
- });
571
+ }
589
572
 
590
573
  // ─── Migration Generator ────────────────────────────────────────────
591
574
 
@@ -777,13 +760,7 @@ function generateMigrationTs(config: Record<string, unknown>): string {
777
760
  return lines.join('\n') + '\n';
778
761
  }
779
762
 
780
- const generateMigrationCommand = new Command('migration')
781
- .description('Generate database migration from ObjectStack schema')
782
- .argument('[config]', 'Configuration file path')
783
- .option('-o, --output <file>', 'Output file path')
784
- .option('--format <format>', 'Output format: sql or typescript', 'typescript')
785
- .option('--dry-run', 'Show output without writing')
786
- .action(async (configPath, options) => {
763
+ async function runMigrationGeneration(configPath: string | undefined, flags: { output?: string; format: string; dryRun?: boolean }): Promise<void> {
787
764
  printHeader('Generate Migration');
788
765
 
789
766
  try {
@@ -792,23 +769,23 @@ const generateMigrationCommand = new Command('migration')
792
769
  printInfo('Loading configuration...');
793
770
  const { config, absolutePath } = await loadConfig(configPath);
794
771
 
795
- const ext = options.format === 'sql' ? 'sql' : 'ts';
772
+ const ext = flags.format === 'sql' ? 'sql' : 'ts';
796
773
  // Format: YYYYMMDDHHmmss (e.g. 20250101120000)
797
774
  const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
798
775
  const defaultOutput = `migrations/${timestamp}_migration.${ext}`;
799
- const output = options.output || defaultOutput;
776
+ const output = flags.output || defaultOutput;
800
777
 
801
778
  console.log(` ${chalk.dim('Config:')} ${chalk.white(absolutePath)}`);
802
- console.log(` ${chalk.dim('Format:')} ${chalk.white(options.format)}`);
779
+ console.log(` ${chalk.dim('Format:')} ${chalk.white(flags.format)}`);
803
780
  console.log(` ${chalk.dim('Output:')} ${chalk.white(output)}`);
804
781
  console.log('');
805
782
 
806
783
  printStep('Generating migration...');
807
- const content = options.format === 'sql'
784
+ const content = flags.format === 'sql'
808
785
  ? generateMigrationSql(config as Record<string, unknown>)
809
786
  : generateMigrationTs(config as Record<string, unknown>);
810
787
 
811
- if (options.dryRun) {
788
+ if (flags.dryRun) {
812
789
  printInfo('Dry run — no files written');
813
790
  console.log('');
814
791
  for (const line of content.split('\n')) {
@@ -831,15 +808,11 @@ const generateMigrationCommand = new Command('migration')
831
808
  printError(error.message || String(error));
832
809
  process.exit(1);
833
810
  }
834
- });
811
+ }
835
812
 
836
813
  // ─── JSON Schema Generator ──────────────────────────────────────────
837
814
 
838
- const generateSchemaCommand = new Command('schema')
839
- .description('Generate JSON Schema for objectstack.config.ts (for IDE autocomplete)')
840
- .option('-o, --output <file>', 'Output file path', 'objectstack.schema.json')
841
- .option('--dry-run', 'Show output without writing')
842
- .action(async (options) => {
815
+ async function runSchemaGeneration(flags: { output: string; dryRun?: boolean }): Promise<void> {
843
816
  printHeader('Generate Schema');
844
817
 
845
818
  try {
@@ -864,20 +837,20 @@ const generateSchemaCommand = new Command('schema')
864
837
 
865
838
  const content = JSON.stringify(schema, null, 2) + '\n';
866
839
 
867
- if (options.dryRun) {
840
+ if (flags.dryRun) {
868
841
  printInfo('Dry run — no files written');
869
842
  console.log('');
870
843
  console.log(content);
871
844
  return;
872
845
  }
873
846
 
874
- const outPath = path.resolve(process.cwd(), options.output);
847
+ const outPath = path.resolve(process.cwd(), flags.output);
875
848
  const outDir = path.dirname(outPath);
876
849
  if (!fs.existsSync(outDir)) {
877
850
  fs.mkdirSync(outDir, { recursive: true });
878
851
  }
879
852
  fs.writeFileSync(outPath, content);
880
- printSuccess(`Generated JSON Schema at ${options.output} (${timer.display()})`);
853
+ printSuccess(`Generated JSON Schema at ${flags.output} (${timer.display()})`);
881
854
  console.log('');
882
855
  console.log(chalk.dim(' Usage: Reference in your IDE or editor for autocomplete'));
883
856
  console.log(chalk.dim(` Path: ${outPath}`));
@@ -887,47 +860,65 @@ const generateSchemaCommand = new Command('schema')
887
860
  printError(error.message || String(error));
888
861
  process.exit(1);
889
862
  }
890
- });
863
+ }
891
864
 
892
865
  // ─── Main Generate Command ──────────────────────────────────────────
893
866
 
894
- export const generateCommand = new Command('generate')
895
- .alias('g')
896
- .description('Generate metadata files or TypeScript types')
897
- .argument('[type]', 'Metadata type to generate (object, view, action, flow, agent, dashboard, app)')
898
- .argument('[name]', 'Name for the metadata (use kebab-case)')
899
- .option('-d, --dir <directory>', 'Target directory (overrides default)')
900
- .option('--dry-run', 'Show what would be created without writing files')
901
- .addCommand(generateTypesCommand)
902
- .addCommand(generateClientCommand)
903
- .addCommand(generateMigrationCommand)
904
- .addCommand(generateSchemaCommand)
905
- .action(async (type: string | undefined, name: string | undefined, options) => {
906
- if (!type) {
907
- printHeader('Generate');
908
- console.log(chalk.bold(' Sub-commands:'));
909
- console.log(` ${chalk.cyan('types'.padEnd(12))} Generate TypeScript type definitions from config`);
910
- console.log(` ${chalk.cyan('client'.padEnd(12))} Generate a type-safe client SDK from config`);
911
- console.log(` ${chalk.cyan('migration'.padEnd(12))} Generate database migration from schema`);
912
- console.log(` ${chalk.cyan('schema'.padEnd(12))} Generate JSON Schema for objectstack.config.ts (IDE autocomplete)`);
913
- console.log('');
914
- console.log(chalk.bold(' Metadata types:'));
915
- for (const [key, gen] of Object.entries(GENERATORS)) {
916
- console.log(` ${chalk.cyan(key.padEnd(12))} ${chalk.dim(gen.description)}`);
917
- }
918
- console.log('');
919
- console.log(chalk.dim(' Usage: objectstack generate <type> <name>'));
920
- console.log(chalk.dim(' Usage: objectstack generate types [config]'));
921
- return;
867
+ export default class Generate extends Command {
868
+ static override description = 'Generate metadata files or TypeScript types';
869
+
870
+ static override aliases = ['g'];
871
+
872
+ static override args = {
873
+ type: Args.string({ description: 'Metadata type to generate (object, view, action, flow, agent, dashboard, app)', required: true }),
874
+ name: Args.string({ description: 'Name for the metadata (use kebab-case)', required: false }),
875
+ };
876
+
877
+ static override flags = {
878
+ dir: Flags.string({ char: 'd', description: 'Target directory (overrides default)' }),
879
+ 'dry-run': Flags.boolean({ description: 'Show what would be created without writing files' }),
880
+ output: Flags.string({ char: 'o', description: 'Output file path' }),
881
+ format: Flags.string({ description: 'Output format: sql or typescript', default: 'typescript' }),
882
+ };
883
+
884
+ async run(): Promise<void> {
885
+ const { args, flags } = await this.parse(Generate);
886
+
887
+ // Route to sub-commands by type name
888
+ switch (args.type) {
889
+ case 'types':
890
+ return runTypesGeneration(args.name, {
891
+ output: flags.output ?? 'src/types/objectstack.d.ts',
892
+ dryRun: flags['dry-run'],
893
+ });
894
+ case 'client':
895
+ return runClientGeneration(args.name, {
896
+ output: flags.output ?? 'src/client/objectstack-client.ts',
897
+ dryRun: flags['dry-run'],
898
+ });
899
+ case 'migration':
900
+ return runMigrationGeneration(args.name, {
901
+ output: flags.output,
902
+ format: flags.format ?? 'typescript',
903
+ dryRun: flags['dry-run'],
904
+ });
905
+ case 'schema':
906
+ return runSchemaGeneration({
907
+ output: flags.output ?? 'objectstack.schema.json',
908
+ dryRun: flags['dry-run'],
909
+ });
922
910
  }
923
911
 
924
- // Delegate to metadata command action
925
- if (!name) {
912
+ // Metadata generation
913
+ if (!args.name) {
926
914
  printError('Missing required argument: <name>');
927
915
  console.log(chalk.dim(' Usage: objectstack generate <type> <name>'));
928
916
  process.exit(1);
929
917
  }
930
918
 
931
- // Execute metadata generation inline
932
- await generateMetadataCommand.parseAsync([type, name, ...process.argv.slice(4)], { from: 'user' });
933
- });
919
+ await runMetadataGeneration(args.type, args.name, {
920
+ dir: flags.dir,
921
+ dryRun: flags['dry-run'],
922
+ });
923
+ }
924
+ }
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
- import { Command } from 'commander';
3
+ import { Args, Command, Flags } from '@oclif/core';
4
4
  import chalk from 'chalk';
5
5
  import { normalizeStackInput } from '@objectstack/spec';
6
6
  import { loadConfig } from '../utils/config.js';
@@ -15,23 +15,31 @@ import {
15
15
  printMetadataStats,
16
16
  } from '../utils/format.js';
17
17
 
18
- export const infoCommand = new Command('info')
19
- .description('Display metadata summary of an ObjectStack configuration')
20
- .argument('[config]', 'Configuration file path')
21
- .option('--json', 'Output as JSON')
22
- .action(async (configPath, options) => {
18
+ export default class Info extends Command {
19
+ static override description = 'Display metadata summary of an ObjectStack configuration';
20
+
21
+ static override args = {
22
+ config: Args.string({ description: 'Configuration file path', required: false }),
23
+ };
24
+
25
+ static override flags = {
26
+ json: Flags.boolean({ description: 'Output as JSON' }),
27
+ };
28
+
29
+ async run(): Promise<void> {
30
+ const { args, flags } = await this.parse(Info);
23
31
  const timer = createTimer();
24
32
 
25
- if (!options.json) {
33
+ if (!flags.json) {
26
34
  printHeader('Info');
27
35
  }
28
36
 
29
37
  try {
30
- const { config: rawConfig, absolutePath, duration } = await loadConfig(configPath);
38
+ const { config: rawConfig, absolutePath, duration } = await loadConfig(args.config);
31
39
  const config: any = normalizeStackInput(rawConfig as Record<string, unknown>);
32
40
  const stats = collectMetadataStats(config);
33
41
 
34
- if (options.json) {
42
+ if (flags.json) {
35
43
  console.log(JSON.stringify({
36
44
  config: absolutePath,
37
45
  manifest: config.manifest || null,
@@ -104,7 +112,7 @@ export const infoCommand = new Command('info')
104
112
  console.log('');
105
113
 
106
114
  } catch (error: any) {
107
- if (options.json) {
115
+ if (flags.json) {
108
116
  console.log(JSON.stringify({ error: error.message }));
109
117
  process.exit(1);
110
118
  }
@@ -112,4 +120,5 @@ export const infoCommand = new Command('info')
112
120
  printError(error.message || String(error));
113
121
  process.exit(1);
114
122
  }
115
- });
123
+ }
124
+ }
@@ -1,12 +1,12 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
- import { Command } from 'commander';
3
+ import { Args, Command, Flags } from '@oclif/core';
4
4
  import chalk from 'chalk';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { printHeader, printSuccess, printError, printStep, printKV, printInfo } from '../utils/format.js';
8
8
 
9
- const TEMPLATES: Record<string, {
9
+ export const TEMPLATES: Record<string, {
10
10
  description: string;
11
11
  dependencies: Record<string, string>;
12
12
  devDependencies: Record<string, string>;
@@ -179,33 +179,48 @@ function toTitleCase(str: string): string {
179
179
  return str.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
180
180
  }
181
181
 
182
- export const initCommand = new Command('init')
183
- .description('Initialize a new ObjectStack project in the current directory')
184
- .argument('[name]', 'Project name (defaults to directory name)')
185
- .option('-t, --template <template>', 'Template: app, plugin, empty', 'app')
186
- .option('--no-install', 'Skip dependency installation')
187
- .action(async (name, options) => {
182
+ function printWarning(msg: string) {
183
+ console.log(chalk.yellow(` ⚠ ${msg}`));
184
+ }
185
+
186
+ export default class Init extends Command {
187
+ static override id = 'init';
188
+
189
+ static override description = 'Initialize a new ObjectStack project in the current directory';
190
+
191
+ static override args = {
192
+ name: Args.string({ description: 'Project name (defaults to directory name)', required: false }),
193
+ };
194
+
195
+ static override flags = {
196
+ template: Flags.string({ char: 't', description: 'Template: app, plugin, empty', default: 'app' }),
197
+ install: Flags.boolean({ description: 'Install dependencies', default: true, allowNo: true }),
198
+ };
199
+
200
+ async run(): Promise<void> {
201
+ const { args, flags } = await this.parse(Init);
202
+
188
203
  printHeader('Init');
189
204
 
190
205
  const cwd = process.cwd();
191
- const projectName = name || path.basename(cwd);
192
- const template = TEMPLATES[options.template];
206
+ const projectName = args.name || path.basename(cwd);
207
+ const template = TEMPLATES[flags.template];
193
208
 
194
209
  if (!template) {
195
- printError(`Unknown template: ${options.template}`);
210
+ printError(`Unknown template: ${flags.template}`);
196
211
  console.log(chalk.dim(` Available: ${Object.keys(TEMPLATES).join(', ')}`));
197
- process.exit(1);
212
+ this.error(`Unknown template: ${flags.template}`);
198
213
  }
199
214
 
200
215
  // Check for existing config
201
216
  if (fs.existsSync(path.join(cwd, 'objectstack.config.ts'))) {
202
217
  printError('objectstack.config.ts already exists in this directory');
203
218
  console.log(chalk.dim(' Use `objectstack generate` to add metadata to an existing project'));
204
- process.exit(1);
219
+ this.error('objectstack.config.ts already exists in this directory');
205
220
  }
206
221
 
207
222
  printKV('Project', projectName);
208
- printKV('Template', `${options.template} — ${template.description}`);
223
+ printKV('Template', `${flags.template} — ${template.description}`);
209
224
  printKV('Directory', cwd);
210
225
  console.log('');
211
226
 
@@ -286,7 +301,7 @@ export const initCommand = new Command('init')
286
301
  console.log('');
287
302
 
288
303
  // Install dependencies
289
- if (options.install !== false) {
304
+ if (flags.install) {
290
305
  printStep('Installing dependencies...');
291
306
  const { execSync } = await import('child_process');
292
307
  try {
@@ -306,10 +321,7 @@ export const initCommand = new Command('init')
306
321
 
307
322
  } catch (error: any) {
308
323
  printError(error.message || String(error));
309
- process.exit(1);
324
+ this.error(error.message || String(error));
310
325
  }
311
- });
312
-
313
- function printWarning(msg: string) {
314
- console.log(chalk.yellow(` ⚠ ${msg}`));
326
+ }
315
327
  }
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
- import { Command } from 'commander';
3
+ import { Args, Command, Flags } from '@oclif/core';
4
4
  import chalk from 'chalk';
5
5
  import { normalizeStackInput } from '@objectstack/spec';
6
6
  import { loadConfig } from '../utils/config.js';
@@ -188,15 +188,24 @@ function lintConfig(config: any): LintIssue[] {
188
188
 
189
189
  // ─── Command ────────────────────────────────────────────────────────
190
190
 
191
- export const lintCommand = new Command('lint')
192
- .description('Check ObjectStack configuration for style and convention issues')
193
- .argument('[config]', 'Configuration file path')
194
- .option('--json', 'Output as JSON')
195
- .option('--fix', 'Show what would be fixed (dry-run)')
196
- .action(async (configPath, options) => {
191
+ export default class Lint extends Command {
192
+ static override description = 'Check ObjectStack configuration for style and convention issues';
193
+
194
+ static override args = {
195
+ config: Args.string({ description: 'Configuration file path', required: false }),
196
+ };
197
+
198
+ static override flags = {
199
+ json: Flags.boolean({ description: 'Output as JSON' }),
200
+ fix: Flags.boolean({ description: 'Show what would be fixed (dry-run)' }),
201
+ };
202
+
203
+ async run(): Promise<void> {
204
+ const { args, flags } = await this.parse(Lint);
205
+ const configPath = args.config;
197
206
  const timer = createTimer();
198
207
 
199
- if (!options.json) {
208
+ if (!flags.json) {
200
209
  printHeader('Lint');
201
210
  printStep('Loading configuration...');
202
211
  }
@@ -204,7 +213,7 @@ export const lintCommand = new Command('lint')
204
213
  try {
205
214
  const { config, absolutePath } = await loadConfig(configPath);
206
215
 
207
- if (!options.json) {
216
+ if (!flags.json) {
208
217
  printInfo(`Config: ${chalk.white(absolutePath)}`);
209
218
  }
210
219
 
@@ -212,7 +221,7 @@ export const lintCommand = new Command('lint')
212
221
  const issues = lintConfig(normalized);
213
222
 
214
223
  // ── JSON output ──
215
- if (options.json) {
224
+ if (flags.json) {
216
225
  const errors = issues.filter((i) => i.severity === 'error');
217
226
  const warnings = issues.filter((i) => i.severity === 'warning');
218
227
  const suggestions = issues.filter((i) => i.severity === 'suggestion');
@@ -254,7 +263,7 @@ export const lintCommand = new Command('lint')
254
263
 
255
264
  console.log(` ${color(icon)} ${color(issue.message)}`);
256
265
  console.log(chalk.dim(` ${issue.rule} at ${issue.path}`));
257
- if (options.fix && issue.fix) {
266
+ if (flags.fix && issue.fix) {
258
267
  console.log(chalk.green(` → fix: ${issue.fix}`));
259
268
  }
260
269
  };
@@ -284,7 +293,7 @@ export const lintCommand = new Command('lint')
284
293
  if (suggestions.length > 0) parts.push(chalk.blue(`${suggestions.length} suggestion(s)`));
285
294
  console.log(` ${parts.join(', ')} ${chalk.dim(`(${timer.display()})`)}`);
286
295
 
287
- if (options.fix) {
296
+ if (flags.fix) {
288
297
  console.log('');
289
298
  printInfo('Dry-run mode: no files were modified.');
290
299
  }
@@ -294,7 +303,7 @@ export const lintCommand = new Command('lint')
294
303
  if (errors.length > 0) process.exit(1);
295
304
 
296
305
  } catch (error: any) {
297
- if (options.json) {
306
+ if (flags.json) {
298
307
  console.log(JSON.stringify({ error: error.message }));
299
308
  process.exit(1);
300
309
  }
@@ -302,4 +311,5 @@ export const lintCommand = new Command('lint')
302
311
  printError(error.message || String(error));
303
312
  process.exit(1);
304
313
  }
305
- });
314
+ }
315
+ }