@objectstack/cli 2.0.7 → 3.0.1
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/dist/bin.js +1988 -487
- 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.js +1058 -449
- package/package.json +9 -9
- package/src/bin.ts +12 -0
- package/src/commands/codemod.ts +178 -0
- package/src/commands/diff.ts +285 -0
- package/src/commands/doctor.ts +385 -3
- package/src/commands/explain.ts +402 -0
- package/src/commands/generate.ts +638 -4
- package/src/commands/lint.ts +303 -0
package/dist/index.js
CHANGED
|
@@ -1,237 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectMetadataStats,
|
|
3
|
+
configExists,
|
|
4
|
+
createTimer,
|
|
5
|
+
formatZodErrors,
|
|
6
|
+
loadConfig,
|
|
7
|
+
printError,
|
|
8
|
+
printHeader,
|
|
9
|
+
printInfo,
|
|
10
|
+
printKV,
|
|
11
|
+
printMetadataStats,
|
|
12
|
+
printServerReady,
|
|
13
|
+
printStep,
|
|
14
|
+
printSuccess,
|
|
15
|
+
printWarning,
|
|
16
|
+
resolveConfigPath
|
|
17
|
+
} from "./chunk-CSHQEILI.js";
|
|
18
|
+
|
|
1
19
|
// src/commands/compile.ts
|
|
2
20
|
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
21
|
import path from "path";
|
|
10
22
|
import fs from "fs";
|
|
11
|
-
import chalk2 from "chalk";
|
|
12
|
-
import { bundleRequire } from "bundle-require";
|
|
13
|
-
|
|
14
|
-
// src/utils/format.ts
|
|
15
23
|
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 path12 = issue.path?.join(".") || "";
|
|
66
|
-
const code = issue.code || "";
|
|
67
|
-
const msg = issue.message || "";
|
|
68
|
-
console.log(chalk.red(` \u2717 ${path12}`));
|
|
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
|
|
24
|
+
import { ObjectStackDefinitionSchema } from "@objectstack/spec";
|
|
235
25
|
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
26
|
const timer = createTimer();
|
|
237
27
|
if (!options.json) {
|
|
@@ -241,7 +31,7 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
241
31
|
if (!options.json) printStep("Loading configuration...");
|
|
242
32
|
const { config, absolutePath, duration } = await loadConfig(configPath);
|
|
243
33
|
if (!options.json) {
|
|
244
|
-
printKV("Config",
|
|
34
|
+
printKV("Config", path.relative(process.cwd(), absolutePath));
|
|
245
35
|
printKV("Load time", `${duration}ms`);
|
|
246
36
|
}
|
|
247
37
|
if (!options.json) printStep("Validating protocol compliance...");
|
|
@@ -258,13 +48,13 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
258
48
|
}
|
|
259
49
|
if (!options.json) printStep("Writing artifact...");
|
|
260
50
|
const output = options.output;
|
|
261
|
-
const artifactPath =
|
|
262
|
-
const artifactDir =
|
|
263
|
-
if (!
|
|
264
|
-
|
|
51
|
+
const artifactPath = path.resolve(process.cwd(), output);
|
|
52
|
+
const artifactDir = path.dirname(artifactPath);
|
|
53
|
+
if (!fs.existsSync(artifactDir)) {
|
|
54
|
+
fs.mkdirSync(artifactDir, { recursive: true });
|
|
265
55
|
}
|
|
266
56
|
const jsonContent = JSON.stringify(result.data, null, 2);
|
|
267
|
-
|
|
57
|
+
fs.writeFileSync(artifactPath, jsonContent);
|
|
268
58
|
const sizeKB = (jsonContent.length / 1024).toFixed(1);
|
|
269
59
|
const stats = collectMetadataStats(config);
|
|
270
60
|
if (options.json) {
|
|
@@ -278,11 +68,11 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
278
68
|
return;
|
|
279
69
|
}
|
|
280
70
|
console.log("");
|
|
281
|
-
printSuccess(`Build complete ${
|
|
71
|
+
printSuccess(`Build complete ${chalk.dim(`(${timer.display()})`)}`);
|
|
282
72
|
console.log("");
|
|
283
73
|
printMetadataStats(stats);
|
|
284
74
|
console.log("");
|
|
285
|
-
printKV("Artifact", `${output} ${
|
|
75
|
+
printKV("Artifact", `${output} ${chalk.dim(`(${sizeKB} KB`)})`);
|
|
286
76
|
console.log("");
|
|
287
77
|
} catch (error) {
|
|
288
78
|
if (options.json) {
|
|
@@ -297,7 +87,7 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
297
87
|
|
|
298
88
|
// src/commands/validate.ts
|
|
299
89
|
import { Command as Command2 } from "commander";
|
|
300
|
-
import
|
|
90
|
+
import chalk2 from "chalk";
|
|
301
91
|
import { ObjectStackDefinitionSchema as ObjectStackDefinitionSchema2 } from "@objectstack/spec";
|
|
302
92
|
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
93
|
const timer = createTimer();
|
|
@@ -351,12 +141,12 @@ var validateCommand = new Command2("validate").description("Validate ObjectStack
|
|
|
351
141
|
warnings.push("Missing manifest.namespace \u2014 required for multi-app hosting");
|
|
352
142
|
}
|
|
353
143
|
console.log("");
|
|
354
|
-
printSuccess(`Validation passed ${
|
|
144
|
+
printSuccess(`Validation passed ${chalk2.dim(`(${timer.display()})`)}`);
|
|
355
145
|
console.log("");
|
|
356
146
|
if (config.manifest) {
|
|
357
|
-
console.log(` ${
|
|
147
|
+
console.log(` ${chalk2.bold(config.manifest.name || config.manifest.id || "Unnamed")} ${chalk2.dim(`v${config.manifest.version || "0.0.0"}`)}`);
|
|
358
148
|
if (config.manifest.description) {
|
|
359
|
-
console.log(
|
|
149
|
+
console.log(chalk2.dim(` ${config.manifest.description}`));
|
|
360
150
|
}
|
|
361
151
|
console.log("");
|
|
362
152
|
}
|
|
@@ -364,7 +154,7 @@ var validateCommand = new Command2("validate").description("Validate ObjectStack
|
|
|
364
154
|
if (warnings.length > 0) {
|
|
365
155
|
console.log("");
|
|
366
156
|
for (const w of warnings) {
|
|
367
|
-
console.log(
|
|
157
|
+
console.log(chalk2.yellow(` \u26A0 ${w}`));
|
|
368
158
|
}
|
|
369
159
|
if (options.strict) {
|
|
370
160
|
console.log("");
|
|
@@ -390,7 +180,7 @@ var validateCommand = new Command2("validate").description("Validate ObjectStack
|
|
|
390
180
|
|
|
391
181
|
// src/commands/info.ts
|
|
392
182
|
import { Command as Command3 } from "commander";
|
|
393
|
-
import
|
|
183
|
+
import chalk3 from "chalk";
|
|
394
184
|
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
185
|
const timer = createTimer();
|
|
396
186
|
if (!options.json) {
|
|
@@ -416,9 +206,9 @@ var infoCommand = new Command3("info").description("Display metadata summary of
|
|
|
416
206
|
if (config.manifest) {
|
|
417
207
|
const m = config.manifest;
|
|
418
208
|
console.log("");
|
|
419
|
-
console.log(` ${
|
|
420
|
-
if (m.id) console.log(
|
|
421
|
-
if (m.description) console.log(
|
|
209
|
+
console.log(` ${chalk3.bold(m.name || m.id || "Unnamed")} ${chalk3.dim(`v${m.version || "0.0.0"}`)}`);
|
|
210
|
+
if (m.id) console.log(chalk3.dim(` ${m.id}`));
|
|
211
|
+
if (m.description) console.log(chalk3.dim(` ${m.description}`));
|
|
422
212
|
if (m.namespace) printKV(" Namespace", m.namespace);
|
|
423
213
|
if (m.type) printKV(" Type", m.type);
|
|
424
214
|
}
|
|
@@ -426,35 +216,35 @@ var infoCommand = new Command3("info").description("Display metadata summary of
|
|
|
426
216
|
printMetadataStats(stats);
|
|
427
217
|
if (config.objects && config.objects.length > 0) {
|
|
428
218
|
console.log("");
|
|
429
|
-
console.log(
|
|
219
|
+
console.log(chalk3.bold(" Objects:"));
|
|
430
220
|
for (const obj of config.objects) {
|
|
431
221
|
const fieldCount = obj.fields ? Object.keys(obj.fields).length : 0;
|
|
432
222
|
const ownership = obj.ownership || "own";
|
|
433
223
|
console.log(
|
|
434
|
-
` ${
|
|
224
|
+
` ${chalk3.cyan(obj.name || "?")}` + chalk3.dim(` (${fieldCount} fields, ${ownership})`) + (obj.label ? chalk3.dim(` \u2014 ${obj.label}`) : "")
|
|
435
225
|
);
|
|
436
226
|
}
|
|
437
227
|
}
|
|
438
228
|
if (config.agents && config.agents.length > 0) {
|
|
439
229
|
console.log("");
|
|
440
|
-
console.log(
|
|
230
|
+
console.log(chalk3.bold(" Agents:"));
|
|
441
231
|
for (const agent of config.agents) {
|
|
442
232
|
console.log(
|
|
443
|
-
` ${
|
|
233
|
+
` ${chalk3.magenta(agent.name || "?")}` + (agent.role ? chalk3.dim(` \u2014 ${agent.role}`) : "")
|
|
444
234
|
);
|
|
445
235
|
}
|
|
446
236
|
}
|
|
447
237
|
if (config.apps && config.apps.length > 0) {
|
|
448
238
|
console.log("");
|
|
449
|
-
console.log(
|
|
239
|
+
console.log(chalk3.bold(" Apps:"));
|
|
450
240
|
for (const app of config.apps) {
|
|
451
241
|
console.log(
|
|
452
|
-
` ${
|
|
242
|
+
` ${chalk3.green(app.name || "?")}` + (app.label ? chalk3.dim(` \u2014 ${app.label}`) : "")
|
|
453
243
|
);
|
|
454
244
|
}
|
|
455
245
|
}
|
|
456
246
|
console.log("");
|
|
457
|
-
console.log(
|
|
247
|
+
console.log(chalk3.dim(` Loaded in ${duration}ms`));
|
|
458
248
|
console.log("");
|
|
459
249
|
} catch (error) {
|
|
460
250
|
if (options.json) {
|
|
@@ -469,9 +259,9 @@ var infoCommand = new Command3("info").description("Display metadata summary of
|
|
|
469
259
|
|
|
470
260
|
// src/commands/init.ts
|
|
471
261
|
import { Command as Command4 } from "commander";
|
|
472
|
-
import
|
|
473
|
-
import
|
|
474
|
-
import
|
|
262
|
+
import chalk4 from "chalk";
|
|
263
|
+
import fs2 from "fs";
|
|
264
|
+
import path2 from "path";
|
|
475
265
|
var TEMPLATES = {
|
|
476
266
|
app: {
|
|
477
267
|
description: "Full application with objects, views, and actions",
|
|
@@ -636,16 +426,16 @@ function toTitleCase(str) {
|
|
|
636
426
|
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
427
|
printHeader("Init");
|
|
638
428
|
const cwd = process.cwd();
|
|
639
|
-
const projectName = name ||
|
|
429
|
+
const projectName = name || path2.basename(cwd);
|
|
640
430
|
const template = TEMPLATES[options.template];
|
|
641
431
|
if (!template) {
|
|
642
432
|
printError(`Unknown template: ${options.template}`);
|
|
643
|
-
console.log(
|
|
433
|
+
console.log(chalk4.dim(` Available: ${Object.keys(TEMPLATES).join(", ")}`));
|
|
644
434
|
process.exit(1);
|
|
645
435
|
}
|
|
646
|
-
if (
|
|
436
|
+
if (fs2.existsSync(path2.join(cwd, "objectstack.config.ts"))) {
|
|
647
437
|
printError("objectstack.config.ts already exists in this directory");
|
|
648
|
-
console.log(
|
|
438
|
+
console.log(chalk4.dim(" Use `objectstack generate` to add metadata to an existing project"));
|
|
649
439
|
process.exit(1);
|
|
650
440
|
}
|
|
651
441
|
printKV("Project", projectName);
|
|
@@ -654,8 +444,8 @@ var initCommand = new Command4("init").description("Initialize a new ObjectStack
|
|
|
654
444
|
console.log("");
|
|
655
445
|
const createdFiles = [];
|
|
656
446
|
try {
|
|
657
|
-
const pkgPath =
|
|
658
|
-
if (!
|
|
447
|
+
const pkgPath = path2.join(cwd, "package.json");
|
|
448
|
+
if (!fs2.existsSync(pkgPath)) {
|
|
659
449
|
const pkg = {
|
|
660
450
|
name: projectName,
|
|
661
451
|
version: "0.1.0",
|
|
@@ -665,16 +455,16 @@ var initCommand = new Command4("init").description("Initialize a new ObjectStack
|
|
|
665
455
|
dependencies: template.dependencies,
|
|
666
456
|
devDependencies: template.devDependencies
|
|
667
457
|
};
|
|
668
|
-
|
|
458
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
669
459
|
createdFiles.push("package.json");
|
|
670
460
|
} else {
|
|
671
461
|
printInfo("package.json already exists, skipping");
|
|
672
462
|
}
|
|
673
463
|
const configContent = template.configContent(projectName);
|
|
674
|
-
|
|
464
|
+
fs2.writeFileSync(path2.join(cwd, "objectstack.config.ts"), configContent);
|
|
675
465
|
createdFiles.push("objectstack.config.ts");
|
|
676
|
-
const tsconfigPath =
|
|
677
|
-
if (!
|
|
466
|
+
const tsconfigPath = path2.join(cwd, "tsconfig.json");
|
|
467
|
+
if (!fs2.existsSync(tsconfigPath)) {
|
|
678
468
|
const tsconfig = {
|
|
679
469
|
compilerOptions: {
|
|
680
470
|
target: "ES2022",
|
|
@@ -690,30 +480,30 @@ var initCommand = new Command4("init").description("Initialize a new ObjectStack
|
|
|
690
480
|
include: ["*.ts", "src/**/*"],
|
|
691
481
|
exclude: ["dist", "node_modules"]
|
|
692
482
|
};
|
|
693
|
-
|
|
483
|
+
fs2.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
694
484
|
createdFiles.push("tsconfig.json");
|
|
695
485
|
}
|
|
696
486
|
for (const [filePath, contentFn] of Object.entries(template.srcFiles)) {
|
|
697
487
|
const resolvedPath = filePath.replace("__name__", projectName);
|
|
698
|
-
const fullPath =
|
|
699
|
-
const dir =
|
|
700
|
-
if (!
|
|
701
|
-
|
|
488
|
+
const fullPath = path2.join(cwd, resolvedPath);
|
|
489
|
+
const dir = path2.dirname(fullPath);
|
|
490
|
+
if (!fs2.existsSync(dir)) {
|
|
491
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
702
492
|
}
|
|
703
|
-
|
|
493
|
+
fs2.writeFileSync(fullPath, contentFn(projectName));
|
|
704
494
|
createdFiles.push(resolvedPath);
|
|
705
495
|
}
|
|
706
|
-
const gitignorePath =
|
|
707
|
-
if (!
|
|
708
|
-
|
|
496
|
+
const gitignorePath = path2.join(cwd, ".gitignore");
|
|
497
|
+
if (!fs2.existsSync(gitignorePath)) {
|
|
498
|
+
fs2.writeFileSync(gitignorePath, `node_modules/
|
|
709
499
|
dist/
|
|
710
500
|
*.tsbuildinfo
|
|
711
501
|
`);
|
|
712
502
|
createdFiles.push(".gitignore");
|
|
713
503
|
}
|
|
714
|
-
console.log(
|
|
504
|
+
console.log(chalk4.bold(" Created files:"));
|
|
715
505
|
for (const f of createdFiles) {
|
|
716
|
-
console.log(
|
|
506
|
+
console.log(chalk4.green(` + ${f}`));
|
|
717
507
|
}
|
|
718
508
|
console.log("");
|
|
719
509
|
if (options.install !== false) {
|
|
@@ -727,10 +517,10 @@ dist/
|
|
|
727
517
|
}
|
|
728
518
|
printSuccess("Project initialized!");
|
|
729
519
|
console.log("");
|
|
730
|
-
console.log(
|
|
731
|
-
console.log(
|
|
732
|
-
console.log(
|
|
733
|
-
console.log(
|
|
520
|
+
console.log(chalk4.bold(" Next steps:"));
|
|
521
|
+
console.log(chalk4.dim(" objectstack validate # Check configuration"));
|
|
522
|
+
console.log(chalk4.dim(" objectstack dev # Start development server"));
|
|
523
|
+
console.log(chalk4.dim(" objectstack generate # Add objects, views, etc."));
|
|
734
524
|
console.log("");
|
|
735
525
|
} catch (error) {
|
|
736
526
|
printError(error.message || String(error));
|
|
@@ -738,14 +528,14 @@ dist/
|
|
|
738
528
|
}
|
|
739
529
|
});
|
|
740
530
|
function printWarning2(msg) {
|
|
741
|
-
console.log(
|
|
531
|
+
console.log(chalk4.yellow(` \u26A0 ${msg}`));
|
|
742
532
|
}
|
|
743
533
|
|
|
744
534
|
// src/commands/generate.ts
|
|
745
535
|
import { Command as Command5 } from "commander";
|
|
746
|
-
import
|
|
747
|
-
import
|
|
748
|
-
import
|
|
536
|
+
import chalk5 from "chalk";
|
|
537
|
+
import fs3 from "fs";
|
|
538
|
+
import path3 from "path";
|
|
749
539
|
var GENERATORS = {
|
|
750
540
|
object: {
|
|
751
541
|
description: "Business data object",
|
|
@@ -926,81 +716,589 @@ function toTitleCase2(str) {
|
|
|
926
716
|
function toSnakeCase(str) {
|
|
927
717
|
return str.replace(/[-]/g, "_").replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`).replace(/^_/, "");
|
|
928
718
|
}
|
|
929
|
-
var
|
|
719
|
+
var FIELD_TYPE_MAP = {
|
|
720
|
+
text: "string",
|
|
721
|
+
textarea: "string",
|
|
722
|
+
richtext: "string",
|
|
723
|
+
html: "string",
|
|
724
|
+
markdown: "string",
|
|
725
|
+
number: "number",
|
|
726
|
+
integer: "number",
|
|
727
|
+
currency: "number",
|
|
728
|
+
percent: "number",
|
|
729
|
+
boolean: "boolean",
|
|
730
|
+
date: "string",
|
|
731
|
+
datetime: "string",
|
|
732
|
+
time: "string",
|
|
733
|
+
email: "string",
|
|
734
|
+
phone: "string",
|
|
735
|
+
url: "string",
|
|
736
|
+
select: "string",
|
|
737
|
+
multiselect: "string[]",
|
|
738
|
+
lookup: "string",
|
|
739
|
+
master_detail: "string",
|
|
740
|
+
formula: "unknown",
|
|
741
|
+
autonumber: "string",
|
|
742
|
+
json: "Record<string, unknown>",
|
|
743
|
+
file: "string",
|
|
744
|
+
image: "string",
|
|
745
|
+
password: "string",
|
|
746
|
+
slug: "string",
|
|
747
|
+
uuid: "string",
|
|
748
|
+
ip_address: "string",
|
|
749
|
+
color: "string",
|
|
750
|
+
rating: "number",
|
|
751
|
+
geo_point: "{ lat: number; lng: number }",
|
|
752
|
+
vector: "number[]",
|
|
753
|
+
encrypted: "string"
|
|
754
|
+
};
|
|
755
|
+
function fieldTypeToTs(fieldType, multiple) {
|
|
756
|
+
const base = FIELD_TYPE_MAP[fieldType] || "unknown";
|
|
757
|
+
return multiple ? `${base}[]` : base;
|
|
758
|
+
}
|
|
759
|
+
function generateTypesFromConfig(config) {
|
|
760
|
+
const lines = [
|
|
761
|
+
"// Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
762
|
+
`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
763
|
+
"",
|
|
764
|
+
"import type { Data } from '@objectstack/spec';",
|
|
765
|
+
""
|
|
766
|
+
];
|
|
767
|
+
const objects = [];
|
|
768
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
769
|
+
if (Array.isArray(rawObjects)) {
|
|
770
|
+
objects.push(...rawObjects);
|
|
771
|
+
} else if (typeof rawObjects === "object") {
|
|
772
|
+
for (const val of Object.values(rawObjects)) {
|
|
773
|
+
if (val && typeof val === "object") objects.push(val);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (objects.length === 0) {
|
|
777
|
+
lines.push("// No objects found in configuration");
|
|
778
|
+
return lines.join("\n") + "\n";
|
|
779
|
+
}
|
|
780
|
+
for (const obj of objects) {
|
|
781
|
+
const name = String(obj.name || "unknown");
|
|
782
|
+
const typeName = name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
783
|
+
const fields = obj.fields ?? {};
|
|
784
|
+
lines.push(`/** ${String(obj.label || typeName)} record type */`);
|
|
785
|
+
lines.push(`export interface ${typeName}Record {`);
|
|
786
|
+
lines.push(" id: string;");
|
|
787
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
788
|
+
const fType = String(fieldDef.type || "text");
|
|
789
|
+
const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);
|
|
790
|
+
const required = fieldDef.required ? "" : "?";
|
|
791
|
+
if (fieldDef.label) {
|
|
792
|
+
lines.push(` /** ${fieldDef.label} */`);
|
|
793
|
+
}
|
|
794
|
+
lines.push(` ${fieldName}${required}: ${tsType};`);
|
|
795
|
+
}
|
|
796
|
+
lines.push("}");
|
|
797
|
+
lines.push("");
|
|
798
|
+
}
|
|
799
|
+
return lines.join("\n") + "\n";
|
|
800
|
+
}
|
|
801
|
+
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
802
|
printHeader("Generate");
|
|
931
803
|
const generator = GENERATORS[type];
|
|
932
804
|
if (!generator) {
|
|
933
805
|
printError(`Unknown type: ${type}`);
|
|
934
806
|
console.log("");
|
|
935
|
-
console.log(
|
|
807
|
+
console.log(chalk5.bold(" Available types:"));
|
|
936
808
|
for (const [key, gen] of Object.entries(GENERATORS)) {
|
|
937
|
-
console.log(` ${
|
|
809
|
+
console.log(` ${chalk5.cyan(key.padEnd(12))} ${chalk5.dim(gen.description)}`);
|
|
938
810
|
}
|
|
939
811
|
console.log("");
|
|
940
|
-
console.log(
|
|
941
|
-
console.log(
|
|
942
|
-
console.log(
|
|
812
|
+
console.log(chalk5.dim(" Usage: objectstack generate <type> <name>"));
|
|
813
|
+
console.log(chalk5.dim(" Example: objectstack generate object project"));
|
|
814
|
+
console.log(chalk5.dim(" Alias: os g object project"));
|
|
943
815
|
process.exit(1);
|
|
944
816
|
}
|
|
945
817
|
const dir = options.dir || generator.defaultDir;
|
|
946
818
|
const fileName = `${toSnakeCase(name)}.ts`;
|
|
947
|
-
const filePath =
|
|
948
|
-
console.log(` ${
|
|
949
|
-
console.log(` ${
|
|
950
|
-
console.log(` ${
|
|
819
|
+
const filePath = path3.join(process.cwd(), dir, fileName);
|
|
820
|
+
console.log(` ${chalk5.dim("Type:")} ${chalk5.cyan(type)} \u2014 ${generator.description}`);
|
|
821
|
+
console.log(` ${chalk5.dim("Name:")} ${chalk5.white(name)}`);
|
|
822
|
+
console.log(` ${chalk5.dim("File:")} ${chalk5.white(path3.join(dir, fileName))}`);
|
|
951
823
|
console.log("");
|
|
952
824
|
if (options.dryRun) {
|
|
953
825
|
printInfo("Dry run \u2014 no files written");
|
|
954
826
|
console.log("");
|
|
955
|
-
console.log(
|
|
956
|
-
console.log(
|
|
827
|
+
console.log(chalk5.dim(" Content:"));
|
|
828
|
+
console.log(chalk5.dim(" " + "-".repeat(38)));
|
|
957
829
|
const content = generator.generate(name);
|
|
958
830
|
for (const line of content.split("\n")) {
|
|
959
|
-
console.log(
|
|
831
|
+
console.log(chalk5.dim(` ${line}`));
|
|
960
832
|
}
|
|
961
833
|
console.log("");
|
|
962
834
|
return;
|
|
963
835
|
}
|
|
964
|
-
if (
|
|
836
|
+
if (fs3.existsSync(filePath)) {
|
|
965
837
|
printError(`File already exists: ${filePath}`);
|
|
966
838
|
process.exit(1);
|
|
967
839
|
}
|
|
968
840
|
try {
|
|
969
|
-
const fullDir =
|
|
970
|
-
if (!
|
|
971
|
-
|
|
841
|
+
const fullDir = path3.dirname(filePath);
|
|
842
|
+
if (!fs3.existsSync(fullDir)) {
|
|
843
|
+
fs3.mkdirSync(fullDir, { recursive: true });
|
|
972
844
|
}
|
|
973
845
|
const content = generator.generate(name);
|
|
974
|
-
|
|
975
|
-
printSuccess(`Created ${
|
|
976
|
-
const indexPath =
|
|
977
|
-
if (
|
|
978
|
-
const indexContent =
|
|
846
|
+
fs3.writeFileSync(filePath, content);
|
|
847
|
+
printSuccess(`Created ${path3.join(dir, fileName)}`);
|
|
848
|
+
const indexPath = path3.join(process.cwd(), dir, "index.ts");
|
|
849
|
+
if (fs3.existsSync(indexPath)) {
|
|
850
|
+
const indexContent = fs3.readFileSync(indexPath, "utf-8");
|
|
979
851
|
const exportLine = `export { default as ${toCamelCase2(name)} } from './${toSnakeCase(name)}';`;
|
|
980
852
|
if (!indexContent.includes(toCamelCase2(name))) {
|
|
981
|
-
|
|
853
|
+
fs3.appendFileSync(indexPath, exportLine + "\n");
|
|
982
854
|
printSuccess(`Updated ${dir}/index.ts with export`);
|
|
983
855
|
}
|
|
984
856
|
} else {
|
|
985
857
|
const exportLine = `export { default as ${toCamelCase2(name)} } from './${toSnakeCase(name)}';
|
|
986
858
|
`;
|
|
987
|
-
|
|
859
|
+
fs3.writeFileSync(indexPath, exportLine);
|
|
988
860
|
printSuccess(`Created ${dir}/index.ts`);
|
|
989
861
|
}
|
|
990
862
|
console.log("");
|
|
991
|
-
console.log(
|
|
863
|
+
console.log(chalk5.dim(` Tip: Run \`objectstack validate\` to check your config`));
|
|
992
864
|
console.log("");
|
|
993
865
|
} catch (error) {
|
|
994
866
|
printError(error.message || String(error));
|
|
995
867
|
process.exit(1);
|
|
996
868
|
}
|
|
997
869
|
});
|
|
870
|
+
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) => {
|
|
871
|
+
printHeader("Generate Types");
|
|
872
|
+
try {
|
|
873
|
+
const { loadConfig: loadConfig2 } = await import("./config-UN34WBHT.js");
|
|
874
|
+
printInfo("Loading configuration...");
|
|
875
|
+
const { config, absolutePath } = await loadConfig2(configPath);
|
|
876
|
+
console.log(` ${chalk5.dim("Config:")} ${chalk5.white(absolutePath)}`);
|
|
877
|
+
console.log(` ${chalk5.dim("Output:")} ${chalk5.white(options.output)}`);
|
|
878
|
+
console.log("");
|
|
879
|
+
const content = generateTypesFromConfig(config);
|
|
880
|
+
if (options.dryRun) {
|
|
881
|
+
printInfo("Dry run \u2014 no files written");
|
|
882
|
+
console.log("");
|
|
883
|
+
for (const line of content.split("\n")) {
|
|
884
|
+
console.log(chalk5.dim(` ${line}`));
|
|
885
|
+
}
|
|
886
|
+
console.log("");
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const outPath = path3.resolve(process.cwd(), options.output);
|
|
890
|
+
const outDir = path3.dirname(outPath);
|
|
891
|
+
if (!fs3.existsSync(outDir)) {
|
|
892
|
+
fs3.mkdirSync(outDir, { recursive: true });
|
|
893
|
+
}
|
|
894
|
+
fs3.writeFileSync(outPath, content);
|
|
895
|
+
printSuccess(`Generated types at ${options.output}`);
|
|
896
|
+
console.log("");
|
|
897
|
+
} catch (error) {
|
|
898
|
+
printError(error.message || String(error));
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
function generateClientFromConfig(config) {
|
|
903
|
+
const lines = [
|
|
904
|
+
"// Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
905
|
+
`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
906
|
+
"",
|
|
907
|
+
"import type { Data } from '@objectstack/spec';",
|
|
908
|
+
""
|
|
909
|
+
];
|
|
910
|
+
const objects = [];
|
|
911
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
912
|
+
if (Array.isArray(rawObjects)) {
|
|
913
|
+
objects.push(...rawObjects);
|
|
914
|
+
} else if (typeof rawObjects === "object") {
|
|
915
|
+
for (const val of Object.values(rawObjects)) {
|
|
916
|
+
if (val && typeof val === "object") objects.push(val);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (objects.length === 0) {
|
|
920
|
+
lines.push("// No objects found in configuration");
|
|
921
|
+
return lines.join("\n") + "\n";
|
|
922
|
+
}
|
|
923
|
+
for (const obj of objects) {
|
|
924
|
+
const name = String(obj.name || "unknown");
|
|
925
|
+
const typeName = name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
926
|
+
const fields = obj.fields ?? {};
|
|
927
|
+
lines.push(`export interface ${typeName}Record {`);
|
|
928
|
+
lines.push(" id: string;");
|
|
929
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
930
|
+
const fType = String(fieldDef.type || "text");
|
|
931
|
+
const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);
|
|
932
|
+
const required = fieldDef.required ? "" : "?";
|
|
933
|
+
lines.push(` ${fieldName}${required}: ${tsType};`);
|
|
934
|
+
}
|
|
935
|
+
lines.push("}");
|
|
936
|
+
lines.push("");
|
|
937
|
+
}
|
|
938
|
+
lines.push("export class ObjectStackClient {");
|
|
939
|
+
lines.push(" constructor(private baseUrl: string, private headers: Record<string, string> = {}) {}");
|
|
940
|
+
lines.push("");
|
|
941
|
+
lines.push(" private async request<T>(method: string, path: string, body?: unknown): Promise<T> {");
|
|
942
|
+
lines.push(" const res = await fetch(`${this.baseUrl}${path}`, {");
|
|
943
|
+
lines.push(" method,");
|
|
944
|
+
lines.push(" headers: { 'Content-Type': 'application/json', ...this.headers },");
|
|
945
|
+
lines.push(" body: body ? JSON.stringify(body) : undefined,");
|
|
946
|
+
lines.push(" });");
|
|
947
|
+
lines.push(" if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);");
|
|
948
|
+
lines.push(" return res.json() as Promise<T>;");
|
|
949
|
+
lines.push(" }");
|
|
950
|
+
for (const obj of objects) {
|
|
951
|
+
const name = String(obj.name || "unknown");
|
|
952
|
+
const typeName = name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
953
|
+
const endpoint = `/api/${name}`;
|
|
954
|
+
lines.push("");
|
|
955
|
+
lines.push(` async list${typeName}(): Promise<${typeName}Record[]> {`);
|
|
956
|
+
lines.push(` return this.request<${typeName}Record[]>('GET', '${endpoint}');`);
|
|
957
|
+
lines.push(" }");
|
|
958
|
+
lines.push("");
|
|
959
|
+
lines.push(` async get${typeName}(id: string): Promise<${typeName}Record> {`);
|
|
960
|
+
lines.push(` return this.request<${typeName}Record>('GET', '${endpoint}/\${id}');`);
|
|
961
|
+
lines.push(" }");
|
|
962
|
+
lines.push("");
|
|
963
|
+
lines.push(` async create${typeName}(data: Omit<${typeName}Record, 'id'>): Promise<${typeName}Record> {`);
|
|
964
|
+
lines.push(` return this.request<${typeName}Record>('POST', '${endpoint}', data);`);
|
|
965
|
+
lines.push(" }");
|
|
966
|
+
lines.push("");
|
|
967
|
+
lines.push(` async update${typeName}(id: string, data: Partial<${typeName}Record>): Promise<${typeName}Record> {`);
|
|
968
|
+
lines.push(` return this.request<${typeName}Record>('PATCH', '${endpoint}/\${id}', data);`);
|
|
969
|
+
lines.push(" }");
|
|
970
|
+
lines.push("");
|
|
971
|
+
lines.push(` async delete${typeName}(id: string): Promise<void> {`);
|
|
972
|
+
lines.push(` return this.request<void>('DELETE', '${endpoint}/\${id}');`);
|
|
973
|
+
lines.push(" }");
|
|
974
|
+
}
|
|
975
|
+
lines.push("}");
|
|
976
|
+
lines.push("");
|
|
977
|
+
return lines.join("\n") + "\n";
|
|
978
|
+
}
|
|
979
|
+
var generateClientCommand = new Command5("client").description("Generate a type-safe client SDK from ObjectStack configuration").argument("[config]", "Configuration file path").option("-o, --output <file>", "Output file path", "src/client/objectstack-client.ts").option("--dry-run", "Show output without writing").action(async (configPath, options) => {
|
|
980
|
+
printHeader("Generate Client SDK");
|
|
981
|
+
try {
|
|
982
|
+
const { loadConfig: loadConfig2 } = await import("./config-UN34WBHT.js");
|
|
983
|
+
const timer = createTimer();
|
|
984
|
+
printInfo("Loading configuration...");
|
|
985
|
+
const { config, absolutePath } = await loadConfig2(configPath);
|
|
986
|
+
console.log(` ${chalk5.dim("Config:")} ${chalk5.white(absolutePath)}`);
|
|
987
|
+
console.log(` ${chalk5.dim("Output:")} ${chalk5.white(options.output)}`);
|
|
988
|
+
console.log("");
|
|
989
|
+
printStep("Generating client SDK...");
|
|
990
|
+
const content = generateClientFromConfig(config);
|
|
991
|
+
if (options.dryRun) {
|
|
992
|
+
printInfo("Dry run \u2014 no files written");
|
|
993
|
+
console.log("");
|
|
994
|
+
for (const line of content.split("\n")) {
|
|
995
|
+
console.log(chalk5.dim(` ${line}`));
|
|
996
|
+
}
|
|
997
|
+
console.log("");
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const outPath = path3.resolve(process.cwd(), options.output);
|
|
1001
|
+
const outDir = path3.dirname(outPath);
|
|
1002
|
+
if (!fs3.existsSync(outDir)) {
|
|
1003
|
+
fs3.mkdirSync(outDir, { recursive: true });
|
|
1004
|
+
}
|
|
1005
|
+
fs3.writeFileSync(outPath, content);
|
|
1006
|
+
printSuccess(`Generated client SDK at ${options.output} (${timer.display()})`);
|
|
1007
|
+
console.log("");
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
printError(error.message || String(error));
|
|
1010
|
+
process.exit(1);
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
var FIELD_TYPE_SQL_MAP = {
|
|
1014
|
+
text: "VARCHAR(255)",
|
|
1015
|
+
textarea: "TEXT",
|
|
1016
|
+
richtext: "TEXT",
|
|
1017
|
+
html: "TEXT",
|
|
1018
|
+
markdown: "TEXT",
|
|
1019
|
+
number: "DECIMAL(18,2)",
|
|
1020
|
+
integer: "INTEGER",
|
|
1021
|
+
currency: "DECIMAL(18,2)",
|
|
1022
|
+
percent: "DECIMAL(5,2)",
|
|
1023
|
+
boolean: "BOOLEAN",
|
|
1024
|
+
date: "DATE",
|
|
1025
|
+
datetime: "TIMESTAMP",
|
|
1026
|
+
time: "TIME",
|
|
1027
|
+
email: "VARCHAR(255)",
|
|
1028
|
+
phone: "VARCHAR(50)",
|
|
1029
|
+
url: "VARCHAR(2048)",
|
|
1030
|
+
select: "VARCHAR(255)",
|
|
1031
|
+
multiselect: "TEXT",
|
|
1032
|
+
lookup: "VARCHAR(36)",
|
|
1033
|
+
master_detail: "VARCHAR(36)",
|
|
1034
|
+
formula: "TEXT",
|
|
1035
|
+
autonumber: "SERIAL",
|
|
1036
|
+
json: "JSONB",
|
|
1037
|
+
file: "VARCHAR(2048)",
|
|
1038
|
+
image: "VARCHAR(2048)",
|
|
1039
|
+
password: "VARCHAR(255)",
|
|
1040
|
+
slug: "VARCHAR(255)",
|
|
1041
|
+
uuid: "UUID",
|
|
1042
|
+
ip_address: "VARCHAR(45)",
|
|
1043
|
+
color: "VARCHAR(7)",
|
|
1044
|
+
rating: "INTEGER",
|
|
1045
|
+
geo_point: "POINT",
|
|
1046
|
+
vector: "VECTOR",
|
|
1047
|
+
encrypted: "TEXT"
|
|
1048
|
+
};
|
|
1049
|
+
function fieldTypeToSql(fieldType) {
|
|
1050
|
+
return FIELD_TYPE_SQL_MAP[fieldType] || "TEXT";
|
|
1051
|
+
}
|
|
1052
|
+
function generateMigrationSql(config) {
|
|
1053
|
+
const lines = [
|
|
1054
|
+
"-- Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
1055
|
+
`-- Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1056
|
+
""
|
|
1057
|
+
];
|
|
1058
|
+
const objects = [];
|
|
1059
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
1060
|
+
if (Array.isArray(rawObjects)) {
|
|
1061
|
+
objects.push(...rawObjects);
|
|
1062
|
+
} else if (typeof rawObjects === "object") {
|
|
1063
|
+
for (const val of Object.values(rawObjects)) {
|
|
1064
|
+
if (val && typeof val === "object") objects.push(val);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (objects.length === 0) {
|
|
1068
|
+
lines.push("-- No objects found in configuration");
|
|
1069
|
+
return lines.join("\n") + "\n";
|
|
1070
|
+
}
|
|
1071
|
+
for (const obj of objects) {
|
|
1072
|
+
const tableName = String(obj.name || "unknown");
|
|
1073
|
+
const fields = obj.fields ?? {};
|
|
1074
|
+
lines.push(`CREATE TABLE IF NOT EXISTS "${tableName}" (`);
|
|
1075
|
+
lines.push(' "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),');
|
|
1076
|
+
const fieldLines = [];
|
|
1077
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
1078
|
+
const sqlType = fieldTypeToSql(String(fieldDef.type || "text"));
|
|
1079
|
+
const notNull = fieldDef.required ? " NOT NULL" : "";
|
|
1080
|
+
fieldLines.push(` "${fieldName}" ${sqlType}${notNull}`);
|
|
1081
|
+
}
|
|
1082
|
+
fieldLines.push(' "created_at" TIMESTAMP NOT NULL DEFAULT now()');
|
|
1083
|
+
fieldLines.push(' "updated_at" TIMESTAMP NOT NULL DEFAULT now()');
|
|
1084
|
+
lines.push(fieldLines.join(",\n"));
|
|
1085
|
+
lines.push(");");
|
|
1086
|
+
lines.push("");
|
|
1087
|
+
}
|
|
1088
|
+
return lines.join("\n") + "\n";
|
|
1089
|
+
}
|
|
1090
|
+
function generateMigrationTs(config) {
|
|
1091
|
+
const lines = [
|
|
1092
|
+
"// Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
1093
|
+
`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1094
|
+
"",
|
|
1095
|
+
"export async function up(db: any): Promise<void> {"
|
|
1096
|
+
];
|
|
1097
|
+
const objects = [];
|
|
1098
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
1099
|
+
if (Array.isArray(rawObjects)) {
|
|
1100
|
+
objects.push(...rawObjects);
|
|
1101
|
+
} else if (typeof rawObjects === "object") {
|
|
1102
|
+
for (const val of Object.values(rawObjects)) {
|
|
1103
|
+
if (val && typeof val === "object") objects.push(val);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (objects.length === 0) {
|
|
1107
|
+
lines.push(" // No objects found in configuration");
|
|
1108
|
+
lines.push("}");
|
|
1109
|
+
lines.push("");
|
|
1110
|
+
lines.push("export async function down(db: any): Promise<void> {");
|
|
1111
|
+
lines.push(" // No objects found in configuration");
|
|
1112
|
+
lines.push("}");
|
|
1113
|
+
return lines.join("\n") + "\n";
|
|
1114
|
+
}
|
|
1115
|
+
for (const obj of objects) {
|
|
1116
|
+
const tableName = String(obj.name || "unknown");
|
|
1117
|
+
const fields = obj.fields ?? {};
|
|
1118
|
+
lines.push(` await db.schema.createTable('${tableName}', (table: any) => {`);
|
|
1119
|
+
lines.push(" table.uuid('id').primary().defaultTo(db.fn.uuid());");
|
|
1120
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
1121
|
+
const fType = String(fieldDef.type || "text");
|
|
1122
|
+
const required = fieldDef.required ? ".notNullable()" : ".nullable()";
|
|
1123
|
+
let colMethod;
|
|
1124
|
+
switch (fType) {
|
|
1125
|
+
case "text":
|
|
1126
|
+
case "email":
|
|
1127
|
+
case "phone":
|
|
1128
|
+
case "url":
|
|
1129
|
+
case "select":
|
|
1130
|
+
case "slug":
|
|
1131
|
+
case "password":
|
|
1132
|
+
case "color":
|
|
1133
|
+
case "ip_address":
|
|
1134
|
+
colMethod = `table.string('${fieldName}')`;
|
|
1135
|
+
break;
|
|
1136
|
+
case "textarea":
|
|
1137
|
+
case "richtext":
|
|
1138
|
+
case "html":
|
|
1139
|
+
case "markdown":
|
|
1140
|
+
case "formula":
|
|
1141
|
+
case "encrypted":
|
|
1142
|
+
colMethod = `table.text('${fieldName}')`;
|
|
1143
|
+
break;
|
|
1144
|
+
case "number":
|
|
1145
|
+
case "currency":
|
|
1146
|
+
case "percent":
|
|
1147
|
+
colMethod = `table.decimal('${fieldName}')`;
|
|
1148
|
+
break;
|
|
1149
|
+
case "integer":
|
|
1150
|
+
case "rating":
|
|
1151
|
+
colMethod = `table.integer('${fieldName}')`;
|
|
1152
|
+
break;
|
|
1153
|
+
case "boolean":
|
|
1154
|
+
colMethod = `table.boolean('${fieldName}')`;
|
|
1155
|
+
break;
|
|
1156
|
+
case "date":
|
|
1157
|
+
colMethod = `table.date('${fieldName}')`;
|
|
1158
|
+
break;
|
|
1159
|
+
case "datetime":
|
|
1160
|
+
colMethod = `table.timestamp('${fieldName}')`;
|
|
1161
|
+
break;
|
|
1162
|
+
case "time":
|
|
1163
|
+
colMethod = `table.time('${fieldName}')`;
|
|
1164
|
+
break;
|
|
1165
|
+
case "json":
|
|
1166
|
+
case "multiselect":
|
|
1167
|
+
colMethod = `table.jsonb('${fieldName}')`;
|
|
1168
|
+
break;
|
|
1169
|
+
case "uuid":
|
|
1170
|
+
case "lookup":
|
|
1171
|
+
case "master_detail":
|
|
1172
|
+
colMethod = `table.uuid('${fieldName}')`;
|
|
1173
|
+
break;
|
|
1174
|
+
default:
|
|
1175
|
+
colMethod = `table.text('${fieldName}')`;
|
|
1176
|
+
}
|
|
1177
|
+
lines.push(` ${colMethod}${required};`);
|
|
1178
|
+
}
|
|
1179
|
+
lines.push(" table.timestamps(true, true);");
|
|
1180
|
+
lines.push(" });");
|
|
1181
|
+
}
|
|
1182
|
+
lines.push("}");
|
|
1183
|
+
lines.push("");
|
|
1184
|
+
lines.push("export async function down(db: any): Promise<void> {");
|
|
1185
|
+
const tableNames = objects.map((o) => String(o.name || "unknown")).reverse();
|
|
1186
|
+
for (const tableName of tableNames) {
|
|
1187
|
+
lines.push(` await db.schema.dropTableIfExists('${tableName}');`);
|
|
1188
|
+
}
|
|
1189
|
+
lines.push("}");
|
|
1190
|
+
return lines.join("\n") + "\n";
|
|
1191
|
+
}
|
|
1192
|
+
var generateMigrationCommand = new Command5("migration").description("Generate database migration from ObjectStack schema").argument("[config]", "Configuration file path").option("-o, --output <file>", "Output file path").option("--format <format>", "Output format: sql or typescript", "typescript").option("--dry-run", "Show output without writing").action(async (configPath, options) => {
|
|
1193
|
+
printHeader("Generate Migration");
|
|
1194
|
+
try {
|
|
1195
|
+
const { loadConfig: loadConfig2 } = await import("./config-UN34WBHT.js");
|
|
1196
|
+
const timer = createTimer();
|
|
1197
|
+
printInfo("Loading configuration...");
|
|
1198
|
+
const { config, absolutePath } = await loadConfig2(configPath);
|
|
1199
|
+
const ext = options.format === "sql" ? "sql" : "ts";
|
|
1200
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
|
1201
|
+
const defaultOutput = `migrations/${timestamp}_migration.${ext}`;
|
|
1202
|
+
const output = options.output || defaultOutput;
|
|
1203
|
+
console.log(` ${chalk5.dim("Config:")} ${chalk5.white(absolutePath)}`);
|
|
1204
|
+
console.log(` ${chalk5.dim("Format:")} ${chalk5.white(options.format)}`);
|
|
1205
|
+
console.log(` ${chalk5.dim("Output:")} ${chalk5.white(output)}`);
|
|
1206
|
+
console.log("");
|
|
1207
|
+
printStep("Generating migration...");
|
|
1208
|
+
const content = options.format === "sql" ? generateMigrationSql(config) : generateMigrationTs(config);
|
|
1209
|
+
if (options.dryRun) {
|
|
1210
|
+
printInfo("Dry run \u2014 no files written");
|
|
1211
|
+
console.log("");
|
|
1212
|
+
for (const line of content.split("\n")) {
|
|
1213
|
+
console.log(chalk5.dim(` ${line}`));
|
|
1214
|
+
}
|
|
1215
|
+
console.log("");
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
const outPath = path3.resolve(process.cwd(), output);
|
|
1219
|
+
const outDir = path3.dirname(outPath);
|
|
1220
|
+
if (!fs3.existsSync(outDir)) {
|
|
1221
|
+
fs3.mkdirSync(outDir, { recursive: true });
|
|
1222
|
+
}
|
|
1223
|
+
fs3.writeFileSync(outPath, content);
|
|
1224
|
+
printSuccess(`Generated migration at ${output} (${timer.display()})`);
|
|
1225
|
+
console.log("");
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
printError(error.message || String(error));
|
|
1228
|
+
process.exit(1);
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
var generateSchemaCommand = new Command5("schema").description("Generate JSON Schema for objectstack.config.ts (for IDE autocomplete)").option("-o, --output <file>", "Output file path", "objectstack.schema.json").option("--dry-run", "Show output without writing").action(async (options) => {
|
|
1232
|
+
printHeader("Generate Schema");
|
|
1233
|
+
try {
|
|
1234
|
+
const timer = createTimer();
|
|
1235
|
+
printStep("Loading ObjectStackDefinitionSchema...");
|
|
1236
|
+
const { z } = await import("zod");
|
|
1237
|
+
const { ObjectStackDefinitionSchema: ObjectStackDefinitionSchema3 } = await import("@objectstack/spec");
|
|
1238
|
+
printStep("Converting to JSON Schema...");
|
|
1239
|
+
const jsonSchema = z.toJSONSchema(ObjectStackDefinitionSchema3, {
|
|
1240
|
+
target: "draft-2020-12"
|
|
1241
|
+
});
|
|
1242
|
+
const schema = {
|
|
1243
|
+
...jsonSchema,
|
|
1244
|
+
$id: "https://schema.objectstack.io/objectstack.config.json",
|
|
1245
|
+
title: "ObjectStack Configuration",
|
|
1246
|
+
description: "JSON Schema for objectstack.config.ts \u2014 generated from ObjectStackDefinitionSchema"
|
|
1247
|
+
};
|
|
1248
|
+
const content = JSON.stringify(schema, null, 2) + "\n";
|
|
1249
|
+
if (options.dryRun) {
|
|
1250
|
+
printInfo("Dry run \u2014 no files written");
|
|
1251
|
+
console.log("");
|
|
1252
|
+
console.log(content);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
const outPath = path3.resolve(process.cwd(), options.output);
|
|
1256
|
+
const outDir = path3.dirname(outPath);
|
|
1257
|
+
if (!fs3.existsSync(outDir)) {
|
|
1258
|
+
fs3.mkdirSync(outDir, { recursive: true });
|
|
1259
|
+
}
|
|
1260
|
+
fs3.writeFileSync(outPath, content);
|
|
1261
|
+
printSuccess(`Generated JSON Schema at ${options.output} (${timer.display()})`);
|
|
1262
|
+
console.log("");
|
|
1263
|
+
console.log(chalk5.dim(" Usage: Reference in your IDE or editor for autocomplete"));
|
|
1264
|
+
console.log(chalk5.dim(` Path: ${outPath}`));
|
|
1265
|
+
console.log("");
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
printError(error.message || String(error));
|
|
1268
|
+
process.exit(1);
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
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).addCommand(generateClientCommand).addCommand(generateMigrationCommand).addCommand(generateSchemaCommand).action(async (type, name, options) => {
|
|
1272
|
+
if (!type) {
|
|
1273
|
+
printHeader("Generate");
|
|
1274
|
+
console.log(chalk5.bold(" Sub-commands:"));
|
|
1275
|
+
console.log(` ${chalk5.cyan("types".padEnd(12))} Generate TypeScript type definitions from config`);
|
|
1276
|
+
console.log(` ${chalk5.cyan("client".padEnd(12))} Generate a type-safe client SDK from config`);
|
|
1277
|
+
console.log(` ${chalk5.cyan("migration".padEnd(12))} Generate database migration from schema`);
|
|
1278
|
+
console.log(` ${chalk5.cyan("schema".padEnd(12))} Generate JSON Schema for objectstack.config.ts (IDE autocomplete)`);
|
|
1279
|
+
console.log("");
|
|
1280
|
+
console.log(chalk5.bold(" Metadata types:"));
|
|
1281
|
+
for (const [key, gen] of Object.entries(GENERATORS)) {
|
|
1282
|
+
console.log(` ${chalk5.cyan(key.padEnd(12))} ${chalk5.dim(gen.description)}`);
|
|
1283
|
+
}
|
|
1284
|
+
console.log("");
|
|
1285
|
+
console.log(chalk5.dim(" Usage: objectstack generate <type> <name>"));
|
|
1286
|
+
console.log(chalk5.dim(" Usage: objectstack generate types [config]"));
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if (!name) {
|
|
1290
|
+
printError("Missing required argument: <name>");
|
|
1291
|
+
console.log(chalk5.dim(" Usage: objectstack generate <type> <name>"));
|
|
1292
|
+
process.exit(1);
|
|
1293
|
+
}
|
|
1294
|
+
await generateMetadataCommand.parseAsync([type, name, ...process.argv.slice(4)], { from: "user" });
|
|
1295
|
+
});
|
|
998
1296
|
|
|
999
1297
|
// src/commands/create.ts
|
|
1000
1298
|
import { Command as Command6 } from "commander";
|
|
1001
|
-
import
|
|
1002
|
-
import
|
|
1003
|
-
import
|
|
1299
|
+
import chalk6 from "chalk";
|
|
1300
|
+
import fs4 from "fs";
|
|
1301
|
+
import path4 from "path";
|
|
1004
1302
|
var templates = {
|
|
1005
1303
|
plugin: {
|
|
1006
1304
|
description: "Create a new ObjectStack plugin",
|
|
@@ -1175,64 +1473,64 @@ function toCamelCase3(str) {
|
|
|
1175
1473
|
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
1176
1474
|
}
|
|
1177
1475
|
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(
|
|
1476
|
+
console.log(chalk6.bold(`
|
|
1179
1477
|
\u{1F4E6} ObjectStack Project Creator`));
|
|
1180
|
-
console.log(
|
|
1478
|
+
console.log(chalk6.dim(`-------------------------------`));
|
|
1181
1479
|
if (!templates[type]) {
|
|
1182
|
-
console.error(
|
|
1480
|
+
console.error(chalk6.red(`
|
|
1183
1481
|
\u274C Unknown type: ${type}`));
|
|
1184
|
-
console.log(
|
|
1482
|
+
console.log(chalk6.dim("Available types: plugin, example"));
|
|
1185
1483
|
process.exit(1);
|
|
1186
1484
|
}
|
|
1187
1485
|
if (!name) {
|
|
1188
|
-
console.error(
|
|
1189
|
-
console.log(
|
|
1486
|
+
console.error(chalk6.red("\n\u274C Project name is required"));
|
|
1487
|
+
console.log(chalk6.dim(`Usage: objectstack create ${type} <name>`));
|
|
1190
1488
|
process.exit(1);
|
|
1191
1489
|
}
|
|
1192
1490
|
const template = templates[type];
|
|
1193
1491
|
const cwd = process.cwd();
|
|
1194
1492
|
let targetDir;
|
|
1195
1493
|
if (options?.dir) {
|
|
1196
|
-
targetDir =
|
|
1494
|
+
targetDir = path4.resolve(cwd, options.dir);
|
|
1197
1495
|
} else {
|
|
1198
1496
|
const baseDir = type === "plugin" ? "packages/plugins" : "examples";
|
|
1199
1497
|
const projectName = type === "plugin" ? `plugin-${name}` : name;
|
|
1200
|
-
targetDir =
|
|
1498
|
+
targetDir = path4.join(cwd, baseDir, projectName);
|
|
1201
1499
|
}
|
|
1202
|
-
if (
|
|
1203
|
-
console.error(
|
|
1500
|
+
if (fs4.existsSync(targetDir)) {
|
|
1501
|
+
console.error(chalk6.red(`
|
|
1204
1502
|
\u274C Directory already exists: ${targetDir}`));
|
|
1205
1503
|
process.exit(1);
|
|
1206
1504
|
}
|
|
1207
|
-
console.log(`\u{1F4C1} Creating ${type}: ${
|
|
1208
|
-
console.log(`\u{1F4C2} Location: ${
|
|
1505
|
+
console.log(`\u{1F4C1} Creating ${type}: ${chalk6.blue(name)}`);
|
|
1506
|
+
console.log(`\u{1F4C2} Location: ${chalk6.dim(targetDir)}`);
|
|
1209
1507
|
console.log("");
|
|
1210
1508
|
try {
|
|
1211
|
-
|
|
1509
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
1212
1510
|
for (const [filePath, contentFn] of Object.entries(template.files)) {
|
|
1213
|
-
const fullPath =
|
|
1214
|
-
const dir =
|
|
1215
|
-
if (!
|
|
1216
|
-
|
|
1511
|
+
const fullPath = path4.join(targetDir, filePath);
|
|
1512
|
+
const dir = path4.dirname(fullPath);
|
|
1513
|
+
if (!fs4.existsSync(dir)) {
|
|
1514
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1217
1515
|
}
|
|
1218
1516
|
const content = contentFn(name);
|
|
1219
1517
|
const fileContent = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
1220
|
-
|
|
1221
|
-
console.log(
|
|
1518
|
+
fs4.writeFileSync(fullPath, fileContent);
|
|
1519
|
+
console.log(chalk6.green(`\u2713 Created ${filePath}`));
|
|
1222
1520
|
}
|
|
1223
1521
|
console.log("");
|
|
1224
|
-
console.log(
|
|
1522
|
+
console.log(chalk6.green("\u2705 Project created successfully!"));
|
|
1225
1523
|
console.log("");
|
|
1226
|
-
console.log(
|
|
1227
|
-
console.log(
|
|
1228
|
-
console.log(
|
|
1229
|
-
console.log(
|
|
1524
|
+
console.log(chalk6.bold("Next steps:"));
|
|
1525
|
+
console.log(chalk6.dim(` cd ${path4.relative(cwd, targetDir)}`));
|
|
1526
|
+
console.log(chalk6.dim(" pnpm install"));
|
|
1527
|
+
console.log(chalk6.dim(" pnpm build"));
|
|
1230
1528
|
console.log("");
|
|
1231
1529
|
} catch (error) {
|
|
1232
|
-
console.error(
|
|
1530
|
+
console.error(chalk6.red("\n\u274C Failed to create project:"));
|
|
1233
1531
|
console.error(error.message || error);
|
|
1234
|
-
if (
|
|
1235
|
-
|
|
1532
|
+
if (fs4.existsSync(targetDir)) {
|
|
1533
|
+
fs4.rmSync(targetDir, { recursive: true });
|
|
1236
1534
|
}
|
|
1237
1535
|
process.exit(1);
|
|
1238
1536
|
}
|
|
@@ -1240,9 +1538,9 @@ var createCommand = new Command6("create").description("Create a new package, pl
|
|
|
1240
1538
|
|
|
1241
1539
|
// src/commands/plugin.ts
|
|
1242
1540
|
import { Command as Command7 } from "commander";
|
|
1243
|
-
import
|
|
1244
|
-
import
|
|
1245
|
-
import
|
|
1541
|
+
import chalk7 from "chalk";
|
|
1542
|
+
import fs5 from "fs";
|
|
1543
|
+
import path5 from "path";
|
|
1246
1544
|
function resolvePluginName(plugin) {
|
|
1247
1545
|
if (typeof plugin === "string") return plugin;
|
|
1248
1546
|
if (plugin && typeof plugin === "object") {
|
|
@@ -1267,7 +1565,7 @@ function resolvePluginType(plugin) {
|
|
|
1267
1565
|
return "standard";
|
|
1268
1566
|
}
|
|
1269
1567
|
function readConfigText(configPath) {
|
|
1270
|
-
return
|
|
1568
|
+
return fs5.readFileSync(configPath, "utf-8");
|
|
1271
1569
|
}
|
|
1272
1570
|
function addPluginToConfig(configPath, packageName) {
|
|
1273
1571
|
let content = readConfigText(configPath);
|
|
@@ -1309,7 +1607,7 @@ function addPluginToConfig(configPath, packageName) {
|
|
|
1309
1607
|
$2`
|
|
1310
1608
|
);
|
|
1311
1609
|
}
|
|
1312
|
-
|
|
1610
|
+
fs5.writeFileSync(configPath, content);
|
|
1313
1611
|
}
|
|
1314
1612
|
function removePluginFromConfig(configPath, pluginName) {
|
|
1315
1613
|
let content = readConfigText(configPath);
|
|
@@ -1331,7 +1629,7 @@ function removePluginFromConfig(configPath, pluginName) {
|
|
|
1331
1629
|
content = content.replace(pattern, "\n");
|
|
1332
1630
|
}
|
|
1333
1631
|
content = content.replace(/plugins\s*:\s*\[\s*\],?\n?/g, "");
|
|
1334
|
-
|
|
1632
|
+
fs5.writeFileSync(configPath, content);
|
|
1335
1633
|
}
|
|
1336
1634
|
function escapeRegex(str) {
|
|
1337
1635
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -1363,31 +1661,31 @@ var listCommand = new Command7("list").alias("ls").description("List plugins def
|
|
|
1363
1661
|
if (plugins.length === 0 && devPlugins.length === 0) {
|
|
1364
1662
|
printInfo("No plugins configured");
|
|
1365
1663
|
console.log("");
|
|
1366
|
-
console.log(
|
|
1367
|
-
console.log(
|
|
1664
|
+
console.log(chalk7.dim(" Hint: Add plugins to your objectstack.config.ts"));
|
|
1665
|
+
console.log(chalk7.dim(" Or run: os plugin add <package-name>"));
|
|
1368
1666
|
console.log("");
|
|
1369
1667
|
return;
|
|
1370
1668
|
}
|
|
1371
1669
|
if (plugins.length > 0) {
|
|
1372
|
-
console.log(
|
|
1670
|
+
console.log(chalk7.bold(`
|
|
1373
1671
|
Plugins (${plugins.length}):`));
|
|
1374
1672
|
for (const plugin of plugins) {
|
|
1375
1673
|
const name = resolvePluginName(plugin);
|
|
1376
1674
|
const version = resolvePluginVersion(plugin);
|
|
1377
1675
|
const type = resolvePluginType(plugin);
|
|
1378
1676
|
console.log(
|
|
1379
|
-
` ${
|
|
1677
|
+
` ${chalk7.cyan("\u25CF")} ${chalk7.white(name)}` + (version !== "-" ? chalk7.dim(` v${version}`) : "") + (type !== "standard" ? chalk7.dim(` [${type}]`) : "")
|
|
1380
1678
|
);
|
|
1381
1679
|
}
|
|
1382
1680
|
}
|
|
1383
1681
|
if (devPlugins.length > 0) {
|
|
1384
|
-
console.log(
|
|
1682
|
+
console.log(chalk7.bold(`
|
|
1385
1683
|
Dev Plugins (${devPlugins.length}):`));
|
|
1386
1684
|
for (const plugin of devPlugins) {
|
|
1387
1685
|
const name = resolvePluginName(plugin);
|
|
1388
1686
|
const version = resolvePluginVersion(plugin);
|
|
1389
1687
|
console.log(
|
|
1390
|
-
` ${
|
|
1688
|
+
` ${chalk7.yellow("\u25CF")} ${chalk7.white(name)}` + (version !== "-" ? chalk7.dim(` v${version}`) : "") + chalk7.dim(" [dev]")
|
|
1391
1689
|
);
|
|
1392
1690
|
}
|
|
1393
1691
|
}
|
|
@@ -1411,9 +1709,9 @@ var infoSubCommand = new Command7("info").description("Show detailed information
|
|
|
1411
1709
|
if (!found) {
|
|
1412
1710
|
printError(`Plugin '${name}' not found in configuration`);
|
|
1413
1711
|
console.log("");
|
|
1414
|
-
console.log(
|
|
1712
|
+
console.log(chalk7.dim(" Available plugins:"));
|
|
1415
1713
|
for (const p of allPlugins) {
|
|
1416
|
-
console.log(
|
|
1714
|
+
console.log(chalk7.dim(` - ${resolvePluginName(p)}`));
|
|
1417
1715
|
}
|
|
1418
1716
|
console.log("");
|
|
1419
1717
|
process.exit(1);
|
|
@@ -1449,15 +1747,15 @@ var addCommand = new Command7("add").description("Add a plugin to objectstack.co
|
|
|
1449
1747
|
try {
|
|
1450
1748
|
const configPath = resolveConfigPath(options?.config);
|
|
1451
1749
|
printHeader("Add Plugin");
|
|
1452
|
-
console.log(` ${
|
|
1453
|
-
console.log(` ${
|
|
1750
|
+
console.log(` ${chalk7.dim("Package:")} ${chalk7.white(packageName)}`);
|
|
1751
|
+
console.log(` ${chalk7.dim("Config:")} ${chalk7.white(path5.relative(process.cwd(), configPath))}`);
|
|
1454
1752
|
console.log("");
|
|
1455
1753
|
addPluginToConfig(configPath, packageName);
|
|
1456
|
-
printSuccess(`Added ${
|
|
1754
|
+
printSuccess(`Added ${chalk7.cyan(packageName)} to config`);
|
|
1457
1755
|
console.log("");
|
|
1458
|
-
console.log(
|
|
1459
|
-
console.log(
|
|
1460
|
-
console.log(
|
|
1756
|
+
console.log(chalk7.dim(" Next steps:"));
|
|
1757
|
+
console.log(chalk7.dim(` 1. Install the package: pnpm add ${packageName}`));
|
|
1758
|
+
console.log(chalk7.dim(" 2. Run: os validate"));
|
|
1461
1759
|
console.log("");
|
|
1462
1760
|
} catch (error) {
|
|
1463
1761
|
printError(error.message || String(error));
|
|
@@ -1468,13 +1766,13 @@ var removeCommand = new Command7("remove").alias("rm").description("Remove a plu
|
|
|
1468
1766
|
try {
|
|
1469
1767
|
const configPath = resolveConfigPath(options?.config);
|
|
1470
1768
|
printHeader("Remove Plugin");
|
|
1471
|
-
console.log(` ${
|
|
1472
|
-
console.log(` ${
|
|
1769
|
+
console.log(` ${chalk7.dim("Plugin:")} ${chalk7.white(pluginName)}`);
|
|
1770
|
+
console.log(` ${chalk7.dim("Config:")} ${chalk7.white(path5.relative(process.cwd(), configPath))}`);
|
|
1473
1771
|
console.log("");
|
|
1474
1772
|
removePluginFromConfig(configPath, pluginName);
|
|
1475
|
-
printSuccess(`Removed ${
|
|
1773
|
+
printSuccess(`Removed ${chalk7.cyan(pluginName)} from config`);
|
|
1476
1774
|
console.log("");
|
|
1477
|
-
console.log(
|
|
1775
|
+
console.log(chalk7.dim(" Tip: Run `pnpm remove " + pluginName + "` to uninstall the package"));
|
|
1478
1776
|
console.log("");
|
|
1479
1777
|
} catch (error) {
|
|
1480
1778
|
printError(error.message || String(error));
|
|
@@ -1485,14 +1783,14 @@ var pluginCommand = new Command7("plugin").description("Manage plugins (list, in
|
|
|
1485
1783
|
|
|
1486
1784
|
// src/commands/dev.ts
|
|
1487
1785
|
import { Command as Command8 } from "commander";
|
|
1488
|
-
import
|
|
1786
|
+
import chalk8 from "chalk";
|
|
1489
1787
|
import { execSync, spawn } from "child_process";
|
|
1490
|
-
import
|
|
1491
|
-
import
|
|
1788
|
+
import fs6 from "fs";
|
|
1789
|
+
import path6 from "path";
|
|
1492
1790
|
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) => {
|
|
1493
1791
|
printHeader("Development Mode");
|
|
1494
|
-
const configPath =
|
|
1495
|
-
if (packageName === "all" &&
|
|
1792
|
+
const configPath = path6.resolve(process.cwd(), "objectstack.config.ts");
|
|
1793
|
+
if (packageName === "all" && fs6.existsSync(configPath)) {
|
|
1496
1794
|
printKV("Config", configPath, "\u{1F4C2}");
|
|
1497
1795
|
printStep("Starting dev server...");
|
|
1498
1796
|
const binPath = process.argv[1];
|
|
@@ -1504,18 +1802,18 @@ var devCommand = new Command8("dev").description("Start development mode with ho
|
|
|
1504
1802
|
}
|
|
1505
1803
|
try {
|
|
1506
1804
|
const cwd = process.cwd();
|
|
1507
|
-
const workspaceConfigPath =
|
|
1508
|
-
const isWorkspaceRoot =
|
|
1805
|
+
const workspaceConfigPath = path6.resolve(cwd, "pnpm-workspace.yaml");
|
|
1806
|
+
const isWorkspaceRoot = fs6.existsSync(workspaceConfigPath);
|
|
1509
1807
|
if (packageName === "all" && !isWorkspaceRoot) {
|
|
1510
1808
|
printError(`Config file not found in ${cwd}`);
|
|
1511
|
-
console.error(
|
|
1809
|
+
console.error(chalk8.yellow(" Run in a directory with objectstack.config.ts, or from the monorepo root."));
|
|
1512
1810
|
process.exit(1);
|
|
1513
1811
|
}
|
|
1514
1812
|
const filter = packageName === "all" ? "" : `--filter ${packageName}`;
|
|
1515
1813
|
printKV("Package", packageName === "all" ? "All packages" : packageName, "\u{1F4E6}");
|
|
1516
1814
|
printKV("Watch", "enabled", "\u{1F504}");
|
|
1517
1815
|
const command = `pnpm ${filter} dev`.trim();
|
|
1518
|
-
console.log(
|
|
1816
|
+
console.log(chalk8.dim(`$ ${command}`));
|
|
1519
1817
|
console.log("");
|
|
1520
1818
|
execSync(command, {
|
|
1521
1819
|
stdio: "inherit",
|
|
@@ -1529,37 +1827,37 @@ var devCommand = new Command8("dev").description("Start development mode with ho
|
|
|
1529
1827
|
|
|
1530
1828
|
// src/commands/serve.ts
|
|
1531
1829
|
import { Command as Command9 } from "commander";
|
|
1532
|
-
import
|
|
1533
|
-
import
|
|
1830
|
+
import path8 from "path";
|
|
1831
|
+
import fs8 from "fs";
|
|
1534
1832
|
import net from "net";
|
|
1535
|
-
import
|
|
1536
|
-
import { bundleRequire
|
|
1833
|
+
import chalk9 from "chalk";
|
|
1834
|
+
import { bundleRequire } from "bundle-require";
|
|
1537
1835
|
|
|
1538
1836
|
// src/utils/studio.ts
|
|
1539
|
-
import
|
|
1540
|
-
import
|
|
1837
|
+
import path7 from "path";
|
|
1838
|
+
import fs7 from "fs";
|
|
1541
1839
|
import { createRequire } from "module";
|
|
1542
1840
|
import { pathToFileURL } from "url";
|
|
1543
1841
|
var STUDIO_PATH = "/_studio";
|
|
1544
1842
|
function resolveStudioPath() {
|
|
1545
1843
|
const cwd = process.cwd();
|
|
1546
1844
|
const candidates = [
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1845
|
+
path7.resolve(cwd, "apps/studio"),
|
|
1846
|
+
path7.resolve(cwd, "../../apps/studio"),
|
|
1847
|
+
path7.resolve(cwd, "../apps/studio")
|
|
1550
1848
|
];
|
|
1551
1849
|
for (const candidate of candidates) {
|
|
1552
|
-
const pkgPath =
|
|
1553
|
-
if (
|
|
1850
|
+
const pkgPath = path7.join(candidate, "package.json");
|
|
1851
|
+
if (fs7.existsSync(pkgPath)) {
|
|
1554
1852
|
try {
|
|
1555
|
-
const pkg = JSON.parse(
|
|
1853
|
+
const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
|
|
1556
1854
|
if (pkg.name === "@objectstack/studio") return candidate;
|
|
1557
1855
|
} catch {
|
|
1558
1856
|
}
|
|
1559
1857
|
}
|
|
1560
1858
|
}
|
|
1561
1859
|
const resolutionBases = [
|
|
1562
|
-
pathToFileURL(
|
|
1860
|
+
pathToFileURL(path7.join(cwd, "package.json")).href,
|
|
1563
1861
|
// consumer workspace
|
|
1564
1862
|
import.meta.url
|
|
1565
1863
|
// CLI package itself
|
|
@@ -1568,18 +1866,18 @@ function resolveStudioPath() {
|
|
|
1568
1866
|
try {
|
|
1569
1867
|
const req = createRequire(base);
|
|
1570
1868
|
const resolved = req.resolve("@objectstack/studio/package.json");
|
|
1571
|
-
return
|
|
1869
|
+
return path7.dirname(resolved);
|
|
1572
1870
|
} catch {
|
|
1573
1871
|
}
|
|
1574
1872
|
}
|
|
1575
|
-
const directPath =
|
|
1576
|
-
if (
|
|
1873
|
+
const directPath = path7.join(cwd, "node_modules", "@objectstack", "studio");
|
|
1874
|
+
if (fs7.existsSync(path7.join(directPath, "package.json"))) {
|
|
1577
1875
|
return directPath;
|
|
1578
1876
|
}
|
|
1579
1877
|
return null;
|
|
1580
1878
|
}
|
|
1581
1879
|
function hasStudioDist(studioPath) {
|
|
1582
|
-
return
|
|
1880
|
+
return fs7.existsSync(path7.join(studioPath, "dist", "index.html"));
|
|
1583
1881
|
}
|
|
1584
1882
|
function createStudioStaticPlugin(distPath, options) {
|
|
1585
1883
|
return {
|
|
@@ -1593,13 +1891,13 @@ function createStudioStaticPlugin(distPath, options) {
|
|
|
1593
1891
|
return;
|
|
1594
1892
|
}
|
|
1595
1893
|
const app = httpServer.getRawApp();
|
|
1596
|
-
const absoluteDist =
|
|
1597
|
-
const indexPath =
|
|
1598
|
-
if (!
|
|
1894
|
+
const absoluteDist = path7.resolve(distPath);
|
|
1895
|
+
const indexPath = path7.join(absoluteDist, "index.html");
|
|
1896
|
+
if (!fs7.existsSync(indexPath)) {
|
|
1599
1897
|
ctx.logger?.warn?.(`Studio static: dist not found at ${absoluteDist}`);
|
|
1600
1898
|
return;
|
|
1601
1899
|
}
|
|
1602
|
-
const rawHtml =
|
|
1900
|
+
const rawHtml = fs7.readFileSync(indexPath, "utf-8");
|
|
1603
1901
|
const rewrittenHtml = rawHtml.replace(
|
|
1604
1902
|
/(\s(?:href|src))="\/(?!\/)/g,
|
|
1605
1903
|
`$1="${STUDIO_PATH}/`
|
|
@@ -1610,12 +1908,12 @@ function createStudioStaticPlugin(distPath, options) {
|
|
|
1610
1908
|
app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
1611
1909
|
app.get(`${STUDIO_PATH}/*`, async (c) => {
|
|
1612
1910
|
const reqPath = c.req.path.substring(STUDIO_PATH.length) || "/";
|
|
1613
|
-
const filePath =
|
|
1911
|
+
const filePath = path7.join(absoluteDist, reqPath);
|
|
1614
1912
|
if (!filePath.startsWith(absoluteDist)) {
|
|
1615
1913
|
return c.text("Forbidden", 403);
|
|
1616
1914
|
}
|
|
1617
|
-
if (
|
|
1618
|
-
const content =
|
|
1915
|
+
if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
|
|
1916
|
+
const content = fs7.readFileSync(filePath);
|
|
1619
1917
|
return new Response(content, {
|
|
1620
1918
|
headers: { "content-type": mimeType(filePath) }
|
|
1621
1919
|
});
|
|
@@ -1645,7 +1943,7 @@ var MIME_TYPES = {
|
|
|
1645
1943
|
".map": "application/json"
|
|
1646
1944
|
};
|
|
1647
1945
|
function mimeType(filePath) {
|
|
1648
|
-
const ext =
|
|
1946
|
+
const ext = path7.extname(filePath).toLowerCase();
|
|
1649
1947
|
return MIME_TYPES[ext] || "application/octet-stream";
|
|
1650
1948
|
}
|
|
1651
1949
|
|
|
@@ -1682,15 +1980,15 @@ var serveCommand = new Command9("serve").description("Start ObjectStack server w
|
|
|
1682
1980
|
} catch (e) {
|
|
1683
1981
|
}
|
|
1684
1982
|
const isDev = options.dev || process.env.NODE_ENV === "development";
|
|
1685
|
-
const absolutePath =
|
|
1686
|
-
const relativeConfig =
|
|
1687
|
-
if (!
|
|
1983
|
+
const absolutePath = path8.resolve(process.cwd(), configPath);
|
|
1984
|
+
const relativeConfig = path8.relative(process.cwd(), absolutePath);
|
|
1985
|
+
if (!fs8.existsSync(absolutePath)) {
|
|
1688
1986
|
printError(`Configuration file not found: ${absolutePath}`);
|
|
1689
|
-
console.log(
|
|
1987
|
+
console.log(chalk9.dim(" Hint: Run `objectstack init` to create a new project"));
|
|
1690
1988
|
process.exit(1);
|
|
1691
1989
|
}
|
|
1692
1990
|
console.log("");
|
|
1693
|
-
console.log(
|
|
1991
|
+
console.log(chalk9.dim(` Loading ${relativeConfig}...`));
|
|
1694
1992
|
const loadedPlugins = [];
|
|
1695
1993
|
const shortPluginName = (raw) => {
|
|
1696
1994
|
if (raw.includes("objectql")) return "ObjectQL";
|
|
@@ -1725,7 +2023,7 @@ var serveCommand = new Command9("serve").description("Start ObjectStack server w
|
|
|
1725
2023
|
console.debug = (...args) => {
|
|
1726
2024
|
if (!bootQuiet) originalConsoleDebug(...args);
|
|
1727
2025
|
};
|
|
1728
|
-
const { mod } = await
|
|
2026
|
+
const { mod } = await bundleRequire({
|
|
1729
2027
|
filepath: absolutePath
|
|
1730
2028
|
});
|
|
1731
2029
|
const config = mod.default || mod;
|
|
@@ -1794,7 +2092,7 @@ var serveCommand = new Command9("serve").description("Start ObjectStack server w
|
|
|
1794
2092
|
const pluginName = plugin.name || plugin.constructor?.name || "unnamed";
|
|
1795
2093
|
trackPlugin(pluginName);
|
|
1796
2094
|
} catch (e) {
|
|
1797
|
-
console.error(
|
|
2095
|
+
console.error(chalk9.red(` \u2717 Failed to load plugin: ${e.message}`));
|
|
1798
2096
|
}
|
|
1799
2097
|
}
|
|
1800
2098
|
}
|
|
@@ -1805,7 +2103,7 @@ var serveCommand = new Command9("serve").description("Start ObjectStack server w
|
|
|
1805
2103
|
await kernel.use(serverPlugin);
|
|
1806
2104
|
trackPlugin("HonoServer");
|
|
1807
2105
|
} catch (e) {
|
|
1808
|
-
console.warn(
|
|
2106
|
+
console.warn(chalk9.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
|
|
1809
2107
|
}
|
|
1810
2108
|
try {
|
|
1811
2109
|
const { createRestApiPlugin } = await import("@objectstack/rest");
|
|
@@ -1824,13 +2122,13 @@ var serveCommand = new Command9("serve").description("Start ObjectStack server w
|
|
|
1824
2122
|
if (enableUI) {
|
|
1825
2123
|
const studioPath = resolveStudioPath();
|
|
1826
2124
|
if (!studioPath) {
|
|
1827
|
-
console.warn(
|
|
2125
|
+
console.warn(chalk9.yellow(` \u26A0 @objectstack/studio not found \u2014 skipping UI`));
|
|
1828
2126
|
} else if (hasStudioDist(studioPath)) {
|
|
1829
|
-
const distPath =
|
|
2127
|
+
const distPath = path8.join(studioPath, "dist");
|
|
1830
2128
|
await kernel.use(createStudioStaticPlugin(distPath, { isDev }));
|
|
1831
2129
|
trackPlugin("StudioUI");
|
|
1832
2130
|
} else {
|
|
1833
|
-
console.warn(
|
|
2131
|
+
console.warn(chalk9.yellow(` \u26A0 Studio dist not found \u2014 run "pnpm --filter @objectstack/studio build" first`));
|
|
1834
2132
|
}
|
|
1835
2133
|
}
|
|
1836
2134
|
await runtime.start();
|
|
@@ -1846,33 +2144,33 @@ var serveCommand = new Command9("serve").description("Start ObjectStack server w
|
|
|
1846
2144
|
studioPath: STUDIO_PATH
|
|
1847
2145
|
});
|
|
1848
2146
|
process.on("SIGINT", async () => {
|
|
1849
|
-
console.warn(
|
|
2147
|
+
console.warn(chalk9.yellow(`
|
|
1850
2148
|
|
|
1851
2149
|
\u23F9 Stopping server...`));
|
|
1852
2150
|
await runtime.getKernel().shutdown();
|
|
1853
|
-
console.log(
|
|
2151
|
+
console.log(chalk9.green(`\u2705 Server stopped`));
|
|
1854
2152
|
process.exit(0);
|
|
1855
2153
|
});
|
|
1856
2154
|
} catch (error) {
|
|
1857
2155
|
restoreOutput();
|
|
1858
2156
|
console.log("");
|
|
1859
2157
|
printError(error.message || String(error));
|
|
1860
|
-
if (process.env.DEBUG) console.error(
|
|
2158
|
+
if (process.env.DEBUG) console.error(chalk9.dim(error.stack));
|
|
1861
2159
|
process.exit(1);
|
|
1862
2160
|
}
|
|
1863
2161
|
});
|
|
1864
2162
|
|
|
1865
2163
|
// src/commands/test.ts
|
|
1866
2164
|
import { Command as Command10 } from "commander";
|
|
1867
|
-
import
|
|
1868
|
-
import
|
|
1869
|
-
import
|
|
2165
|
+
import chalk10 from "chalk";
|
|
2166
|
+
import path9 from "path";
|
|
2167
|
+
import fs9 from "fs";
|
|
1870
2168
|
import { QA as CoreQA } from "@objectstack/core";
|
|
1871
2169
|
function resolveGlob(pattern) {
|
|
1872
2170
|
if (!pattern.includes("*")) {
|
|
1873
|
-
return
|
|
2171
|
+
return fs9.existsSync(pattern) ? [pattern] : [];
|
|
1874
2172
|
}
|
|
1875
|
-
const parts = pattern.split(
|
|
2173
|
+
const parts = pattern.split(path9.sep.replace("\\", "/"));
|
|
1876
2174
|
const segments = pattern.includes("/") ? pattern.split("/") : parts;
|
|
1877
2175
|
let baseDir = ".";
|
|
1878
2176
|
let globStart = 0;
|
|
@@ -1881,25 +2179,25 @@ function resolveGlob(pattern) {
|
|
|
1881
2179
|
globStart = i;
|
|
1882
2180
|
break;
|
|
1883
2181
|
}
|
|
1884
|
-
baseDir = i === 0 ? segments[i] :
|
|
2182
|
+
baseDir = i === 0 ? segments[i] : path9.join(baseDir, segments[i]);
|
|
1885
2183
|
}
|
|
1886
|
-
if (!
|
|
2184
|
+
if (!fs9.existsSync(baseDir)) return [];
|
|
1887
2185
|
const globPortion = segments.slice(globStart).join("/");
|
|
1888
2186
|
const regexStr = globPortion.replace(/\./g, "\\.").replace(/\*\*\//g, "(.+/)?").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
1889
2187
|
const regex = new RegExp(`^${regexStr}$`);
|
|
1890
|
-
const entries =
|
|
1891
|
-
return entries.filter((entry) => regex.test(entry.replace(/\\/g, "/"))).map((entry) =>
|
|
2188
|
+
const entries = fs9.readdirSync(baseDir, { recursive: true, encoding: "utf-8" });
|
|
2189
|
+
return entries.filter((entry) => regex.test(entry.replace(/\\/g, "/"))).map((entry) => path9.join(baseDir, entry)).filter((fullPath) => fs9.statSync(fullPath).isFile());
|
|
1892
2190
|
}
|
|
1893
2191
|
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) => {
|
|
1894
|
-
console.log(
|
|
2192
|
+
console.log(chalk10.bold(`
|
|
1895
2193
|
\u{1F9EA} ObjectStack Quality Protocol Runner`));
|
|
1896
|
-
console.log(
|
|
1897
|
-
console.log(`Target: ${
|
|
2194
|
+
console.log(chalk10.dim(`-------------------------------------`));
|
|
2195
|
+
console.log(`Target: ${chalk10.blue(options.url)}`);
|
|
1898
2196
|
const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
|
|
1899
2197
|
const runner = new CoreQA.TestRunner(adapter);
|
|
1900
2198
|
const testFiles = resolveGlob(filesPattern);
|
|
1901
2199
|
if (testFiles.length === 0) {
|
|
1902
|
-
console.warn(
|
|
2200
|
+
console.warn(chalk10.yellow(`No test files found matching: ${filesPattern}`));
|
|
1903
2201
|
return;
|
|
1904
2202
|
}
|
|
1905
2203
|
console.log(`Found ${testFiles.length} test suites.`);
|
|
@@ -1907,19 +2205,19 @@ var testCommand = new Command10("test").description("Run Quality Protocol test s
|
|
|
1907
2205
|
let totalFailed = 0;
|
|
1908
2206
|
for (const file of testFiles) {
|
|
1909
2207
|
console.log(`
|
|
1910
|
-
\u{1F4C4} Running suite: ${
|
|
2208
|
+
\u{1F4C4} Running suite: ${chalk10.bold(path9.basename(file))}`);
|
|
1911
2209
|
try {
|
|
1912
|
-
const content =
|
|
2210
|
+
const content = fs9.readFileSync(file, "utf-8");
|
|
1913
2211
|
const suite = JSON.parse(content);
|
|
1914
2212
|
const results = await runner.runSuite(suite);
|
|
1915
2213
|
for (const result of results) {
|
|
1916
2214
|
const icon = result.passed ? "\u2705" : "\u274C";
|
|
1917
2215
|
console.log(` ${icon} Scenario: ${result.scenarioId} (${result.duration}ms)`);
|
|
1918
2216
|
if (!result.passed) {
|
|
1919
|
-
console.error(
|
|
2217
|
+
console.error(chalk10.red(` Error: ${result.error}`));
|
|
1920
2218
|
result.steps.forEach((step) => {
|
|
1921
2219
|
if (!step.passed) {
|
|
1922
|
-
console.error(
|
|
2220
|
+
console.error(chalk10.red(` Step Failed: ${step.stepName}`));
|
|
1923
2221
|
if (step.output) console.error(` Output:`, step.output);
|
|
1924
2222
|
if (step.error) console.error(` Error:`, step.error);
|
|
1925
2223
|
}
|
|
@@ -1930,28 +2228,258 @@ var testCommand = new Command10("test").description("Run Quality Protocol test s
|
|
|
1930
2228
|
}
|
|
1931
2229
|
}
|
|
1932
2230
|
} catch (e) {
|
|
1933
|
-
console.error(
|
|
2231
|
+
console.error(chalk10.red(`Failed to load or run suite ${file}: ${e}`));
|
|
1934
2232
|
totalFailed++;
|
|
1935
2233
|
}
|
|
1936
2234
|
}
|
|
1937
|
-
console.log(
|
|
2235
|
+
console.log(chalk10.dim(`
|
|
1938
2236
|
-------------------------------------`));
|
|
1939
2237
|
if (totalFailed > 0) {
|
|
1940
|
-
console.log(
|
|
2238
|
+
console.log(chalk10.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
|
|
1941
2239
|
process.exit(1);
|
|
1942
2240
|
} else {
|
|
1943
|
-
console.log(
|
|
2241
|
+
console.log(chalk10.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
|
|
1944
2242
|
process.exit(0);
|
|
1945
2243
|
}
|
|
1946
2244
|
});
|
|
1947
2245
|
|
|
1948
2246
|
// src/commands/doctor.ts
|
|
1949
2247
|
import { Command as Command11 } from "commander";
|
|
1950
|
-
import
|
|
2248
|
+
import chalk11 from "chalk";
|
|
1951
2249
|
import { execSync as execSync2 } from "child_process";
|
|
1952
|
-
import
|
|
1953
|
-
import
|
|
1954
|
-
|
|
2250
|
+
import fs10 from "fs";
|
|
2251
|
+
import path10 from "path";
|
|
2252
|
+
function detectCircularDependencies(objects) {
|
|
2253
|
+
const issues = [];
|
|
2254
|
+
const graph = /* @__PURE__ */ new Map();
|
|
2255
|
+
for (const obj of objects) {
|
|
2256
|
+
const deps = [];
|
|
2257
|
+
if (obj.fields && typeof obj.fields === "object") {
|
|
2258
|
+
for (const field of Object.values(obj.fields)) {
|
|
2259
|
+
if (field?.type === "lookup" && field?.reference) {
|
|
2260
|
+
deps.push(field.reference);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
graph.set(obj.name, deps);
|
|
2265
|
+
}
|
|
2266
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2267
|
+
const stack = /* @__PURE__ */ new Set();
|
|
2268
|
+
function dfs(node, path11) {
|
|
2269
|
+
if (stack.has(node)) {
|
|
2270
|
+
const cycleStart = path11.indexOf(node);
|
|
2271
|
+
const cycle = path11.slice(cycleStart).concat(node);
|
|
2272
|
+
issues.push(`Circular dependency: ${cycle.join(" \u2192 ")}`);
|
|
2273
|
+
return true;
|
|
2274
|
+
}
|
|
2275
|
+
if (visited.has(node)) return false;
|
|
2276
|
+
visited.add(node);
|
|
2277
|
+
stack.add(node);
|
|
2278
|
+
for (const dep of graph.get(node) || []) {
|
|
2279
|
+
if (graph.has(dep)) {
|
|
2280
|
+
dfs(dep, [...path11, node]);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
stack.delete(node);
|
|
2284
|
+
return false;
|
|
2285
|
+
}
|
|
2286
|
+
for (const name of graph.keys()) {
|
|
2287
|
+
if (!visited.has(name)) {
|
|
2288
|
+
dfs(name, []);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
return issues;
|
|
2292
|
+
}
|
|
2293
|
+
function findOrphanViews(config) {
|
|
2294
|
+
const objectNames = /* @__PURE__ */ new Set();
|
|
2295
|
+
if (Array.isArray(config.objects)) {
|
|
2296
|
+
for (const obj of config.objects) {
|
|
2297
|
+
if (obj.name) objectNames.add(obj.name);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
const orphans = [];
|
|
2301
|
+
if (Array.isArray(config.views)) {
|
|
2302
|
+
for (const view of config.views) {
|
|
2303
|
+
if (view.object && !objectNames.has(view.object)) {
|
|
2304
|
+
orphans.push(`View "${view.name || "?"}" references non-existent object "${view.object}"`);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
return orphans;
|
|
2309
|
+
}
|
|
2310
|
+
function findUnusedObjects(config) {
|
|
2311
|
+
const objectNames = /* @__PURE__ */ new Set();
|
|
2312
|
+
if (Array.isArray(config.objects)) {
|
|
2313
|
+
for (const obj of config.objects) {
|
|
2314
|
+
if (obj.name) objectNames.add(obj.name);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
const referencedObjects = /* @__PURE__ */ new Set();
|
|
2318
|
+
if (Array.isArray(config.views)) {
|
|
2319
|
+
for (const view of config.views) {
|
|
2320
|
+
if (view.object) referencedObjects.add(view.object);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
if (Array.isArray(config.flows)) {
|
|
2324
|
+
for (const flow of config.flows) {
|
|
2325
|
+
if (flow.trigger?.object) referencedObjects.add(flow.trigger.object);
|
|
2326
|
+
if (flow.object) referencedObjects.add(flow.object);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
if (Array.isArray(config.apps)) {
|
|
2330
|
+
for (const app of config.apps) {
|
|
2331
|
+
if (Array.isArray(app.navigation)) {
|
|
2332
|
+
for (const nav of app.navigation) {
|
|
2333
|
+
if (nav.object) referencedObjects.add(nav.object);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
if (Array.isArray(config.agents)) {
|
|
2339
|
+
for (const agent of config.agents) {
|
|
2340
|
+
if (Array.isArray(agent.objects)) {
|
|
2341
|
+
for (const o of agent.objects) referencedObjects.add(o);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
if (Array.isArray(config.objects)) {
|
|
2346
|
+
for (const obj of config.objects) {
|
|
2347
|
+
if (obj.fields && typeof obj.fields === "object") {
|
|
2348
|
+
for (const field of Object.values(obj.fields)) {
|
|
2349
|
+
if (field?.type === "lookup" && field?.reference) {
|
|
2350
|
+
referencedObjects.add(field.reference);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
const unused = [];
|
|
2357
|
+
for (const name of objectNames) {
|
|
2358
|
+
if (!referencedObjects.has(name)) {
|
|
2359
|
+
unused.push(`Object "${name}" is defined but not referenced by any view, flow, app, or agent`);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return unused;
|
|
2363
|
+
}
|
|
2364
|
+
function walkDir(dir, ext) {
|
|
2365
|
+
const results = [];
|
|
2366
|
+
if (!fs10.existsSync(dir)) return results;
|
|
2367
|
+
const entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
2368
|
+
for (const entry of entries) {
|
|
2369
|
+
if (entry.name === "node_modules") continue;
|
|
2370
|
+
const fullPath = path10.join(dir, entry.name);
|
|
2371
|
+
if (entry.isDirectory()) {
|
|
2372
|
+
results.push(...walkDir(fullPath, ext));
|
|
2373
|
+
} else if (entry.name.endsWith(ext)) {
|
|
2374
|
+
results.push(fullPath);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
return results;
|
|
2378
|
+
}
|
|
2379
|
+
function findMissingTests(cwd) {
|
|
2380
|
+
const specSrcDir = path10.join(cwd, "packages/spec/src");
|
|
2381
|
+
if (!fs10.existsSync(specSrcDir)) return [];
|
|
2382
|
+
const missing = [];
|
|
2383
|
+
const zodFiles = walkDir(specSrcDir, ".zod.ts");
|
|
2384
|
+
for (const zodFile of zodFiles) {
|
|
2385
|
+
const testFile = zodFile.replace(".zod.ts", ".test.ts");
|
|
2386
|
+
if (!fs10.existsSync(testFile)) {
|
|
2387
|
+
const relZod = path10.relative(specSrcDir, zodFile);
|
|
2388
|
+
const relTest = path10.relative(specSrcDir, testFile);
|
|
2389
|
+
missing.push(`Missing test: ${relTest} (for ${relZod})`);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
return missing;
|
|
2393
|
+
}
|
|
2394
|
+
function findDeprecatedUsages(cwd) {
|
|
2395
|
+
const specSrcDir = path10.join(cwd, "packages/spec/src");
|
|
2396
|
+
if (!fs10.existsSync(specSrcDir)) return [];
|
|
2397
|
+
const deprecated = [];
|
|
2398
|
+
const tsFiles = walkDir(specSrcDir, ".ts").filter((f) => !f.endsWith(".test.ts"));
|
|
2399
|
+
for (const tsFile of tsFiles) {
|
|
2400
|
+
try {
|
|
2401
|
+
const content = fs10.readFileSync(tsFile, "utf-8");
|
|
2402
|
+
const lines = content.split("\n");
|
|
2403
|
+
const relPath = path10.relative(specSrcDir, tsFile);
|
|
2404
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2405
|
+
if (lines[i].includes("@deprecated")) {
|
|
2406
|
+
deprecated.push(`${relPath}:${i + 1} \u2014 @deprecated tag found`);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
} catch {
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
return deprecated;
|
|
2413
|
+
}
|
|
2414
|
+
var DEPRECATED_PATTERNS = [
|
|
2415
|
+
{
|
|
2416
|
+
pattern: /\bEnhancedObjectKernel\b/,
|
|
2417
|
+
description: "EnhancedObjectKernel is deprecated in v3",
|
|
2418
|
+
replacement: "Use ObjectKernel instead"
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
pattern: /\bmax_length\b/,
|
|
2422
|
+
description: "snake_case config key: max_length",
|
|
2423
|
+
replacement: "Use maxLength (camelCase)"
|
|
2424
|
+
},
|
|
2425
|
+
{
|
|
2426
|
+
pattern: /\bdefault_value\b/,
|
|
2427
|
+
description: "snake_case config key: default_value",
|
|
2428
|
+
replacement: "Use defaultValue (camelCase)"
|
|
2429
|
+
},
|
|
2430
|
+
{
|
|
2431
|
+
pattern: /\bmin_length\b/,
|
|
2432
|
+
description: "snake_case config key: min_length",
|
|
2433
|
+
replacement: "Use minLength (camelCase)"
|
|
2434
|
+
},
|
|
2435
|
+
{
|
|
2436
|
+
pattern: /\breference_filters\b/,
|
|
2437
|
+
description: "snake_case config key: reference_filters",
|
|
2438
|
+
replacement: "Use referenceFilters (camelCase)"
|
|
2439
|
+
},
|
|
2440
|
+
{
|
|
2441
|
+
pattern: /\bunique_name\b/,
|
|
2442
|
+
description: "snake_case config key: unique_name",
|
|
2443
|
+
replacement: "Use uniqueName (camelCase)"
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
pattern: /from\s+['"]@objectstack\/core\/enhanced['"]/,
|
|
2447
|
+
description: "Import from deprecated @objectstack/core/enhanced path",
|
|
2448
|
+
replacement: "Use import from '@objectstack/core'"
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
pattern: /from\s+['"]@objectstack\/spec\/dist\/[^'"]+['"]/,
|
|
2452
|
+
description: "Import from deprecated @objectstack/spec/dist/ deep path",
|
|
2453
|
+
replacement: "Use import from '@objectstack/spec'"
|
|
2454
|
+
}
|
|
2455
|
+
];
|
|
2456
|
+
function scanDeprecatedPatterns(dir) {
|
|
2457
|
+
const results = [];
|
|
2458
|
+
if (!fs10.existsSync(dir)) return results;
|
|
2459
|
+
const tsFiles = walkDir(dir, ".ts").filter((f) => !f.endsWith(".test.ts"));
|
|
2460
|
+
for (const tsFile of tsFiles) {
|
|
2461
|
+
try {
|
|
2462
|
+
const content = fs10.readFileSync(tsFile, "utf-8");
|
|
2463
|
+
const lines = content.split("\n");
|
|
2464
|
+
const relPath = path10.relative(process.cwd(), tsFile);
|
|
2465
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2466
|
+
for (const dp of DEPRECATED_PATTERNS) {
|
|
2467
|
+
if (dp.pattern.test(lines[i])) {
|
|
2468
|
+
results.push({
|
|
2469
|
+
file: relPath,
|
|
2470
|
+
line: i + 1,
|
|
2471
|
+
description: dp.description,
|
|
2472
|
+
replacement: dp.replacement
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
} catch {
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
return results;
|
|
2481
|
+
}
|
|
2482
|
+
var doctorCommand = new Command11("doctor").description("Check development environment and configuration health").option("-v, --verbose", "Show detailed information").option("--scan-deprecations", "Scan for deprecated ObjectStack patterns").action(async (options) => {
|
|
1955
2483
|
printHeader("Environment Health Check");
|
|
1956
2484
|
const results = [];
|
|
1957
2485
|
try {
|
|
@@ -2010,8 +2538,8 @@ var doctorCommand = new Command11("doctor").description("Check development envir
|
|
|
2010
2538
|
});
|
|
2011
2539
|
}
|
|
2012
2540
|
const cwd = process.cwd();
|
|
2013
|
-
const nodeModulesPath =
|
|
2014
|
-
if (
|
|
2541
|
+
const nodeModulesPath = path10.join(cwd, "node_modules");
|
|
2542
|
+
if (fs10.existsSync(nodeModulesPath)) {
|
|
2015
2543
|
results.push({
|
|
2016
2544
|
name: "Dependencies",
|
|
2017
2545
|
status: "ok",
|
|
@@ -2025,8 +2553,8 @@ var doctorCommand = new Command11("doctor").description("Check development envir
|
|
|
2025
2553
|
fix: "Run: pnpm install"
|
|
2026
2554
|
});
|
|
2027
2555
|
}
|
|
2028
|
-
const specDistPath =
|
|
2029
|
-
if (
|
|
2556
|
+
const specDistPath = path10.join(cwd, "packages/spec/dist");
|
|
2557
|
+
if (fs10.existsSync(specDistPath)) {
|
|
2030
2558
|
results.push({
|
|
2031
2559
|
name: "@objectstack/spec",
|
|
2032
2560
|
status: "ok",
|
|
@@ -2068,27 +2596,108 @@ var doctorCommand = new Command11("doctor").description("Check development envir
|
|
|
2068
2596
|
printError(`${padded} ${result.message}`);
|
|
2069
2597
|
}
|
|
2070
2598
|
if (result.fix && (options.verbose || result.status === "error")) {
|
|
2071
|
-
console.log(
|
|
2599
|
+
console.log(chalk11.dim(` \u2192 ${result.fix}`));
|
|
2072
2600
|
}
|
|
2073
2601
|
if (result.status === "error") hasErrors = true;
|
|
2074
2602
|
if (result.status === "warning") hasWarnings = true;
|
|
2075
2603
|
});
|
|
2604
|
+
printStep("Checking for missing test files...");
|
|
2605
|
+
const missingTests = findMissingTests(cwd);
|
|
2606
|
+
if (missingTests.length > 0) {
|
|
2607
|
+
hasWarnings = true;
|
|
2608
|
+
for (const msg of missingTests) {
|
|
2609
|
+
printWarning(msg);
|
|
2610
|
+
}
|
|
2611
|
+
} else {
|
|
2612
|
+
printSuccess("Test coverage All *.zod.ts files have matching tests");
|
|
2613
|
+
}
|
|
2614
|
+
printStep("Scanning for @deprecated usage...");
|
|
2615
|
+
const deprecatedUsages = findDeprecatedUsages(cwd);
|
|
2616
|
+
if (deprecatedUsages.length > 0) {
|
|
2617
|
+
hasWarnings = true;
|
|
2618
|
+
for (const msg of deprecatedUsages) {
|
|
2619
|
+
printWarning(`Deprecated: ${msg}`);
|
|
2620
|
+
}
|
|
2621
|
+
} else {
|
|
2622
|
+
printSuccess("Deprecations No @deprecated tags found");
|
|
2623
|
+
}
|
|
2624
|
+
if (configExists()) {
|
|
2625
|
+
printStep("Loading configuration for analysis...");
|
|
2626
|
+
try {
|
|
2627
|
+
const { config } = await loadConfig();
|
|
2628
|
+
if (Array.isArray(config.objects) && config.objects.length > 0) {
|
|
2629
|
+
printStep("Checking for circular dependencies...");
|
|
2630
|
+
const cycles = detectCircularDependencies(config.objects);
|
|
2631
|
+
if (cycles.length > 0) {
|
|
2632
|
+
hasWarnings = true;
|
|
2633
|
+
for (const msg of cycles) {
|
|
2634
|
+
printWarning(msg);
|
|
2635
|
+
}
|
|
2636
|
+
} else {
|
|
2637
|
+
printSuccess("Dependencies No circular references detected");
|
|
2638
|
+
}
|
|
2639
|
+
printStep("Checking for unused objects...");
|
|
2640
|
+
const unused = findUnusedObjects(config);
|
|
2641
|
+
if (unused.length > 0) {
|
|
2642
|
+
hasWarnings = true;
|
|
2643
|
+
for (const msg of unused) {
|
|
2644
|
+
printWarning(msg);
|
|
2645
|
+
}
|
|
2646
|
+
} else {
|
|
2647
|
+
printSuccess("Object usage All objects are referenced");
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
if (Array.isArray(config.views) && config.views.length > 0) {
|
|
2651
|
+
printStep("Checking for orphan views...");
|
|
2652
|
+
const orphans = findOrphanViews(config);
|
|
2653
|
+
if (orphans.length > 0) {
|
|
2654
|
+
hasWarnings = true;
|
|
2655
|
+
for (const msg of orphans) {
|
|
2656
|
+
printWarning(msg);
|
|
2657
|
+
}
|
|
2658
|
+
} else {
|
|
2659
|
+
printSuccess("View integrity All views reference valid objects");
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
} catch {
|
|
2663
|
+
printWarning("Could not load config for analysis (config checks skipped)");
|
|
2664
|
+
hasWarnings = true;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
if (options.scanDeprecations) {
|
|
2668
|
+
printStep("Scanning for deprecated ObjectStack patterns...");
|
|
2669
|
+
const scanDir = path10.join(cwd, "src");
|
|
2670
|
+
const deprecations = scanDeprecatedPatterns(scanDir);
|
|
2671
|
+
if (deprecations.length > 0) {
|
|
2672
|
+
hasWarnings = true;
|
|
2673
|
+
for (const dep of deprecations) {
|
|
2674
|
+
printWarning(`${dep.file}:${dep.line} \u2014 ${dep.description}`);
|
|
2675
|
+
if (options.verbose) {
|
|
2676
|
+
console.log(chalk11.dim(` \u2192 ${dep.replacement}`));
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
console.log("");
|
|
2680
|
+
printInfo(`Found ${deprecations.length} deprecated pattern(s). Run \`objectstack codemod v2-to-v3\` to auto-fix.`);
|
|
2681
|
+
} else {
|
|
2682
|
+
printSuccess("Deprecation scan No deprecated patterns found");
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2076
2685
|
console.log("");
|
|
2077
2686
|
if (hasErrors) {
|
|
2078
|
-
console.log(
|
|
2079
|
-
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(
|
|
2687
|
+
console.log(chalk11.red("\u274C Some critical issues found. Please fix them before continuing."));
|
|
2688
|
+
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(chalk11.dim(` ${r.fix}`)));
|
|
2080
2689
|
process.exit(1);
|
|
2081
2690
|
} else if (hasWarnings) {
|
|
2082
|
-
console.log(
|
|
2083
|
-
console.log(
|
|
2691
|
+
console.log(chalk11.yellow("\u26A0\uFE0F Environment is functional but has some warnings."));
|
|
2692
|
+
console.log(chalk11.dim(" Run with --verbose to see fix suggestions."));
|
|
2084
2693
|
} else {
|
|
2085
|
-
console.log(
|
|
2694
|
+
console.log(chalk11.green("\u2705 Environment is healthy and ready for development!"));
|
|
2086
2695
|
}
|
|
2087
2696
|
console.log("");
|
|
2088
2697
|
});
|
|
2089
2698
|
|
|
2090
2699
|
// src/utils/plugin-commands.ts
|
|
2091
|
-
import
|
|
2700
|
+
import chalk12 from "chalk";
|
|
2092
2701
|
async function loadPluginCommands(program) {
|
|
2093
2702
|
let config;
|
|
2094
2703
|
try {
|
|
@@ -2132,7 +2741,7 @@ async function loadPluginCommands(program) {
|
|
|
2132
2741
|
if (process.env.DEBUG) {
|
|
2133
2742
|
const message = error instanceof Error ? error.message : String(error);
|
|
2134
2743
|
console.error(
|
|
2135
|
-
|
|
2744
|
+
chalk12.yellow(` \u26A0 Failed to load CLI command '${contribution.name}' from plugin '${contribution.pluginName}': ${message}`)
|
|
2136
2745
|
);
|
|
2137
2746
|
}
|
|
2138
2747
|
}
|