@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.
- package/.turbo/turbo-build.log +2 -26
- package/CHANGELOG.md +27 -0
- package/README.md +98 -54
- package/bin/run-dev.js +5 -0
- package/bin/run.js +5 -0
- package/dist/bin.d.ts +11 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +12 -3759
- package/dist/bin.js.map +1 -0
- package/dist/commands/codemod/v2-to-v3.d.ts +10 -0
- package/dist/commands/codemod/v2-to-v3.d.ts.map +1 -0
- package/dist/commands/codemod/v2-to-v3.js +145 -0
- package/dist/commands/codemod/v2-to-v3.js.map +1 -0
- package/dist/commands/compile.d.ts +13 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +91 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/create.d.ts +91 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +259 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts +14 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +67 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/diff.d.ts +16 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +239 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +532 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/explain.d.ts +12 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +368 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/generate.d.ts +17 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +833 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/info.d.ts +12 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +100 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/init.d.ts +22 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +295 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +13 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +255 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/plugin/add.d.ts +22 -0
- package/dist/commands/plugin/add.d.ts.map +1 -0
- package/dist/commands/plugin/add.js +93 -0
- package/dist/commands/plugin/add.js.map +1 -0
- package/dist/commands/plugin/info.d.ts +10 -0
- package/dist/commands/plugin/info.d.ts.map +1 -0
- package/dist/commands/plugin/info.js +65 -0
- package/dist/commands/plugin/info.js.map +1 -0
- package/dist/commands/plugin/list.d.ts +13 -0
- package/dist/commands/plugin/list.d.ts.map +1 -0
- package/dist/commands/plugin/list.js +78 -0
- package/dist/commands/plugin/list.js.map +1 -0
- package/dist/commands/plugin/remove.d.ts +20 -0
- package/dist/commands/plugin/remove.d.ts.map +1 -0
- package/dist/commands/plugin/remove.js +79 -0
- package/dist/commands/plugin/remove.js.map +1 -0
- package/dist/commands/serve.d.ts +15 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +286 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/studio.d.ts +19 -0
- package/dist/commands/studio.d.ts.map +1 -0
- package/dist/commands/studio.js +43 -0
- package/dist/commands/studio.js.map +1 -0
- package/dist/commands/test.d.ts +13 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +120 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/validate.d.ts +13 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +115 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +15 -114
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -2799
- package/dist/index.js.map +1 -0
- package/dist/utils/config.d.ts +21 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +66 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/format.d.ts +52 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +202 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/plugin-helpers.d.ts +14 -0
- package/dist/utils/plugin-helpers.d.ts.map +1 -0
- package/dist/utils/plugin-helpers.js +40 -0
- package/dist/utils/plugin-helpers.js.map +1 -0
- package/dist/utils/studio.d.ts +58 -0
- package/dist/utils/studio.d.ts.map +1 -0
- package/dist/utils/studio.js +288 -0
- package/dist/utils/studio.js.map +1 -0
- package/package.json +33 -15
- package/src/bin.ts +11 -104
- package/src/commands/{codemod.ts → codemod/v2-to-v3.ts} +21 -28
- package/src/commands/compile.ts +35 -25
- package/src/commands/create.ts +29 -19
- package/src/commands/dev.ts +21 -10
- package/src/commands/diff.ts +28 -19
- package/src/commands/doctor.ts +20 -11
- package/src/commands/explain.ts +20 -10
- package/src/commands/generate.ts +81 -90
- package/src/commands/info.ts +22 -11
- package/src/commands/init.ts +32 -20
- package/src/commands/lint.ts +27 -15
- package/src/commands/plugin/add.ts +112 -0
- package/src/commands/plugin/info.ts +79 -0
- package/src/commands/plugin/list.ts +93 -0
- package/src/commands/plugin/remove.ts +97 -0
- package/src/commands/serve.ts +30 -20
- package/src/commands/studio.ts +21 -11
- package/src/commands/test.ts +21 -10
- package/src/commands/validate.ts +36 -25
- package/src/index.ts +20 -12
- package/src/utils/format.ts +10 -6
- package/src/utils/plugin-helpers.ts +37 -0
- package/src/utils/studio.ts +0 -1
- package/test/commands.test.ts +76 -37
- package/test/plugin-commands.test.ts +42 -160
- package/test/plugin.test.ts +19 -23
- package/tsconfig.build.json +18 -0
- package/bin/objectstack.js +0 -2
- package/dist/chunk-CSHQEILI.js +0 -246
- package/dist/chunk-Q74JNWKD.js +0 -248
- package/dist/config-A7BN6UIT.js +0 -11
- package/dist/config-UN34WBHT.js +0 -10
- package/src/commands/plugin.ts +0 -372
- package/src/utils/plugin-commands.ts +0 -163
package/src/commands/plugin.ts
DELETED
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { loadConfig, resolveConfigPath } from '../utils/config.js';
|
|
8
|
-
import { printHeader, printSuccess, printError, printInfo, printWarning, printKV } from '../utils/format.js';
|
|
9
|
-
|
|
10
|
-
// ─── Helpers ────────────────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Resolve plugin display name from a plugin entry.
|
|
14
|
-
* Plugins can be string package names, objects with `.name`, or class instances.
|
|
15
|
-
*/
|
|
16
|
-
function resolvePluginName(plugin: unknown): string {
|
|
17
|
-
if (typeof plugin === 'string') return plugin;
|
|
18
|
-
if (plugin && typeof plugin === 'object') {
|
|
19
|
-
const p = plugin as Record<string, unknown>;
|
|
20
|
-
if (typeof p.name === 'string') return p.name;
|
|
21
|
-
if (p.constructor && p.constructor.name !== 'Object') return p.constructor.name;
|
|
22
|
-
}
|
|
23
|
-
return 'unknown';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Resolve plugin version from a plugin entry.
|
|
28
|
-
*/
|
|
29
|
-
function resolvePluginVersion(plugin: unknown): string {
|
|
30
|
-
if (plugin && typeof plugin === 'object') {
|
|
31
|
-
const p = plugin as Record<string, unknown>;
|
|
32
|
-
if (typeof p.version === 'string') return p.version;
|
|
33
|
-
}
|
|
34
|
-
return '-';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Resolve plugin type from a plugin entry.
|
|
39
|
-
*/
|
|
40
|
-
function resolvePluginType(plugin: unknown): string {
|
|
41
|
-
if (plugin && typeof plugin === 'object') {
|
|
42
|
-
const p = plugin as Record<string, unknown>;
|
|
43
|
-
if (typeof p.type === 'string') return p.type;
|
|
44
|
-
}
|
|
45
|
-
return 'standard';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Read the raw text of the config file.
|
|
50
|
-
*/
|
|
51
|
-
function readConfigText(configPath: string): string {
|
|
52
|
-
return fs.readFileSync(configPath, 'utf-8');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Add a plugin import and entry to objectstack.config.ts.
|
|
57
|
-
*
|
|
58
|
-
* This performs a simple text-based transformation:
|
|
59
|
-
* 1. Adds an import statement for the package at the top of the file.
|
|
60
|
-
* 2. Inserts the imported identifier into the `plugins` array, creating one if absent.
|
|
61
|
-
*/
|
|
62
|
-
function addPluginToConfig(configPath: string, packageName: string): void {
|
|
63
|
-
let content = readConfigText(configPath);
|
|
64
|
-
|
|
65
|
-
// Derive a variable name from the package name
|
|
66
|
-
// e.g. "@objectstack/plugin-auth" → "authPlugin"
|
|
67
|
-
const shortName = packageName
|
|
68
|
-
.replace(/^@[^/]+\//, '') // strip scope
|
|
69
|
-
.replace(/^plugin-/, '') // strip "plugin-" prefix
|
|
70
|
-
.replace(/-+/g, '-') // collapse consecutive hyphens
|
|
71
|
-
.replace(/^-|-$/g, ''); // trim leading/trailing hyphens
|
|
72
|
-
const varName = shortName.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()) + 'Plugin';
|
|
73
|
-
|
|
74
|
-
// 1. Add import
|
|
75
|
-
const importLine = `import ${varName} from '${packageName}';\n`;
|
|
76
|
-
|
|
77
|
-
if (content.includes(packageName)) {
|
|
78
|
-
throw new Error(`Plugin '${packageName}' is already referenced in the config`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Insert import after the last existing import
|
|
82
|
-
const importRegex = /^import .+$/gm;
|
|
83
|
-
let lastImportEnd = 0;
|
|
84
|
-
let match: RegExpExecArray | null;
|
|
85
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
86
|
-
lastImportEnd = match.index + match[0].length;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (lastImportEnd > 0) {
|
|
90
|
-
content = content.slice(0, lastImportEnd) + '\n' + importLine + content.slice(lastImportEnd);
|
|
91
|
-
} else {
|
|
92
|
-
content = importLine + '\n' + content;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 2. Add to plugins array (target only the first plugins: [ within defineStack)
|
|
96
|
-
if (/plugins\s*:\s*\[/.test(content)) {
|
|
97
|
-
// plugins array exists — append to it (first occurrence only)
|
|
98
|
-
let replaced = false;
|
|
99
|
-
content = content.replace(
|
|
100
|
-
/(plugins\s*:\s*\[)/,
|
|
101
|
-
(match) => {
|
|
102
|
-
if (replaced) return match;
|
|
103
|
-
replaced = true;
|
|
104
|
-
return `${match}\n ${varName},`;
|
|
105
|
-
}
|
|
106
|
-
);
|
|
107
|
-
} else {
|
|
108
|
-
// No plugins array — add one before the closing of defineStack({...})
|
|
109
|
-
// Look for the last property before the closing `})` or `})`
|
|
110
|
-
content = content.replace(
|
|
111
|
-
/(defineStack\(\{[\s\S]*?)(}\s*\))/,
|
|
112
|
-
`$1 plugins: [\n ${varName},\n ],\n$2`
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
fs.writeFileSync(configPath, content);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Remove a plugin reference from objectstack.config.ts.
|
|
121
|
-
*
|
|
122
|
-
* Removes matching import line and the entry from the plugins array.
|
|
123
|
-
*/
|
|
124
|
-
function removePluginFromConfig(configPath: string, pluginName: string): void {
|
|
125
|
-
let content = readConfigText(configPath);
|
|
126
|
-
|
|
127
|
-
// Remove the import line that references this plugin (exact package name match)
|
|
128
|
-
const importRegex = new RegExp(`^import .+['"]${escapeRegex(pluginName)}['"]\\s*;?\\s*$\\n?`, 'gm');
|
|
129
|
-
const hadImport = importRegex.test(content);
|
|
130
|
-
// Reset regex lastIndex after test()
|
|
131
|
-
importRegex.lastIndex = 0;
|
|
132
|
-
content = content.replace(importRegex, '');
|
|
133
|
-
|
|
134
|
-
// Also try to remove by a derived variable name
|
|
135
|
-
const shortName = pluginName
|
|
136
|
-
.replace(/^@[^/]+\//, '')
|
|
137
|
-
.replace(/^plugin-/, '')
|
|
138
|
-
.replace(/-+/g, '-')
|
|
139
|
-
.replace(/^-|-$/g, '');
|
|
140
|
-
const varName = shortName.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()) + 'Plugin';
|
|
141
|
-
|
|
142
|
-
// Remove import by variable name if it wasn't caught above
|
|
143
|
-
if (!hadImport) {
|
|
144
|
-
const varImportRegex = new RegExp(`^import .* ${escapeRegex(varName)} .+$\\n?`, 'gm');
|
|
145
|
-
content = content.replace(varImportRegex, '');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Remove the entry from the plugins array
|
|
149
|
-
// Match: varName, or 'package-name', or "package-name"
|
|
150
|
-
const entryPatterns = [
|
|
151
|
-
new RegExp(`\\s*${escapeRegex(varName)},?\\n?`, 'g'),
|
|
152
|
-
new RegExp(`\\s*['"]${escapeRegex(pluginName)}['"],?\\n?`, 'g'),
|
|
153
|
-
];
|
|
154
|
-
|
|
155
|
-
for (const pattern of entryPatterns) {
|
|
156
|
-
content = content.replace(pattern, '\n');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Clean up empty plugins array: plugins: [\n ],
|
|
160
|
-
content = content.replace(/plugins\s*:\s*\[\s*\],?\n?/g, '');
|
|
161
|
-
|
|
162
|
-
fs.writeFileSync(configPath, content);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function escapeRegex(str: string): string {
|
|
166
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// ─── Subcommands ────────────────────────────────────────────────────
|
|
170
|
-
|
|
171
|
-
const listCommand = new Command('list')
|
|
172
|
-
.alias('ls')
|
|
173
|
-
.description('List plugins defined in the configuration')
|
|
174
|
-
.argument('[config]', 'Configuration file path')
|
|
175
|
-
.option('--json', 'Output as JSON')
|
|
176
|
-
.action(async (configSource?: string, options?: { json?: boolean }) => {
|
|
177
|
-
try {
|
|
178
|
-
const { config } = await loadConfig(configSource);
|
|
179
|
-
const plugins: unknown[] = config.plugins || [];
|
|
180
|
-
const devPlugins: unknown[] = config.devPlugins || [];
|
|
181
|
-
|
|
182
|
-
if (options?.json) {
|
|
183
|
-
const data = {
|
|
184
|
-
plugins: plugins.map(p => ({
|
|
185
|
-
name: resolvePluginName(p),
|
|
186
|
-
version: resolvePluginVersion(p),
|
|
187
|
-
type: resolvePluginType(p),
|
|
188
|
-
dev: false,
|
|
189
|
-
})),
|
|
190
|
-
devPlugins: devPlugins.map(p => ({
|
|
191
|
-
name: resolvePluginName(p),
|
|
192
|
-
version: resolvePluginVersion(p),
|
|
193
|
-
type: resolvePluginType(p),
|
|
194
|
-
dev: true,
|
|
195
|
-
})),
|
|
196
|
-
};
|
|
197
|
-
console.log(JSON.stringify(data, null, 2));
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
printHeader('Plugins');
|
|
202
|
-
|
|
203
|
-
if (plugins.length === 0 && devPlugins.length === 0) {
|
|
204
|
-
printInfo('No plugins configured');
|
|
205
|
-
console.log('');
|
|
206
|
-
console.log(chalk.dim(' Hint: Add plugins to your objectstack.config.ts'));
|
|
207
|
-
console.log(chalk.dim(' Or run: os plugin add <package-name>'));
|
|
208
|
-
console.log('');
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (plugins.length > 0) {
|
|
213
|
-
console.log(chalk.bold(`\n Plugins (${plugins.length}):`));
|
|
214
|
-
for (const plugin of plugins) {
|
|
215
|
-
const name = resolvePluginName(plugin);
|
|
216
|
-
const version = resolvePluginVersion(plugin);
|
|
217
|
-
const type = resolvePluginType(plugin);
|
|
218
|
-
console.log(
|
|
219
|
-
` ${chalk.cyan('●')} ${chalk.white(name)}` +
|
|
220
|
-
(version !== '-' ? chalk.dim(` v${version}`) : '') +
|
|
221
|
-
(type !== 'standard' ? chalk.dim(` [${type}]`) : '')
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (devPlugins.length > 0) {
|
|
227
|
-
console.log(chalk.bold(`\n Dev Plugins (${devPlugins.length}):`));
|
|
228
|
-
for (const plugin of devPlugins) {
|
|
229
|
-
const name = resolvePluginName(plugin);
|
|
230
|
-
const version = resolvePluginVersion(plugin);
|
|
231
|
-
console.log(
|
|
232
|
-
` ${chalk.yellow('●')} ${chalk.white(name)}` +
|
|
233
|
-
(version !== '-' ? chalk.dim(` v${version}`) : '') +
|
|
234
|
-
chalk.dim(' [dev]')
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
console.log('');
|
|
240
|
-
} catch (error: any) {
|
|
241
|
-
printError(error.message || String(error));
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const infoSubCommand = new Command('info')
|
|
247
|
-
.description('Show detailed information about a plugin')
|
|
248
|
-
.argument('<name>', 'Plugin name or package name')
|
|
249
|
-
.argument('[config]', 'Configuration file path')
|
|
250
|
-
.action(async (name: string, configSource?: string) => {
|
|
251
|
-
try {
|
|
252
|
-
const { config } = await loadConfig(configSource);
|
|
253
|
-
const allPlugins: unknown[] = [
|
|
254
|
-
...(config.plugins || []),
|
|
255
|
-
...(config.devPlugins || []),
|
|
256
|
-
];
|
|
257
|
-
|
|
258
|
-
const found = allPlugins.find((p) => {
|
|
259
|
-
const pName = resolvePluginName(p);
|
|
260
|
-
return pName === name || pName.includes(name);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
if (!found) {
|
|
264
|
-
printError(`Plugin '${name}' not found in configuration`);
|
|
265
|
-
console.log('');
|
|
266
|
-
console.log(chalk.dim(' Available plugins:'));
|
|
267
|
-
for (const p of allPlugins) {
|
|
268
|
-
console.log(chalk.dim(` - ${resolvePluginName(p)}`));
|
|
269
|
-
}
|
|
270
|
-
console.log('');
|
|
271
|
-
process.exit(1);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
printHeader(`Plugin: ${resolvePluginName(found)}`);
|
|
275
|
-
|
|
276
|
-
printKV('Name', resolvePluginName(found));
|
|
277
|
-
printKV('Version', resolvePluginVersion(found));
|
|
278
|
-
printKV('Type', resolvePluginType(found));
|
|
279
|
-
|
|
280
|
-
const isDev = (config.devPlugins || []).includes(found);
|
|
281
|
-
printKV('Environment', isDev ? 'development' : 'production');
|
|
282
|
-
|
|
283
|
-
if (found && typeof found === 'object') {
|
|
284
|
-
const p = found as Record<string, unknown>;
|
|
285
|
-
|
|
286
|
-
if (typeof p.description === 'string') {
|
|
287
|
-
printKV('Description', p.description);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (Array.isArray(p.dependencies) && p.dependencies.length > 0) {
|
|
291
|
-
printKV('Dependencies', p.dependencies.join(', '));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Show services if it's a loaded plugin instance
|
|
295
|
-
if (typeof p.init === 'function') {
|
|
296
|
-
printInfo('This is a runtime plugin instance (has init function)');
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (typeof found === 'string') {
|
|
301
|
-
printInfo('This is a string reference (will be imported at runtime)');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
console.log('');
|
|
305
|
-
} catch (error: any) {
|
|
306
|
-
printError(error.message || String(error));
|
|
307
|
-
process.exit(1);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
const addCommand = new Command('add')
|
|
312
|
-
.description('Add a plugin to objectstack.config.ts')
|
|
313
|
-
.argument('<package>', 'Plugin package name (e.g. @objectstack/plugin-auth)')
|
|
314
|
-
.option('-d, --dev', 'Add as a dev-only plugin')
|
|
315
|
-
.option('-c, --config <path>', 'Configuration file path')
|
|
316
|
-
.action(async (packageName: string, options?: { dev?: boolean; config?: string }) => {
|
|
317
|
-
try {
|
|
318
|
-
const configPath = resolveConfigPath(options?.config);
|
|
319
|
-
|
|
320
|
-
printHeader('Add Plugin');
|
|
321
|
-
console.log(` ${chalk.dim('Package:')} ${chalk.white(packageName)}`);
|
|
322
|
-
console.log(` ${chalk.dim('Config:')} ${chalk.white(path.relative(process.cwd(), configPath))}`);
|
|
323
|
-
console.log('');
|
|
324
|
-
|
|
325
|
-
addPluginToConfig(configPath, packageName);
|
|
326
|
-
printSuccess(`Added ${chalk.cyan(packageName)} to config`);
|
|
327
|
-
|
|
328
|
-
console.log('');
|
|
329
|
-
console.log(chalk.dim(' Next steps:'));
|
|
330
|
-
console.log(chalk.dim(` 1. Install the package: pnpm add ${packageName}`));
|
|
331
|
-
console.log(chalk.dim(' 2. Run: os validate'));
|
|
332
|
-
console.log('');
|
|
333
|
-
} catch (error: any) {
|
|
334
|
-
printError(error.message || String(error));
|
|
335
|
-
process.exit(1);
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
const removeCommand = new Command('remove')
|
|
340
|
-
.alias('rm')
|
|
341
|
-
.description('Remove a plugin from objectstack.config.ts')
|
|
342
|
-
.argument('<name>', 'Plugin name or package name to remove')
|
|
343
|
-
.option('-c, --config <path>', 'Configuration file path')
|
|
344
|
-
.action(async (pluginName: string, options?: { config?: string }) => {
|
|
345
|
-
try {
|
|
346
|
-
const configPath = resolveConfigPath(options?.config);
|
|
347
|
-
|
|
348
|
-
printHeader('Remove Plugin');
|
|
349
|
-
console.log(` ${chalk.dim('Plugin:')} ${chalk.white(pluginName)}`);
|
|
350
|
-
console.log(` ${chalk.dim('Config:')} ${chalk.white(path.relative(process.cwd(), configPath))}`);
|
|
351
|
-
console.log('');
|
|
352
|
-
|
|
353
|
-
removePluginFromConfig(configPath, pluginName);
|
|
354
|
-
printSuccess(`Removed ${chalk.cyan(pluginName)} from config`);
|
|
355
|
-
|
|
356
|
-
console.log('');
|
|
357
|
-
console.log(chalk.dim(' Tip: Run `pnpm remove ' + pluginName + '` to uninstall the package'));
|
|
358
|
-
console.log('');
|
|
359
|
-
} catch (error: any) {
|
|
360
|
-
printError(error.message || String(error));
|
|
361
|
-
process.exit(1);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
// ─── Main Plugin Command ────────────────────────────────────────────
|
|
366
|
-
|
|
367
|
-
export const pluginCommand = new Command('plugin')
|
|
368
|
-
.description('Manage plugins (list, info, add, remove)')
|
|
369
|
-
.addCommand(listCommand)
|
|
370
|
-
.addCommand(infoSubCommand)
|
|
371
|
-
.addCommand(addCommand)
|
|
372
|
-
.addCommand(removeCommand);
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import { loadConfig } from './config.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* CLI Command Contribution resolved from a plugin manifest.
|
|
9
|
-
*/
|
|
10
|
-
interface ResolvedCommandContribution {
|
|
11
|
-
/** CLI command name */
|
|
12
|
-
name: string;
|
|
13
|
-
/** Brief description */
|
|
14
|
-
description?: string;
|
|
15
|
-
/** Module path to import */
|
|
16
|
-
module?: string;
|
|
17
|
-
/** Source plugin package name */
|
|
18
|
-
pluginName: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Discover CLI command contributions from installed plugins.
|
|
23
|
-
*
|
|
24
|
-
* Scans the project's `objectstack.config.ts` for plugins that declare
|
|
25
|
-
* `contributes.commands` in their manifest, then dynamically imports
|
|
26
|
-
* those plugin modules to register Commander.js commands.
|
|
27
|
-
*
|
|
28
|
-
* @param program - The root Commander.js program to register commands on
|
|
29
|
-
*/
|
|
30
|
-
export async function loadPluginCommands(program: Command): Promise<void> {
|
|
31
|
-
let config: Record<string, unknown>;
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const loaded = await loadConfig();
|
|
35
|
-
config = loaded.config;
|
|
36
|
-
} catch {
|
|
37
|
-
// No config file found — nothing to load
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const plugins: unknown[] = [
|
|
42
|
-
...((config.plugins as unknown[] | undefined) || []),
|
|
43
|
-
...((config.devPlugins as unknown[] | undefined) || []),
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
// Collect command contributions from plugin manifests
|
|
47
|
-
const contributions: ResolvedCommandContribution[] = [];
|
|
48
|
-
|
|
49
|
-
for (const plugin of plugins) {
|
|
50
|
-
if (!plugin || typeof plugin !== 'object') continue;
|
|
51
|
-
const p = plugin as Record<string, unknown>;
|
|
52
|
-
|
|
53
|
-
const manifest = p.manifest as Record<string, unknown> | undefined;
|
|
54
|
-
const contributes = (manifest?.contributes ?? p.contributes) as Record<string, unknown> | undefined;
|
|
55
|
-
if (!contributes) continue;
|
|
56
|
-
|
|
57
|
-
const commands = contributes.commands as Array<Record<string, unknown>> | undefined;
|
|
58
|
-
if (!Array.isArray(commands)) continue;
|
|
59
|
-
|
|
60
|
-
const pluginName = resolvePluginName(p);
|
|
61
|
-
|
|
62
|
-
for (const cmd of commands) {
|
|
63
|
-
if (!cmd || typeof cmd.name !== 'string') continue;
|
|
64
|
-
contributions.push({
|
|
65
|
-
name: cmd.name,
|
|
66
|
-
description: typeof cmd.description === 'string' ? cmd.description : undefined,
|
|
67
|
-
module: typeof cmd.module === 'string' ? cmd.module : undefined,
|
|
68
|
-
pluginName,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (contributions.length === 0) return;
|
|
74
|
-
|
|
75
|
-
// Load and register each contributed command
|
|
76
|
-
for (const contribution of contributions) {
|
|
77
|
-
try {
|
|
78
|
-
const commands = await importPluginCommands(contribution);
|
|
79
|
-
for (const cmd of commands) {
|
|
80
|
-
program.addCommand(cmd);
|
|
81
|
-
}
|
|
82
|
-
} catch (error: unknown) {
|
|
83
|
-
// Log warning but don't crash — plugin commands are optional
|
|
84
|
-
if (process.env.DEBUG) {
|
|
85
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
-
console.error(
|
|
87
|
-
chalk.yellow(` ⚠ Failed to load CLI command '${contribution.name}' from plugin '${contribution.pluginName}': ${message}`)
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Import Commander.js commands from a plugin module.
|
|
96
|
-
*
|
|
97
|
-
* The module must export commands in one of these forms:
|
|
98
|
-
* - `export const commands: Command[]`
|
|
99
|
-
* - `export default Command`
|
|
100
|
-
* - `export default Command[]`
|
|
101
|
-
*/
|
|
102
|
-
async function importPluginCommands(
|
|
103
|
-
contribution: ResolvedCommandContribution
|
|
104
|
-
): Promise<Command[]> {
|
|
105
|
-
// Resolve the module specifier
|
|
106
|
-
const moduleId = contribution.module
|
|
107
|
-
? `${contribution.pluginName}/${contribution.module.replace(/^\.\//, '')}`
|
|
108
|
-
: contribution.pluginName;
|
|
109
|
-
|
|
110
|
-
const mod = await import(moduleId);
|
|
111
|
-
|
|
112
|
-
// Form 1: Named export `commands`
|
|
113
|
-
if (Array.isArray(mod.commands)) {
|
|
114
|
-
return mod.commands.filter(isCommandInstance);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Form 2: Default export (single or array)
|
|
118
|
-
const defaultExport = mod.default;
|
|
119
|
-
if (defaultExport) {
|
|
120
|
-
if (Array.isArray(defaultExport)) {
|
|
121
|
-
return defaultExport.filter(isCommandInstance);
|
|
122
|
-
}
|
|
123
|
-
if (isCommandInstance(defaultExport)) {
|
|
124
|
-
return [defaultExport];
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Fallback: search for any Command instances in module exports
|
|
129
|
-
const commands: Command[] = [];
|
|
130
|
-
for (const key of Object.keys(mod)) {
|
|
131
|
-
if (isCommandInstance(mod[key])) {
|
|
132
|
-
commands.push(mod[key]);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return commands;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Check if a value is a Commander.js Command instance.
|
|
141
|
-
* Uses duck-typing to avoid import dependency issues.
|
|
142
|
-
*/
|
|
143
|
-
function isCommandInstance(value: unknown): value is Command {
|
|
144
|
-
if (value === null || typeof value !== 'object') return false;
|
|
145
|
-
const obj = value as Record<string, unknown>;
|
|
146
|
-
return (
|
|
147
|
-
typeof obj.name === 'function' &&
|
|
148
|
-
typeof obj.description === 'function' &&
|
|
149
|
-
typeof obj.action === 'function' &&
|
|
150
|
-
typeof obj.parse === 'function'
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Resolve a human-readable name from a plugin object.
|
|
156
|
-
*/
|
|
157
|
-
function resolvePluginName(plugin: Record<string, unknown>): string {
|
|
158
|
-
if (typeof plugin.name === 'string') return plugin.name;
|
|
159
|
-
const manifest = plugin.manifest as Record<string, unknown> | undefined;
|
|
160
|
-
if (manifest && typeof manifest.name === 'string') return manifest.name;
|
|
161
|
-
if (plugin.constructor && plugin.constructor.name !== 'Object') return plugin.constructor.name;
|
|
162
|
-
return 'unknown';
|
|
163
|
-
}
|