@objectstack/cli 3.0.6 → 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 (140) hide show
  1. package/.turbo/turbo-build.log +2 -26
  2. package/CHANGELOG.md +14 -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 path from 'path';
5
5
  import fs from 'fs';
6
6
  import chalk from 'chalk';
@@ -19,47 +19,55 @@ import {
19
19
  printMetadataStats,
20
20
  } from '../utils/format.js';
21
21
 
22
- export const compileCommand = new Command('compile')
23
- .description('Compile ObjectStack configuration to JSON artifact')
24
- .argument('[config]', 'Source configuration file')
25
- .option('-o, --output <path>', 'Output JSON file', 'dist/objectstack.json')
26
- .option('--json', 'Output compile result as JSON (for CI)')
27
- .action(async (configPath, options) => {
22
+ export default class Compile extends Command {
23
+ static override description = 'Compile ObjectStack configuration to JSON artifact';
24
+
25
+ static override args = {
26
+ config: Args.string({ description: 'Source configuration file', required: false }),
27
+ };
28
+
29
+ static override flags = {
30
+ output: Flags.string({ char: 'o', description: 'Output JSON file', default: 'dist/objectstack.json' }),
31
+ json: Flags.boolean({ description: 'Output compile result as JSON (for CI)' }),
32
+ };
33
+
34
+ async run(): Promise<void> {
35
+ const { args, flags } = await this.parse(Compile);
28
36
  const timer = createTimer();
29
37
 
30
- if (!options.json) {
38
+ if (!flags.json) {
31
39
  printHeader('Compile');
32
40
  }
33
41
 
34
42
  try {
35
43
  // 1. Load Configuration
36
- if (!options.json) printStep('Loading configuration...');
37
- const { config, absolutePath, duration } = await loadConfig(configPath);
44
+ if (!flags.json) printStep('Loading configuration...');
45
+ const { config, absolutePath, duration } = await loadConfig(args.config);
38
46
 
39
- if (!options.json) {
47
+ if (!flags.json) {
40
48
  printKV('Config', path.relative(process.cwd(), absolutePath));
41
49
  printKV('Load time', `${duration}ms`);
42
50
  }
43
51
 
44
52
  // 2. Normalize map-formatted stack definition and validate against Protocol
45
- if (!options.json) printStep('Validating protocol compliance...');
53
+ if (!flags.json) printStep('Validating protocol compliance...');
46
54
  const normalized = normalizeStackInput(config as Record<string, unknown>);
47
55
  const result = ObjectStackDefinitionSchema.safeParse(normalized);
48
56
 
49
57
  if (!result.success) {
50
- if (options.json) {
58
+ if (flags.json) {
51
59
  console.log(JSON.stringify({ success: false, errors: (result.error as unknown as ZodError).issues }));
52
- process.exit(1);
60
+ this.exit(1);
53
61
  }
54
62
  console.log('');
55
63
  printError('Validation failed');
56
64
  formatZodErrors(result.error as unknown as ZodError);
57
- process.exit(1);
65
+ this.exit(1);
58
66
  }
59
67
 
60
68
  // 3. Generate Artifact
61
- if (!options.json) printStep('Writing artifact...');
62
- const output = options.output;
69
+ if (!flags.json) printStep('Writing artifact...');
70
+ const output = flags.output!;
63
71
  const artifactPath = path.resolve(process.cwd(), output);
64
72
  const artifactDir = path.dirname(artifactPath);
65
73
 
@@ -73,7 +81,7 @@ export const compileCommand = new Command('compile')
73
81
  const sizeKB = (jsonContent.length / 1024).toFixed(1);
74
82
  const stats = collectMetadataStats(config);
75
83
 
76
- if (options.json) {
84
+ if (flags.json) {
77
85
  console.log(JSON.stringify({
78
86
  success: true,
79
87
  output: artifactPath,
@@ -94,12 +102,13 @@ export const compileCommand = new Command('compile')
94
102
  console.log('');
95
103
 
96
104
  } catch (error: any) {
97
- if (options.json) {
105
+ if (flags.json) {
98
106
  console.log(JSON.stringify({ success: false, error: error.message }));
99
- process.exit(1);
107
+ this.exit(1);
100
108
  }
101
109
  console.log('');
102
110
  printError(error.message || String(error));
103
- process.exit(1);
111
+ this.error(error.message || String(error));
104
112
  }
105
- });
113
+ }
114
+ }
@@ -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';
@@ -182,37 +182,46 @@ function toCamelCase(str: string): string {
182
182
  return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
183
183
  }
184
184
 
185
- export const createCommand = new Command('create')
186
- .description('Create a new package, plugin, or example from template')
187
- .argument('<type>', 'Type of project to create (plugin, example)')
188
- .argument('[name]', 'Name of the project')
189
- .option('-d, --dir <directory>', 'Target directory')
190
- .action(async (type: string, name?: string, options?: { dir?: string }) => {
185
+ export default class Create extends Command {
186
+ static override description = 'Create a new package, plugin, or example from template';
187
+
188
+ static override args = {
189
+ type: Args.string({ description: 'Type of project to create (plugin, example)', required: true }),
190
+ name: Args.string({ description: 'Name of the project', required: false }),
191
+ };
192
+
193
+ static override flags = {
194
+ dir: Flags.string({ char: 'd', description: 'Target directory' }),
195
+ };
196
+
197
+ async run(): Promise<void> {
198
+ const { args, flags } = await this.parse(Create);
199
+
191
200
  console.log(chalk.bold(`\n📦 ObjectStack Project Creator`));
192
201
  console.log(chalk.dim(`-------------------------------`));
193
202
 
194
- if (!templates[type as keyof typeof templates]) {
195
- console.error(chalk.red(`\n❌ Unknown type: ${type}`));
203
+ if (!templates[args.type as keyof typeof templates]) {
204
+ console.error(chalk.red(`\n❌ Unknown type: ${args.type}`));
196
205
  console.log(chalk.dim('Available types: plugin, example'));
197
206
  process.exit(1);
198
207
  }
199
208
 
200
- if (!name) {
209
+ if (!args.name) {
201
210
  console.error(chalk.red('\n❌ Project name is required'));
202
- console.log(chalk.dim(`Usage: objectstack create ${type} <name>`));
211
+ console.log(chalk.dim(`Usage: objectstack create ${args.type} <name>`));
203
212
  process.exit(1);
204
213
  }
205
214
 
206
- const template = templates[type as keyof typeof templates];
215
+ const template = templates[args.type as keyof typeof templates];
207
216
  const cwd = process.cwd();
208
217
 
209
218
  // Determine target directory
210
219
  let targetDir: string;
211
- if (options?.dir) {
212
- targetDir = path.resolve(cwd, options.dir);
220
+ if (flags.dir) {
221
+ targetDir = path.resolve(cwd, flags.dir);
213
222
  } else {
214
- const baseDir = type === 'plugin' ? 'packages/plugins' : 'examples';
215
- const projectName = type === 'plugin' ? `plugin-${name}` : name;
223
+ const baseDir = args.type === 'plugin' ? 'packages/plugins' : 'examples';
224
+ const projectName = args.type === 'plugin' ? `plugin-${args.name}` : args.name;
216
225
  targetDir = path.join(cwd, baseDir, projectName);
217
226
  }
218
227
 
@@ -222,7 +231,7 @@ export const createCommand = new Command('create')
222
231
  process.exit(1);
223
232
  }
224
233
 
225
- console.log(`📁 Creating ${type}: ${chalk.blue(name)}`);
234
+ console.log(`📁 Creating ${args.type}: ${chalk.blue(args.name)}`);
226
235
  console.log(`📂 Location: ${chalk.dim(targetDir)}`);
227
236
  console.log('');
228
237
 
@@ -239,7 +248,7 @@ export const createCommand = new Command('create')
239
248
  fs.mkdirSync(dir, { recursive: true });
240
249
  }
241
250
 
242
- const content = contentFn(name);
251
+ const content = contentFn(args.name);
243
252
  const fileContent = typeof content === 'string'
244
253
  ? content
245
254
  : JSON.stringify(content, null, 2);
@@ -268,4 +277,5 @@ export const createCommand = new Command('create')
268
277
 
269
278
  process.exit(1);
270
279
  }
271
- });
280
+ }
281
+ }
@@ -1,19 +1,29 @@
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 { execSync, spawn } from 'child_process';
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
8
  import { printHeader, printKV, printStep, printError } from '../utils/format.js';
9
9
 
10
- export const devCommand = new Command('dev')
11
- .description('Start development mode with hot-reload')
12
- .argument('[package]', 'Package name or filter pattern', 'all')
13
- .option('-w, --watch', 'Enable watch mode (default)', true)
14
- .option('--ui', 'Enable Studio UI at /_studio/')
15
- .option('-v, --verbose', 'Verbose output')
16
- .action(async (packageName, options) => {
10
+ export default class Dev extends Command {
11
+ static override description = 'Start development mode with hot-reload';
12
+
13
+ static override args = {
14
+ package: Args.string({ description: 'Package name or filter pattern', default: 'all', required: false }),
15
+ };
16
+
17
+ static override flags = {
18
+ watch: Flags.boolean({ char: 'w', description: 'Enable watch mode (default)', default: true }),
19
+ ui: Flags.boolean({ description: 'Enable Studio UI at /_studio/' }),
20
+ verbose: Flags.boolean({ char: 'v', description: 'Verbose output' }),
21
+ };
22
+
23
+ async run(): Promise<void> {
24
+ const { args, flags } = await this.parse(Dev);
25
+ const packageName = args.package;
26
+
17
27
  printHeader('Development Mode');
18
28
 
19
29
  // Check if we are running inside a package (Single Package Mode)
@@ -28,7 +38,7 @@ export const devCommand = new Command('dev')
28
38
  // usage: objectstack serve --dev
29
39
  const binPath = process.argv[1]; // path to objectstack bin
30
40
 
31
- const child = spawn(process.execPath, [binPath, 'serve', '--dev', ...(options.ui ? ['--ui'] : []), ...(options.verbose ? ['--verbose'] : [])], {
41
+ const child = spawn(process.execPath, [binPath, 'serve', '--dev', ...(flags.ui ? ['--ui'] : []), ...(flags.verbose ? ['--verbose'] : [])], {
32
42
  stdio: 'inherit',
33
43
  env: { ...process.env, NODE_ENV: 'development' }
34
44
  });
@@ -69,4 +79,5 @@ export const devCommand = new Command('dev')
69
79
  printError(`Development mode failed: ${error.message || error}`);
70
80
  process.exit(1);
71
81
  }
72
- });
82
+ }
83
+ }
@@ -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 { loadConfig } from '../utils/config.js';
6
6
  import {
@@ -153,19 +153,27 @@ function diffNamedArrays(
153
153
 
154
154
  // ─── Command ────────────────────────────────────────────────────────
155
155
 
156
- export const diffCommand = new Command('diff')
157
- .description('Compare two ObjectStack configurations and detect breaking changes')
158
- .argument('[before]', 'Path to the "before" config file')
159
- .argument('[after]', 'Path to the "after" config file')
160
- .option('--before <path>', 'Path to the "before" config (alternative)')
161
- .option('--after <path>', 'Path to the "after" config (alternative)')
162
- .option('--json', 'Output as JSON')
163
- .option('--breaking-only', 'Show only breaking changes')
164
- .action(async (beforeArg, afterArg, options) => {
156
+ export default class Diff extends Command {
157
+ static override description = 'Compare two ObjectStack configurations and detect breaking changes';
158
+
159
+ static override args = {
160
+ before: Args.string({ description: 'Path to the "before" config file', required: false }),
161
+ after: Args.string({ description: 'Path to the "after" config file', required: false }),
162
+ };
163
+
164
+ static override flags = {
165
+ before: Flags.string({ description: 'Path to the "before" config (alternative)' }),
166
+ after: Flags.string({ description: 'Path to the "after" config (alternative)' }),
167
+ json: Flags.boolean({ description: 'Output as JSON' }),
168
+ 'breaking-only': Flags.boolean({ description: 'Show only breaking changes' }),
169
+ };
170
+
171
+ async run(): Promise<void> {
172
+ const { args, flags } = await this.parse(Diff);
165
173
  const timer = createTimer();
166
174
 
167
- const beforePath: string | undefined = beforeArg || options.before;
168
- const afterPath: string | undefined = afterArg || options.after;
175
+ const beforePath: string | undefined = args.before || flags.before;
176
+ const afterPath: string | undefined = args.after || flags.after;
169
177
 
170
178
  if (!beforePath || !afterPath) {
171
179
  printError('Two config file paths are required.');
@@ -175,7 +183,7 @@ export const diffCommand = new Command('diff')
175
183
  process.exit(1);
176
184
  }
177
185
 
178
- if (!options.json) {
186
+ if (!flags.json) {
179
187
  printHeader('Diff');
180
188
  printStep('Loading configurations...');
181
189
  }
@@ -184,7 +192,7 @@ export const diffCommand = new Command('diff')
184
192
  const { config: beforeConfig } = await loadConfig(beforePath);
185
193
  const { config: afterConfig } = await loadConfig(afterPath);
186
194
 
187
- if (!options.json) {
195
+ if (!flags.json) {
188
196
  printInfo(`Before: ${chalk.white(beforePath)}`);
189
197
  printInfo(`After: ${chalk.white(afterPath)}`);
190
198
  }
@@ -215,14 +223,14 @@ export const diffCommand = new Command('diff')
215
223
  }
216
224
 
217
225
  // ── Filter ──
218
- const diffs = options.breakingOnly
226
+ const diffs = flags['breaking-only']
219
227
  ? allDiffs.filter((d) => d.breaking)
220
228
  : allDiffs;
221
229
 
222
230
  const breakingCount = allDiffs.filter((d) => d.breaking).length;
223
231
 
224
232
  // ── Output ──
225
- if (options.json) {
233
+ if (flags.json) {
226
234
  console.log(JSON.stringify({
227
235
  before: beforePath,
228
236
  after: afterPath,
@@ -237,7 +245,7 @@ export const diffCommand = new Command('diff')
237
245
  console.log('');
238
246
 
239
247
  if (diffs.length === 0) {
240
- printSuccess(options.breakingOnly
248
+ printSuccess(flags['breaking-only']
241
249
  ? 'No breaking changes detected.'
242
250
  : 'No changes detected.');
243
251
  console.log('');
@@ -274,7 +282,7 @@ export const diffCommand = new Command('diff')
274
282
  console.log('');
275
283
 
276
284
  } catch (error: any) {
277
- if (options.json) {
285
+ if (flags.json) {
278
286
  console.log(JSON.stringify({ error: error.message }));
279
287
  process.exit(1);
280
288
  }
@@ -282,4 +290,5 @@ export const diffCommand = new Command('diff')
282
290
  printError(error.message || String(error));
283
291
  process.exit(1);
284
292
  }
285
- });
293
+ }
294
+ }
@@ -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 { Command, Flags } from '@oclif/core';
4
4
  import chalk from 'chalk';
5
5
  import { execSync } from 'child_process';
6
6
  import fs from 'fs';
@@ -299,11 +299,17 @@ function scanDeprecatedPatterns(dir: string): Array<{ file: string; line: number
299
299
 
300
300
  // ─── Command ────────────────────────────────────────────────────────
301
301
 
302
- export const doctorCommand = new Command('doctor')
303
- .description('Check development environment and configuration health')
304
- .option('-v, --verbose', 'Show detailed information')
305
- .option('--scan-deprecations', 'Scan for deprecated ObjectStack patterns')
306
- .action(async (options) => {
302
+ export default class Doctor extends Command {
303
+ static override description = 'Check development environment and configuration health';
304
+
305
+ static override flags = {
306
+ verbose: Flags.boolean({ char: 'v', description: 'Show detailed information' }),
307
+ 'scan-deprecations': Flags.boolean({ description: 'Scan for deprecated ObjectStack patterns' }),
308
+ };
309
+
310
+ async run(): Promise<void> {
311
+ const { flags } = await this.parse(Doctor);
312
+
307
313
  printHeader('Environment Health Check');
308
314
 
309
315
  const results: HealthCheckResult[] = [];
@@ -439,7 +445,7 @@ export const doctorCommand = new Command('doctor')
439
445
  printError(`${padded} ${result.message}`);
440
446
  }
441
447
 
442
- if (result.fix && (options.verbose || result.status === 'error')) {
448
+ if (result.fix && (flags.verbose || result.status === 'error')) {
443
449
  console.log(chalk.dim(` → ${result.fix}`));
444
450
  }
445
451
 
@@ -526,7 +532,7 @@ export const doctorCommand = new Command('doctor')
526
532
  }
527
533
 
528
534
  // ── Deprecation Pattern Scan ─────────────────────────────────────
529
- if (options.scanDeprecations) {
535
+ if (flags['scan-deprecations']) {
530
536
  printStep('Scanning for deprecated ObjectStack patterns...');
531
537
  const scanDir = path.join(cwd, 'src');
532
538
  const deprecations = scanDeprecatedPatterns(scanDir);
@@ -534,7 +540,7 @@ export const doctorCommand = new Command('doctor')
534
540
  hasWarnings = true;
535
541
  for (const dep of deprecations) {
536
542
  printWarning(`${dep.file}:${dep.line} — ${dep.description}`);
537
- if (options.verbose) {
543
+ if (flags.verbose) {
538
544
  console.log(chalk.dim(` → ${dep.replacement}`));
539
545
  }
540
546
  }
@@ -562,4 +568,5 @@ export const doctorCommand = new Command('doctor')
562
568
  }
563
569
 
564
570
  console.log('');
565
- });
571
+ }
572
+ }
@@ -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 {
6
6
  printHeader,
@@ -314,15 +314,24 @@ const SCHEMAS: Record<string, SchemaInfo> = {
314
314
 
315
315
  // ─── Command ────────────────────────────────────────────────────────
316
316
 
317
- export const explainCommand = new Command('explain')
318
- .description('Display human-readable explanation of an ObjectStack schema')
319
- .argument('[schema]', 'Schema name (e.g., object, field, view, flow, agent, app)')
320
- .option('--json', 'Output as JSON')
321
- .action(async (schemaName, options) => {
317
+ export default class Explain extends Command {
318
+ static override description = 'Display human-readable explanation of an ObjectStack schema';
319
+
320
+ static override args = {
321
+ schema: Args.string({ description: 'Schema name (e.g., object, field, view, flow, agent, app)', required: false }),
322
+ };
323
+
324
+ static override flags = {
325
+ json: Flags.boolean({ description: 'Output as JSON' }),
326
+ };
327
+
328
+ async run(): Promise<void> {
329
+ const { args, flags } = await this.parse(Explain);
330
+ const schemaName = args.schema;
322
331
 
323
332
  // ── No argument: list all schemas ──
324
333
  if (!schemaName) {
325
- if (options.json) {
334
+ if (flags.json) {
326
335
  console.log(JSON.stringify({
327
336
  schemas: Object.entries(SCHEMAS).map(([key, s]) => ({
328
337
  name: key,
@@ -347,7 +356,7 @@ export const explainCommand = new Command('explain')
347
356
  // ── Lookup schema ──
348
357
  const schema = SCHEMAS[schemaName.toLowerCase()];
349
358
  if (!schema) {
350
- if (options.json) {
359
+ if (flags.json) {
351
360
  console.log(JSON.stringify({ error: `Unknown schema: ${schemaName}` }));
352
361
  process.exit(1);
353
362
  }
@@ -359,7 +368,7 @@ export const explainCommand = new Command('explain')
359
368
  }
360
369
 
361
370
  // ── JSON output ──
362
- if (options.json) {
371
+ if (flags.json) {
363
372
  console.log(JSON.stringify(schema, null, 2));
364
373
  return;
365
374
  }
@@ -399,4 +408,5 @@ export const explainCommand = new Command('explain')
399
408
  // Documentation link
400
409
  printKV(' Docs', `https://objectstack.dev/docs/${schema.docsPath}`);
401
410
  console.log('');
402
- });
411
+ }
412
+ }