@objectstack/cli 2.0.6 → 3.0.0
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 +10 -6
- package/CHANGELOG.md +30 -0
- package/README.md +88 -3
- package/dist/bin.js +691 -425
- package/dist/chunk-CSHQEILI.js +246 -0
- package/dist/chunk-Q74JNWKD.js +248 -0
- package/dist/config-A7BN6UIT.js +11 -0
- package/dist/config-UN34WBHT.js +10 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +641 -382
- package/package.json +9 -9
- package/src/bin.ts +18 -1
- package/src/commands/generate.ts +181 -3
- package/src/commands/plugin.ts +372 -0
- package/src/index.ts +2 -0
- package/src/utils/plugin-commands.ts +163 -0
- package/test/commands.test.ts +6 -0
- package/test/plugin-commands.test.ts +162 -0
- package/test/plugin.test.ts +173 -0
package/dist/index.js
CHANGED
|
@@ -1,237 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectMetadataStats,
|
|
3
|
+
createTimer,
|
|
4
|
+
formatZodErrors,
|
|
5
|
+
loadConfig,
|
|
6
|
+
printError,
|
|
7
|
+
printHeader,
|
|
8
|
+
printInfo,
|
|
9
|
+
printKV,
|
|
10
|
+
printMetadataStats,
|
|
11
|
+
printServerReady,
|
|
12
|
+
printStep,
|
|
13
|
+
printSuccess,
|
|
14
|
+
printWarning,
|
|
15
|
+
resolveConfigPath
|
|
16
|
+
} from "./chunk-CSHQEILI.js";
|
|
17
|
+
|
|
1
18
|
// src/commands/compile.ts
|
|
2
19
|
import { Command } from "commander";
|
|
3
|
-
import path2 from "path";
|
|
4
|
-
import fs2 from "fs";
|
|
5
|
-
import chalk3 from "chalk";
|
|
6
|
-
import { ObjectStackDefinitionSchema } from "@objectstack/spec";
|
|
7
|
-
|
|
8
|
-
// src/utils/config.ts
|
|
9
20
|
import path from "path";
|
|
10
21
|
import fs from "fs";
|
|
11
|
-
import chalk2 from "chalk";
|
|
12
|
-
import { bundleRequire } from "bundle-require";
|
|
13
|
-
|
|
14
|
-
// src/utils/format.ts
|
|
15
22
|
import chalk from "chalk";
|
|
16
|
-
|
|
17
|
-
console.log(chalk.bold(`
|
|
18
|
-
\u25C6 ${title}`));
|
|
19
|
-
console.log(chalk.dim("\u2500".repeat(40)));
|
|
20
|
-
}
|
|
21
|
-
function printKV(key, value, icon) {
|
|
22
|
-
const prefix = icon ? `${icon} ` : " ";
|
|
23
|
-
console.log(`${prefix}${chalk.dim(key + ":")} ${chalk.white(String(value))}`);
|
|
24
|
-
}
|
|
25
|
-
function printSuccess(msg) {
|
|
26
|
-
console.log(chalk.green(` \u2713 ${msg}`));
|
|
27
|
-
}
|
|
28
|
-
function printWarning(msg) {
|
|
29
|
-
console.log(chalk.yellow(` \u26A0 ${msg}`));
|
|
30
|
-
}
|
|
31
|
-
function printError(msg) {
|
|
32
|
-
console.log(chalk.red(` \u2717 ${msg}`));
|
|
33
|
-
}
|
|
34
|
-
function printInfo(msg) {
|
|
35
|
-
console.log(chalk.blue(` \u2139 ${msg}`));
|
|
36
|
-
}
|
|
37
|
-
function printStep(msg) {
|
|
38
|
-
console.log(chalk.yellow(` \u2192 ${msg}`));
|
|
39
|
-
}
|
|
40
|
-
function createTimer() {
|
|
41
|
-
const start = Date.now();
|
|
42
|
-
return {
|
|
43
|
-
elapsed: () => Date.now() - start,
|
|
44
|
-
display: () => `${Date.now() - start}ms`
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function formatZodErrors(error) {
|
|
48
|
-
const issues = error.issues || error.errors || [];
|
|
49
|
-
if (issues.length === 0) {
|
|
50
|
-
console.log(chalk.red(" Unknown validation error"));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
54
|
-
for (const issue of issues) {
|
|
55
|
-
const topPath = issue.path?.[0] || "_root";
|
|
56
|
-
if (!grouped.has(String(topPath))) {
|
|
57
|
-
grouped.set(String(topPath), []);
|
|
58
|
-
}
|
|
59
|
-
grouped.get(String(topPath)).push(issue);
|
|
60
|
-
}
|
|
61
|
-
for (const [section, sectionIssues] of grouped) {
|
|
62
|
-
console.log(chalk.bold.red(`
|
|
63
|
-
${section}:`));
|
|
64
|
-
for (const issue of sectionIssues) {
|
|
65
|
-
const path11 = issue.path?.join(".") || "";
|
|
66
|
-
const code = issue.code || "";
|
|
67
|
-
const msg = issue.message || "";
|
|
68
|
-
console.log(chalk.red(` \u2717 ${path11}`));
|
|
69
|
-
console.log(chalk.dim(` ${code}: ${msg}`));
|
|
70
|
-
if (issue.expected) {
|
|
71
|
-
console.log(chalk.dim(` expected: ${chalk.green(issue.expected)}`));
|
|
72
|
-
}
|
|
73
|
-
if (issue.received) {
|
|
74
|
-
console.log(chalk.dim(` received: ${chalk.red(issue.received)}`));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
console.log("");
|
|
79
|
-
console.log(chalk.dim(` ${issues.length} validation error(s) total`));
|
|
80
|
-
}
|
|
81
|
-
function collectMetadataStats(config) {
|
|
82
|
-
const count = (arr) => Array.isArray(arr) ? arr.length : 0;
|
|
83
|
-
let fields = 0;
|
|
84
|
-
if (Array.isArray(config.objects)) {
|
|
85
|
-
for (const obj of config.objects) {
|
|
86
|
-
if (obj.fields && typeof obj.fields === "object") {
|
|
87
|
-
fields += Object.keys(obj.fields).length;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
objects: count(config.objects),
|
|
93
|
-
objectExtensions: count(config.objectExtensions),
|
|
94
|
-
fields,
|
|
95
|
-
views: count(config.views),
|
|
96
|
-
pages: count(config.pages),
|
|
97
|
-
apps: count(config.apps),
|
|
98
|
-
dashboards: count(config.dashboards),
|
|
99
|
-
reports: count(config.reports),
|
|
100
|
-
actions: count(config.actions),
|
|
101
|
-
flows: count(config.flows),
|
|
102
|
-
workflows: count(config.workflows),
|
|
103
|
-
approvals: count(config.approvals),
|
|
104
|
-
agents: count(config.agents),
|
|
105
|
-
apis: count(config.apis),
|
|
106
|
-
roles: count(config.roles),
|
|
107
|
-
permissions: count(config.permissions),
|
|
108
|
-
themes: count(config.themes),
|
|
109
|
-
datasources: count(config.datasources),
|
|
110
|
-
translations: count(config.translations),
|
|
111
|
-
plugins: count(config.plugins),
|
|
112
|
-
devPlugins: count(config.devPlugins)
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function printServerReady(opts) {
|
|
116
|
-
const base = `http://localhost:${opts.port}`;
|
|
117
|
-
console.log("");
|
|
118
|
-
console.log(chalk.bold.green(" \u2713 Server is ready"));
|
|
119
|
-
console.log("");
|
|
120
|
-
console.log(chalk.cyan(" \u279C") + chalk.bold(" API: ") + chalk.cyan(base + "/"));
|
|
121
|
-
if (opts.uiEnabled && opts.studioPath) {
|
|
122
|
-
console.log(chalk.cyan(" \u279C") + chalk.bold(" Studio: ") + chalk.cyan(base + opts.studioPath + "/"));
|
|
123
|
-
}
|
|
124
|
-
console.log("");
|
|
125
|
-
console.log(chalk.dim(` Config: ${opts.configFile}`));
|
|
126
|
-
console.log(chalk.dim(` Mode: ${opts.isDev ? "development" : "production"}`));
|
|
127
|
-
console.log(chalk.dim(` Plugins: ${opts.pluginCount} loaded`));
|
|
128
|
-
if (opts.pluginNames && opts.pluginNames.length > 0) {
|
|
129
|
-
console.log(chalk.dim(` ${opts.pluginNames.join(", ")}`));
|
|
130
|
-
}
|
|
131
|
-
console.log("");
|
|
132
|
-
console.log(chalk.dim(" Press Ctrl+C to stop"));
|
|
133
|
-
console.log("");
|
|
134
|
-
}
|
|
135
|
-
function printMetadataStats(stats) {
|
|
136
|
-
const sections = [
|
|
137
|
-
{
|
|
138
|
-
label: "Data",
|
|
139
|
-
items: [
|
|
140
|
-
["Objects", stats.objects],
|
|
141
|
-
["Fields", stats.fields],
|
|
142
|
-
["Extensions", stats.objectExtensions],
|
|
143
|
-
["Datasources", stats.datasources]
|
|
144
|
-
]
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
label: "UI",
|
|
148
|
-
items: [
|
|
149
|
-
["Apps", stats.apps],
|
|
150
|
-
["Views", stats.views],
|
|
151
|
-
["Pages", stats.pages],
|
|
152
|
-
["Dashboards", stats.dashboards],
|
|
153
|
-
["Reports", stats.reports],
|
|
154
|
-
["Actions", stats.actions],
|
|
155
|
-
["Themes", stats.themes]
|
|
156
|
-
]
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
label: "Logic",
|
|
160
|
-
items: [
|
|
161
|
-
["Flows", stats.flows],
|
|
162
|
-
["Workflows", stats.workflows],
|
|
163
|
-
["Approvals", stats.approvals],
|
|
164
|
-
["Agents", stats.agents],
|
|
165
|
-
["APIs", stats.apis]
|
|
166
|
-
]
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
label: "Security",
|
|
170
|
-
items: [
|
|
171
|
-
["Roles", stats.roles],
|
|
172
|
-
["Permissions", stats.permissions]
|
|
173
|
-
]
|
|
174
|
-
}
|
|
175
|
-
];
|
|
176
|
-
for (const section of sections) {
|
|
177
|
-
const nonZero = section.items.filter(([, v]) => v > 0);
|
|
178
|
-
if (nonZero.length === 0) continue;
|
|
179
|
-
const line = nonZero.map(([k, v]) => `${chalk.white(v)} ${chalk.dim(k)}`).join(" ");
|
|
180
|
-
console.log(` ${chalk.bold(section.label + ":")} ${line}`);
|
|
181
|
-
}
|
|
182
|
-
if (stats.plugins > 0 || stats.devPlugins > 0) {
|
|
183
|
-
const parts = [];
|
|
184
|
-
if (stats.plugins > 0) parts.push(`${stats.plugins} plugins`);
|
|
185
|
-
if (stats.devPlugins > 0) parts.push(`${stats.devPlugins} devPlugins`);
|
|
186
|
-
console.log(` ${chalk.bold("Runtime:")} ${chalk.dim(parts.join(", "))}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// src/utils/config.ts
|
|
191
|
-
function resolveConfigPath(source) {
|
|
192
|
-
if (source) {
|
|
193
|
-
const abs = path.resolve(process.cwd(), source);
|
|
194
|
-
if (!fs.existsSync(abs)) {
|
|
195
|
-
printError(`Config file not found: ${chalk2.white(abs)}`);
|
|
196
|
-
console.log("");
|
|
197
|
-
console.log(chalk2.dim(" Hint: Run this command from a directory with objectstack.config.ts"));
|
|
198
|
-
console.log(chalk2.dim(" Or specify the path: objectstack <command> path/to/config.ts"));
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
return abs;
|
|
202
|
-
}
|
|
203
|
-
const candidates = [
|
|
204
|
-
"objectstack.config.ts",
|
|
205
|
-
"objectstack.config.js",
|
|
206
|
-
"objectstack.config.mjs"
|
|
207
|
-
];
|
|
208
|
-
for (const candidate of candidates) {
|
|
209
|
-
const abs = path.resolve(process.cwd(), candidate);
|
|
210
|
-
if (fs.existsSync(abs)) return abs;
|
|
211
|
-
}
|
|
212
|
-
printError("No objectstack.config.{ts,js,mjs} found in current directory");
|
|
213
|
-
console.log("");
|
|
214
|
-
console.log(chalk2.dim(" Hint: Run `objectstack init` to create a new project"));
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
async function loadConfig(source) {
|
|
218
|
-
const absolutePath = resolveConfigPath(source);
|
|
219
|
-
const start = Date.now();
|
|
220
|
-
const { mod } = await bundleRequire({
|
|
221
|
-
filepath: absolutePath
|
|
222
|
-
});
|
|
223
|
-
const config = mod.default || mod;
|
|
224
|
-
if (!config) {
|
|
225
|
-
throw new Error(`No default export found in ${path.basename(absolutePath)}`);
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
config,
|
|
229
|
-
absolutePath,
|
|
230
|
-
duration: Date.now() - start
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// src/commands/compile.ts
|
|
23
|
+
import { ObjectStackDefinitionSchema } from "@objectstack/spec";
|
|
235
24
|
var compileCommand = new Command("compile").description("Compile ObjectStack configuration to JSON artifact").argument("[config]", "Source configuration file").option("-o, --output <path>", "Output JSON file", "dist/objectstack.json").option("--json", "Output compile result as JSON (for CI)").action(async (configPath, options) => {
|
|
236
25
|
const timer = createTimer();
|
|
237
26
|
if (!options.json) {
|
|
@@ -241,7 +30,7 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
241
30
|
if (!options.json) printStep("Loading configuration...");
|
|
242
31
|
const { config, absolutePath, duration } = await loadConfig(configPath);
|
|
243
32
|
if (!options.json) {
|
|
244
|
-
printKV("Config",
|
|
33
|
+
printKV("Config", path.relative(process.cwd(), absolutePath));
|
|
245
34
|
printKV("Load time", `${duration}ms`);
|
|
246
35
|
}
|
|
247
36
|
if (!options.json) printStep("Validating protocol compliance...");
|
|
@@ -258,13 +47,13 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
258
47
|
}
|
|
259
48
|
if (!options.json) printStep("Writing artifact...");
|
|
260
49
|
const output = options.output;
|
|
261
|
-
const artifactPath =
|
|
262
|
-
const artifactDir =
|
|
263
|
-
if (!
|
|
264
|
-
|
|
50
|
+
const artifactPath = path.resolve(process.cwd(), output);
|
|
51
|
+
const artifactDir = path.dirname(artifactPath);
|
|
52
|
+
if (!fs.existsSync(artifactDir)) {
|
|
53
|
+
fs.mkdirSync(artifactDir, { recursive: true });
|
|
265
54
|
}
|
|
266
55
|
const jsonContent = JSON.stringify(result.data, null, 2);
|
|
267
|
-
|
|
56
|
+
fs.writeFileSync(artifactPath, jsonContent);
|
|
268
57
|
const sizeKB = (jsonContent.length / 1024).toFixed(1);
|
|
269
58
|
const stats = collectMetadataStats(config);
|
|
270
59
|
if (options.json) {
|
|
@@ -278,11 +67,11 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
278
67
|
return;
|
|
279
68
|
}
|
|
280
69
|
console.log("");
|
|
281
|
-
printSuccess(`Build complete ${
|
|
70
|
+
printSuccess(`Build complete ${chalk.dim(`(${timer.display()})`)}`);
|
|
282
71
|
console.log("");
|
|
283
72
|
printMetadataStats(stats);
|
|
284
73
|
console.log("");
|
|
285
|
-
printKV("Artifact", `${output} ${
|
|
74
|
+
printKV("Artifact", `${output} ${chalk.dim(`(${sizeKB} KB`)})`);
|
|
286
75
|
console.log("");
|
|
287
76
|
} catch (error) {
|
|
288
77
|
if (options.json) {
|
|
@@ -297,7 +86,7 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
297
86
|
|
|
298
87
|
// src/commands/validate.ts
|
|
299
88
|
import { Command as Command2 } from "commander";
|
|
300
|
-
import
|
|
89
|
+
import chalk2 from "chalk";
|
|
301
90
|
import { ObjectStackDefinitionSchema as ObjectStackDefinitionSchema2 } from "@objectstack/spec";
|
|
302
91
|
var validateCommand = new Command2("validate").description("Validate ObjectStack configuration against the protocol schema").argument("[config]", "Configuration file path").option("--strict", "Treat warnings as errors").option("--json", "Output results as JSON").action(async (configPath, options) => {
|
|
303
92
|
const timer = createTimer();
|
|
@@ -351,12 +140,12 @@ var validateCommand = new Command2("validate").description("Validate ObjectStack
|
|
|
351
140
|
warnings.push("Missing manifest.namespace \u2014 required for multi-app hosting");
|
|
352
141
|
}
|
|
353
142
|
console.log("");
|
|
354
|
-
printSuccess(`Validation passed ${
|
|
143
|
+
printSuccess(`Validation passed ${chalk2.dim(`(${timer.display()})`)}`);
|
|
355
144
|
console.log("");
|
|
356
145
|
if (config.manifest) {
|
|
357
|
-
console.log(` ${
|
|
146
|
+
console.log(` ${chalk2.bold(config.manifest.name || config.manifest.id || "Unnamed")} ${chalk2.dim(`v${config.manifest.version || "0.0.0"}`)}`);
|
|
358
147
|
if (config.manifest.description) {
|
|
359
|
-
console.log(
|
|
148
|
+
console.log(chalk2.dim(` ${config.manifest.description}`));
|
|
360
149
|
}
|
|
361
150
|
console.log("");
|
|
362
151
|
}
|
|
@@ -364,7 +153,7 @@ var validateCommand = new Command2("validate").description("Validate ObjectStack
|
|
|
364
153
|
if (warnings.length > 0) {
|
|
365
154
|
console.log("");
|
|
366
155
|
for (const w of warnings) {
|
|
367
|
-
console.log(
|
|
156
|
+
console.log(chalk2.yellow(` \u26A0 ${w}`));
|
|
368
157
|
}
|
|
369
158
|
if (options.strict) {
|
|
370
159
|
console.log("");
|
|
@@ -390,7 +179,7 @@ var validateCommand = new Command2("validate").description("Validate ObjectStack
|
|
|
390
179
|
|
|
391
180
|
// src/commands/info.ts
|
|
392
181
|
import { Command as Command3 } from "commander";
|
|
393
|
-
import
|
|
182
|
+
import chalk3 from "chalk";
|
|
394
183
|
var infoCommand = new Command3("info").description("Display metadata summary of an ObjectStack configuration").argument("[config]", "Configuration file path").option("--json", "Output as JSON").action(async (configPath, options) => {
|
|
395
184
|
const timer = createTimer();
|
|
396
185
|
if (!options.json) {
|
|
@@ -416,9 +205,9 @@ var infoCommand = new Command3("info").description("Display metadata summary of
|
|
|
416
205
|
if (config.manifest) {
|
|
417
206
|
const m = config.manifest;
|
|
418
207
|
console.log("");
|
|
419
|
-
console.log(` ${
|
|
420
|
-
if (m.id) console.log(
|
|
421
|
-
if (m.description) console.log(
|
|
208
|
+
console.log(` ${chalk3.bold(m.name || m.id || "Unnamed")} ${chalk3.dim(`v${m.version || "0.0.0"}`)}`);
|
|
209
|
+
if (m.id) console.log(chalk3.dim(` ${m.id}`));
|
|
210
|
+
if (m.description) console.log(chalk3.dim(` ${m.description}`));
|
|
422
211
|
if (m.namespace) printKV(" Namespace", m.namespace);
|
|
423
212
|
if (m.type) printKV(" Type", m.type);
|
|
424
213
|
}
|
|
@@ -426,35 +215,35 @@ var infoCommand = new Command3("info").description("Display metadata summary of
|
|
|
426
215
|
printMetadataStats(stats);
|
|
427
216
|
if (config.objects && config.objects.length > 0) {
|
|
428
217
|
console.log("");
|
|
429
|
-
console.log(
|
|
218
|
+
console.log(chalk3.bold(" Objects:"));
|
|
430
219
|
for (const obj of config.objects) {
|
|
431
220
|
const fieldCount = obj.fields ? Object.keys(obj.fields).length : 0;
|
|
432
221
|
const ownership = obj.ownership || "own";
|
|
433
222
|
console.log(
|
|
434
|
-
` ${
|
|
223
|
+
` ${chalk3.cyan(obj.name || "?")}` + chalk3.dim(` (${fieldCount} fields, ${ownership})`) + (obj.label ? chalk3.dim(` \u2014 ${obj.label}`) : "")
|
|
435
224
|
);
|
|
436
225
|
}
|
|
437
226
|
}
|
|
438
227
|
if (config.agents && config.agents.length > 0) {
|
|
439
228
|
console.log("");
|
|
440
|
-
console.log(
|
|
229
|
+
console.log(chalk3.bold(" Agents:"));
|
|
441
230
|
for (const agent of config.agents) {
|
|
442
231
|
console.log(
|
|
443
|
-
` ${
|
|
232
|
+
` ${chalk3.magenta(agent.name || "?")}` + (agent.role ? chalk3.dim(` \u2014 ${agent.role}`) : "")
|
|
444
233
|
);
|
|
445
234
|
}
|
|
446
235
|
}
|
|
447
236
|
if (config.apps && config.apps.length > 0) {
|
|
448
237
|
console.log("");
|
|
449
|
-
console.log(
|
|
238
|
+
console.log(chalk3.bold(" Apps:"));
|
|
450
239
|
for (const app of config.apps) {
|
|
451
240
|
console.log(
|
|
452
|
-
` ${
|
|
241
|
+
` ${chalk3.green(app.name || "?")}` + (app.label ? chalk3.dim(` \u2014 ${app.label}`) : "")
|
|
453
242
|
);
|
|
454
243
|
}
|
|
455
244
|
}
|
|
456
245
|
console.log("");
|
|
457
|
-
console.log(
|
|
246
|
+
console.log(chalk3.dim(` Loaded in ${duration}ms`));
|
|
458
247
|
console.log("");
|
|
459
248
|
} catch (error) {
|
|
460
249
|
if (options.json) {
|
|
@@ -469,9 +258,9 @@ var infoCommand = new Command3("info").description("Display metadata summary of
|
|
|
469
258
|
|
|
470
259
|
// src/commands/init.ts
|
|
471
260
|
import { Command as Command4 } from "commander";
|
|
472
|
-
import
|
|
473
|
-
import
|
|
474
|
-
import
|
|
261
|
+
import chalk4 from "chalk";
|
|
262
|
+
import fs2 from "fs";
|
|
263
|
+
import path2 from "path";
|
|
475
264
|
var TEMPLATES = {
|
|
476
265
|
app: {
|
|
477
266
|
description: "Full application with objects, views, and actions",
|
|
@@ -636,16 +425,16 @@ function toTitleCase(str) {
|
|
|
636
425
|
var initCommand = new Command4("init").description("Initialize a new ObjectStack project in the current directory").argument("[name]", "Project name (defaults to directory name)").option("-t, --template <template>", "Template: app, plugin, empty", "app").option("--no-install", "Skip dependency installation").action(async (name, options) => {
|
|
637
426
|
printHeader("Init");
|
|
638
427
|
const cwd = process.cwd();
|
|
639
|
-
const projectName = name ||
|
|
428
|
+
const projectName = name || path2.basename(cwd);
|
|
640
429
|
const template = TEMPLATES[options.template];
|
|
641
430
|
if (!template) {
|
|
642
431
|
printError(`Unknown template: ${options.template}`);
|
|
643
|
-
console.log(
|
|
432
|
+
console.log(chalk4.dim(` Available: ${Object.keys(TEMPLATES).join(", ")}`));
|
|
644
433
|
process.exit(1);
|
|
645
434
|
}
|
|
646
|
-
if (
|
|
435
|
+
if (fs2.existsSync(path2.join(cwd, "objectstack.config.ts"))) {
|
|
647
436
|
printError("objectstack.config.ts already exists in this directory");
|
|
648
|
-
console.log(
|
|
437
|
+
console.log(chalk4.dim(" Use `objectstack generate` to add metadata to an existing project"));
|
|
649
438
|
process.exit(1);
|
|
650
439
|
}
|
|
651
440
|
printKV("Project", projectName);
|
|
@@ -654,8 +443,8 @@ var initCommand = new Command4("init").description("Initialize a new ObjectStack
|
|
|
654
443
|
console.log("");
|
|
655
444
|
const createdFiles = [];
|
|
656
445
|
try {
|
|
657
|
-
const pkgPath =
|
|
658
|
-
if (!
|
|
446
|
+
const pkgPath = path2.join(cwd, "package.json");
|
|
447
|
+
if (!fs2.existsSync(pkgPath)) {
|
|
659
448
|
const pkg = {
|
|
660
449
|
name: projectName,
|
|
661
450
|
version: "0.1.0",
|
|
@@ -665,16 +454,16 @@ var initCommand = new Command4("init").description("Initialize a new ObjectStack
|
|
|
665
454
|
dependencies: template.dependencies,
|
|
666
455
|
devDependencies: template.devDependencies
|
|
667
456
|
};
|
|
668
|
-
|
|
457
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
669
458
|
createdFiles.push("package.json");
|
|
670
459
|
} else {
|
|
671
460
|
printInfo("package.json already exists, skipping");
|
|
672
461
|
}
|
|
673
462
|
const configContent = template.configContent(projectName);
|
|
674
|
-
|
|
463
|
+
fs2.writeFileSync(path2.join(cwd, "objectstack.config.ts"), configContent);
|
|
675
464
|
createdFiles.push("objectstack.config.ts");
|
|
676
|
-
const tsconfigPath =
|
|
677
|
-
if (!
|
|
465
|
+
const tsconfigPath = path2.join(cwd, "tsconfig.json");
|
|
466
|
+
if (!fs2.existsSync(tsconfigPath)) {
|
|
678
467
|
const tsconfig = {
|
|
679
468
|
compilerOptions: {
|
|
680
469
|
target: "ES2022",
|
|
@@ -690,30 +479,30 @@ var initCommand = new Command4("init").description("Initialize a new ObjectStack
|
|
|
690
479
|
include: ["*.ts", "src/**/*"],
|
|
691
480
|
exclude: ["dist", "node_modules"]
|
|
692
481
|
};
|
|
693
|
-
|
|
482
|
+
fs2.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
694
483
|
createdFiles.push("tsconfig.json");
|
|
695
484
|
}
|
|
696
485
|
for (const [filePath, contentFn] of Object.entries(template.srcFiles)) {
|
|
697
486
|
const resolvedPath = filePath.replace("__name__", projectName);
|
|
698
|
-
const fullPath =
|
|
699
|
-
const dir =
|
|
700
|
-
if (!
|
|
701
|
-
|
|
487
|
+
const fullPath = path2.join(cwd, resolvedPath);
|
|
488
|
+
const dir = path2.dirname(fullPath);
|
|
489
|
+
if (!fs2.existsSync(dir)) {
|
|
490
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
702
491
|
}
|
|
703
|
-
|
|
492
|
+
fs2.writeFileSync(fullPath, contentFn(projectName));
|
|
704
493
|
createdFiles.push(resolvedPath);
|
|
705
494
|
}
|
|
706
|
-
const gitignorePath =
|
|
707
|
-
if (!
|
|
708
|
-
|
|
495
|
+
const gitignorePath = path2.join(cwd, ".gitignore");
|
|
496
|
+
if (!fs2.existsSync(gitignorePath)) {
|
|
497
|
+
fs2.writeFileSync(gitignorePath, `node_modules/
|
|
709
498
|
dist/
|
|
710
499
|
*.tsbuildinfo
|
|
711
500
|
`);
|
|
712
501
|
createdFiles.push(".gitignore");
|
|
713
502
|
}
|
|
714
|
-
console.log(
|
|
503
|
+
console.log(chalk4.bold(" Created files:"));
|
|
715
504
|
for (const f of createdFiles) {
|
|
716
|
-
console.log(
|
|
505
|
+
console.log(chalk4.green(` + ${f}`));
|
|
717
506
|
}
|
|
718
507
|
console.log("");
|
|
719
508
|
if (options.install !== false) {
|
|
@@ -727,10 +516,10 @@ dist/
|
|
|
727
516
|
}
|
|
728
517
|
printSuccess("Project initialized!");
|
|
729
518
|
console.log("");
|
|
730
|
-
console.log(
|
|
731
|
-
console.log(
|
|
732
|
-
console.log(
|
|
733
|
-
console.log(
|
|
519
|
+
console.log(chalk4.bold(" Next steps:"));
|
|
520
|
+
console.log(chalk4.dim(" objectstack validate # Check configuration"));
|
|
521
|
+
console.log(chalk4.dim(" objectstack dev # Start development server"));
|
|
522
|
+
console.log(chalk4.dim(" objectstack generate # Add objects, views, etc."));
|
|
734
523
|
console.log("");
|
|
735
524
|
} catch (error) {
|
|
736
525
|
printError(error.message || String(error));
|
|
@@ -738,14 +527,14 @@ dist/
|
|
|
738
527
|
}
|
|
739
528
|
});
|
|
740
529
|
function printWarning2(msg) {
|
|
741
|
-
console.log(
|
|
530
|
+
console.log(chalk4.yellow(` \u26A0 ${msg}`));
|
|
742
531
|
}
|
|
743
532
|
|
|
744
533
|
// src/commands/generate.ts
|
|
745
534
|
import { Command as Command5 } from "commander";
|
|
746
|
-
import
|
|
747
|
-
import
|
|
748
|
-
import
|
|
535
|
+
import chalk5 from "chalk";
|
|
536
|
+
import fs3 from "fs";
|
|
537
|
+
import path3 from "path";
|
|
749
538
|
var GENERATORS = {
|
|
750
539
|
object: {
|
|
751
540
|
description: "Business data object",
|
|
@@ -926,81 +715,217 @@ function toTitleCase2(str) {
|
|
|
926
715
|
function toSnakeCase(str) {
|
|
927
716
|
return str.replace(/[-]/g, "_").replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`).replace(/^_/, "");
|
|
928
717
|
}
|
|
929
|
-
var
|
|
718
|
+
var FIELD_TYPE_MAP = {
|
|
719
|
+
text: "string",
|
|
720
|
+
textarea: "string",
|
|
721
|
+
richtext: "string",
|
|
722
|
+
html: "string",
|
|
723
|
+
markdown: "string",
|
|
724
|
+
number: "number",
|
|
725
|
+
integer: "number",
|
|
726
|
+
currency: "number",
|
|
727
|
+
percent: "number",
|
|
728
|
+
boolean: "boolean",
|
|
729
|
+
date: "string",
|
|
730
|
+
datetime: "string",
|
|
731
|
+
time: "string",
|
|
732
|
+
email: "string",
|
|
733
|
+
phone: "string",
|
|
734
|
+
url: "string",
|
|
735
|
+
select: "string",
|
|
736
|
+
multiselect: "string[]",
|
|
737
|
+
lookup: "string",
|
|
738
|
+
master_detail: "string",
|
|
739
|
+
formula: "unknown",
|
|
740
|
+
autonumber: "string",
|
|
741
|
+
json: "Record<string, unknown>",
|
|
742
|
+
file: "string",
|
|
743
|
+
image: "string",
|
|
744
|
+
password: "string",
|
|
745
|
+
slug: "string",
|
|
746
|
+
uuid: "string",
|
|
747
|
+
ip_address: "string",
|
|
748
|
+
color: "string",
|
|
749
|
+
rating: "number",
|
|
750
|
+
geo_point: "{ lat: number; lng: number }",
|
|
751
|
+
vector: "number[]",
|
|
752
|
+
encrypted: "string"
|
|
753
|
+
};
|
|
754
|
+
function fieldTypeToTs(fieldType, multiple) {
|
|
755
|
+
const base = FIELD_TYPE_MAP[fieldType] || "unknown";
|
|
756
|
+
return multiple ? `${base}[]` : base;
|
|
757
|
+
}
|
|
758
|
+
function generateTypesFromConfig(config) {
|
|
759
|
+
const lines = [
|
|
760
|
+
"// Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
761
|
+
`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
762
|
+
"",
|
|
763
|
+
"import type { Data } from '@objectstack/spec';",
|
|
764
|
+
""
|
|
765
|
+
];
|
|
766
|
+
const objects = [];
|
|
767
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
768
|
+
if (Array.isArray(rawObjects)) {
|
|
769
|
+
objects.push(...rawObjects);
|
|
770
|
+
} else if (typeof rawObjects === "object") {
|
|
771
|
+
for (const val of Object.values(rawObjects)) {
|
|
772
|
+
if (val && typeof val === "object") objects.push(val);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (objects.length === 0) {
|
|
776
|
+
lines.push("// No objects found in configuration");
|
|
777
|
+
return lines.join("\n") + "\n";
|
|
778
|
+
}
|
|
779
|
+
for (const obj of objects) {
|
|
780
|
+
const name = String(obj.name || "unknown");
|
|
781
|
+
const typeName = name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
782
|
+
const fields = obj.fields ?? {};
|
|
783
|
+
lines.push(`/** ${String(obj.label || typeName)} record type */`);
|
|
784
|
+
lines.push(`export interface ${typeName}Record {`);
|
|
785
|
+
lines.push(" id: string;");
|
|
786
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
787
|
+
const fType = String(fieldDef.type || "text");
|
|
788
|
+
const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);
|
|
789
|
+
const required = fieldDef.required ? "" : "?";
|
|
790
|
+
if (fieldDef.label) {
|
|
791
|
+
lines.push(` /** ${fieldDef.label} */`);
|
|
792
|
+
}
|
|
793
|
+
lines.push(` ${fieldName}${required}: ${tsType};`);
|
|
794
|
+
}
|
|
795
|
+
lines.push("}");
|
|
796
|
+
lines.push("");
|
|
797
|
+
}
|
|
798
|
+
return lines.join("\n") + "\n";
|
|
799
|
+
}
|
|
800
|
+
var generateMetadataCommand = new Command5("metadata").alias("m").description("Generate metadata scaffold (object, view, action, flow, agent, dashboard, app)").argument("<type>", "Metadata type to generate").argument("<name>", "Name for the metadata (use kebab-case)").option("-d, --dir <directory>", "Target directory (overrides default)").option("--dry-run", "Show what would be created without writing files").action(async (type, name, options) => {
|
|
930
801
|
printHeader("Generate");
|
|
931
802
|
const generator = GENERATORS[type];
|
|
932
803
|
if (!generator) {
|
|
933
804
|
printError(`Unknown type: ${type}`);
|
|
934
805
|
console.log("");
|
|
935
|
-
console.log(
|
|
806
|
+
console.log(chalk5.bold(" Available types:"));
|
|
936
807
|
for (const [key, gen] of Object.entries(GENERATORS)) {
|
|
937
|
-
console.log(` ${
|
|
808
|
+
console.log(` ${chalk5.cyan(key.padEnd(12))} ${chalk5.dim(gen.description)}`);
|
|
938
809
|
}
|
|
939
810
|
console.log("");
|
|
940
|
-
console.log(
|
|
941
|
-
console.log(
|
|
942
|
-
console.log(
|
|
811
|
+
console.log(chalk5.dim(" Usage: objectstack generate <type> <name>"));
|
|
812
|
+
console.log(chalk5.dim(" Example: objectstack generate object project"));
|
|
813
|
+
console.log(chalk5.dim(" Alias: os g object project"));
|
|
943
814
|
process.exit(1);
|
|
944
815
|
}
|
|
945
816
|
const dir = options.dir || generator.defaultDir;
|
|
946
817
|
const fileName = `${toSnakeCase(name)}.ts`;
|
|
947
|
-
const filePath =
|
|
948
|
-
console.log(` ${
|
|
949
|
-
console.log(` ${
|
|
950
|
-
console.log(` ${
|
|
818
|
+
const filePath = path3.join(process.cwd(), dir, fileName);
|
|
819
|
+
console.log(` ${chalk5.dim("Type:")} ${chalk5.cyan(type)} \u2014 ${generator.description}`);
|
|
820
|
+
console.log(` ${chalk5.dim("Name:")} ${chalk5.white(name)}`);
|
|
821
|
+
console.log(` ${chalk5.dim("File:")} ${chalk5.white(path3.join(dir, fileName))}`);
|
|
951
822
|
console.log("");
|
|
952
823
|
if (options.dryRun) {
|
|
953
824
|
printInfo("Dry run \u2014 no files written");
|
|
954
825
|
console.log("");
|
|
955
|
-
console.log(
|
|
956
|
-
console.log(
|
|
826
|
+
console.log(chalk5.dim(" Content:"));
|
|
827
|
+
console.log(chalk5.dim(" " + "-".repeat(38)));
|
|
957
828
|
const content = generator.generate(name);
|
|
958
829
|
for (const line of content.split("\n")) {
|
|
959
|
-
console.log(
|
|
830
|
+
console.log(chalk5.dim(` ${line}`));
|
|
960
831
|
}
|
|
961
832
|
console.log("");
|
|
962
833
|
return;
|
|
963
834
|
}
|
|
964
|
-
if (
|
|
835
|
+
if (fs3.existsSync(filePath)) {
|
|
965
836
|
printError(`File already exists: ${filePath}`);
|
|
966
837
|
process.exit(1);
|
|
967
838
|
}
|
|
968
839
|
try {
|
|
969
|
-
const fullDir =
|
|
970
|
-
if (!
|
|
971
|
-
|
|
840
|
+
const fullDir = path3.dirname(filePath);
|
|
841
|
+
if (!fs3.existsSync(fullDir)) {
|
|
842
|
+
fs3.mkdirSync(fullDir, { recursive: true });
|
|
972
843
|
}
|
|
973
844
|
const content = generator.generate(name);
|
|
974
|
-
|
|
975
|
-
printSuccess(`Created ${
|
|
976
|
-
const indexPath =
|
|
977
|
-
if (
|
|
978
|
-
const indexContent =
|
|
845
|
+
fs3.writeFileSync(filePath, content);
|
|
846
|
+
printSuccess(`Created ${path3.join(dir, fileName)}`);
|
|
847
|
+
const indexPath = path3.join(process.cwd(), dir, "index.ts");
|
|
848
|
+
if (fs3.existsSync(indexPath)) {
|
|
849
|
+
const indexContent = fs3.readFileSync(indexPath, "utf-8");
|
|
979
850
|
const exportLine = `export { default as ${toCamelCase2(name)} } from './${toSnakeCase(name)}';`;
|
|
980
851
|
if (!indexContent.includes(toCamelCase2(name))) {
|
|
981
|
-
|
|
852
|
+
fs3.appendFileSync(indexPath, exportLine + "\n");
|
|
982
853
|
printSuccess(`Updated ${dir}/index.ts with export`);
|
|
983
854
|
}
|
|
984
855
|
} else {
|
|
985
856
|
const exportLine = `export { default as ${toCamelCase2(name)} } from './${toSnakeCase(name)}';
|
|
986
857
|
`;
|
|
987
|
-
|
|
858
|
+
fs3.writeFileSync(indexPath, exportLine);
|
|
988
859
|
printSuccess(`Created ${dir}/index.ts`);
|
|
989
860
|
}
|
|
990
861
|
console.log("");
|
|
991
|
-
console.log(
|
|
862
|
+
console.log(chalk5.dim(` Tip: Run \`objectstack validate\` to check your config`));
|
|
863
|
+
console.log("");
|
|
864
|
+
} catch (error) {
|
|
865
|
+
printError(error.message || String(error));
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
var generateTypesCommand = new Command5("types").description("Generate TypeScript type definitions from ObjectStack configuration").argument("[config]", "Configuration file path").option("-o, --output <file>", "Output file path", "src/types/objectstack.d.ts").option("--dry-run", "Show what would be generated without writing files").action(async (configPath, options) => {
|
|
870
|
+
printHeader("Generate Types");
|
|
871
|
+
try {
|
|
872
|
+
const { loadConfig: loadConfig2 } = await import("./config-UN34WBHT.js");
|
|
873
|
+
printInfo("Loading configuration...");
|
|
874
|
+
const { config, absolutePath } = await loadConfig2(configPath);
|
|
875
|
+
console.log(` ${chalk5.dim("Config:")} ${chalk5.white(absolutePath)}`);
|
|
876
|
+
console.log(` ${chalk5.dim("Output:")} ${chalk5.white(options.output)}`);
|
|
877
|
+
console.log("");
|
|
878
|
+
const content = generateTypesFromConfig(config);
|
|
879
|
+
if (options.dryRun) {
|
|
880
|
+
printInfo("Dry run \u2014 no files written");
|
|
881
|
+
console.log("");
|
|
882
|
+
for (const line of content.split("\n")) {
|
|
883
|
+
console.log(chalk5.dim(` ${line}`));
|
|
884
|
+
}
|
|
885
|
+
console.log("");
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const outPath = path3.resolve(process.cwd(), options.output);
|
|
889
|
+
const outDir = path3.dirname(outPath);
|
|
890
|
+
if (!fs3.existsSync(outDir)) {
|
|
891
|
+
fs3.mkdirSync(outDir, { recursive: true });
|
|
892
|
+
}
|
|
893
|
+
fs3.writeFileSync(outPath, content);
|
|
894
|
+
printSuccess(`Generated types at ${options.output}`);
|
|
992
895
|
console.log("");
|
|
993
896
|
} catch (error) {
|
|
994
897
|
printError(error.message || String(error));
|
|
995
898
|
process.exit(1);
|
|
996
899
|
}
|
|
997
900
|
});
|
|
901
|
+
var generateCommand = new Command5("generate").alias("g").description("Generate metadata files or TypeScript types").argument("[type]", "Metadata type to generate (object, view, action, flow, agent, dashboard, app)").argument("[name]", "Name for the metadata (use kebab-case)").option("-d, --dir <directory>", "Target directory (overrides default)").option("--dry-run", "Show what would be created without writing files").addCommand(generateTypesCommand).action(async (type, name, options) => {
|
|
902
|
+
if (!type) {
|
|
903
|
+
printHeader("Generate");
|
|
904
|
+
console.log(chalk5.bold(" Sub-commands:"));
|
|
905
|
+
console.log(` ${chalk5.cyan("types".padEnd(12))} Generate TypeScript type definitions from config`);
|
|
906
|
+
console.log("");
|
|
907
|
+
console.log(chalk5.bold(" Metadata types:"));
|
|
908
|
+
for (const [key, gen] of Object.entries(GENERATORS)) {
|
|
909
|
+
console.log(` ${chalk5.cyan(key.padEnd(12))} ${chalk5.dim(gen.description)}`);
|
|
910
|
+
}
|
|
911
|
+
console.log("");
|
|
912
|
+
console.log(chalk5.dim(" Usage: objectstack generate <type> <name>"));
|
|
913
|
+
console.log(chalk5.dim(" Usage: objectstack generate types [config]"));
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (!name) {
|
|
917
|
+
printError("Missing required argument: <name>");
|
|
918
|
+
console.log(chalk5.dim(" Usage: objectstack generate <type> <name>"));
|
|
919
|
+
process.exit(1);
|
|
920
|
+
}
|
|
921
|
+
await generateMetadataCommand.parseAsync([type, name, ...process.argv.slice(4)], { from: "user" });
|
|
922
|
+
});
|
|
998
923
|
|
|
999
924
|
// src/commands/create.ts
|
|
1000
925
|
import { Command as Command6 } from "commander";
|
|
1001
|
-
import
|
|
1002
|
-
import
|
|
1003
|
-
import
|
|
926
|
+
import chalk6 from "chalk";
|
|
927
|
+
import fs4 from "fs";
|
|
928
|
+
import path4 from "path";
|
|
1004
929
|
var templates = {
|
|
1005
930
|
plugin: {
|
|
1006
931
|
description: "Create a new ObjectStack plugin",
|
|
@@ -1175,76 +1100,321 @@ function toCamelCase3(str) {
|
|
|
1175
1100
|
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
1176
1101
|
}
|
|
1177
1102
|
var createCommand = new Command6("create").description("Create a new package, plugin, or example from template").argument("<type>", "Type of project to create (plugin, example)").argument("[name]", "Name of the project").option("-d, --dir <directory>", "Target directory").action(async (type, name, options) => {
|
|
1178
|
-
console.log(
|
|
1103
|
+
console.log(chalk6.bold(`
|
|
1179
1104
|
\u{1F4E6} ObjectStack Project Creator`));
|
|
1180
|
-
console.log(
|
|
1105
|
+
console.log(chalk6.dim(`-------------------------------`));
|
|
1181
1106
|
if (!templates[type]) {
|
|
1182
|
-
console.error(
|
|
1107
|
+
console.error(chalk6.red(`
|
|
1183
1108
|
\u274C Unknown type: ${type}`));
|
|
1184
|
-
console.log(
|
|
1109
|
+
console.log(chalk6.dim("Available types: plugin, example"));
|
|
1185
1110
|
process.exit(1);
|
|
1186
1111
|
}
|
|
1187
1112
|
if (!name) {
|
|
1188
|
-
console.error(
|
|
1189
|
-
console.log(
|
|
1113
|
+
console.error(chalk6.red("\n\u274C Project name is required"));
|
|
1114
|
+
console.log(chalk6.dim(`Usage: objectstack create ${type} <name>`));
|
|
1190
1115
|
process.exit(1);
|
|
1191
1116
|
}
|
|
1192
1117
|
const template = templates[type];
|
|
1193
1118
|
const cwd = process.cwd();
|
|
1194
1119
|
let targetDir;
|
|
1195
1120
|
if (options?.dir) {
|
|
1196
|
-
targetDir =
|
|
1121
|
+
targetDir = path4.resolve(cwd, options.dir);
|
|
1197
1122
|
} else {
|
|
1198
1123
|
const baseDir = type === "plugin" ? "packages/plugins" : "examples";
|
|
1199
1124
|
const projectName = type === "plugin" ? `plugin-${name}` : name;
|
|
1200
|
-
targetDir =
|
|
1125
|
+
targetDir = path4.join(cwd, baseDir, projectName);
|
|
1201
1126
|
}
|
|
1202
|
-
if (
|
|
1203
|
-
console.error(
|
|
1127
|
+
if (fs4.existsSync(targetDir)) {
|
|
1128
|
+
console.error(chalk6.red(`
|
|
1204
1129
|
\u274C Directory already exists: ${targetDir}`));
|
|
1205
1130
|
process.exit(1);
|
|
1206
1131
|
}
|
|
1207
|
-
console.log(`\u{1F4C1} Creating ${type}: ${
|
|
1208
|
-
console.log(`\u{1F4C2} Location: ${
|
|
1132
|
+
console.log(`\u{1F4C1} Creating ${type}: ${chalk6.blue(name)}`);
|
|
1133
|
+
console.log(`\u{1F4C2} Location: ${chalk6.dim(targetDir)}`);
|
|
1209
1134
|
console.log("");
|
|
1210
1135
|
try {
|
|
1211
|
-
|
|
1136
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
1212
1137
|
for (const [filePath, contentFn] of Object.entries(template.files)) {
|
|
1213
|
-
const fullPath =
|
|
1214
|
-
const dir =
|
|
1215
|
-
if (!
|
|
1216
|
-
|
|
1138
|
+
const fullPath = path4.join(targetDir, filePath);
|
|
1139
|
+
const dir = path4.dirname(fullPath);
|
|
1140
|
+
if (!fs4.existsSync(dir)) {
|
|
1141
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1217
1142
|
}
|
|
1218
1143
|
const content = contentFn(name);
|
|
1219
1144
|
const fileContent = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
1220
|
-
|
|
1221
|
-
console.log(
|
|
1145
|
+
fs4.writeFileSync(fullPath, fileContent);
|
|
1146
|
+
console.log(chalk6.green(`\u2713 Created ${filePath}`));
|
|
1222
1147
|
}
|
|
1223
1148
|
console.log("");
|
|
1224
|
-
console.log(
|
|
1149
|
+
console.log(chalk6.green("\u2705 Project created successfully!"));
|
|
1225
1150
|
console.log("");
|
|
1226
|
-
console.log(
|
|
1227
|
-
console.log(
|
|
1228
|
-
console.log(
|
|
1229
|
-
console.log(
|
|
1151
|
+
console.log(chalk6.bold("Next steps:"));
|
|
1152
|
+
console.log(chalk6.dim(` cd ${path4.relative(cwd, targetDir)}`));
|
|
1153
|
+
console.log(chalk6.dim(" pnpm install"));
|
|
1154
|
+
console.log(chalk6.dim(" pnpm build"));
|
|
1230
1155
|
console.log("");
|
|
1231
1156
|
} catch (error) {
|
|
1232
|
-
console.error(
|
|
1157
|
+
console.error(chalk6.red("\n\u274C Failed to create project:"));
|
|
1233
1158
|
console.error(error.message || error);
|
|
1234
|
-
if (
|
|
1235
|
-
|
|
1159
|
+
if (fs4.existsSync(targetDir)) {
|
|
1160
|
+
fs4.rmSync(targetDir, { recursive: true });
|
|
1236
1161
|
}
|
|
1237
1162
|
process.exit(1);
|
|
1238
1163
|
}
|
|
1239
1164
|
});
|
|
1240
1165
|
|
|
1241
|
-
// src/commands/
|
|
1166
|
+
// src/commands/plugin.ts
|
|
1242
1167
|
import { Command as Command7 } from "commander";
|
|
1243
|
-
import
|
|
1168
|
+
import chalk7 from "chalk";
|
|
1169
|
+
import fs5 from "fs";
|
|
1170
|
+
import path5 from "path";
|
|
1171
|
+
function resolvePluginName(plugin) {
|
|
1172
|
+
if (typeof plugin === "string") return plugin;
|
|
1173
|
+
if (plugin && typeof plugin === "object") {
|
|
1174
|
+
const p = plugin;
|
|
1175
|
+
if (typeof p.name === "string") return p.name;
|
|
1176
|
+
if (p.constructor && p.constructor.name !== "Object") return p.constructor.name;
|
|
1177
|
+
}
|
|
1178
|
+
return "unknown";
|
|
1179
|
+
}
|
|
1180
|
+
function resolvePluginVersion(plugin) {
|
|
1181
|
+
if (plugin && typeof plugin === "object") {
|
|
1182
|
+
const p = plugin;
|
|
1183
|
+
if (typeof p.version === "string") return p.version;
|
|
1184
|
+
}
|
|
1185
|
+
return "-";
|
|
1186
|
+
}
|
|
1187
|
+
function resolvePluginType(plugin) {
|
|
1188
|
+
if (plugin && typeof plugin === "object") {
|
|
1189
|
+
const p = plugin;
|
|
1190
|
+
if (typeof p.type === "string") return p.type;
|
|
1191
|
+
}
|
|
1192
|
+
return "standard";
|
|
1193
|
+
}
|
|
1194
|
+
function readConfigText(configPath) {
|
|
1195
|
+
return fs5.readFileSync(configPath, "utf-8");
|
|
1196
|
+
}
|
|
1197
|
+
function addPluginToConfig(configPath, packageName) {
|
|
1198
|
+
let content = readConfigText(configPath);
|
|
1199
|
+
const shortName = packageName.replace(/^@[^/]+\//, "").replace(/^plugin-/, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1200
|
+
const varName = shortName.replace(/-([a-z])/g, (_, c) => c.toUpperCase()) + "Plugin";
|
|
1201
|
+
const importLine = `import ${varName} from '${packageName}';
|
|
1202
|
+
`;
|
|
1203
|
+
if (content.includes(packageName)) {
|
|
1204
|
+
throw new Error(`Plugin '${packageName}' is already referenced in the config`);
|
|
1205
|
+
}
|
|
1206
|
+
const importRegex = /^import .+$/gm;
|
|
1207
|
+
let lastImportEnd = 0;
|
|
1208
|
+
let match;
|
|
1209
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
1210
|
+
lastImportEnd = match.index + match[0].length;
|
|
1211
|
+
}
|
|
1212
|
+
if (lastImportEnd > 0) {
|
|
1213
|
+
content = content.slice(0, lastImportEnd) + "\n" + importLine + content.slice(lastImportEnd);
|
|
1214
|
+
} else {
|
|
1215
|
+
content = importLine + "\n" + content;
|
|
1216
|
+
}
|
|
1217
|
+
if (/plugins\s*:\s*\[/.test(content)) {
|
|
1218
|
+
let replaced = false;
|
|
1219
|
+
content = content.replace(
|
|
1220
|
+
/(plugins\s*:\s*\[)/,
|
|
1221
|
+
(match2) => {
|
|
1222
|
+
if (replaced) return match2;
|
|
1223
|
+
replaced = true;
|
|
1224
|
+
return `${match2}
|
|
1225
|
+
${varName},`;
|
|
1226
|
+
}
|
|
1227
|
+
);
|
|
1228
|
+
} else {
|
|
1229
|
+
content = content.replace(
|
|
1230
|
+
/(defineStack\(\{[\s\S]*?)(}\s*\))/,
|
|
1231
|
+
`$1 plugins: [
|
|
1232
|
+
${varName},
|
|
1233
|
+
],
|
|
1234
|
+
$2`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
fs5.writeFileSync(configPath, content);
|
|
1238
|
+
}
|
|
1239
|
+
function removePluginFromConfig(configPath, pluginName) {
|
|
1240
|
+
let content = readConfigText(configPath);
|
|
1241
|
+
const importRegex = new RegExp(`^import .+['"]${escapeRegex(pluginName)}['"]\\s*;?\\s*$\\n?`, "gm");
|
|
1242
|
+
const hadImport = importRegex.test(content);
|
|
1243
|
+
importRegex.lastIndex = 0;
|
|
1244
|
+
content = content.replace(importRegex, "");
|
|
1245
|
+
const shortName = pluginName.replace(/^@[^/]+\//, "").replace(/^plugin-/, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1246
|
+
const varName = shortName.replace(/-([a-z])/g, (_, c) => c.toUpperCase()) + "Plugin";
|
|
1247
|
+
if (!hadImport) {
|
|
1248
|
+
const varImportRegex = new RegExp(`^import .* ${escapeRegex(varName)} .+$\\n?`, "gm");
|
|
1249
|
+
content = content.replace(varImportRegex, "");
|
|
1250
|
+
}
|
|
1251
|
+
const entryPatterns = [
|
|
1252
|
+
new RegExp(`\\s*${escapeRegex(varName)},?\\n?`, "g"),
|
|
1253
|
+
new RegExp(`\\s*['"]${escapeRegex(pluginName)}['"],?\\n?`, "g")
|
|
1254
|
+
];
|
|
1255
|
+
for (const pattern of entryPatterns) {
|
|
1256
|
+
content = content.replace(pattern, "\n");
|
|
1257
|
+
}
|
|
1258
|
+
content = content.replace(/plugins\s*:\s*\[\s*\],?\n?/g, "");
|
|
1259
|
+
fs5.writeFileSync(configPath, content);
|
|
1260
|
+
}
|
|
1261
|
+
function escapeRegex(str) {
|
|
1262
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1263
|
+
}
|
|
1264
|
+
var listCommand = new Command7("list").alias("ls").description("List plugins defined in the configuration").argument("[config]", "Configuration file path").option("--json", "Output as JSON").action(async (configSource, options) => {
|
|
1265
|
+
try {
|
|
1266
|
+
const { config } = await loadConfig(configSource);
|
|
1267
|
+
const plugins = config.plugins || [];
|
|
1268
|
+
const devPlugins = config.devPlugins || [];
|
|
1269
|
+
if (options?.json) {
|
|
1270
|
+
const data = {
|
|
1271
|
+
plugins: plugins.map((p) => ({
|
|
1272
|
+
name: resolvePluginName(p),
|
|
1273
|
+
version: resolvePluginVersion(p),
|
|
1274
|
+
type: resolvePluginType(p),
|
|
1275
|
+
dev: false
|
|
1276
|
+
})),
|
|
1277
|
+
devPlugins: devPlugins.map((p) => ({
|
|
1278
|
+
name: resolvePluginName(p),
|
|
1279
|
+
version: resolvePluginVersion(p),
|
|
1280
|
+
type: resolvePluginType(p),
|
|
1281
|
+
dev: true
|
|
1282
|
+
}))
|
|
1283
|
+
};
|
|
1284
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
printHeader("Plugins");
|
|
1288
|
+
if (plugins.length === 0 && devPlugins.length === 0) {
|
|
1289
|
+
printInfo("No plugins configured");
|
|
1290
|
+
console.log("");
|
|
1291
|
+
console.log(chalk7.dim(" Hint: Add plugins to your objectstack.config.ts"));
|
|
1292
|
+
console.log(chalk7.dim(" Or run: os plugin add <package-name>"));
|
|
1293
|
+
console.log("");
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
if (plugins.length > 0) {
|
|
1297
|
+
console.log(chalk7.bold(`
|
|
1298
|
+
Plugins (${plugins.length}):`));
|
|
1299
|
+
for (const plugin of plugins) {
|
|
1300
|
+
const name = resolvePluginName(plugin);
|
|
1301
|
+
const version = resolvePluginVersion(plugin);
|
|
1302
|
+
const type = resolvePluginType(plugin);
|
|
1303
|
+
console.log(
|
|
1304
|
+
` ${chalk7.cyan("\u25CF")} ${chalk7.white(name)}` + (version !== "-" ? chalk7.dim(` v${version}`) : "") + (type !== "standard" ? chalk7.dim(` [${type}]`) : "")
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (devPlugins.length > 0) {
|
|
1309
|
+
console.log(chalk7.bold(`
|
|
1310
|
+
Dev Plugins (${devPlugins.length}):`));
|
|
1311
|
+
for (const plugin of devPlugins) {
|
|
1312
|
+
const name = resolvePluginName(plugin);
|
|
1313
|
+
const version = resolvePluginVersion(plugin);
|
|
1314
|
+
console.log(
|
|
1315
|
+
` ${chalk7.yellow("\u25CF")} ${chalk7.white(name)}` + (version !== "-" ? chalk7.dim(` v${version}`) : "") + chalk7.dim(" [dev]")
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
console.log("");
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
printError(error.message || String(error));
|
|
1322
|
+
process.exit(1);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
var infoSubCommand = new Command7("info").description("Show detailed information about a plugin").argument("<name>", "Plugin name or package name").argument("[config]", "Configuration file path").action(async (name, configSource) => {
|
|
1326
|
+
try {
|
|
1327
|
+
const { config } = await loadConfig(configSource);
|
|
1328
|
+
const allPlugins = [
|
|
1329
|
+
...config.plugins || [],
|
|
1330
|
+
...config.devPlugins || []
|
|
1331
|
+
];
|
|
1332
|
+
const found = allPlugins.find((p) => {
|
|
1333
|
+
const pName = resolvePluginName(p);
|
|
1334
|
+
return pName === name || pName.includes(name);
|
|
1335
|
+
});
|
|
1336
|
+
if (!found) {
|
|
1337
|
+
printError(`Plugin '${name}' not found in configuration`);
|
|
1338
|
+
console.log("");
|
|
1339
|
+
console.log(chalk7.dim(" Available plugins:"));
|
|
1340
|
+
for (const p of allPlugins) {
|
|
1341
|
+
console.log(chalk7.dim(` - ${resolvePluginName(p)}`));
|
|
1342
|
+
}
|
|
1343
|
+
console.log("");
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
printHeader(`Plugin: ${resolvePluginName(found)}`);
|
|
1347
|
+
printKV("Name", resolvePluginName(found));
|
|
1348
|
+
printKV("Version", resolvePluginVersion(found));
|
|
1349
|
+
printKV("Type", resolvePluginType(found));
|
|
1350
|
+
const isDev = (config.devPlugins || []).includes(found);
|
|
1351
|
+
printKV("Environment", isDev ? "development" : "production");
|
|
1352
|
+
if (found && typeof found === "object") {
|
|
1353
|
+
const p = found;
|
|
1354
|
+
if (typeof p.description === "string") {
|
|
1355
|
+
printKV("Description", p.description);
|
|
1356
|
+
}
|
|
1357
|
+
if (Array.isArray(p.dependencies) && p.dependencies.length > 0) {
|
|
1358
|
+
printKV("Dependencies", p.dependencies.join(", "));
|
|
1359
|
+
}
|
|
1360
|
+
if (typeof p.init === "function") {
|
|
1361
|
+
printInfo("This is a runtime plugin instance (has init function)");
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if (typeof found === "string") {
|
|
1365
|
+
printInfo("This is a string reference (will be imported at runtime)");
|
|
1366
|
+
}
|
|
1367
|
+
console.log("");
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
printError(error.message || String(error));
|
|
1370
|
+
process.exit(1);
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
var addCommand = new Command7("add").description("Add a plugin to objectstack.config.ts").argument("<package>", "Plugin package name (e.g. @objectstack/plugin-auth)").option("-d, --dev", "Add as a dev-only plugin").option("-c, --config <path>", "Configuration file path").action(async (packageName, options) => {
|
|
1374
|
+
try {
|
|
1375
|
+
const configPath = resolveConfigPath(options?.config);
|
|
1376
|
+
printHeader("Add Plugin");
|
|
1377
|
+
console.log(` ${chalk7.dim("Package:")} ${chalk7.white(packageName)}`);
|
|
1378
|
+
console.log(` ${chalk7.dim("Config:")} ${chalk7.white(path5.relative(process.cwd(), configPath))}`);
|
|
1379
|
+
console.log("");
|
|
1380
|
+
addPluginToConfig(configPath, packageName);
|
|
1381
|
+
printSuccess(`Added ${chalk7.cyan(packageName)} to config`);
|
|
1382
|
+
console.log("");
|
|
1383
|
+
console.log(chalk7.dim(" Next steps:"));
|
|
1384
|
+
console.log(chalk7.dim(` 1. Install the package: pnpm add ${packageName}`));
|
|
1385
|
+
console.log(chalk7.dim(" 2. Run: os validate"));
|
|
1386
|
+
console.log("");
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
printError(error.message || String(error));
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
var removeCommand = new Command7("remove").alias("rm").description("Remove a plugin from objectstack.config.ts").argument("<name>", "Plugin name or package name to remove").option("-c, --config <path>", "Configuration file path").action(async (pluginName, options) => {
|
|
1393
|
+
try {
|
|
1394
|
+
const configPath = resolveConfigPath(options?.config);
|
|
1395
|
+
printHeader("Remove Plugin");
|
|
1396
|
+
console.log(` ${chalk7.dim("Plugin:")} ${chalk7.white(pluginName)}`);
|
|
1397
|
+
console.log(` ${chalk7.dim("Config:")} ${chalk7.white(path5.relative(process.cwd(), configPath))}`);
|
|
1398
|
+
console.log("");
|
|
1399
|
+
removePluginFromConfig(configPath, pluginName);
|
|
1400
|
+
printSuccess(`Removed ${chalk7.cyan(pluginName)} from config`);
|
|
1401
|
+
console.log("");
|
|
1402
|
+
console.log(chalk7.dim(" Tip: Run `pnpm remove " + pluginName + "` to uninstall the package"));
|
|
1403
|
+
console.log("");
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
printError(error.message || String(error));
|
|
1406
|
+
process.exit(1);
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
var pluginCommand = new Command7("plugin").description("Manage plugins (list, info, add, remove)").addCommand(listCommand).addCommand(infoSubCommand).addCommand(addCommand).addCommand(removeCommand);
|
|
1410
|
+
|
|
1411
|
+
// src/commands/dev.ts
|
|
1412
|
+
import { Command as Command8 } from "commander";
|
|
1413
|
+
import chalk8 from "chalk";
|
|
1244
1414
|
import { execSync, spawn } from "child_process";
|
|
1245
1415
|
import fs6 from "fs";
|
|
1246
1416
|
import path6 from "path";
|
|
1247
|
-
var devCommand = new
|
|
1417
|
+
var devCommand = new Command8("dev").description("Start development mode with hot-reload").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("--ui", "Enable Studio UI at /_studio/").option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
|
|
1248
1418
|
printHeader("Development Mode");
|
|
1249
1419
|
const configPath = path6.resolve(process.cwd(), "objectstack.config.ts");
|
|
1250
1420
|
if (packageName === "all" && fs6.existsSync(configPath)) {
|
|
@@ -1263,14 +1433,14 @@ var devCommand = new Command7("dev").description("Start development mode with ho
|
|
|
1263
1433
|
const isWorkspaceRoot = fs6.existsSync(workspaceConfigPath);
|
|
1264
1434
|
if (packageName === "all" && !isWorkspaceRoot) {
|
|
1265
1435
|
printError(`Config file not found in ${cwd}`);
|
|
1266
|
-
console.error(
|
|
1436
|
+
console.error(chalk8.yellow(" Run in a directory with objectstack.config.ts, or from the monorepo root."));
|
|
1267
1437
|
process.exit(1);
|
|
1268
1438
|
}
|
|
1269
1439
|
const filter = packageName === "all" ? "" : `--filter ${packageName}`;
|
|
1270
1440
|
printKV("Package", packageName === "all" ? "All packages" : packageName, "\u{1F4E6}");
|
|
1271
1441
|
printKV("Watch", "enabled", "\u{1F504}");
|
|
1272
1442
|
const command = `pnpm ${filter} dev`.trim();
|
|
1273
|
-
console.log(
|
|
1443
|
+
console.log(chalk8.dim(`$ ${command}`));
|
|
1274
1444
|
console.log("");
|
|
1275
1445
|
execSync(command, {
|
|
1276
1446
|
stdio: "inherit",
|
|
@@ -1283,12 +1453,12 @@ var devCommand = new Command7("dev").description("Start development mode with ho
|
|
|
1283
1453
|
});
|
|
1284
1454
|
|
|
1285
1455
|
// src/commands/serve.ts
|
|
1286
|
-
import { Command as
|
|
1456
|
+
import { Command as Command9 } from "commander";
|
|
1287
1457
|
import path8 from "path";
|
|
1288
1458
|
import fs8 from "fs";
|
|
1289
1459
|
import net from "net";
|
|
1290
|
-
import
|
|
1291
|
-
import { bundleRequire
|
|
1460
|
+
import chalk9 from "chalk";
|
|
1461
|
+
import { bundleRequire } from "bundle-require";
|
|
1292
1462
|
|
|
1293
1463
|
// src/utils/studio.ts
|
|
1294
1464
|
import path7 from "path";
|
|
@@ -1427,7 +1597,7 @@ var getAvailablePort = async (startPort) => {
|
|
|
1427
1597
|
}
|
|
1428
1598
|
return port;
|
|
1429
1599
|
};
|
|
1430
|
-
var serveCommand = new
|
|
1600
|
+
var serveCommand = new Command9("serve").description("Start ObjectStack server with plugins from configuration").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").option("--dev", "Run in development mode (load devPlugins)").option("--ui", "Enable Studio UI at /_studio/ (default: true in dev mode)").option("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
|
|
1431
1601
|
let port = parseInt(options.port);
|
|
1432
1602
|
try {
|
|
1433
1603
|
const availablePort = await getAvailablePort(port);
|
|
@@ -1441,11 +1611,11 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
|
|
|
1441
1611
|
const relativeConfig = path8.relative(process.cwd(), absolutePath);
|
|
1442
1612
|
if (!fs8.existsSync(absolutePath)) {
|
|
1443
1613
|
printError(`Configuration file not found: ${absolutePath}`);
|
|
1444
|
-
console.log(
|
|
1614
|
+
console.log(chalk9.dim(" Hint: Run `objectstack init` to create a new project"));
|
|
1445
1615
|
process.exit(1);
|
|
1446
1616
|
}
|
|
1447
1617
|
console.log("");
|
|
1448
|
-
console.log(
|
|
1618
|
+
console.log(chalk9.dim(` Loading ${relativeConfig}...`));
|
|
1449
1619
|
const loadedPlugins = [];
|
|
1450
1620
|
const shortPluginName = (raw) => {
|
|
1451
1621
|
if (raw.includes("objectql")) return "ObjectQL";
|
|
@@ -1480,7 +1650,7 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
|
|
|
1480
1650
|
console.debug = (...args) => {
|
|
1481
1651
|
if (!bootQuiet) originalConsoleDebug(...args);
|
|
1482
1652
|
};
|
|
1483
|
-
const { mod } = await
|
|
1653
|
+
const { mod } = await bundleRequire({
|
|
1484
1654
|
filepath: absolutePath
|
|
1485
1655
|
});
|
|
1486
1656
|
const config = mod.default || mod;
|
|
@@ -1549,7 +1719,7 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
|
|
|
1549
1719
|
const pluginName = plugin.name || plugin.constructor?.name || "unnamed";
|
|
1550
1720
|
trackPlugin(pluginName);
|
|
1551
1721
|
} catch (e) {
|
|
1552
|
-
console.error(
|
|
1722
|
+
console.error(chalk9.red(` \u2717 Failed to load plugin: ${e.message}`));
|
|
1553
1723
|
}
|
|
1554
1724
|
}
|
|
1555
1725
|
}
|
|
@@ -1560,7 +1730,7 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
|
|
|
1560
1730
|
await kernel.use(serverPlugin);
|
|
1561
1731
|
trackPlugin("HonoServer");
|
|
1562
1732
|
} catch (e) {
|
|
1563
|
-
console.warn(
|
|
1733
|
+
console.warn(chalk9.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
|
|
1564
1734
|
}
|
|
1565
1735
|
try {
|
|
1566
1736
|
const { createRestApiPlugin } = await import("@objectstack/rest");
|
|
@@ -1579,13 +1749,13 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
|
|
|
1579
1749
|
if (enableUI) {
|
|
1580
1750
|
const studioPath = resolveStudioPath();
|
|
1581
1751
|
if (!studioPath) {
|
|
1582
|
-
console.warn(
|
|
1752
|
+
console.warn(chalk9.yellow(` \u26A0 @objectstack/studio not found \u2014 skipping UI`));
|
|
1583
1753
|
} else if (hasStudioDist(studioPath)) {
|
|
1584
1754
|
const distPath = path8.join(studioPath, "dist");
|
|
1585
1755
|
await kernel.use(createStudioStaticPlugin(distPath, { isDev }));
|
|
1586
1756
|
trackPlugin("StudioUI");
|
|
1587
1757
|
} else {
|
|
1588
|
-
console.warn(
|
|
1758
|
+
console.warn(chalk9.yellow(` \u26A0 Studio dist not found \u2014 run "pnpm --filter @objectstack/studio build" first`));
|
|
1589
1759
|
}
|
|
1590
1760
|
}
|
|
1591
1761
|
await runtime.start();
|
|
@@ -1601,25 +1771,25 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
|
|
|
1601
1771
|
studioPath: STUDIO_PATH
|
|
1602
1772
|
});
|
|
1603
1773
|
process.on("SIGINT", async () => {
|
|
1604
|
-
console.warn(
|
|
1774
|
+
console.warn(chalk9.yellow(`
|
|
1605
1775
|
|
|
1606
1776
|
\u23F9 Stopping server...`));
|
|
1607
1777
|
await runtime.getKernel().shutdown();
|
|
1608
|
-
console.log(
|
|
1778
|
+
console.log(chalk9.green(`\u2705 Server stopped`));
|
|
1609
1779
|
process.exit(0);
|
|
1610
1780
|
});
|
|
1611
1781
|
} catch (error) {
|
|
1612
1782
|
restoreOutput();
|
|
1613
1783
|
console.log("");
|
|
1614
1784
|
printError(error.message || String(error));
|
|
1615
|
-
if (process.env.DEBUG) console.error(
|
|
1785
|
+
if (process.env.DEBUG) console.error(chalk9.dim(error.stack));
|
|
1616
1786
|
process.exit(1);
|
|
1617
1787
|
}
|
|
1618
1788
|
});
|
|
1619
1789
|
|
|
1620
1790
|
// src/commands/test.ts
|
|
1621
|
-
import { Command as
|
|
1622
|
-
import
|
|
1791
|
+
import { Command as Command10 } from "commander";
|
|
1792
|
+
import chalk10 from "chalk";
|
|
1623
1793
|
import path9 from "path";
|
|
1624
1794
|
import fs9 from "fs";
|
|
1625
1795
|
import { QA as CoreQA } from "@objectstack/core";
|
|
@@ -1645,16 +1815,16 @@ function resolveGlob(pattern) {
|
|
|
1645
1815
|
const entries = fs9.readdirSync(baseDir, { recursive: true, encoding: "utf-8" });
|
|
1646
1816
|
return entries.filter((entry) => regex.test(entry.replace(/\\/g, "/"))).map((entry) => path9.join(baseDir, entry)).filter((fullPath) => fs9.statSync(fullPath).isFile());
|
|
1647
1817
|
}
|
|
1648
|
-
var testCommand = new
|
|
1649
|
-
console.log(
|
|
1818
|
+
var testCommand = new Command10("test").description("Run Quality Protocol test scenarios against a running server").argument("[files]", 'Glob pattern for test files (e.g. "qa/*.test.json")', "qa/*.test.json").option("--url <url>", "Target base URL", "http://localhost:3000").option("--token <token>", "Authentication token").action(async (filesPattern, options) => {
|
|
1819
|
+
console.log(chalk10.bold(`
|
|
1650
1820
|
\u{1F9EA} ObjectStack Quality Protocol Runner`));
|
|
1651
|
-
console.log(
|
|
1652
|
-
console.log(`Target: ${
|
|
1821
|
+
console.log(chalk10.dim(`-------------------------------------`));
|
|
1822
|
+
console.log(`Target: ${chalk10.blue(options.url)}`);
|
|
1653
1823
|
const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
|
|
1654
1824
|
const runner = new CoreQA.TestRunner(adapter);
|
|
1655
1825
|
const testFiles = resolveGlob(filesPattern);
|
|
1656
1826
|
if (testFiles.length === 0) {
|
|
1657
|
-
console.warn(
|
|
1827
|
+
console.warn(chalk10.yellow(`No test files found matching: ${filesPattern}`));
|
|
1658
1828
|
return;
|
|
1659
1829
|
}
|
|
1660
1830
|
console.log(`Found ${testFiles.length} test suites.`);
|
|
@@ -1662,7 +1832,7 @@ var testCommand = new Command9("test").description("Run Quality Protocol test sc
|
|
|
1662
1832
|
let totalFailed = 0;
|
|
1663
1833
|
for (const file of testFiles) {
|
|
1664
1834
|
console.log(`
|
|
1665
|
-
\u{1F4C4} Running suite: ${
|
|
1835
|
+
\u{1F4C4} Running suite: ${chalk10.bold(path9.basename(file))}`);
|
|
1666
1836
|
try {
|
|
1667
1837
|
const content = fs9.readFileSync(file, "utf-8");
|
|
1668
1838
|
const suite = JSON.parse(content);
|
|
@@ -1671,10 +1841,10 @@ var testCommand = new Command9("test").description("Run Quality Protocol test sc
|
|
|
1671
1841
|
const icon = result.passed ? "\u2705" : "\u274C";
|
|
1672
1842
|
console.log(` ${icon} Scenario: ${result.scenarioId} (${result.duration}ms)`);
|
|
1673
1843
|
if (!result.passed) {
|
|
1674
|
-
console.error(
|
|
1844
|
+
console.error(chalk10.red(` Error: ${result.error}`));
|
|
1675
1845
|
result.steps.forEach((step) => {
|
|
1676
1846
|
if (!step.passed) {
|
|
1677
|
-
console.error(
|
|
1847
|
+
console.error(chalk10.red(` Step Failed: ${step.stepName}`));
|
|
1678
1848
|
if (step.output) console.error(` Output:`, step.output);
|
|
1679
1849
|
if (step.error) console.error(` Error:`, step.error);
|
|
1680
1850
|
}
|
|
@@ -1685,28 +1855,28 @@ var testCommand = new Command9("test").description("Run Quality Protocol test sc
|
|
|
1685
1855
|
}
|
|
1686
1856
|
}
|
|
1687
1857
|
} catch (e) {
|
|
1688
|
-
console.error(
|
|
1858
|
+
console.error(chalk10.red(`Failed to load or run suite ${file}: ${e}`));
|
|
1689
1859
|
totalFailed++;
|
|
1690
1860
|
}
|
|
1691
1861
|
}
|
|
1692
|
-
console.log(
|
|
1862
|
+
console.log(chalk10.dim(`
|
|
1693
1863
|
-------------------------------------`));
|
|
1694
1864
|
if (totalFailed > 0) {
|
|
1695
|
-
console.log(
|
|
1865
|
+
console.log(chalk10.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
|
|
1696
1866
|
process.exit(1);
|
|
1697
1867
|
} else {
|
|
1698
|
-
console.log(
|
|
1868
|
+
console.log(chalk10.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
|
|
1699
1869
|
process.exit(0);
|
|
1700
1870
|
}
|
|
1701
1871
|
});
|
|
1702
1872
|
|
|
1703
1873
|
// src/commands/doctor.ts
|
|
1704
|
-
import { Command as
|
|
1705
|
-
import
|
|
1874
|
+
import { Command as Command11 } from "commander";
|
|
1875
|
+
import chalk11 from "chalk";
|
|
1706
1876
|
import { execSync as execSync2 } from "child_process";
|
|
1707
1877
|
import fs10 from "fs";
|
|
1708
1878
|
import path10 from "path";
|
|
1709
|
-
var doctorCommand = new
|
|
1879
|
+
var doctorCommand = new Command11("doctor").description("Check development environment health").option("-v, --verbose", "Show detailed information").action(async (options) => {
|
|
1710
1880
|
printHeader("Environment Health Check");
|
|
1711
1881
|
const results = [];
|
|
1712
1882
|
try {
|
|
@@ -1823,24 +1993,111 @@ var doctorCommand = new Command10("doctor").description("Check development envir
|
|
|
1823
1993
|
printError(`${padded} ${result.message}`);
|
|
1824
1994
|
}
|
|
1825
1995
|
if (result.fix && (options.verbose || result.status === "error")) {
|
|
1826
|
-
console.log(
|
|
1996
|
+
console.log(chalk11.dim(` \u2192 ${result.fix}`));
|
|
1827
1997
|
}
|
|
1828
1998
|
if (result.status === "error") hasErrors = true;
|
|
1829
1999
|
if (result.status === "warning") hasWarnings = true;
|
|
1830
2000
|
});
|
|
1831
2001
|
console.log("");
|
|
1832
2002
|
if (hasErrors) {
|
|
1833
|
-
console.log(
|
|
1834
|
-
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(
|
|
2003
|
+
console.log(chalk11.red("\u274C Some critical issues found. Please fix them before continuing."));
|
|
2004
|
+
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(chalk11.dim(` ${r.fix}`)));
|
|
1835
2005
|
process.exit(1);
|
|
1836
2006
|
} else if (hasWarnings) {
|
|
1837
|
-
console.log(
|
|
1838
|
-
console.log(
|
|
2007
|
+
console.log(chalk11.yellow("\u26A0\uFE0F Environment is functional but has some warnings."));
|
|
2008
|
+
console.log(chalk11.dim(" Run with --verbose to see fix suggestions."));
|
|
1839
2009
|
} else {
|
|
1840
|
-
console.log(
|
|
2010
|
+
console.log(chalk11.green("\u2705 Environment is healthy and ready for development!"));
|
|
1841
2011
|
}
|
|
1842
2012
|
console.log("");
|
|
1843
2013
|
});
|
|
2014
|
+
|
|
2015
|
+
// src/utils/plugin-commands.ts
|
|
2016
|
+
import chalk12 from "chalk";
|
|
2017
|
+
async function loadPluginCommands(program) {
|
|
2018
|
+
let config;
|
|
2019
|
+
try {
|
|
2020
|
+
const loaded = await loadConfig();
|
|
2021
|
+
config = loaded.config;
|
|
2022
|
+
} catch {
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
const plugins = [
|
|
2026
|
+
...config.plugins || [],
|
|
2027
|
+
...config.devPlugins || []
|
|
2028
|
+
];
|
|
2029
|
+
const contributions = [];
|
|
2030
|
+
for (const plugin of plugins) {
|
|
2031
|
+
if (!plugin || typeof plugin !== "object") continue;
|
|
2032
|
+
const p = plugin;
|
|
2033
|
+
const manifest = p.manifest;
|
|
2034
|
+
const contributes = manifest?.contributes ?? p.contributes;
|
|
2035
|
+
if (!contributes) continue;
|
|
2036
|
+
const commands = contributes.commands;
|
|
2037
|
+
if (!Array.isArray(commands)) continue;
|
|
2038
|
+
const pluginName = resolvePluginName2(p);
|
|
2039
|
+
for (const cmd of commands) {
|
|
2040
|
+
if (!cmd || typeof cmd.name !== "string") continue;
|
|
2041
|
+
contributions.push({
|
|
2042
|
+
name: cmd.name,
|
|
2043
|
+
description: typeof cmd.description === "string" ? cmd.description : void 0,
|
|
2044
|
+
module: typeof cmd.module === "string" ? cmd.module : void 0,
|
|
2045
|
+
pluginName
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
if (contributions.length === 0) return;
|
|
2050
|
+
for (const contribution of contributions) {
|
|
2051
|
+
try {
|
|
2052
|
+
const commands = await importPluginCommands(contribution);
|
|
2053
|
+
for (const cmd of commands) {
|
|
2054
|
+
program.addCommand(cmd);
|
|
2055
|
+
}
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
if (process.env.DEBUG) {
|
|
2058
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2059
|
+
console.error(
|
|
2060
|
+
chalk12.yellow(` \u26A0 Failed to load CLI command '${contribution.name}' from plugin '${contribution.pluginName}': ${message}`)
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
async function importPluginCommands(contribution) {
|
|
2067
|
+
const moduleId = contribution.module ? `${contribution.pluginName}/${contribution.module.replace(/^\.\//, "")}` : contribution.pluginName;
|
|
2068
|
+
const mod = await import(moduleId);
|
|
2069
|
+
if (Array.isArray(mod.commands)) {
|
|
2070
|
+
return mod.commands.filter(isCommandInstance);
|
|
2071
|
+
}
|
|
2072
|
+
const defaultExport = mod.default;
|
|
2073
|
+
if (defaultExport) {
|
|
2074
|
+
if (Array.isArray(defaultExport)) {
|
|
2075
|
+
return defaultExport.filter(isCommandInstance);
|
|
2076
|
+
}
|
|
2077
|
+
if (isCommandInstance(defaultExport)) {
|
|
2078
|
+
return [defaultExport];
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
const commands = [];
|
|
2082
|
+
for (const key of Object.keys(mod)) {
|
|
2083
|
+
if (isCommandInstance(mod[key])) {
|
|
2084
|
+
commands.push(mod[key]);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return commands;
|
|
2088
|
+
}
|
|
2089
|
+
function isCommandInstance(value) {
|
|
2090
|
+
if (value === null || typeof value !== "object") return false;
|
|
2091
|
+
const obj = value;
|
|
2092
|
+
return typeof obj.name === "function" && typeof obj.description === "function" && typeof obj.action === "function" && typeof obj.parse === "function";
|
|
2093
|
+
}
|
|
2094
|
+
function resolvePluginName2(plugin) {
|
|
2095
|
+
if (typeof plugin.name === "string") return plugin.name;
|
|
2096
|
+
const manifest = plugin.manifest;
|
|
2097
|
+
if (manifest && typeof manifest.name === "string") return manifest.name;
|
|
2098
|
+
if (plugin.constructor && plugin.constructor.name !== "Object") return plugin.constructor.name;
|
|
2099
|
+
return "unknown";
|
|
2100
|
+
}
|
|
1844
2101
|
export {
|
|
1845
2102
|
compileCommand,
|
|
1846
2103
|
createCommand,
|
|
@@ -1849,6 +2106,8 @@ export {
|
|
|
1849
2106
|
generateCommand,
|
|
1850
2107
|
infoCommand,
|
|
1851
2108
|
initCommand,
|
|
2109
|
+
loadPluginCommands,
|
|
2110
|
+
pluginCommand,
|
|
1852
2111
|
serveCommand,
|
|
1853
2112
|
templates,
|
|
1854
2113
|
testCommand,
|