@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
@@ -0,0 +1,112 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+
3
+ import { Args, Command, Flags } from '@oclif/core';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { resolveConfigPath } from '../../utils/config.js';
8
+ import { printHeader, printSuccess, printError } from '../../utils/format.js';
9
+
10
+ /**
11
+ * Add a plugin import and entry to objectstack.config.ts.
12
+ *
13
+ * This performs a simple text-based transformation:
14
+ * 1. Adds an import statement for the package at the top of the file.
15
+ * 2. Inserts the imported identifier into the `plugins` array, creating one if absent.
16
+ */
17
+ function addPluginToConfig(configPath: string, packageName: string): void {
18
+ let content = fs.readFileSync(configPath, 'utf-8');
19
+
20
+ // Derive a variable name from the package name
21
+ // e.g. "@objectstack/plugin-auth" → "authPlugin"
22
+ const shortName = packageName
23
+ .replace(/^@[^/]+\//, '') // strip scope
24
+ .replace(/^plugin-/, '') // strip "plugin-" prefix
25
+ .replace(/-+/g, '-') // collapse consecutive hyphens
26
+ .replace(/^-|-$/g, ''); // trim leading/trailing hyphens
27
+ const varName = shortName.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()) + 'Plugin';
28
+
29
+ // 1. Add import
30
+ const importLine = `import ${varName} from '${packageName}';\n`;
31
+
32
+ if (content.includes(packageName)) {
33
+ throw new Error(`Plugin '${packageName}' is already referenced in the config`);
34
+ }
35
+
36
+ // Insert import after the last existing import
37
+ const importRegex = /^import .+$/gm;
38
+ let lastImportEnd = 0;
39
+ let match: RegExpExecArray | null;
40
+ while ((match = importRegex.exec(content)) !== null) {
41
+ lastImportEnd = match.index + match[0].length;
42
+ }
43
+
44
+ if (lastImportEnd > 0) {
45
+ content = content.slice(0, lastImportEnd) + '\n' + importLine + content.slice(lastImportEnd);
46
+ } else {
47
+ content = importLine + '\n' + content;
48
+ }
49
+
50
+ // 2. Add to plugins array (target only the first plugins: [ within defineStack)
51
+ if (/plugins\s*:\s*\[/.test(content)) {
52
+ // plugins array exists — append to it (first occurrence only)
53
+ let replaced = false;
54
+ content = content.replace(
55
+ /(plugins\s*:\s*\[)/,
56
+ (match) => {
57
+ if (replaced) return match;
58
+ replaced = true;
59
+ return `${match}\n ${varName},`;
60
+ }
61
+ );
62
+ } else {
63
+ // No plugins array — add one before the closing of defineStack({...})
64
+ // Look for the last property before the closing `})` or `})`
65
+ content = content.replace(
66
+ /(defineStack\(\{[\s\S]*?)(}\s*\))/,
67
+ `$1 plugins: [\n ${varName},\n ],\n$2`
68
+ );
69
+ }
70
+
71
+ fs.writeFileSync(configPath, content);
72
+ }
73
+
74
+ export { addPluginToConfig };
75
+
76
+ export default class PluginAdd extends Command {
77
+ static override description = 'Add a plugin to objectstack.config.ts';
78
+
79
+ static override args = {
80
+ package: Args.string({ description: 'Plugin package name (e.g. @objectstack/plugin-auth)', required: true }),
81
+ };
82
+
83
+ static override flags = {
84
+ dev: Flags.boolean({ char: 'd', description: 'Add as a dev-only plugin' }),
85
+ config: Flags.string({ char: 'c', description: 'Configuration file path' }),
86
+ };
87
+
88
+ async run(): Promise<void> {
89
+ const { args, flags } = await this.parse(PluginAdd);
90
+
91
+ try {
92
+ const configPath = resolveConfigPath(flags.config);
93
+
94
+ printHeader('Add Plugin');
95
+ console.log(` ${chalk.dim('Package:')} ${chalk.white(args.package)}`);
96
+ console.log(` ${chalk.dim('Config:')} ${chalk.white(path.relative(process.cwd(), configPath))}`);
97
+ console.log('');
98
+
99
+ addPluginToConfig(configPath, args.package);
100
+ printSuccess(`Added ${chalk.cyan(args.package)} to config`);
101
+
102
+ console.log('');
103
+ console.log(chalk.dim(' Next steps:'));
104
+ console.log(chalk.dim(` 1. Install the package: pnpm add ${args.package}`));
105
+ console.log(chalk.dim(' 2. Run: os validate'));
106
+ console.log('');
107
+ } catch (error: any) {
108
+ printError(error.message || String(error));
109
+ this.exit(1);
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,79 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+
3
+ import { Args, Command } from '@oclif/core';
4
+ import chalk from 'chalk';
5
+ import { loadConfig } from '../../utils/config.js';
6
+ import { printHeader, printError, printInfo, printKV } from '../../utils/format.js';
7
+ import { resolvePluginName, resolvePluginVersion, resolvePluginType } from '../../utils/plugin-helpers.js';
8
+
9
+ export default class PluginInfo extends Command {
10
+ static override description = 'Show detailed information about a plugin';
11
+
12
+ static override args = {
13
+ name: Args.string({ description: 'Plugin name or package name', required: true }),
14
+ config: Args.string({ description: 'Configuration file path', required: false }),
15
+ };
16
+
17
+ async run(): Promise<void> {
18
+ const { args } = await this.parse(PluginInfo);
19
+
20
+ try {
21
+ const { config } = await loadConfig(args.config);
22
+ const allPlugins: unknown[] = [
23
+ ...(config.plugins || []),
24
+ ...(config.devPlugins || []),
25
+ ];
26
+
27
+ const found = allPlugins.find((p) => {
28
+ const pName = resolvePluginName(p);
29
+ return pName === args.name || pName.includes(args.name);
30
+ });
31
+
32
+ if (!found) {
33
+ printError(`Plugin '${args.name}' not found in configuration`);
34
+ console.log('');
35
+ console.log(chalk.dim(' Available plugins:'));
36
+ for (const p of allPlugins) {
37
+ console.log(chalk.dim(` - ${resolvePluginName(p)}`));
38
+ }
39
+ console.log('');
40
+ this.exit(1);
41
+ }
42
+
43
+ printHeader(`Plugin: ${resolvePluginName(found)}`);
44
+
45
+ printKV('Name', resolvePluginName(found));
46
+ printKV('Version', resolvePluginVersion(found));
47
+ printKV('Type', resolvePluginType(found));
48
+
49
+ const isDev = (config.devPlugins || []).includes(found);
50
+ printKV('Environment', isDev ? 'development' : 'production');
51
+
52
+ if (found && typeof found === 'object') {
53
+ const p = found as Record<string, unknown>;
54
+
55
+ if (typeof p.description === 'string') {
56
+ printKV('Description', p.description);
57
+ }
58
+
59
+ if (Array.isArray(p.dependencies) && p.dependencies.length > 0) {
60
+ printKV('Dependencies', p.dependencies.join(', '));
61
+ }
62
+
63
+ // Show services if it's a loaded plugin instance
64
+ if (typeof p.init === 'function') {
65
+ printInfo('This is a runtime plugin instance (has init function)');
66
+ }
67
+ }
68
+
69
+ if (typeof found === 'string') {
70
+ printInfo('This is a string reference (will be imported at runtime)');
71
+ }
72
+
73
+ console.log('');
74
+ } catch (error: any) {
75
+ printError(error.message || String(error));
76
+ this.exit(1);
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,93 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+
3
+ import { Args, Command, Flags } from '@oclif/core';
4
+ import chalk from 'chalk';
5
+ import { loadConfig } from '../../utils/config.js';
6
+ import { printHeader, printInfo, printError } from '../../utils/format.js';
7
+ import { resolvePluginName, resolvePluginVersion, resolvePluginType } from '../../utils/plugin-helpers.js';
8
+
9
+ export default class PluginList extends Command {
10
+ static override description = 'List plugins defined in the configuration';
11
+
12
+ static override aliases = ['plugin ls'];
13
+
14
+ static override args = {
15
+ config: Args.string({ description: 'Configuration file path', required: false }),
16
+ };
17
+
18
+ static override flags = {
19
+ json: Flags.boolean({ description: 'Output as JSON' }),
20
+ };
21
+
22
+ async run(): Promise<void> {
23
+ const { args, flags } = await this.parse(PluginList);
24
+
25
+ try {
26
+ const { config } = await loadConfig(args.config);
27
+ const plugins: unknown[] = config.plugins || [];
28
+ const devPlugins: unknown[] = config.devPlugins || [];
29
+
30
+ if (flags.json) {
31
+ const data = {
32
+ plugins: plugins.map(p => ({
33
+ name: resolvePluginName(p),
34
+ version: resolvePluginVersion(p),
35
+ type: resolvePluginType(p),
36
+ dev: false,
37
+ })),
38
+ devPlugins: devPlugins.map(p => ({
39
+ name: resolvePluginName(p),
40
+ version: resolvePluginVersion(p),
41
+ type: resolvePluginType(p),
42
+ dev: true,
43
+ })),
44
+ };
45
+ console.log(JSON.stringify(data, null, 2));
46
+ return;
47
+ }
48
+
49
+ printHeader('Plugins');
50
+
51
+ if (plugins.length === 0 && devPlugins.length === 0) {
52
+ printInfo('No plugins configured');
53
+ console.log('');
54
+ console.log(chalk.dim(' Hint: Add plugins to your objectstack.config.ts'));
55
+ console.log(chalk.dim(' Or run: os plugin add <package-name>'));
56
+ console.log('');
57
+ return;
58
+ }
59
+
60
+ if (plugins.length > 0) {
61
+ console.log(chalk.bold(`\n Plugins (${plugins.length}):`));
62
+ for (const plugin of plugins) {
63
+ const name = resolvePluginName(plugin);
64
+ const version = resolvePluginVersion(plugin);
65
+ const type = resolvePluginType(plugin);
66
+ console.log(
67
+ ` ${chalk.cyan('●')} ${chalk.white(name)}` +
68
+ (version !== '-' ? chalk.dim(` v${version}`) : '') +
69
+ (type !== 'standard' ? chalk.dim(` [${type}]`) : '')
70
+ );
71
+ }
72
+ }
73
+
74
+ if (devPlugins.length > 0) {
75
+ console.log(chalk.bold(`\n Dev Plugins (${devPlugins.length}):`));
76
+ for (const plugin of devPlugins) {
77
+ const name = resolvePluginName(plugin);
78
+ const version = resolvePluginVersion(plugin);
79
+ console.log(
80
+ ` ${chalk.yellow('●')} ${chalk.white(name)}` +
81
+ (version !== '-' ? chalk.dim(` v${version}`) : '') +
82
+ chalk.dim(' [dev]')
83
+ );
84
+ }
85
+ }
86
+
87
+ console.log('');
88
+ } catch (error: any) {
89
+ printError(error.message || String(error));
90
+ this.exit(1);
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,97 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+
3
+ import { Args, Command, Flags } from '@oclif/core';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { resolveConfigPath } from '../../utils/config.js';
8
+ import { printHeader, printSuccess, printError } from '../../utils/format.js';
9
+
10
+ function escapeRegex(str: string): string {
11
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
12
+ }
13
+
14
+ /**
15
+ * Remove a plugin reference from objectstack.config.ts.
16
+ *
17
+ * Removes matching import line and the entry from the plugins array.
18
+ */
19
+ function removePluginFromConfig(configPath: string, pluginName: string): void {
20
+ let content = fs.readFileSync(configPath, 'utf-8');
21
+
22
+ // Remove the import line that references this plugin (exact package name match)
23
+ const importRegex = new RegExp(`^import .+['"]${escapeRegex(pluginName)}['"]\\s*;?\\s*$\\n?`, 'gm');
24
+ const hadImport = importRegex.test(content);
25
+ // Reset regex lastIndex after test()
26
+ importRegex.lastIndex = 0;
27
+ content = content.replace(importRegex, '');
28
+
29
+ // Also try to remove by a derived variable name
30
+ const shortName = pluginName
31
+ .replace(/^@[^/]+\//, '')
32
+ .replace(/^plugin-/, '')
33
+ .replace(/-+/g, '-')
34
+ .replace(/^-|-$/g, '');
35
+ const varName = shortName.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()) + 'Plugin';
36
+
37
+ // Remove import by variable name if it wasn't caught above
38
+ if (!hadImport) {
39
+ const varImportRegex = new RegExp(`^import .* ${escapeRegex(varName)} .+$\\n?`, 'gm');
40
+ content = content.replace(varImportRegex, '');
41
+ }
42
+
43
+ // Remove the entry from the plugins array
44
+ // Match: varName, or 'package-name', or "package-name"
45
+ const entryPatterns = [
46
+ new RegExp(`\\s*${escapeRegex(varName)},?\\n?`, 'g'),
47
+ new RegExp(`\\s*['"]${escapeRegex(pluginName)}['"],?\\n?`, 'g'),
48
+ ];
49
+
50
+ for (const pattern of entryPatterns) {
51
+ content = content.replace(pattern, '\n');
52
+ }
53
+
54
+ // Clean up empty plugins array: plugins: [\n ],
55
+ content = content.replace(/plugins\s*:\s*\[\s*\],?\n?/g, '');
56
+
57
+ fs.writeFileSync(configPath, content);
58
+ }
59
+
60
+ export { removePluginFromConfig };
61
+
62
+ export default class PluginRemove extends Command {
63
+ static override description = 'Remove a plugin from objectstack.config.ts';
64
+
65
+ static override aliases = ['plugin rm'];
66
+
67
+ static override args = {
68
+ name: Args.string({ description: 'Plugin name or package name to remove', required: true }),
69
+ };
70
+
71
+ static override flags = {
72
+ config: Flags.string({ char: 'c', description: 'Configuration file path' }),
73
+ };
74
+
75
+ async run(): Promise<void> {
76
+ const { args, flags } = await this.parse(PluginRemove);
77
+
78
+ try {
79
+ const configPath = resolveConfigPath(flags.config);
80
+
81
+ printHeader('Remove Plugin');
82
+ console.log(` ${chalk.dim('Plugin:')} ${chalk.white(args.name)}`);
83
+ console.log(` ${chalk.dim('Config:')} ${chalk.white(path.relative(process.cwd(), configPath))}`);
84
+ console.log('');
85
+
86
+ removePluginFromConfig(configPath, args.name);
87
+ printSuccess(`Removed ${chalk.cyan(args.name)} from config`);
88
+
89
+ console.log('');
90
+ console.log(chalk.dim(' Tip: Run `pnpm remove ' + args.name + '` to uninstall the package'));
91
+ console.log('');
92
+ } catch (error: any) {
93
+ printError(error.message || String(error));
94
+ this.exit(1);
95
+ }
96
+ }
97
+ }
@@ -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 net from 'net';
@@ -48,15 +48,24 @@ const getAvailablePort = async (startPort: number): Promise<number> => {
48
48
  return port;
49
49
  };
50
50
 
51
- export const serveCommand = new Command('serve')
52
- .description('Start ObjectStack server with plugins from configuration')
53
- .argument('[config]', 'Configuration file path', 'objectstack.config.ts')
54
- .option('-p, --port <port>', 'Server port', '3000')
55
- .option('--dev', 'Run in development mode (load devPlugins)')
56
- .option('--ui', 'Enable Studio UI at /_studio/ (default: true in dev mode)')
57
- .option('--no-server', 'Skip starting HTTP server plugin')
58
- .action(async (configPath, options) => {
59
- let port = parseInt(options.port);
51
+ export default class Serve extends Command {
52
+ static override description = 'Start ObjectStack server with plugins from configuration';
53
+
54
+ static override args = {
55
+ config: Args.string({ description: 'Configuration file path', required: false, default: 'objectstack.config.ts' }),
56
+ };
57
+
58
+ static override flags = {
59
+ port: Flags.string({ char: 'p', description: 'Server port', default: '3000' }),
60
+ dev: Flags.boolean({ description: 'Run in development mode (load devPlugins)' }),
61
+ ui: Flags.boolean({ description: 'Enable Studio UI at /_studio/ (default: true in dev mode)' }),
62
+ server: Flags.boolean({ description: 'Start HTTP server plugin', default: true, allowNo: true }),
63
+ };
64
+
65
+ async run(): Promise<void> {
66
+ const { args, flags } = await this.parse(Serve);
67
+
68
+ let port = parseInt(flags.port);
60
69
  try {
61
70
  const availablePort = await getAvailablePort(port);
62
71
  if (availablePort !== port) {
@@ -66,15 +75,15 @@ export const serveCommand = new Command('serve')
66
75
  // Ignore error and try with original port
67
76
  }
68
77
 
69
- const isDev = options.dev || process.env.NODE_ENV === 'development';
78
+ const isDev = flags.dev || process.env.NODE_ENV === 'development';
70
79
 
71
- const absolutePath = path.resolve(process.cwd(), configPath);
80
+ const absolutePath = path.resolve(process.cwd(), args.config!);
72
81
  const relativeConfig = path.relative(process.cwd(), absolutePath);
73
82
 
74
83
  if (!fs.existsSync(absolutePath)) {
75
84
  printError(`Configuration file not found: ${absolutePath}`);
76
85
  console.log(chalk.dim(' Hint: Run `objectstack init` to create a new project'));
77
- process.exit(1);
86
+ this.exit(1);
78
87
  }
79
88
 
80
89
  // Quiet loading — only show a single spinner line
@@ -106,7 +115,7 @@ export const serveCommand = new Command('serve')
106
115
  console.debug = originalConsoleDebug;
107
116
  };
108
117
 
109
- const portShifted = parseInt(options.port) !== port;
118
+ const portShifted = parseInt(flags.port) !== port;
110
119
 
111
120
  try {
112
121
  // ── Suppress ALL runtime noise during boot ────────────────────
@@ -131,7 +140,7 @@ export const serveCommand = new Command('serve')
131
140
  const config = mod.default || mod;
132
141
 
133
142
  if (!config) {
134
- throw new Error(`No default export found in ${configPath}`);
143
+ throw new Error(`No default export found in ${args.config}`);
135
144
  }
136
145
 
137
146
  // Import ObjectStack runtime
@@ -151,7 +160,7 @@ export const serveCommand = new Command('serve')
151
160
  let plugins = config.plugins || [];
152
161
 
153
162
  // Merge devPlugins if in dev mode
154
- if (options.dev && config.devPlugins) {
163
+ if (flags.dev && config.devPlugins) {
155
164
  plugins = [...plugins, ...config.devPlugins];
156
165
  }
157
166
 
@@ -228,7 +237,7 @@ export const serveCommand = new Command('serve')
228
237
  }
229
238
 
230
239
  // Add HTTP server plugin if not disabled
231
- if (options.server !== false) {
240
+ if (flags.server) {
232
241
  try {
233
242
  const { HonoServerPlugin } = await import('@objectstack/plugin-hono-server');
234
243
  const serverPlugin = new HonoServerPlugin({ port });
@@ -260,7 +269,7 @@ export const serveCommand = new Command('serve')
260
269
  // ── Studio UI ─────────────────────────────────────────────────
261
270
  // In dev mode, Studio UI is enabled by default (use --no-ui to disable).
262
271
  // Always serves the pre-built dist/ — no Vite dev server, no extra port.
263
- const enableUI = options.ui ?? isDev;
272
+ const enableUI = flags.ui || isDev;
264
273
 
265
274
  if (enableUI) {
266
275
  const studioPath = resolveStudioPath();
@@ -306,6 +315,7 @@ export const serveCommand = new Command('serve')
306
315
  console.log('');
307
316
  printError(error.message || String(error));
308
317
  if (process.env.DEBUG) console.error(chalk.dim(error.stack));
309
- process.exit(1);
318
+ this.exit(1);
310
319
  }
311
- });
320
+ }
321
+ }
@@ -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 { spawn } from 'child_process';
6
6
  import { printHeader, printKV, printStep } from '../utils/format.js';
@@ -12,11 +12,20 @@ import { printHeader, printKV, printStep } from '../utils/format.js';
12
12
  * Starts the ObjectStack server in development mode with the Studio
13
13
  * UI available at http://localhost:<port>/_studio/
14
14
  */
15
- export const studioCommand = new Command('studio')
16
- .description('Launch Studio UI with development server')
17
- .argument('[config]', 'Configuration file path', 'objectstack.config.ts')
18
- .option('-p, --port <port>', 'Server port', '3000')
19
- .action(async (configPath, options) => {
15
+ export default class Studio extends Command {
16
+ static override description = 'Launch Studio UI with development server';
17
+
18
+ static override args = {
19
+ config: Args.string({ description: 'Configuration file path', required: false, default: 'objectstack.config.ts' }),
20
+ };
21
+
22
+ static override flags = {
23
+ port: Flags.string({ char: 'p', description: 'Server port', default: '3000' }),
24
+ };
25
+
26
+ async run(): Promise<void> {
27
+ const { args, flags } = await this.parse(Studio);
28
+
20
29
  printHeader('Studio');
21
30
  printKV('Mode', 'dev + ui', '🎨');
22
31
  printStep('Delegating to serve --dev --ui …');
@@ -24,19 +33,20 @@ export const studioCommand = new Command('studio')
24
33
 
25
34
  // Delegate to the serve command with --dev --ui flags
26
35
  const binPath = process.argv[1];
27
- const args = [
36
+ const spawnArgs = [
28
37
  binPath,
29
38
  'serve',
30
- configPath,
39
+ args.config!,
31
40
  '--dev',
32
41
  '--ui',
33
- '--port', options.port,
42
+ '--port', flags.port,
34
43
  ];
35
44
 
36
- const child = spawn(process.execPath, args, {
45
+ const child = spawn(process.execPath, spawnArgs, {
37
46
  stdio: 'inherit',
38
47
  env: { ...process.env, NODE_ENV: 'development' },
39
48
  });
40
49
 
41
50
  child.on('exit', (code) => process.exit(code ?? 0));
42
- });
51
+ }
52
+ }
@@ -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 path from 'path';
6
6
  import fs from 'fs';
@@ -52,18 +52,28 @@ function resolveGlob(pattern: string): string[] {
52
52
  .filter(fullPath => fs.statSync(fullPath).isFile());
53
53
  }
54
54
 
55
- export const testCommand = new Command('test')
56
- .description('Run Quality Protocol test scenarios against a running server')
57
- .argument('[files]', 'Glob pattern for test files (e.g. "qa/*.test.json")', 'qa/*.test.json')
58
- .option('--url <url>', 'Target base URL', 'http://localhost:3000')
59
- .option('--token <token>', 'Authentication token')
60
- .action(async (filesPattern, options) => {
55
+ export default class Test extends Command {
56
+ static override description = 'Run Quality Protocol test scenarios against a running server';
57
+
58
+ static override args = {
59
+ files: Args.string({ description: 'Glob pattern for test files (e.g. "qa/*.test.json")', required: false, default: 'qa/*.test.json' }),
60
+ };
61
+
62
+ static override flags = {
63
+ url: Flags.string({ description: 'Target base URL', default: 'http://localhost:3000' }),
64
+ token: Flags.string({ description: 'Authentication token' }),
65
+ };
66
+
67
+ async run(): Promise<void> {
68
+ const { args, flags } = await this.parse(Test);
69
+ const filesPattern = args.files;
70
+
61
71
  console.log(chalk.bold(`\n🧪 ObjectStack Quality Protocol Runner`));
62
72
  console.log(chalk.dim(`-------------------------------------`));
63
- console.log(`Target: ${chalk.blue(options.url)}`);
73
+ console.log(`Target: ${chalk.blue(flags.url)}`);
64
74
 
65
75
  // 1. Setup Runner
66
- const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
76
+ const adapter = new CoreQA.HttpTestAdapter(flags.url, flags.token);
67
77
  const runner = new CoreQA.TestRunner(adapter);
68
78
 
69
79
  // 2. Find test files using glob-style pattern matching
@@ -121,4 +131,5 @@ export const testCommand = new Command('test')
121
131
  console.log(chalk.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
122
132
  process.exit(0);
123
133
  }
124
- });
134
+ }
135
+ }