@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 { ZodError } from 'zod';
6
6
  import { ObjectStackDefinitionSchema, normalizeStackInput } from '@objectstack/spec';
@@ -17,53 +17,62 @@ import {
17
17
  printMetadataStats,
18
18
  } from '../utils/format.js';
19
19
 
20
- export const validateCommand = new Command('validate')
21
- .description('Validate ObjectStack configuration against the protocol schema')
22
- .argument('[config]', 'Configuration file path')
23
- .option('--strict', 'Treat warnings as errors')
24
- .option('--json', 'Output results as JSON')
25
- .action(async (configPath, options) => {
20
+ export default class Validate extends Command {
21
+ static override description = 'Validate ObjectStack configuration against the protocol schema';
22
+
23
+ static override args = {
24
+ config: Args.string({ description: 'Configuration file path', required: false }),
25
+ };
26
+
27
+ static override flags = {
28
+ strict: Flags.boolean({ description: 'Treat warnings as errors' }),
29
+ json: Flags.boolean({ description: 'Output results as JSON' }),
30
+ };
31
+
32
+ async run(): Promise<void> {
33
+ const { args, flags } = await this.parse(Validate);
34
+
26
35
  const timer = createTimer();
27
36
 
28
- if (!options.json) {
37
+ if (!flags.json) {
29
38
  printHeader('Validate');
30
39
  }
31
40
 
32
41
  try {
33
42
  // 1. Load configuration
34
- if (!options.json) printStep('Loading configuration...');
35
- const { config, absolutePath, duration } = await loadConfig(configPath);
43
+ if (!flags.json) printStep('Loading configuration...');
44
+ const { config, absolutePath, duration } = await loadConfig(args.config);
36
45
 
37
- if (!options.json) {
46
+ if (!flags.json) {
38
47
  printKV('Config', absolutePath);
39
48
  printKV('Load time', `${duration}ms`);
40
49
  }
41
50
 
42
51
  // 2. Normalize map-formatted stack definition and validate against schema
43
- if (!options.json) printStep('Validating against ObjectStack Protocol...');
52
+ if (!flags.json) printStep('Validating against ObjectStack Protocol...');
44
53
  const normalized = normalizeStackInput(config as Record<string, unknown>);
45
54
  const result = ObjectStackDefinitionSchema.safeParse(normalized);
46
55
 
47
56
  if (!result.success) {
48
- if (options.json) {
57
+ if (flags.json) {
49
58
  console.log(JSON.stringify({
50
59
  valid: false,
51
60
  errors: (result.error as unknown as ZodError).issues,
52
61
  duration: timer.elapsed(),
53
62
  }, null, 2));
54
- process.exit(1);
63
+ this.exit(1);
55
64
  }
56
65
 
57
66
  console.log('');
58
67
  printError('Validation failed');
59
68
  formatZodErrors(result.error as unknown as ZodError);
60
- process.exit(1);
69
+ this.exit(1);
61
70
  }
62
71
 
63
72
  // 3. Collect and display stats
64
73
  const stats = collectMetadataStats(config);
65
74
 
66
- if (options.json) {
75
+ if (flags.json) {
67
76
  console.log(JSON.stringify({
68
77
  valid: true,
69
78
  manifest: config.manifest,
@@ -109,25 +118,26 @@ export const validateCommand = new Command('validate')
109
118
  for (const w of warnings) {
110
119
  console.log(chalk.yellow(` ⚠ ${w}`));
111
120
  }
112
- if (options.strict) {
121
+ if (flags.strict) {
113
122
  console.log('');
114
123
  printError('Strict mode: warnings treated as errors');
115
- process.exit(1);
124
+ this.exit(1);
116
125
  }
117
126
  }
118
127
 
119
128
  console.log('');
120
129
  } catch (error: any) {
121
- if (options.json) {
130
+ if (flags.json) {
122
131
  console.log(JSON.stringify({
123
132
  valid: false,
124
133
  error: error.message,
125
134
  duration: timer.elapsed(),
126
135
  }, null, 2));
127
- process.exit(1);
136
+ this.exit(1);
128
137
  }
129
138
  console.log('');
130
139
  printError(error.message || String(error));
131
- process.exit(1);
140
+ this.exit(1);
132
141
  }
133
- });
142
+ }
143
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,22 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
- export * from './commands/compile.js';
4
- export * from './commands/validate.js';
5
- export * from './commands/info.js';
6
- export * from './commands/init.js';
7
- export * from './commands/generate.js';
8
- export * from './commands/create.js';
9
- export * from './commands/plugin.js';
10
- export * from './commands/dev.js';
11
- export * from './commands/serve.js';
12
- export * from './commands/test.js';
13
- export * from './commands/doctor.js';
14
- export * from './utils/plugin-commands.js';
3
+ // ─── oclif Command Classes ──────────────────────────────────────────
4
+ // Each command is auto-discovered by oclif from `src/commands/`.
5
+ // These re-exports provide programmatic access for testing and integration.
6
+
7
+ export { default as CompileCommand } from './commands/compile.js';
8
+ export { default as ValidateCommand } from './commands/validate.js';
9
+ export { default as InfoCommand } from './commands/info.js';
10
+ export { default as InitCommand } from './commands/init.js';
11
+ export { default as GenerateCommand } from './commands/generate.js';
12
+ export { default as CreateCommand } from './commands/create.js';
13
+ export { default as DevCommand } from './commands/dev.js';
14
+ export { default as ServeCommand } from './commands/serve.js';
15
+ export { default as TestCommand } from './commands/test.js';
16
+ export { default as DoctorCommand } from './commands/doctor.js';
17
+
18
+ // ─── Plugin topic subcommands ───────────────────────────────────────
19
+ export { default as PluginListCommand } from './commands/plugin/list.js';
20
+ export { default as PluginInfoCommand } from './commands/plugin/info.js';
21
+ export { default as PluginAddCommand } from './commands/plugin/add.js';
22
+ export { default as PluginRemoveCommand } from './commands/plugin/remove.js';
@@ -0,0 +1,37 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+
3
+ /**
4
+ * Resolve plugin display name from a plugin entry.
5
+ * Plugins can be string package names, objects with `.name`, or class instances.
6
+ */
7
+ export function resolvePluginName(plugin: unknown): string {
8
+ if (typeof plugin === 'string') return plugin;
9
+ if (plugin && typeof plugin === 'object') {
10
+ const p = plugin as Record<string, unknown>;
11
+ if (typeof p.name === 'string') return p.name;
12
+ if (p.constructor && p.constructor.name !== 'Object') return p.constructor.name;
13
+ }
14
+ return 'unknown';
15
+ }
16
+
17
+ /**
18
+ * Resolve plugin version from a plugin entry.
19
+ */
20
+ export function resolvePluginVersion(plugin: unknown): string {
21
+ if (plugin && typeof plugin === 'object') {
22
+ const p = plugin as Record<string, unknown>;
23
+ if (typeof p.version === 'string') return p.version;
24
+ }
25
+ return '-';
26
+ }
27
+
28
+ /**
29
+ * Resolve plugin type from a plugin entry.
30
+ */
31
+ export function resolvePluginType(plugin: unknown): string {
32
+ if (plugin && typeof plugin === 'object') {
33
+ const p = plugin as Record<string, unknown>;
34
+ if (typeof p.type === 'string') return p.type;
35
+ }
36
+ return 'standard';
37
+ }
@@ -236,7 +236,6 @@ export function createStudioProxyPlugin(vitePort: number) {
236
236
  method: c.req.method,
237
237
  headers,
238
238
  body: isBodyAllowed ? c.req.raw.body : undefined,
239
- // @ts-expect-error — duplex required for streaming request body
240
239
  duplex: isBodyAllowed ? 'half' : undefined,
241
240
  });
242
241
 
@@ -1,70 +1,109 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { compileCommand } from '../src/commands/compile';
3
- import { serveCommand } from '../src/commands/serve';
4
- import { devCommand } from '../src/commands/dev';
5
- import { doctorCommand } from '../src/commands/doctor';
6
- import { createCommand } from '../src/commands/create';
7
- import { testCommand } from '../src/commands/test';
8
- import { validateCommand } from '../src/commands/validate';
9
- import { initCommand } from '../src/commands/init';
10
- import { infoCommand } from '../src/commands/info';
11
- import { generateCommand } from '../src/commands/generate';
12
- import { pluginCommand } from '../src/commands/plugin';
13
-
14
- describe('CLI Commands', () => {
2
+ import Compile from '../src/commands/compile';
3
+ import Serve from '../src/commands/serve';
4
+ import Dev from '../src/commands/dev';
5
+ import Doctor from '../src/commands/doctor';
6
+ import Create from '../src/commands/create';
7
+ import Test from '../src/commands/test';
8
+ import Validate from '../src/commands/validate';
9
+ import Init from '../src/commands/init';
10
+ import Info from '../src/commands/info';
11
+ import Generate from '../src/commands/generate';
12
+ import Lint from '../src/commands/lint';
13
+ import Diff from '../src/commands/diff';
14
+ import Explain from '../src/commands/explain';
15
+ import Studio from '../src/commands/studio';
16
+ import PluginList from '../src/commands/plugin/list';
17
+ import PluginInfo from '../src/commands/plugin/info';
18
+ import PluginAdd from '../src/commands/plugin/add';
19
+ import PluginRemove from '../src/commands/plugin/remove';
20
+ import V2ToV3 from '../src/commands/codemod/v2-to-v3';
21
+
22
+ describe('CLI Commands (oclif)', () => {
15
23
  it('should have compile command', () => {
16
- expect(compileCommand.name()).toBe('compile');
17
- expect(compileCommand.description()).toContain('Compile');
24
+ expect(Compile.description).toContain('Compile');
18
25
  });
19
26
 
20
27
  it('should have serve command', () => {
21
- expect(serveCommand.name()).toBe('serve');
22
- expect(serveCommand.description()).toContain('server');
28
+ expect(Serve.description).toContain('server');
23
29
  });
24
30
 
25
31
  it('should have dev command', () => {
26
- expect(devCommand.name()).toBe('dev');
27
- expect(devCommand.description()).toContain('development mode');
32
+ expect(Dev.description).toContain('development mode');
28
33
  });
29
34
 
30
35
  it('should have doctor command', () => {
31
- expect(doctorCommand.name()).toBe('doctor');
32
- expect(doctorCommand.description()).toContain('health');
36
+ expect(Doctor.description).toContain('health');
33
37
  });
34
38
 
35
39
  it('should have create command', () => {
36
- expect(createCommand.name()).toBe('create');
37
- expect(createCommand.description()).toContain('Create');
40
+ expect(Create.description).toContain('Create');
38
41
  });
39
42
 
40
43
  it('should have test command', () => {
41
- expect(testCommand.name()).toBe('test');
42
- expect(testCommand.description()).toContain('Quality Protocol');
44
+ expect(Test.description).toContain('Quality Protocol');
43
45
  });
44
46
 
45
47
  it('should have validate command', () => {
46
- expect(validateCommand.name()).toBe('validate');
47
- expect(validateCommand.description()).toContain('Validate');
48
+ expect(Validate.description).toContain('Validate');
48
49
  });
49
50
 
50
51
  it('should have init command', () => {
51
- expect(initCommand.name()).toBe('init');
52
- expect(initCommand.description()).toContain('Initialize');
52
+ expect(Init.description).toContain('Initialize');
53
53
  });
54
54
 
55
55
  it('should have info command', () => {
56
- expect(infoCommand.name()).toBe('info');
57
- expect(infoCommand.description()).toContain('summary');
56
+ expect(Info.description).toContain('summary');
58
57
  });
59
58
 
60
59
  it('should have generate command with alias', () => {
61
- expect(generateCommand.name()).toBe('generate');
62
- expect(generateCommand.alias()).toBe('g');
63
- expect(generateCommand.description()).toContain('Generate');
60
+ expect(Generate.aliases).toContain('g');
61
+ expect(Generate.description).toContain('Generate');
62
+ });
63
+
64
+ it('should have lint command', () => {
65
+ expect(Lint.description).toContain('style');
66
+ });
67
+
68
+ it('should have diff command', () => {
69
+ expect(Diff.description).toContain('Compare');
70
+ });
71
+
72
+ it('should have explain command', () => {
73
+ expect(Explain.description).toContain('explanation');
74
+ });
75
+
76
+ it('should have studio command', () => {
77
+ expect(Studio.description).toContain('Studio');
78
+ });
79
+
80
+ it('should have codemod v2-to-v3 command', () => {
81
+ expect(V2ToV3.description).toContain('v2');
64
82
  });
65
83
 
66
- it('should have plugin command with subcommands', () => {
67
- expect(pluginCommand.name()).toBe('plugin');
68
- expect(pluginCommand.description()).toContain('plugin');
84
+ describe('Plugin subcommands', () => {
85
+ it('should have plugin list command', () => {
86
+ expect(PluginList.description).toContain('List');
87
+ });
88
+
89
+ it('should have plugin info command', () => {
90
+ expect(PluginInfo.description).toContain('information');
91
+ });
92
+
93
+ it('should have plugin add command', () => {
94
+ expect(PluginAdd.description).toContain('Add');
95
+ });
96
+
97
+ it('should have plugin remove command', () => {
98
+ expect(PluginRemove.description).toContain('Remove');
99
+ });
100
+
101
+ it('should have plugin remove alias', () => {
102
+ expect(PluginRemove.aliases).toContain('plugin rm');
103
+ });
104
+
105
+ it('should have plugin list alias', () => {
106
+ expect(PluginList.aliases).toContain('plugin ls');
107
+ });
69
108
  });
70
109
  });
@@ -1,162 +1,44 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { Command } from 'commander';
3
- import { loadPluginCommands } from '../src/utils/plugin-commands';
4
-
5
- // Mock the config loader
6
- vi.mock('../src/utils/config.js', () => ({
7
- loadConfig: vi.fn(),
8
- }));
9
-
10
- import { loadConfig } from '../src/utils/config';
11
-
12
- const mockedLoadConfig = vi.mocked(loadConfig);
13
-
14
- describe('loadPluginCommands', () => {
15
- let program: Command;
16
-
17
- beforeEach(() => {
18
- program = new Command();
19
- program.name('objectstack');
20
- vi.clearAllMocks();
21
- });
22
-
23
- it('should do nothing when no config file exists', async () => {
24
- mockedLoadConfig.mockRejectedValue(new Error('No config found'));
25
-
26
- await loadPluginCommands(program);
27
- expect(program.commands).toHaveLength(0);
28
- });
29
-
30
- it('should do nothing when no plugins have command contributions', async () => {
31
- mockedLoadConfig.mockResolvedValue({
32
- config: {
33
- plugins: [
34
- { name: 'simple-plugin' },
35
- ],
36
- },
37
- absolutePath: '/test/objectstack.config.ts',
38
- duration: 10,
39
- });
40
-
41
- await loadPluginCommands(program);
42
- expect(program.commands).toHaveLength(0);
43
- });
44
-
45
- it('should do nothing when plugins array is empty', async () => {
46
- mockedLoadConfig.mockResolvedValue({
47
- config: {
48
- plugins: [],
49
- },
50
- absolutePath: '/test/objectstack.config.ts',
51
- duration: 10,
52
- });
53
-
54
- await loadPluginCommands(program);
55
- expect(program.commands).toHaveLength(0);
56
- });
57
-
58
- it('should do nothing when config has no plugins key', async () => {
59
- mockedLoadConfig.mockResolvedValue({
60
- config: {},
61
- absolutePath: '/test/objectstack.config.ts',
62
- duration: 10,
63
- });
64
-
65
- await loadPluginCommands(program);
66
- expect(program.commands).toHaveLength(0);
67
- });
68
-
69
- it('should detect command contributions from plugin manifest', async () => {
70
- // This test verifies that the contribution detection logic works,
71
- // even though the dynamic import will fail (no real module to load)
72
- mockedLoadConfig.mockResolvedValue({
73
- config: {
74
- plugins: [
75
- {
76
- name: '@acme/plugin-marketplace',
77
- manifest: {
78
- contributes: {
79
- commands: [
80
- {
81
- name: 'marketplace',
82
- description: 'Manage marketplace apps',
83
- module: './cli',
84
- },
85
- ],
86
- },
87
- },
88
- },
89
- ],
90
- },
91
- absolutePath: '/test/objectstack.config.ts',
92
- duration: 10,
93
- });
94
-
95
- // loadPluginCommands will try to import the module and fail silently
96
- // (since no real module exists), but it should not throw
97
- await loadPluginCommands(program);
98
- });
99
-
100
- it('should detect contributions from top-level contributes field', async () => {
101
- mockedLoadConfig.mockResolvedValue({
102
- config: {
103
- plugins: [
104
- {
105
- name: '@acme/plugin-deploy',
106
- contributes: {
107
- commands: [
108
- {
109
- name: 'deploy',
110
- description: 'Deploy to cloud',
111
- },
112
- ],
113
- },
114
- },
115
- ],
116
- },
117
- absolutePath: '/test/objectstack.config.ts',
118
- duration: 10,
119
- });
120
-
121
- // Should not throw even if import fails
122
- await loadPluginCommands(program);
123
- });
124
-
125
- it('should skip non-object plugins', async () => {
126
- mockedLoadConfig.mockResolvedValue({
127
- config: {
128
- plugins: [
129
- 'some-string-plugin',
130
- null,
131
- undefined,
132
- 42,
133
- ],
134
- },
135
- absolutePath: '/test/objectstack.config.ts',
136
- duration: 10,
137
- });
138
-
139
- await loadPluginCommands(program);
140
- expect(program.commands).toHaveLength(0);
141
- });
142
-
143
- it('should handle plugins with non-array commands gracefully', async () => {
144
- mockedLoadConfig.mockResolvedValue({
145
- config: {
146
- plugins: [
147
- {
148
- name: '@acme/bad-plugin',
149
- contributes: {
150
- commands: 'not-an-array',
151
- },
152
- },
153
- ],
154
- },
155
- absolutePath: '/test/objectstack.config.ts',
156
- duration: 10,
157
- });
158
-
159
- await loadPluginCommands(program);
160
- expect(program.commands).toHaveLength(0);
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ /**
4
+ * The custom loadPluginCommands mechanism has been removed.
5
+ * Plugin command extension is now handled by oclif's built-in plugin system.
6
+ *
7
+ * Plugins extend the CLI by:
8
+ * 1. Including `oclif` config in their package.json
9
+ * 2. Exporting oclif Command classes from `src/commands/`
10
+ * 3. Being installed via `os plugins install <package>`
11
+ *
12
+ * The objectstack.config.ts no longer determines CLI command availability.
13
+ */
14
+
15
+ describe('oclif Plugin System', () => {
16
+ it('should have oclif plugins configured in package.json', async () => {
17
+ const { createRequire } = await import('module');
18
+ const require = createRequire(import.meta.url);
19
+ const pkg = require('../package.json');
20
+
21
+ expect(pkg.oclif).toBeDefined();
22
+ expect(pkg.oclif.plugins).toContain('@oclif/plugin-help');
23
+ expect(pkg.oclif.plugins).toContain('@oclif/plugin-plugins');
24
+ });
25
+
26
+ it('should have oclif command discovery configured', async () => {
27
+ const { createRequire } = await import('module');
28
+ const require = createRequire(import.meta.url);
29
+ const pkg = require('../package.json');
30
+
31
+ expect(pkg.oclif.commands).toBeDefined();
32
+ expect(pkg.oclif.commands.strategy).toBe('pattern');
33
+ expect(pkg.oclif.commands.target).toBe('./dist/commands');
34
+ });
35
+
36
+ it('should have bin entries pointing to oclif runner', async () => {
37
+ const { createRequire } = await import('module');
38
+ const require = createRequire(import.meta.url);
39
+ const pkg = require('../package.json');
40
+
41
+ expect(pkg.bin.os).toBe('./bin/run.js');
42
+ expect(pkg.bin.objectstack).toBe('./bin/run.js');
161
43
  });
162
44
  });
@@ -1,39 +1,35 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { pluginCommand } from '../src/commands/plugin';
2
+ import PluginList from '../src/commands/plugin/list';
3
+ import PluginInfo from '../src/commands/plugin/info';
4
+ import PluginAdd from '../src/commands/plugin/add';
5
+ import PluginRemove from '../src/commands/plugin/remove';
3
6
  import fs from 'fs';
4
7
  import path from 'path';
5
8
  import os from 'os';
6
9
 
7
- describe('Plugin Command', () => {
8
- it('should have plugin command with subcommands', () => {
9
- expect(pluginCommand.name()).toBe('plugin');
10
- expect(pluginCommand.description()).toContain('plugin');
10
+ describe('Plugin Commands (oclif)', () => {
11
+ it('should have plugin list command', () => {
12
+ expect(PluginList.description).toContain('List');
11
13
  });
12
14
 
13
- it('should have list subcommand with alias', () => {
14
- const list = pluginCommand.commands.find(c => c.name() === 'list');
15
- expect(list).toBeDefined();
16
- expect(list!.alias()).toBe('ls');
17
- expect(list!.description()).toContain('List');
15
+ it('should have plugin list alias', () => {
16
+ expect(PluginList.aliases).toContain('plugin ls');
18
17
  });
19
18
 
20
- it('should have info subcommand', () => {
21
- const info = pluginCommand.commands.find(c => c.name() === 'info');
22
- expect(info).toBeDefined();
23
- expect(info!.description()).toContain('information');
19
+ it('should have plugin info command', () => {
20
+ expect(PluginInfo.description).toContain('information');
24
21
  });
25
22
 
26
- it('should have add subcommand', () => {
27
- const add = pluginCommand.commands.find(c => c.name() === 'add');
28
- expect(add).toBeDefined();
29
- expect(add!.description()).toContain('Add');
23
+ it('should have plugin add command', () => {
24
+ expect(PluginAdd.description).toContain('Add');
30
25
  });
31
26
 
32
- it('should have remove subcommand with alias', () => {
33
- const remove = pluginCommand.commands.find(c => c.name() === 'remove');
34
- expect(remove).toBeDefined();
35
- expect(remove!.alias()).toBe('rm');
36
- expect(remove!.description()).toContain('Remove');
27
+ it('should have plugin remove command', () => {
28
+ expect(PluginRemove.description).toContain('Remove');
29
+ });
30
+
31
+ it('should have plugin remove alias', () => {
32
+ expect(PluginRemove.aliases).toContain('plugin rm');
37
33
  });
38
34
  });
39
35
 
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "outDir": "dist",
12
+ "rootDir": "src",
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src"]
18
+ }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import '../dist/bin.js';