@objectstack/cli 3.0.5 → 3.0.7

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 (141) 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 -3759
  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 -2799
  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 +35 -25
  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 +20 -11
  114. package/src/commands/explain.ts +20 -10
  115. package/src/commands/generate.ts +81 -90
  116. package/src/commands/info.ts +22 -11
  117. package/src/commands/init.ts +32 -20
  118. package/src/commands/lint.ts +27 -15
  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 +36 -25
  127. package/src/index.ts +20 -12
  128. package/src/utils/format.ts +10 -6
  129. package/src/utils/plugin-helpers.ts +37 -0
  130. package/src/utils/studio.ts +0 -1
  131. package/test/commands.test.ts +76 -37
  132. package/test/plugin-commands.test.ts +42 -160
  133. package/test/plugin.test.ts +19 -23
  134. package/tsconfig.build.json +18 -0
  135. package/bin/objectstack.js +0 -2
  136. package/dist/chunk-CSHQEILI.js +0 -246
  137. package/dist/chunk-Q74JNWKD.js +0 -248
  138. package/dist/config-A7BN6UIT.js +0 -11
  139. package/dist/config-UN34WBHT.js +0 -10
  140. package/src/commands/plugin.ts +0 -372
  141. 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,7 +1,8 @@
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
+ import { normalizeStackInput } from '@objectstack/spec';
5
6
  import { loadConfig } from '../utils/config.js';
6
7
  import {
7
8
  printHeader,
@@ -14,22 +15,31 @@ import {
14
15
  printMetadataStats,
15
16
  } from '../utils/format.js';
16
17
 
17
- export const infoCommand = new Command('info')
18
- .description('Display metadata summary of an ObjectStack configuration')
19
- .argument('[config]', 'Configuration file path')
20
- .option('--json', 'Output as JSON')
21
- .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);
22
31
  const timer = createTimer();
23
32
 
24
- if (!options.json) {
33
+ if (!flags.json) {
25
34
  printHeader('Info');
26
35
  }
27
36
 
28
37
  try {
29
- const { config, absolutePath, duration } = await loadConfig(configPath);
38
+ const { config: rawConfig, absolutePath, duration } = await loadConfig(args.config);
39
+ const config: any = normalizeStackInput(rawConfig as Record<string, unknown>);
30
40
  const stats = collectMetadataStats(config);
31
41
 
32
- if (options.json) {
42
+ if (flags.json) {
33
43
  console.log(JSON.stringify({
34
44
  config: absolutePath,
35
45
  manifest: config.manifest || null,
@@ -102,7 +112,7 @@ export const infoCommand = new Command('info')
102
112
  console.log('');
103
113
 
104
114
  } catch (error: any) {
105
- if (options.json) {
115
+ if (flags.json) {
106
116
  console.log(JSON.stringify({ error: error.message }));
107
117
  process.exit(1);
108
118
  }
@@ -110,4 +120,5 @@ export const infoCommand = new Command('info')
110
120
  printError(error.message || String(error));
111
121
  process.exit(1);
112
122
  }
113
- });
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,7 +1,8 @@
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
+ import { normalizeStackInput } from '@objectstack/spec';
5
6
  import { loadConfig } from '../utils/config.js';
6
7
  import {
7
8
  printHeader,
@@ -187,15 +188,24 @@ function lintConfig(config: any): LintIssue[] {
187
188
 
188
189
  // ─── Command ────────────────────────────────────────────────────────
189
190
 
190
- export const lintCommand = new Command('lint')
191
- .description('Check ObjectStack configuration for style and convention issues')
192
- .argument('[config]', 'Configuration file path')
193
- .option('--json', 'Output as JSON')
194
- .option('--fix', 'Show what would be fixed (dry-run)')
195
- .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;
196
206
  const timer = createTimer();
197
207
 
198
- if (!options.json) {
208
+ if (!flags.json) {
199
209
  printHeader('Lint');
200
210
  printStep('Loading configuration...');
201
211
  }
@@ -203,14 +213,15 @@ export const lintCommand = new Command('lint')
203
213
  try {
204
214
  const { config, absolutePath } = await loadConfig(configPath);
205
215
 
206
- if (!options.json) {
216
+ if (!flags.json) {
207
217
  printInfo(`Config: ${chalk.white(absolutePath)}`);
208
218
  }
209
219
 
210
- const issues = lintConfig(config);
220
+ const normalized = normalizeStackInput(config as Record<string, unknown>);
221
+ const issues = lintConfig(normalized);
211
222
 
212
223
  // ── JSON output ──
213
- if (options.json) {
224
+ if (flags.json) {
214
225
  const errors = issues.filter((i) => i.severity === 'error');
215
226
  const warnings = issues.filter((i) => i.severity === 'warning');
216
227
  const suggestions = issues.filter((i) => i.severity === 'suggestion');
@@ -252,7 +263,7 @@ export const lintCommand = new Command('lint')
252
263
 
253
264
  console.log(` ${color(icon)} ${color(issue.message)}`);
254
265
  console.log(chalk.dim(` ${issue.rule} at ${issue.path}`));
255
- if (options.fix && issue.fix) {
266
+ if (flags.fix && issue.fix) {
256
267
  console.log(chalk.green(` → fix: ${issue.fix}`));
257
268
  }
258
269
  };
@@ -282,7 +293,7 @@ export const lintCommand = new Command('lint')
282
293
  if (suggestions.length > 0) parts.push(chalk.blue(`${suggestions.length} suggestion(s)`));
283
294
  console.log(` ${parts.join(', ')} ${chalk.dim(`(${timer.display()})`)}`);
284
295
 
285
- if (options.fix) {
296
+ if (flags.fix) {
286
297
  console.log('');
287
298
  printInfo('Dry-run mode: no files were modified.');
288
299
  }
@@ -292,7 +303,7 @@ export const lintCommand = new Command('lint')
292
303
  if (errors.length > 0) process.exit(1);
293
304
 
294
305
  } catch (error: any) {
295
- if (options.json) {
306
+ if (flags.json) {
296
307
  console.log(JSON.stringify({ error: error.message }));
297
308
  process.exit(1);
298
309
  }
@@ -300,4 +311,5 @@ export const lintCommand = new Command('lint')
300
311
  printError(error.message || String(error));
301
312
  process.exit(1);
302
313
  }
303
- });
314
+ }
315
+ }