@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/bin.js
CHANGED
|
@@ -1,244 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
collectMetadataStats,
|
|
4
|
+
configExists,
|
|
5
|
+
createTimer,
|
|
6
|
+
formatZodErrors,
|
|
7
|
+
loadConfig,
|
|
8
|
+
printError,
|
|
9
|
+
printHeader,
|
|
10
|
+
printInfo,
|
|
11
|
+
printKV,
|
|
12
|
+
printMetadataStats,
|
|
13
|
+
printServerReady,
|
|
14
|
+
printStep,
|
|
15
|
+
printSuccess,
|
|
16
|
+
printWarning,
|
|
17
|
+
resolveConfigPath
|
|
18
|
+
} from "./chunk-Q74JNWKD.js";
|
|
2
19
|
|
|
3
20
|
// src/bin.ts
|
|
4
21
|
import { createRequire as createRequire2 } from "module";
|
|
5
|
-
import { Command as
|
|
6
|
-
import
|
|
22
|
+
import { Command as Command17 } from "commander";
|
|
23
|
+
import chalk17 from "chalk";
|
|
7
24
|
|
|
8
25
|
// src/commands/compile.ts
|
|
9
26
|
import { Command } from "commander";
|
|
10
|
-
import path2 from "path";
|
|
11
|
-
import fs2 from "fs";
|
|
12
|
-
import chalk3 from "chalk";
|
|
13
|
-
import { ObjectStackDefinitionSchema } from "@objectstack/spec";
|
|
14
|
-
|
|
15
|
-
// src/utils/config.ts
|
|
16
27
|
import path from "path";
|
|
17
28
|
import fs from "fs";
|
|
18
|
-
import chalk2 from "chalk";
|
|
19
|
-
import { bundleRequire } from "bundle-require";
|
|
20
|
-
|
|
21
|
-
// src/utils/format.ts
|
|
22
29
|
import chalk from "chalk";
|
|
23
|
-
|
|
24
|
-
console.log(chalk.bold(`
|
|
25
|
-
\u25C6 ${title}`));
|
|
26
|
-
console.log(chalk.dim("\u2500".repeat(40)));
|
|
27
|
-
}
|
|
28
|
-
function printKV(key, value, icon) {
|
|
29
|
-
const prefix = icon ? `${icon} ` : " ";
|
|
30
|
-
console.log(`${prefix}${chalk.dim(key + ":")} ${chalk.white(String(value))}`);
|
|
31
|
-
}
|
|
32
|
-
function printSuccess(msg) {
|
|
33
|
-
console.log(chalk.green(` \u2713 ${msg}`));
|
|
34
|
-
}
|
|
35
|
-
function printWarning(msg) {
|
|
36
|
-
console.log(chalk.yellow(` \u26A0 ${msg}`));
|
|
37
|
-
}
|
|
38
|
-
function printError(msg) {
|
|
39
|
-
console.log(chalk.red(` \u2717 ${msg}`));
|
|
40
|
-
}
|
|
41
|
-
function printInfo(msg) {
|
|
42
|
-
console.log(chalk.blue(` \u2139 ${msg}`));
|
|
43
|
-
}
|
|
44
|
-
function printStep(msg) {
|
|
45
|
-
console.log(chalk.yellow(` \u2192 ${msg}`));
|
|
46
|
-
}
|
|
47
|
-
function createTimer() {
|
|
48
|
-
const start = Date.now();
|
|
49
|
-
return {
|
|
50
|
-
elapsed: () => Date.now() - start,
|
|
51
|
-
display: () => `${Date.now() - start}ms`
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function formatZodErrors(error) {
|
|
55
|
-
const issues = error.issues || error.errors || [];
|
|
56
|
-
if (issues.length === 0) {
|
|
57
|
-
console.log(chalk.red(" Unknown validation error"));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
61
|
-
for (const issue of issues) {
|
|
62
|
-
const topPath = issue.path?.[0] || "_root";
|
|
63
|
-
if (!grouped.has(String(topPath))) {
|
|
64
|
-
grouped.set(String(topPath), []);
|
|
65
|
-
}
|
|
66
|
-
grouped.get(String(topPath)).push(issue);
|
|
67
|
-
}
|
|
68
|
-
for (const [section, sectionIssues] of grouped) {
|
|
69
|
-
console.log(chalk.bold.red(`
|
|
70
|
-
${section}:`));
|
|
71
|
-
for (const issue of sectionIssues) {
|
|
72
|
-
const path12 = issue.path?.join(".") || "";
|
|
73
|
-
const code = issue.code || "";
|
|
74
|
-
const msg = issue.message || "";
|
|
75
|
-
console.log(chalk.red(` \u2717 ${path12}`));
|
|
76
|
-
console.log(chalk.dim(` ${code}: ${msg}`));
|
|
77
|
-
if (issue.expected) {
|
|
78
|
-
console.log(chalk.dim(` expected: ${chalk.green(issue.expected)}`));
|
|
79
|
-
}
|
|
80
|
-
if (issue.received) {
|
|
81
|
-
console.log(chalk.dim(` received: ${chalk.red(issue.received)}`));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
console.log("");
|
|
86
|
-
console.log(chalk.dim(` ${issues.length} validation error(s) total`));
|
|
87
|
-
}
|
|
88
|
-
function collectMetadataStats(config) {
|
|
89
|
-
const count = (arr) => Array.isArray(arr) ? arr.length : 0;
|
|
90
|
-
let fields = 0;
|
|
91
|
-
if (Array.isArray(config.objects)) {
|
|
92
|
-
for (const obj of config.objects) {
|
|
93
|
-
if (obj.fields && typeof obj.fields === "object") {
|
|
94
|
-
fields += Object.keys(obj.fields).length;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
objects: count(config.objects),
|
|
100
|
-
objectExtensions: count(config.objectExtensions),
|
|
101
|
-
fields,
|
|
102
|
-
views: count(config.views),
|
|
103
|
-
pages: count(config.pages),
|
|
104
|
-
apps: count(config.apps),
|
|
105
|
-
dashboards: count(config.dashboards),
|
|
106
|
-
reports: count(config.reports),
|
|
107
|
-
actions: count(config.actions),
|
|
108
|
-
flows: count(config.flows),
|
|
109
|
-
workflows: count(config.workflows),
|
|
110
|
-
approvals: count(config.approvals),
|
|
111
|
-
agents: count(config.agents),
|
|
112
|
-
apis: count(config.apis),
|
|
113
|
-
roles: count(config.roles),
|
|
114
|
-
permissions: count(config.permissions),
|
|
115
|
-
themes: count(config.themes),
|
|
116
|
-
datasources: count(config.datasources),
|
|
117
|
-
translations: count(config.translations),
|
|
118
|
-
plugins: count(config.plugins),
|
|
119
|
-
devPlugins: count(config.devPlugins)
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
function printServerReady(opts) {
|
|
123
|
-
const base = `http://localhost:${opts.port}`;
|
|
124
|
-
console.log("");
|
|
125
|
-
console.log(chalk.bold.green(" \u2713 Server is ready"));
|
|
126
|
-
console.log("");
|
|
127
|
-
console.log(chalk.cyan(" \u279C") + chalk.bold(" API: ") + chalk.cyan(base + "/"));
|
|
128
|
-
if (opts.uiEnabled && opts.studioPath) {
|
|
129
|
-
console.log(chalk.cyan(" \u279C") + chalk.bold(" Studio: ") + chalk.cyan(base + opts.studioPath + "/"));
|
|
130
|
-
}
|
|
131
|
-
console.log("");
|
|
132
|
-
console.log(chalk.dim(` Config: ${opts.configFile}`));
|
|
133
|
-
console.log(chalk.dim(` Mode: ${opts.isDev ? "development" : "production"}`));
|
|
134
|
-
console.log(chalk.dim(` Plugins: ${opts.pluginCount} loaded`));
|
|
135
|
-
if (opts.pluginNames && opts.pluginNames.length > 0) {
|
|
136
|
-
console.log(chalk.dim(` ${opts.pluginNames.join(", ")}`));
|
|
137
|
-
}
|
|
138
|
-
console.log("");
|
|
139
|
-
console.log(chalk.dim(" Press Ctrl+C to stop"));
|
|
140
|
-
console.log("");
|
|
141
|
-
}
|
|
142
|
-
function printMetadataStats(stats) {
|
|
143
|
-
const sections = [
|
|
144
|
-
{
|
|
145
|
-
label: "Data",
|
|
146
|
-
items: [
|
|
147
|
-
["Objects", stats.objects],
|
|
148
|
-
["Fields", stats.fields],
|
|
149
|
-
["Extensions", stats.objectExtensions],
|
|
150
|
-
["Datasources", stats.datasources]
|
|
151
|
-
]
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
label: "UI",
|
|
155
|
-
items: [
|
|
156
|
-
["Apps", stats.apps],
|
|
157
|
-
["Views", stats.views],
|
|
158
|
-
["Pages", stats.pages],
|
|
159
|
-
["Dashboards", stats.dashboards],
|
|
160
|
-
["Reports", stats.reports],
|
|
161
|
-
["Actions", stats.actions],
|
|
162
|
-
["Themes", stats.themes]
|
|
163
|
-
]
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
label: "Logic",
|
|
167
|
-
items: [
|
|
168
|
-
["Flows", stats.flows],
|
|
169
|
-
["Workflows", stats.workflows],
|
|
170
|
-
["Approvals", stats.approvals],
|
|
171
|
-
["Agents", stats.agents],
|
|
172
|
-
["APIs", stats.apis]
|
|
173
|
-
]
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
label: "Security",
|
|
177
|
-
items: [
|
|
178
|
-
["Roles", stats.roles],
|
|
179
|
-
["Permissions", stats.permissions]
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
];
|
|
183
|
-
for (const section of sections) {
|
|
184
|
-
const nonZero = section.items.filter(([, v]) => v > 0);
|
|
185
|
-
if (nonZero.length === 0) continue;
|
|
186
|
-
const line = nonZero.map(([k, v]) => `${chalk.white(v)} ${chalk.dim(k)}`).join(" ");
|
|
187
|
-
console.log(` ${chalk.bold(section.label + ":")} ${line}`);
|
|
188
|
-
}
|
|
189
|
-
if (stats.plugins > 0 || stats.devPlugins > 0) {
|
|
190
|
-
const parts = [];
|
|
191
|
-
if (stats.plugins > 0) parts.push(`${stats.plugins} plugins`);
|
|
192
|
-
if (stats.devPlugins > 0) parts.push(`${stats.devPlugins} devPlugins`);
|
|
193
|
-
console.log(` ${chalk.bold("Runtime:")} ${chalk.dim(parts.join(", "))}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// src/utils/config.ts
|
|
198
|
-
function resolveConfigPath(source) {
|
|
199
|
-
if (source) {
|
|
200
|
-
const abs = path.resolve(process.cwd(), source);
|
|
201
|
-
if (!fs.existsSync(abs)) {
|
|
202
|
-
printError(`Config file not found: ${chalk2.white(abs)}`);
|
|
203
|
-
console.log("");
|
|
204
|
-
console.log(chalk2.dim(" Hint: Run this command from a directory with objectstack.config.ts"));
|
|
205
|
-
console.log(chalk2.dim(" Or specify the path: objectstack <command> path/to/config.ts"));
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
return abs;
|
|
209
|
-
}
|
|
210
|
-
const candidates = [
|
|
211
|
-
"objectstack.config.ts",
|
|
212
|
-
"objectstack.config.js",
|
|
213
|
-
"objectstack.config.mjs"
|
|
214
|
-
];
|
|
215
|
-
for (const candidate of candidates) {
|
|
216
|
-
const abs = path.resolve(process.cwd(), candidate);
|
|
217
|
-
if (fs.existsSync(abs)) return abs;
|
|
218
|
-
}
|
|
219
|
-
printError("No objectstack.config.{ts,js,mjs} found in current directory");
|
|
220
|
-
console.log("");
|
|
221
|
-
console.log(chalk2.dim(" Hint: Run `objectstack init` to create a new project"));
|
|
222
|
-
process.exit(1);
|
|
223
|
-
}
|
|
224
|
-
async function loadConfig(source) {
|
|
225
|
-
const absolutePath = resolveConfigPath(source);
|
|
226
|
-
const start = Date.now();
|
|
227
|
-
const { mod } = await bundleRequire({
|
|
228
|
-
filepath: absolutePath
|
|
229
|
-
});
|
|
230
|
-
const config = mod.default || mod;
|
|
231
|
-
if (!config) {
|
|
232
|
-
throw new Error(`No default export found in ${path.basename(absolutePath)}`);
|
|
233
|
-
}
|
|
234
|
-
return {
|
|
235
|
-
config,
|
|
236
|
-
absolutePath,
|
|
237
|
-
duration: Date.now() - start
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// src/commands/compile.ts
|
|
30
|
+
import { ObjectStackDefinitionSchema } from "@objectstack/spec";
|
|
242
31
|
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) => {
|
|
243
32
|
const timer = createTimer();
|
|
244
33
|
if (!options.json) {
|
|
@@ -248,7 +37,7 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
248
37
|
if (!options.json) printStep("Loading configuration...");
|
|
249
38
|
const { config, absolutePath, duration } = await loadConfig(configPath);
|
|
250
39
|
if (!options.json) {
|
|
251
|
-
printKV("Config",
|
|
40
|
+
printKV("Config", path.relative(process.cwd(), absolutePath));
|
|
252
41
|
printKV("Load time", `${duration}ms`);
|
|
253
42
|
}
|
|
254
43
|
if (!options.json) printStep("Validating protocol compliance...");
|
|
@@ -265,13 +54,13 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
265
54
|
}
|
|
266
55
|
if (!options.json) printStep("Writing artifact...");
|
|
267
56
|
const output = options.output;
|
|
268
|
-
const artifactPath =
|
|
269
|
-
const artifactDir =
|
|
270
|
-
if (!
|
|
271
|
-
|
|
57
|
+
const artifactPath = path.resolve(process.cwd(), output);
|
|
58
|
+
const artifactDir = path.dirname(artifactPath);
|
|
59
|
+
if (!fs.existsSync(artifactDir)) {
|
|
60
|
+
fs.mkdirSync(artifactDir, { recursive: true });
|
|
272
61
|
}
|
|
273
62
|
const jsonContent = JSON.stringify(result.data, null, 2);
|
|
274
|
-
|
|
63
|
+
fs.writeFileSync(artifactPath, jsonContent);
|
|
275
64
|
const sizeKB = (jsonContent.length / 1024).toFixed(1);
|
|
276
65
|
const stats = collectMetadataStats(config);
|
|
277
66
|
if (options.json) {
|
|
@@ -285,11 +74,11 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
285
74
|
return;
|
|
286
75
|
}
|
|
287
76
|
console.log("");
|
|
288
|
-
printSuccess(`Build complete ${
|
|
77
|
+
printSuccess(`Build complete ${chalk.dim(`(${timer.display()})`)}`);
|
|
289
78
|
console.log("");
|
|
290
79
|
printMetadataStats(stats);
|
|
291
80
|
console.log("");
|
|
292
|
-
printKV("Artifact", `${output} ${
|
|
81
|
+
printKV("Artifact", `${output} ${chalk.dim(`(${sizeKB} KB`)})`);
|
|
293
82
|
console.log("");
|
|
294
83
|
} catch (error) {
|
|
295
84
|
if (options.json) {
|
|
@@ -304,14 +93,14 @@ var compileCommand = new Command("compile").description("Compile ObjectStack con
|
|
|
304
93
|
|
|
305
94
|
// src/commands/dev.ts
|
|
306
95
|
import { Command as Command2 } from "commander";
|
|
307
|
-
import
|
|
96
|
+
import chalk2 from "chalk";
|
|
308
97
|
import { execSync, spawn } from "child_process";
|
|
309
|
-
import
|
|
310
|
-
import
|
|
98
|
+
import fs2 from "fs";
|
|
99
|
+
import path2 from "path";
|
|
311
100
|
var devCommand = new Command2("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) => {
|
|
312
101
|
printHeader("Development Mode");
|
|
313
|
-
const configPath =
|
|
314
|
-
if (packageName === "all" &&
|
|
102
|
+
const configPath = path2.resolve(process.cwd(), "objectstack.config.ts");
|
|
103
|
+
if (packageName === "all" && fs2.existsSync(configPath)) {
|
|
315
104
|
printKV("Config", configPath, "\u{1F4C2}");
|
|
316
105
|
printStep("Starting dev server...");
|
|
317
106
|
const binPath = process.argv[1];
|
|
@@ -323,18 +112,18 @@ var devCommand = new Command2("dev").description("Start development mode with ho
|
|
|
323
112
|
}
|
|
324
113
|
try {
|
|
325
114
|
const cwd = process.cwd();
|
|
326
|
-
const workspaceConfigPath =
|
|
327
|
-
const isWorkspaceRoot =
|
|
115
|
+
const workspaceConfigPath = path2.resolve(cwd, "pnpm-workspace.yaml");
|
|
116
|
+
const isWorkspaceRoot = fs2.existsSync(workspaceConfigPath);
|
|
328
117
|
if (packageName === "all" && !isWorkspaceRoot) {
|
|
329
118
|
printError(`Config file not found in ${cwd}`);
|
|
330
|
-
console.error(
|
|
119
|
+
console.error(chalk2.yellow(" Run in a directory with objectstack.config.ts, or from the monorepo root."));
|
|
331
120
|
process.exit(1);
|
|
332
121
|
}
|
|
333
122
|
const filter = packageName === "all" ? "" : `--filter ${packageName}`;
|
|
334
123
|
printKV("Package", packageName === "all" ? "All packages" : packageName, "\u{1F4E6}");
|
|
335
124
|
printKV("Watch", "enabled", "\u{1F504}");
|
|
336
125
|
const command = `pnpm ${filter} dev`.trim();
|
|
337
|
-
console.log(
|
|
126
|
+
console.log(chalk2.dim(`$ ${command}`));
|
|
338
127
|
console.log("");
|
|
339
128
|
execSync(command, {
|
|
340
129
|
stdio: "inherit",
|
|
@@ -348,11 +137,241 @@ var devCommand = new Command2("dev").description("Start development mode with ho
|
|
|
348
137
|
|
|
349
138
|
// src/commands/doctor.ts
|
|
350
139
|
import { Command as Command3 } from "commander";
|
|
351
|
-
import
|
|
140
|
+
import chalk3 from "chalk";
|
|
352
141
|
import { execSync as execSync2 } from "child_process";
|
|
353
|
-
import
|
|
354
|
-
import
|
|
355
|
-
|
|
142
|
+
import fs3 from "fs";
|
|
143
|
+
import path3 from "path";
|
|
144
|
+
function detectCircularDependencies(objects) {
|
|
145
|
+
const issues = [];
|
|
146
|
+
const graph = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const obj of objects) {
|
|
148
|
+
const deps = [];
|
|
149
|
+
if (obj.fields && typeof obj.fields === "object") {
|
|
150
|
+
for (const field of Object.values(obj.fields)) {
|
|
151
|
+
if (field?.type === "lookup" && field?.reference) {
|
|
152
|
+
deps.push(field.reference);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
graph.set(obj.name, deps);
|
|
157
|
+
}
|
|
158
|
+
const visited = /* @__PURE__ */ new Set();
|
|
159
|
+
const stack = /* @__PURE__ */ new Set();
|
|
160
|
+
function dfs(node, path12) {
|
|
161
|
+
if (stack.has(node)) {
|
|
162
|
+
const cycleStart = path12.indexOf(node);
|
|
163
|
+
const cycle = path12.slice(cycleStart).concat(node);
|
|
164
|
+
issues.push(`Circular dependency: ${cycle.join(" \u2192 ")}`);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (visited.has(node)) return false;
|
|
168
|
+
visited.add(node);
|
|
169
|
+
stack.add(node);
|
|
170
|
+
for (const dep of graph.get(node) || []) {
|
|
171
|
+
if (graph.has(dep)) {
|
|
172
|
+
dfs(dep, [...path12, node]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
stack.delete(node);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
for (const name of graph.keys()) {
|
|
179
|
+
if (!visited.has(name)) {
|
|
180
|
+
dfs(name, []);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return issues;
|
|
184
|
+
}
|
|
185
|
+
function findOrphanViews(config) {
|
|
186
|
+
const objectNames = /* @__PURE__ */ new Set();
|
|
187
|
+
if (Array.isArray(config.objects)) {
|
|
188
|
+
for (const obj of config.objects) {
|
|
189
|
+
if (obj.name) objectNames.add(obj.name);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const orphans = [];
|
|
193
|
+
if (Array.isArray(config.views)) {
|
|
194
|
+
for (const view of config.views) {
|
|
195
|
+
if (view.object && !objectNames.has(view.object)) {
|
|
196
|
+
orphans.push(`View "${view.name || "?"}" references non-existent object "${view.object}"`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return orphans;
|
|
201
|
+
}
|
|
202
|
+
function findUnusedObjects(config) {
|
|
203
|
+
const objectNames = /* @__PURE__ */ new Set();
|
|
204
|
+
if (Array.isArray(config.objects)) {
|
|
205
|
+
for (const obj of config.objects) {
|
|
206
|
+
if (obj.name) objectNames.add(obj.name);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const referencedObjects = /* @__PURE__ */ new Set();
|
|
210
|
+
if (Array.isArray(config.views)) {
|
|
211
|
+
for (const view of config.views) {
|
|
212
|
+
if (view.object) referencedObjects.add(view.object);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (Array.isArray(config.flows)) {
|
|
216
|
+
for (const flow of config.flows) {
|
|
217
|
+
if (flow.trigger?.object) referencedObjects.add(flow.trigger.object);
|
|
218
|
+
if (flow.object) referencedObjects.add(flow.object);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (Array.isArray(config.apps)) {
|
|
222
|
+
for (const app of config.apps) {
|
|
223
|
+
if (Array.isArray(app.navigation)) {
|
|
224
|
+
for (const nav of app.navigation) {
|
|
225
|
+
if (nav.object) referencedObjects.add(nav.object);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (Array.isArray(config.agents)) {
|
|
231
|
+
for (const agent of config.agents) {
|
|
232
|
+
if (Array.isArray(agent.objects)) {
|
|
233
|
+
for (const o of agent.objects) referencedObjects.add(o);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(config.objects)) {
|
|
238
|
+
for (const obj of config.objects) {
|
|
239
|
+
if (obj.fields && typeof obj.fields === "object") {
|
|
240
|
+
for (const field of Object.values(obj.fields)) {
|
|
241
|
+
if (field?.type === "lookup" && field?.reference) {
|
|
242
|
+
referencedObjects.add(field.reference);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const unused = [];
|
|
249
|
+
for (const name of objectNames) {
|
|
250
|
+
if (!referencedObjects.has(name)) {
|
|
251
|
+
unused.push(`Object "${name}" is defined but not referenced by any view, flow, app, or agent`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return unused;
|
|
255
|
+
}
|
|
256
|
+
function walkDir(dir, ext) {
|
|
257
|
+
const results = [];
|
|
258
|
+
if (!fs3.existsSync(dir)) return results;
|
|
259
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
if (entry.name === "node_modules") continue;
|
|
262
|
+
const fullPath = path3.join(dir, entry.name);
|
|
263
|
+
if (entry.isDirectory()) {
|
|
264
|
+
results.push(...walkDir(fullPath, ext));
|
|
265
|
+
} else if (entry.name.endsWith(ext)) {
|
|
266
|
+
results.push(fullPath);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return results;
|
|
270
|
+
}
|
|
271
|
+
function findMissingTests(cwd) {
|
|
272
|
+
const specSrcDir = path3.join(cwd, "packages/spec/src");
|
|
273
|
+
if (!fs3.existsSync(specSrcDir)) return [];
|
|
274
|
+
const missing = [];
|
|
275
|
+
const zodFiles = walkDir(specSrcDir, ".zod.ts");
|
|
276
|
+
for (const zodFile of zodFiles) {
|
|
277
|
+
const testFile = zodFile.replace(".zod.ts", ".test.ts");
|
|
278
|
+
if (!fs3.existsSync(testFile)) {
|
|
279
|
+
const relZod = path3.relative(specSrcDir, zodFile);
|
|
280
|
+
const relTest = path3.relative(specSrcDir, testFile);
|
|
281
|
+
missing.push(`Missing test: ${relTest} (for ${relZod})`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return missing;
|
|
285
|
+
}
|
|
286
|
+
function findDeprecatedUsages(cwd) {
|
|
287
|
+
const specSrcDir = path3.join(cwd, "packages/spec/src");
|
|
288
|
+
if (!fs3.existsSync(specSrcDir)) return [];
|
|
289
|
+
const deprecated = [];
|
|
290
|
+
const tsFiles = walkDir(specSrcDir, ".ts").filter((f) => !f.endsWith(".test.ts"));
|
|
291
|
+
for (const tsFile of tsFiles) {
|
|
292
|
+
try {
|
|
293
|
+
const content = fs3.readFileSync(tsFile, "utf-8");
|
|
294
|
+
const lines = content.split("\n");
|
|
295
|
+
const relPath = path3.relative(specSrcDir, tsFile);
|
|
296
|
+
for (let i = 0; i < lines.length; i++) {
|
|
297
|
+
if (lines[i].includes("@deprecated")) {
|
|
298
|
+
deprecated.push(`${relPath}:${i + 1} \u2014 @deprecated tag found`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return deprecated;
|
|
305
|
+
}
|
|
306
|
+
var DEPRECATED_PATTERNS = [
|
|
307
|
+
{
|
|
308
|
+
pattern: /\bEnhancedObjectKernel\b/,
|
|
309
|
+
description: "EnhancedObjectKernel is deprecated in v3",
|
|
310
|
+
replacement: "Use ObjectKernel instead"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
pattern: /\bmax_length\b/,
|
|
314
|
+
description: "snake_case config key: max_length",
|
|
315
|
+
replacement: "Use maxLength (camelCase)"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
pattern: /\bdefault_value\b/,
|
|
319
|
+
description: "snake_case config key: default_value",
|
|
320
|
+
replacement: "Use defaultValue (camelCase)"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
pattern: /\bmin_length\b/,
|
|
324
|
+
description: "snake_case config key: min_length",
|
|
325
|
+
replacement: "Use minLength (camelCase)"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
pattern: /\breference_filters\b/,
|
|
329
|
+
description: "snake_case config key: reference_filters",
|
|
330
|
+
replacement: "Use referenceFilters (camelCase)"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
pattern: /\bunique_name\b/,
|
|
334
|
+
description: "snake_case config key: unique_name",
|
|
335
|
+
replacement: "Use uniqueName (camelCase)"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
pattern: /from\s+['"]@objectstack\/core\/enhanced['"]/,
|
|
339
|
+
description: "Import from deprecated @objectstack/core/enhanced path",
|
|
340
|
+
replacement: "Use import from '@objectstack/core'"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
pattern: /from\s+['"]@objectstack\/spec\/dist\/[^'"]+['"]/,
|
|
344
|
+
description: "Import from deprecated @objectstack/spec/dist/ deep path",
|
|
345
|
+
replacement: "Use import from '@objectstack/spec'"
|
|
346
|
+
}
|
|
347
|
+
];
|
|
348
|
+
function scanDeprecatedPatterns(dir) {
|
|
349
|
+
const results = [];
|
|
350
|
+
if (!fs3.existsSync(dir)) return results;
|
|
351
|
+
const tsFiles = walkDir(dir, ".ts").filter((f) => !f.endsWith(".test.ts"));
|
|
352
|
+
for (const tsFile of tsFiles) {
|
|
353
|
+
try {
|
|
354
|
+
const content = fs3.readFileSync(tsFile, "utf-8");
|
|
355
|
+
const lines = content.split("\n");
|
|
356
|
+
const relPath = path3.relative(process.cwd(), tsFile);
|
|
357
|
+
for (let i = 0; i < lines.length; i++) {
|
|
358
|
+
for (const dp of DEPRECATED_PATTERNS) {
|
|
359
|
+
if (dp.pattern.test(lines[i])) {
|
|
360
|
+
results.push({
|
|
361
|
+
file: relPath,
|
|
362
|
+
line: i + 1,
|
|
363
|
+
description: dp.description,
|
|
364
|
+
replacement: dp.replacement
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return results;
|
|
373
|
+
}
|
|
374
|
+
var doctorCommand = new Command3("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) => {
|
|
356
375
|
printHeader("Environment Health Check");
|
|
357
376
|
const results = [];
|
|
358
377
|
try {
|
|
@@ -411,8 +430,8 @@ var doctorCommand = new Command3("doctor").description("Check development enviro
|
|
|
411
430
|
});
|
|
412
431
|
}
|
|
413
432
|
const cwd = process.cwd();
|
|
414
|
-
const nodeModulesPath =
|
|
415
|
-
if (
|
|
433
|
+
const nodeModulesPath = path3.join(cwd, "node_modules");
|
|
434
|
+
if (fs3.existsSync(nodeModulesPath)) {
|
|
416
435
|
results.push({
|
|
417
436
|
name: "Dependencies",
|
|
418
437
|
status: "ok",
|
|
@@ -426,8 +445,8 @@ var doctorCommand = new Command3("doctor").description("Check development enviro
|
|
|
426
445
|
fix: "Run: pnpm install"
|
|
427
446
|
});
|
|
428
447
|
}
|
|
429
|
-
const specDistPath =
|
|
430
|
-
if (
|
|
448
|
+
const specDistPath = path3.join(cwd, "packages/spec/dist");
|
|
449
|
+
if (fs3.existsSync(specDistPath)) {
|
|
431
450
|
results.push({
|
|
432
451
|
name: "@objectstack/spec",
|
|
433
452
|
status: "ok",
|
|
@@ -469,30 +488,111 @@ var doctorCommand = new Command3("doctor").description("Check development enviro
|
|
|
469
488
|
printError(`${padded} ${result.message}`);
|
|
470
489
|
}
|
|
471
490
|
if (result.fix && (options.verbose || result.status === "error")) {
|
|
472
|
-
console.log(
|
|
491
|
+
console.log(chalk3.dim(` \u2192 ${result.fix}`));
|
|
473
492
|
}
|
|
474
493
|
if (result.status === "error") hasErrors = true;
|
|
475
494
|
if (result.status === "warning") hasWarnings = true;
|
|
476
495
|
});
|
|
496
|
+
printStep("Checking for missing test files...");
|
|
497
|
+
const missingTests = findMissingTests(cwd);
|
|
498
|
+
if (missingTests.length > 0) {
|
|
499
|
+
hasWarnings = true;
|
|
500
|
+
for (const msg of missingTests) {
|
|
501
|
+
printWarning(msg);
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
printSuccess("Test coverage All *.zod.ts files have matching tests");
|
|
505
|
+
}
|
|
506
|
+
printStep("Scanning for @deprecated usage...");
|
|
507
|
+
const deprecatedUsages = findDeprecatedUsages(cwd);
|
|
508
|
+
if (deprecatedUsages.length > 0) {
|
|
509
|
+
hasWarnings = true;
|
|
510
|
+
for (const msg of deprecatedUsages) {
|
|
511
|
+
printWarning(`Deprecated: ${msg}`);
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
printSuccess("Deprecations No @deprecated tags found");
|
|
515
|
+
}
|
|
516
|
+
if (configExists()) {
|
|
517
|
+
printStep("Loading configuration for analysis...");
|
|
518
|
+
try {
|
|
519
|
+
const { config } = await loadConfig();
|
|
520
|
+
if (Array.isArray(config.objects) && config.objects.length > 0) {
|
|
521
|
+
printStep("Checking for circular dependencies...");
|
|
522
|
+
const cycles = detectCircularDependencies(config.objects);
|
|
523
|
+
if (cycles.length > 0) {
|
|
524
|
+
hasWarnings = true;
|
|
525
|
+
for (const msg of cycles) {
|
|
526
|
+
printWarning(msg);
|
|
527
|
+
}
|
|
528
|
+
} else {
|
|
529
|
+
printSuccess("Dependencies No circular references detected");
|
|
530
|
+
}
|
|
531
|
+
printStep("Checking for unused objects...");
|
|
532
|
+
const unused = findUnusedObjects(config);
|
|
533
|
+
if (unused.length > 0) {
|
|
534
|
+
hasWarnings = true;
|
|
535
|
+
for (const msg of unused) {
|
|
536
|
+
printWarning(msg);
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
printSuccess("Object usage All objects are referenced");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (Array.isArray(config.views) && config.views.length > 0) {
|
|
543
|
+
printStep("Checking for orphan views...");
|
|
544
|
+
const orphans = findOrphanViews(config);
|
|
545
|
+
if (orphans.length > 0) {
|
|
546
|
+
hasWarnings = true;
|
|
547
|
+
for (const msg of orphans) {
|
|
548
|
+
printWarning(msg);
|
|
549
|
+
}
|
|
550
|
+
} else {
|
|
551
|
+
printSuccess("View integrity All views reference valid objects");
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} catch {
|
|
555
|
+
printWarning("Could not load config for analysis (config checks skipped)");
|
|
556
|
+
hasWarnings = true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (options.scanDeprecations) {
|
|
560
|
+
printStep("Scanning for deprecated ObjectStack patterns...");
|
|
561
|
+
const scanDir = path3.join(cwd, "src");
|
|
562
|
+
const deprecations = scanDeprecatedPatterns(scanDir);
|
|
563
|
+
if (deprecations.length > 0) {
|
|
564
|
+
hasWarnings = true;
|
|
565
|
+
for (const dep of deprecations) {
|
|
566
|
+
printWarning(`${dep.file}:${dep.line} \u2014 ${dep.description}`);
|
|
567
|
+
if (options.verbose) {
|
|
568
|
+
console.log(chalk3.dim(` \u2192 ${dep.replacement}`));
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
console.log("");
|
|
572
|
+
printInfo(`Found ${deprecations.length} deprecated pattern(s). Run \`objectstack codemod v2-to-v3\` to auto-fix.`);
|
|
573
|
+
} else {
|
|
574
|
+
printSuccess("Deprecation scan No deprecated patterns found");
|
|
575
|
+
}
|
|
576
|
+
}
|
|
477
577
|
console.log("");
|
|
478
578
|
if (hasErrors) {
|
|
479
|
-
console.log(
|
|
480
|
-
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(
|
|
579
|
+
console.log(chalk3.red("\u274C Some critical issues found. Please fix them before continuing."));
|
|
580
|
+
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(chalk3.dim(` ${r.fix}`)));
|
|
481
581
|
process.exit(1);
|
|
482
582
|
} else if (hasWarnings) {
|
|
483
|
-
console.log(
|
|
484
|
-
console.log(
|
|
583
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Environment is functional but has some warnings."));
|
|
584
|
+
console.log(chalk3.dim(" Run with --verbose to see fix suggestions."));
|
|
485
585
|
} else {
|
|
486
|
-
console.log(
|
|
586
|
+
console.log(chalk3.green("\u2705 Environment is healthy and ready for development!"));
|
|
487
587
|
}
|
|
488
588
|
console.log("");
|
|
489
589
|
});
|
|
490
590
|
|
|
491
591
|
// src/commands/create.ts
|
|
492
592
|
import { Command as Command4 } from "commander";
|
|
493
|
-
import
|
|
494
|
-
import
|
|
495
|
-
import
|
|
593
|
+
import chalk4 from "chalk";
|
|
594
|
+
import fs4 from "fs";
|
|
595
|
+
import path4 from "path";
|
|
496
596
|
var templates = {
|
|
497
597
|
plugin: {
|
|
498
598
|
description: "Create a new ObjectStack plugin",
|
|
@@ -667,64 +767,64 @@ function toCamelCase(str) {
|
|
|
667
767
|
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
668
768
|
}
|
|
669
769
|
var createCommand = new Command4("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) => {
|
|
670
|
-
console.log(
|
|
770
|
+
console.log(chalk4.bold(`
|
|
671
771
|
\u{1F4E6} ObjectStack Project Creator`));
|
|
672
|
-
console.log(
|
|
772
|
+
console.log(chalk4.dim(`-------------------------------`));
|
|
673
773
|
if (!templates[type]) {
|
|
674
|
-
console.error(
|
|
774
|
+
console.error(chalk4.red(`
|
|
675
775
|
\u274C Unknown type: ${type}`));
|
|
676
|
-
console.log(
|
|
776
|
+
console.log(chalk4.dim("Available types: plugin, example"));
|
|
677
777
|
process.exit(1);
|
|
678
778
|
}
|
|
679
779
|
if (!name) {
|
|
680
|
-
console.error(
|
|
681
|
-
console.log(
|
|
780
|
+
console.error(chalk4.red("\n\u274C Project name is required"));
|
|
781
|
+
console.log(chalk4.dim(`Usage: objectstack create ${type} <name>`));
|
|
682
782
|
process.exit(1);
|
|
683
783
|
}
|
|
684
784
|
const template = templates[type];
|
|
685
785
|
const cwd = process.cwd();
|
|
686
786
|
let targetDir;
|
|
687
787
|
if (options?.dir) {
|
|
688
|
-
targetDir =
|
|
788
|
+
targetDir = path4.resolve(cwd, options.dir);
|
|
689
789
|
} else {
|
|
690
790
|
const baseDir = type === "plugin" ? "packages/plugins" : "examples";
|
|
691
791
|
const projectName = type === "plugin" ? `plugin-${name}` : name;
|
|
692
|
-
targetDir =
|
|
792
|
+
targetDir = path4.join(cwd, baseDir, projectName);
|
|
693
793
|
}
|
|
694
|
-
if (
|
|
695
|
-
console.error(
|
|
794
|
+
if (fs4.existsSync(targetDir)) {
|
|
795
|
+
console.error(chalk4.red(`
|
|
696
796
|
\u274C Directory already exists: ${targetDir}`));
|
|
697
797
|
process.exit(1);
|
|
698
798
|
}
|
|
699
|
-
console.log(`\u{1F4C1} Creating ${type}: ${
|
|
700
|
-
console.log(`\u{1F4C2} Location: ${
|
|
799
|
+
console.log(`\u{1F4C1} Creating ${type}: ${chalk4.blue(name)}`);
|
|
800
|
+
console.log(`\u{1F4C2} Location: ${chalk4.dim(targetDir)}`);
|
|
701
801
|
console.log("");
|
|
702
802
|
try {
|
|
703
|
-
|
|
803
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
704
804
|
for (const [filePath, contentFn] of Object.entries(template.files)) {
|
|
705
|
-
const fullPath =
|
|
706
|
-
const dir =
|
|
707
|
-
if (!
|
|
708
|
-
|
|
805
|
+
const fullPath = path4.join(targetDir, filePath);
|
|
806
|
+
const dir = path4.dirname(fullPath);
|
|
807
|
+
if (!fs4.existsSync(dir)) {
|
|
808
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
709
809
|
}
|
|
710
810
|
const content = contentFn(name);
|
|
711
811
|
const fileContent = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
712
|
-
|
|
713
|
-
console.log(
|
|
812
|
+
fs4.writeFileSync(fullPath, fileContent);
|
|
813
|
+
console.log(chalk4.green(`\u2713 Created ${filePath}`));
|
|
714
814
|
}
|
|
715
815
|
console.log("");
|
|
716
|
-
console.log(
|
|
816
|
+
console.log(chalk4.green("\u2705 Project created successfully!"));
|
|
717
817
|
console.log("");
|
|
718
|
-
console.log(
|
|
719
|
-
console.log(
|
|
720
|
-
console.log(
|
|
721
|
-
console.log(
|
|
818
|
+
console.log(chalk4.bold("Next steps:"));
|
|
819
|
+
console.log(chalk4.dim(` cd ${path4.relative(cwd, targetDir)}`));
|
|
820
|
+
console.log(chalk4.dim(" pnpm install"));
|
|
821
|
+
console.log(chalk4.dim(" pnpm build"));
|
|
722
822
|
console.log("");
|
|
723
823
|
} catch (error) {
|
|
724
|
-
console.error(
|
|
824
|
+
console.error(chalk4.red("\n\u274C Failed to create project:"));
|
|
725
825
|
console.error(error.message || error);
|
|
726
|
-
if (
|
|
727
|
-
|
|
826
|
+
if (fs4.existsSync(targetDir)) {
|
|
827
|
+
fs4.rmSync(targetDir, { recursive: true });
|
|
728
828
|
}
|
|
729
829
|
process.exit(1);
|
|
730
830
|
}
|
|
@@ -732,37 +832,37 @@ var createCommand = new Command4("create").description("Create a new package, pl
|
|
|
732
832
|
|
|
733
833
|
// src/commands/serve.ts
|
|
734
834
|
import { Command as Command5 } from "commander";
|
|
735
|
-
import
|
|
736
|
-
import
|
|
835
|
+
import path6 from "path";
|
|
836
|
+
import fs6 from "fs";
|
|
737
837
|
import net from "net";
|
|
738
|
-
import
|
|
739
|
-
import { bundleRequire
|
|
838
|
+
import chalk5 from "chalk";
|
|
839
|
+
import { bundleRequire } from "bundle-require";
|
|
740
840
|
|
|
741
841
|
// src/utils/studio.ts
|
|
742
|
-
import
|
|
743
|
-
import
|
|
842
|
+
import path5 from "path";
|
|
843
|
+
import fs5 from "fs";
|
|
744
844
|
import { createRequire } from "module";
|
|
745
845
|
import { pathToFileURL } from "url";
|
|
746
846
|
var STUDIO_PATH = "/_studio";
|
|
747
847
|
function resolveStudioPath() {
|
|
748
848
|
const cwd = process.cwd();
|
|
749
849
|
const candidates = [
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
850
|
+
path5.resolve(cwd, "apps/studio"),
|
|
851
|
+
path5.resolve(cwd, "../../apps/studio"),
|
|
852
|
+
path5.resolve(cwd, "../apps/studio")
|
|
753
853
|
];
|
|
754
854
|
for (const candidate of candidates) {
|
|
755
|
-
const pkgPath =
|
|
756
|
-
if (
|
|
855
|
+
const pkgPath = path5.join(candidate, "package.json");
|
|
856
|
+
if (fs5.existsSync(pkgPath)) {
|
|
757
857
|
try {
|
|
758
|
-
const pkg2 = JSON.parse(
|
|
858
|
+
const pkg2 = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
759
859
|
if (pkg2.name === "@objectstack/studio") return candidate;
|
|
760
860
|
} catch {
|
|
761
861
|
}
|
|
762
862
|
}
|
|
763
863
|
}
|
|
764
864
|
const resolutionBases = [
|
|
765
|
-
pathToFileURL(
|
|
865
|
+
pathToFileURL(path5.join(cwd, "package.json")).href,
|
|
766
866
|
// consumer workspace
|
|
767
867
|
import.meta.url
|
|
768
868
|
// CLI package itself
|
|
@@ -771,18 +871,18 @@ function resolveStudioPath() {
|
|
|
771
871
|
try {
|
|
772
872
|
const req = createRequire(base);
|
|
773
873
|
const resolved = req.resolve("@objectstack/studio/package.json");
|
|
774
|
-
return
|
|
874
|
+
return path5.dirname(resolved);
|
|
775
875
|
} catch {
|
|
776
876
|
}
|
|
777
877
|
}
|
|
778
|
-
const directPath =
|
|
779
|
-
if (
|
|
878
|
+
const directPath = path5.join(cwd, "node_modules", "@objectstack", "studio");
|
|
879
|
+
if (fs5.existsSync(path5.join(directPath, "package.json"))) {
|
|
780
880
|
return directPath;
|
|
781
881
|
}
|
|
782
882
|
return null;
|
|
783
883
|
}
|
|
784
884
|
function hasStudioDist(studioPath) {
|
|
785
|
-
return
|
|
885
|
+
return fs5.existsSync(path5.join(studioPath, "dist", "index.html"));
|
|
786
886
|
}
|
|
787
887
|
function createStudioStaticPlugin(distPath, options) {
|
|
788
888
|
return {
|
|
@@ -796,13 +896,13 @@ function createStudioStaticPlugin(distPath, options) {
|
|
|
796
896
|
return;
|
|
797
897
|
}
|
|
798
898
|
const app = httpServer.getRawApp();
|
|
799
|
-
const absoluteDist =
|
|
800
|
-
const indexPath =
|
|
801
|
-
if (!
|
|
899
|
+
const absoluteDist = path5.resolve(distPath);
|
|
900
|
+
const indexPath = path5.join(absoluteDist, "index.html");
|
|
901
|
+
if (!fs5.existsSync(indexPath)) {
|
|
802
902
|
ctx.logger?.warn?.(`Studio static: dist not found at ${absoluteDist}`);
|
|
803
903
|
return;
|
|
804
904
|
}
|
|
805
|
-
const rawHtml =
|
|
905
|
+
const rawHtml = fs5.readFileSync(indexPath, "utf-8");
|
|
806
906
|
const rewrittenHtml = rawHtml.replace(
|
|
807
907
|
/(\s(?:href|src))="\/(?!\/)/g,
|
|
808
908
|
`$1="${STUDIO_PATH}/`
|
|
@@ -813,12 +913,12 @@ function createStudioStaticPlugin(distPath, options) {
|
|
|
813
913
|
app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
814
914
|
app.get(`${STUDIO_PATH}/*`, async (c) => {
|
|
815
915
|
const reqPath = c.req.path.substring(STUDIO_PATH.length) || "/";
|
|
816
|
-
const filePath =
|
|
916
|
+
const filePath = path5.join(absoluteDist, reqPath);
|
|
817
917
|
if (!filePath.startsWith(absoluteDist)) {
|
|
818
918
|
return c.text("Forbidden", 403);
|
|
819
919
|
}
|
|
820
|
-
if (
|
|
821
|
-
const content =
|
|
920
|
+
if (fs5.existsSync(filePath) && fs5.statSync(filePath).isFile()) {
|
|
921
|
+
const content = fs5.readFileSync(filePath);
|
|
822
922
|
return new Response(content, {
|
|
823
923
|
headers: { "content-type": mimeType(filePath) }
|
|
824
924
|
});
|
|
@@ -848,7 +948,7 @@ var MIME_TYPES = {
|
|
|
848
948
|
".map": "application/json"
|
|
849
949
|
};
|
|
850
950
|
function mimeType(filePath) {
|
|
851
|
-
const ext =
|
|
951
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
852
952
|
return MIME_TYPES[ext] || "application/octet-stream";
|
|
853
953
|
}
|
|
854
954
|
|
|
@@ -885,15 +985,15 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
885
985
|
} catch (e) {
|
|
886
986
|
}
|
|
887
987
|
const isDev = options.dev || process.env.NODE_ENV === "development";
|
|
888
|
-
const absolutePath =
|
|
889
|
-
const relativeConfig =
|
|
890
|
-
if (!
|
|
988
|
+
const absolutePath = path6.resolve(process.cwd(), configPath);
|
|
989
|
+
const relativeConfig = path6.relative(process.cwd(), absolutePath);
|
|
990
|
+
if (!fs6.existsSync(absolutePath)) {
|
|
891
991
|
printError(`Configuration file not found: ${absolutePath}`);
|
|
892
|
-
console.log(
|
|
992
|
+
console.log(chalk5.dim(" Hint: Run `objectstack init` to create a new project"));
|
|
893
993
|
process.exit(1);
|
|
894
994
|
}
|
|
895
995
|
console.log("");
|
|
896
|
-
console.log(
|
|
996
|
+
console.log(chalk5.dim(` Loading ${relativeConfig}...`));
|
|
897
997
|
const loadedPlugins = [];
|
|
898
998
|
const shortPluginName = (raw) => {
|
|
899
999
|
if (raw.includes("objectql")) return "ObjectQL";
|
|
@@ -928,7 +1028,7 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
928
1028
|
console.debug = (...args) => {
|
|
929
1029
|
if (!bootQuiet) originalConsoleDebug(...args);
|
|
930
1030
|
};
|
|
931
|
-
const { mod } = await
|
|
1031
|
+
const { mod } = await bundleRequire({
|
|
932
1032
|
filepath: absolutePath
|
|
933
1033
|
});
|
|
934
1034
|
const config = mod.default || mod;
|
|
@@ -997,7 +1097,7 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
997
1097
|
const pluginName = plugin.name || plugin.constructor?.name || "unnamed";
|
|
998
1098
|
trackPlugin(pluginName);
|
|
999
1099
|
} catch (e) {
|
|
1000
|
-
console.error(
|
|
1100
|
+
console.error(chalk5.red(` \u2717 Failed to load plugin: ${e.message}`));
|
|
1001
1101
|
}
|
|
1002
1102
|
}
|
|
1003
1103
|
}
|
|
@@ -1008,7 +1108,7 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
1008
1108
|
await kernel.use(serverPlugin);
|
|
1009
1109
|
trackPlugin("HonoServer");
|
|
1010
1110
|
} catch (e) {
|
|
1011
|
-
console.warn(
|
|
1111
|
+
console.warn(chalk5.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
|
|
1012
1112
|
}
|
|
1013
1113
|
try {
|
|
1014
1114
|
const { createRestApiPlugin } = await import("@objectstack/rest");
|
|
@@ -1027,13 +1127,13 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
1027
1127
|
if (enableUI) {
|
|
1028
1128
|
const studioPath = resolveStudioPath();
|
|
1029
1129
|
if (!studioPath) {
|
|
1030
|
-
console.warn(
|
|
1130
|
+
console.warn(chalk5.yellow(` \u26A0 @objectstack/studio not found \u2014 skipping UI`));
|
|
1031
1131
|
} else if (hasStudioDist(studioPath)) {
|
|
1032
|
-
const distPath =
|
|
1132
|
+
const distPath = path6.join(studioPath, "dist");
|
|
1033
1133
|
await kernel.use(createStudioStaticPlugin(distPath, { isDev }));
|
|
1034
1134
|
trackPlugin("StudioUI");
|
|
1035
1135
|
} else {
|
|
1036
|
-
console.warn(
|
|
1136
|
+
console.warn(chalk5.yellow(` \u26A0 Studio dist not found \u2014 run "pnpm --filter @objectstack/studio build" first`));
|
|
1037
1137
|
}
|
|
1038
1138
|
}
|
|
1039
1139
|
await runtime.start();
|
|
@@ -1049,18 +1149,18 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
1049
1149
|
studioPath: STUDIO_PATH
|
|
1050
1150
|
});
|
|
1051
1151
|
process.on("SIGINT", async () => {
|
|
1052
|
-
console.warn(
|
|
1152
|
+
console.warn(chalk5.yellow(`
|
|
1053
1153
|
|
|
1054
1154
|
\u23F9 Stopping server...`));
|
|
1055
1155
|
await runtime.getKernel().shutdown();
|
|
1056
|
-
console.log(
|
|
1156
|
+
console.log(chalk5.green(`\u2705 Server stopped`));
|
|
1057
1157
|
process.exit(0);
|
|
1058
1158
|
});
|
|
1059
1159
|
} catch (error) {
|
|
1060
1160
|
restoreOutput();
|
|
1061
1161
|
console.log("");
|
|
1062
1162
|
printError(error.message || String(error));
|
|
1063
|
-
if (process.env.DEBUG) console.error(
|
|
1163
|
+
if (process.env.DEBUG) console.error(chalk5.dim(error.stack));
|
|
1064
1164
|
process.exit(1);
|
|
1065
1165
|
}
|
|
1066
1166
|
});
|
|
@@ -1092,15 +1192,15 @@ var studioCommand = new Command6("studio").description("Launch Studio UI with de
|
|
|
1092
1192
|
|
|
1093
1193
|
// src/commands/test.ts
|
|
1094
1194
|
import { Command as Command7 } from "commander";
|
|
1095
|
-
import
|
|
1096
|
-
import
|
|
1097
|
-
import
|
|
1195
|
+
import chalk6 from "chalk";
|
|
1196
|
+
import path7 from "path";
|
|
1197
|
+
import fs7 from "fs";
|
|
1098
1198
|
import { QA as CoreQA } from "@objectstack/core";
|
|
1099
1199
|
function resolveGlob(pattern) {
|
|
1100
1200
|
if (!pattern.includes("*")) {
|
|
1101
|
-
return
|
|
1201
|
+
return fs7.existsSync(pattern) ? [pattern] : [];
|
|
1102
1202
|
}
|
|
1103
|
-
const parts = pattern.split(
|
|
1203
|
+
const parts = pattern.split(path7.sep.replace("\\", "/"));
|
|
1104
1204
|
const segments = pattern.includes("/") ? pattern.split("/") : parts;
|
|
1105
1205
|
let baseDir = ".";
|
|
1106
1206
|
let globStart = 0;
|
|
@@ -1109,25 +1209,25 @@ function resolveGlob(pattern) {
|
|
|
1109
1209
|
globStart = i;
|
|
1110
1210
|
break;
|
|
1111
1211
|
}
|
|
1112
|
-
baseDir = i === 0 ? segments[i] :
|
|
1212
|
+
baseDir = i === 0 ? segments[i] : path7.join(baseDir, segments[i]);
|
|
1113
1213
|
}
|
|
1114
|
-
if (!
|
|
1214
|
+
if (!fs7.existsSync(baseDir)) return [];
|
|
1115
1215
|
const globPortion = segments.slice(globStart).join("/");
|
|
1116
1216
|
const regexStr = globPortion.replace(/\./g, "\\.").replace(/\*\*\//g, "(.+/)?").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
1117
1217
|
const regex = new RegExp(`^${regexStr}$`);
|
|
1118
|
-
const entries =
|
|
1119
|
-
return entries.filter((entry) => regex.test(entry.replace(/\\/g, "/"))).map((entry) =>
|
|
1218
|
+
const entries = fs7.readdirSync(baseDir, { recursive: true, encoding: "utf-8" });
|
|
1219
|
+
return entries.filter((entry) => regex.test(entry.replace(/\\/g, "/"))).map((entry) => path7.join(baseDir, entry)).filter((fullPath) => fs7.statSync(fullPath).isFile());
|
|
1120
1220
|
}
|
|
1121
1221
|
var testCommand = new Command7("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) => {
|
|
1122
|
-
console.log(
|
|
1222
|
+
console.log(chalk6.bold(`
|
|
1123
1223
|
\u{1F9EA} ObjectStack Quality Protocol Runner`));
|
|
1124
|
-
console.log(
|
|
1125
|
-
console.log(`Target: ${
|
|
1224
|
+
console.log(chalk6.dim(`-------------------------------------`));
|
|
1225
|
+
console.log(`Target: ${chalk6.blue(options.url)}`);
|
|
1126
1226
|
const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
|
|
1127
1227
|
const runner = new CoreQA.TestRunner(adapter);
|
|
1128
1228
|
const testFiles = resolveGlob(filesPattern);
|
|
1129
1229
|
if (testFiles.length === 0) {
|
|
1130
|
-
console.warn(
|
|
1230
|
+
console.warn(chalk6.yellow(`No test files found matching: ${filesPattern}`));
|
|
1131
1231
|
return;
|
|
1132
1232
|
}
|
|
1133
1233
|
console.log(`Found ${testFiles.length} test suites.`);
|
|
@@ -1135,19 +1235,19 @@ var testCommand = new Command7("test").description("Run Quality Protocol test sc
|
|
|
1135
1235
|
let totalFailed = 0;
|
|
1136
1236
|
for (const file of testFiles) {
|
|
1137
1237
|
console.log(`
|
|
1138
|
-
\u{1F4C4} Running suite: ${
|
|
1238
|
+
\u{1F4C4} Running suite: ${chalk6.bold(path7.basename(file))}`);
|
|
1139
1239
|
try {
|
|
1140
|
-
const content =
|
|
1240
|
+
const content = fs7.readFileSync(file, "utf-8");
|
|
1141
1241
|
const suite = JSON.parse(content);
|
|
1142
1242
|
const results = await runner.runSuite(suite);
|
|
1143
1243
|
for (const result of results) {
|
|
1144
1244
|
const icon = result.passed ? "\u2705" : "\u274C";
|
|
1145
1245
|
console.log(` ${icon} Scenario: ${result.scenarioId} (${result.duration}ms)`);
|
|
1146
1246
|
if (!result.passed) {
|
|
1147
|
-
console.error(
|
|
1247
|
+
console.error(chalk6.red(` Error: ${result.error}`));
|
|
1148
1248
|
result.steps.forEach((step) => {
|
|
1149
1249
|
if (!step.passed) {
|
|
1150
|
-
console.error(
|
|
1250
|
+
console.error(chalk6.red(` Step Failed: ${step.stepName}`));
|
|
1151
1251
|
if (step.output) console.error(` Output:`, step.output);
|
|
1152
1252
|
if (step.error) console.error(` Error:`, step.error);
|
|
1153
1253
|
}
|
|
@@ -1158,24 +1258,24 @@ var testCommand = new Command7("test").description("Run Quality Protocol test sc
|
|
|
1158
1258
|
}
|
|
1159
1259
|
}
|
|
1160
1260
|
} catch (e) {
|
|
1161
|
-
console.error(
|
|
1261
|
+
console.error(chalk6.red(`Failed to load or run suite ${file}: ${e}`));
|
|
1162
1262
|
totalFailed++;
|
|
1163
1263
|
}
|
|
1164
1264
|
}
|
|
1165
|
-
console.log(
|
|
1265
|
+
console.log(chalk6.dim(`
|
|
1166
1266
|
-------------------------------------`));
|
|
1167
1267
|
if (totalFailed > 0) {
|
|
1168
|
-
console.log(
|
|
1268
|
+
console.log(chalk6.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
|
|
1169
1269
|
process.exit(1);
|
|
1170
1270
|
} else {
|
|
1171
|
-
console.log(
|
|
1271
|
+
console.log(chalk6.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
|
|
1172
1272
|
process.exit(0);
|
|
1173
1273
|
}
|
|
1174
1274
|
});
|
|
1175
1275
|
|
|
1176
1276
|
// src/commands/validate.ts
|
|
1177
1277
|
import { Command as Command8 } from "commander";
|
|
1178
|
-
import
|
|
1278
|
+
import chalk7 from "chalk";
|
|
1179
1279
|
import { ObjectStackDefinitionSchema as ObjectStackDefinitionSchema2 } from "@objectstack/spec";
|
|
1180
1280
|
var validateCommand = new Command8("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) => {
|
|
1181
1281
|
const timer = createTimer();
|
|
@@ -1229,12 +1329,12 @@ var validateCommand = new Command8("validate").description("Validate ObjectStack
|
|
|
1229
1329
|
warnings.push("Missing manifest.namespace \u2014 required for multi-app hosting");
|
|
1230
1330
|
}
|
|
1231
1331
|
console.log("");
|
|
1232
|
-
printSuccess(`Validation passed ${
|
|
1332
|
+
printSuccess(`Validation passed ${chalk7.dim(`(${timer.display()})`)}`);
|
|
1233
1333
|
console.log("");
|
|
1234
1334
|
if (config.manifest) {
|
|
1235
|
-
console.log(` ${
|
|
1335
|
+
console.log(` ${chalk7.bold(config.manifest.name || config.manifest.id || "Unnamed")} ${chalk7.dim(`v${config.manifest.version || "0.0.0"}`)}`);
|
|
1236
1336
|
if (config.manifest.description) {
|
|
1237
|
-
console.log(
|
|
1337
|
+
console.log(chalk7.dim(` ${config.manifest.description}`));
|
|
1238
1338
|
}
|
|
1239
1339
|
console.log("");
|
|
1240
1340
|
}
|
|
@@ -1242,7 +1342,7 @@ var validateCommand = new Command8("validate").description("Validate ObjectStack
|
|
|
1242
1342
|
if (warnings.length > 0) {
|
|
1243
1343
|
console.log("");
|
|
1244
1344
|
for (const w of warnings) {
|
|
1245
|
-
console.log(
|
|
1345
|
+
console.log(chalk7.yellow(` \u26A0 ${w}`));
|
|
1246
1346
|
}
|
|
1247
1347
|
if (options.strict) {
|
|
1248
1348
|
console.log("");
|
|
@@ -1268,9 +1368,9 @@ var validateCommand = new Command8("validate").description("Validate ObjectStack
|
|
|
1268
1368
|
|
|
1269
1369
|
// src/commands/init.ts
|
|
1270
1370
|
import { Command as Command9 } from "commander";
|
|
1271
|
-
import
|
|
1272
|
-
import
|
|
1273
|
-
import
|
|
1371
|
+
import chalk8 from "chalk";
|
|
1372
|
+
import fs8 from "fs";
|
|
1373
|
+
import path8 from "path";
|
|
1274
1374
|
var TEMPLATES = {
|
|
1275
1375
|
app: {
|
|
1276
1376
|
description: "Full application with objects, views, and actions",
|
|
@@ -1435,16 +1535,16 @@ function toTitleCase(str) {
|
|
|
1435
1535
|
var initCommand = new Command9("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) => {
|
|
1436
1536
|
printHeader("Init");
|
|
1437
1537
|
const cwd = process.cwd();
|
|
1438
|
-
const projectName = name ||
|
|
1538
|
+
const projectName = name || path8.basename(cwd);
|
|
1439
1539
|
const template = TEMPLATES[options.template];
|
|
1440
1540
|
if (!template) {
|
|
1441
1541
|
printError(`Unknown template: ${options.template}`);
|
|
1442
|
-
console.log(
|
|
1542
|
+
console.log(chalk8.dim(` Available: ${Object.keys(TEMPLATES).join(", ")}`));
|
|
1443
1543
|
process.exit(1);
|
|
1444
1544
|
}
|
|
1445
|
-
if (
|
|
1545
|
+
if (fs8.existsSync(path8.join(cwd, "objectstack.config.ts"))) {
|
|
1446
1546
|
printError("objectstack.config.ts already exists in this directory");
|
|
1447
|
-
console.log(
|
|
1547
|
+
console.log(chalk8.dim(" Use `objectstack generate` to add metadata to an existing project"));
|
|
1448
1548
|
process.exit(1);
|
|
1449
1549
|
}
|
|
1450
1550
|
printKV("Project", projectName);
|
|
@@ -1453,8 +1553,8 @@ var initCommand = new Command9("init").description("Initialize a new ObjectStack
|
|
|
1453
1553
|
console.log("");
|
|
1454
1554
|
const createdFiles = [];
|
|
1455
1555
|
try {
|
|
1456
|
-
const pkgPath =
|
|
1457
|
-
if (!
|
|
1556
|
+
const pkgPath = path8.join(cwd, "package.json");
|
|
1557
|
+
if (!fs8.existsSync(pkgPath)) {
|
|
1458
1558
|
const pkg2 = {
|
|
1459
1559
|
name: projectName,
|
|
1460
1560
|
version: "0.1.0",
|
|
@@ -1464,16 +1564,16 @@ var initCommand = new Command9("init").description("Initialize a new ObjectStack
|
|
|
1464
1564
|
dependencies: template.dependencies,
|
|
1465
1565
|
devDependencies: template.devDependencies
|
|
1466
1566
|
};
|
|
1467
|
-
|
|
1567
|
+
fs8.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n");
|
|
1468
1568
|
createdFiles.push("package.json");
|
|
1469
1569
|
} else {
|
|
1470
1570
|
printInfo("package.json already exists, skipping");
|
|
1471
1571
|
}
|
|
1472
1572
|
const configContent = template.configContent(projectName);
|
|
1473
|
-
|
|
1573
|
+
fs8.writeFileSync(path8.join(cwd, "objectstack.config.ts"), configContent);
|
|
1474
1574
|
createdFiles.push("objectstack.config.ts");
|
|
1475
|
-
const tsconfigPath =
|
|
1476
|
-
if (!
|
|
1575
|
+
const tsconfigPath = path8.join(cwd, "tsconfig.json");
|
|
1576
|
+
if (!fs8.existsSync(tsconfigPath)) {
|
|
1477
1577
|
const tsconfig = {
|
|
1478
1578
|
compilerOptions: {
|
|
1479
1579
|
target: "ES2022",
|
|
@@ -1489,30 +1589,30 @@ var initCommand = new Command9("init").description("Initialize a new ObjectStack
|
|
|
1489
1589
|
include: ["*.ts", "src/**/*"],
|
|
1490
1590
|
exclude: ["dist", "node_modules"]
|
|
1491
1591
|
};
|
|
1492
|
-
|
|
1592
|
+
fs8.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
1493
1593
|
createdFiles.push("tsconfig.json");
|
|
1494
1594
|
}
|
|
1495
1595
|
for (const [filePath, contentFn] of Object.entries(template.srcFiles)) {
|
|
1496
1596
|
const resolvedPath = filePath.replace("__name__", projectName);
|
|
1497
|
-
const fullPath =
|
|
1498
|
-
const dir =
|
|
1499
|
-
if (!
|
|
1500
|
-
|
|
1597
|
+
const fullPath = path8.join(cwd, resolvedPath);
|
|
1598
|
+
const dir = path8.dirname(fullPath);
|
|
1599
|
+
if (!fs8.existsSync(dir)) {
|
|
1600
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1501
1601
|
}
|
|
1502
|
-
|
|
1602
|
+
fs8.writeFileSync(fullPath, contentFn(projectName));
|
|
1503
1603
|
createdFiles.push(resolvedPath);
|
|
1504
1604
|
}
|
|
1505
|
-
const gitignorePath =
|
|
1506
|
-
if (!
|
|
1507
|
-
|
|
1605
|
+
const gitignorePath = path8.join(cwd, ".gitignore");
|
|
1606
|
+
if (!fs8.existsSync(gitignorePath)) {
|
|
1607
|
+
fs8.writeFileSync(gitignorePath, `node_modules/
|
|
1508
1608
|
dist/
|
|
1509
1609
|
*.tsbuildinfo
|
|
1510
1610
|
`);
|
|
1511
1611
|
createdFiles.push(".gitignore");
|
|
1512
1612
|
}
|
|
1513
|
-
console.log(
|
|
1613
|
+
console.log(chalk8.bold(" Created files:"));
|
|
1514
1614
|
for (const f of createdFiles) {
|
|
1515
|
-
console.log(
|
|
1615
|
+
console.log(chalk8.green(` + ${f}`));
|
|
1516
1616
|
}
|
|
1517
1617
|
console.log("");
|
|
1518
1618
|
if (options.install !== false) {
|
|
@@ -1526,10 +1626,10 @@ dist/
|
|
|
1526
1626
|
}
|
|
1527
1627
|
printSuccess("Project initialized!");
|
|
1528
1628
|
console.log("");
|
|
1529
|
-
console.log(
|
|
1530
|
-
console.log(
|
|
1531
|
-
console.log(
|
|
1532
|
-
console.log(
|
|
1629
|
+
console.log(chalk8.bold(" Next steps:"));
|
|
1630
|
+
console.log(chalk8.dim(" objectstack validate # Check configuration"));
|
|
1631
|
+
console.log(chalk8.dim(" objectstack dev # Start development server"));
|
|
1632
|
+
console.log(chalk8.dim(" objectstack generate # Add objects, views, etc."));
|
|
1533
1633
|
console.log("");
|
|
1534
1634
|
} catch (error) {
|
|
1535
1635
|
printError(error.message || String(error));
|
|
@@ -1537,12 +1637,12 @@ dist/
|
|
|
1537
1637
|
}
|
|
1538
1638
|
});
|
|
1539
1639
|
function printWarning2(msg) {
|
|
1540
|
-
console.log(
|
|
1640
|
+
console.log(chalk8.yellow(` \u26A0 ${msg}`));
|
|
1541
1641
|
}
|
|
1542
1642
|
|
|
1543
1643
|
// src/commands/info.ts
|
|
1544
1644
|
import { Command as Command10 } from "commander";
|
|
1545
|
-
import
|
|
1645
|
+
import chalk9 from "chalk";
|
|
1546
1646
|
var infoCommand = new Command10("info").description("Display metadata summary of an ObjectStack configuration").argument("[config]", "Configuration file path").option("--json", "Output as JSON").action(async (configPath, options) => {
|
|
1547
1647
|
const timer = createTimer();
|
|
1548
1648
|
if (!options.json) {
|
|
@@ -1568,9 +1668,9 @@ var infoCommand = new Command10("info").description("Display metadata summary of
|
|
|
1568
1668
|
if (config.manifest) {
|
|
1569
1669
|
const m = config.manifest;
|
|
1570
1670
|
console.log("");
|
|
1571
|
-
console.log(` ${
|
|
1572
|
-
if (m.id) console.log(
|
|
1573
|
-
if (m.description) console.log(
|
|
1671
|
+
console.log(` ${chalk9.bold(m.name || m.id || "Unnamed")} ${chalk9.dim(`v${m.version || "0.0.0"}`)}`);
|
|
1672
|
+
if (m.id) console.log(chalk9.dim(` ${m.id}`));
|
|
1673
|
+
if (m.description) console.log(chalk9.dim(` ${m.description}`));
|
|
1574
1674
|
if (m.namespace) printKV(" Namespace", m.namespace);
|
|
1575
1675
|
if (m.type) printKV(" Type", m.type);
|
|
1576
1676
|
}
|
|
@@ -1578,35 +1678,35 @@ var infoCommand = new Command10("info").description("Display metadata summary of
|
|
|
1578
1678
|
printMetadataStats(stats);
|
|
1579
1679
|
if (config.objects && config.objects.length > 0) {
|
|
1580
1680
|
console.log("");
|
|
1581
|
-
console.log(
|
|
1681
|
+
console.log(chalk9.bold(" Objects:"));
|
|
1582
1682
|
for (const obj of config.objects) {
|
|
1583
1683
|
const fieldCount = obj.fields ? Object.keys(obj.fields).length : 0;
|
|
1584
1684
|
const ownership = obj.ownership || "own";
|
|
1585
1685
|
console.log(
|
|
1586
|
-
` ${
|
|
1686
|
+
` ${chalk9.cyan(obj.name || "?")}` + chalk9.dim(` (${fieldCount} fields, ${ownership})`) + (obj.label ? chalk9.dim(` \u2014 ${obj.label}`) : "")
|
|
1587
1687
|
);
|
|
1588
1688
|
}
|
|
1589
1689
|
}
|
|
1590
1690
|
if (config.agents && config.agents.length > 0) {
|
|
1591
1691
|
console.log("");
|
|
1592
|
-
console.log(
|
|
1692
|
+
console.log(chalk9.bold(" Agents:"));
|
|
1593
1693
|
for (const agent of config.agents) {
|
|
1594
1694
|
console.log(
|
|
1595
|
-
` ${
|
|
1695
|
+
` ${chalk9.magenta(agent.name || "?")}` + (agent.role ? chalk9.dim(` \u2014 ${agent.role}`) : "")
|
|
1596
1696
|
);
|
|
1597
1697
|
}
|
|
1598
1698
|
}
|
|
1599
1699
|
if (config.apps && config.apps.length > 0) {
|
|
1600
1700
|
console.log("");
|
|
1601
|
-
console.log(
|
|
1701
|
+
console.log(chalk9.bold(" Apps:"));
|
|
1602
1702
|
for (const app of config.apps) {
|
|
1603
1703
|
console.log(
|
|
1604
|
-
` ${
|
|
1704
|
+
` ${chalk9.green(app.name || "?")}` + (app.label ? chalk9.dim(` \u2014 ${app.label}`) : "")
|
|
1605
1705
|
);
|
|
1606
1706
|
}
|
|
1607
1707
|
}
|
|
1608
1708
|
console.log("");
|
|
1609
|
-
console.log(
|
|
1709
|
+
console.log(chalk9.dim(` Loaded in ${duration}ms`));
|
|
1610
1710
|
console.log("");
|
|
1611
1711
|
} catch (error) {
|
|
1612
1712
|
if (options.json) {
|
|
@@ -1621,9 +1721,9 @@ var infoCommand = new Command10("info").description("Display metadata summary of
|
|
|
1621
1721
|
|
|
1622
1722
|
// src/commands/generate.ts
|
|
1623
1723
|
import { Command as Command11 } from "commander";
|
|
1624
|
-
import
|
|
1625
|
-
import
|
|
1626
|
-
import
|
|
1724
|
+
import chalk10 from "chalk";
|
|
1725
|
+
import fs9 from "fs";
|
|
1726
|
+
import path9 from "path";
|
|
1627
1727
|
var GENERATORS = {
|
|
1628
1728
|
object: {
|
|
1629
1729
|
description: "Business data object",
|
|
@@ -1804,98 +1904,606 @@ function toTitleCase2(str) {
|
|
|
1804
1904
|
function toSnakeCase(str) {
|
|
1805
1905
|
return str.replace(/[-]/g, "_").replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`).replace(/^_/, "");
|
|
1806
1906
|
}
|
|
1807
|
-
var
|
|
1907
|
+
var FIELD_TYPE_MAP = {
|
|
1908
|
+
text: "string",
|
|
1909
|
+
textarea: "string",
|
|
1910
|
+
richtext: "string",
|
|
1911
|
+
html: "string",
|
|
1912
|
+
markdown: "string",
|
|
1913
|
+
number: "number",
|
|
1914
|
+
integer: "number",
|
|
1915
|
+
currency: "number",
|
|
1916
|
+
percent: "number",
|
|
1917
|
+
boolean: "boolean",
|
|
1918
|
+
date: "string",
|
|
1919
|
+
datetime: "string",
|
|
1920
|
+
time: "string",
|
|
1921
|
+
email: "string",
|
|
1922
|
+
phone: "string",
|
|
1923
|
+
url: "string",
|
|
1924
|
+
select: "string",
|
|
1925
|
+
multiselect: "string[]",
|
|
1926
|
+
lookup: "string",
|
|
1927
|
+
master_detail: "string",
|
|
1928
|
+
formula: "unknown",
|
|
1929
|
+
autonumber: "string",
|
|
1930
|
+
json: "Record<string, unknown>",
|
|
1931
|
+
file: "string",
|
|
1932
|
+
image: "string",
|
|
1933
|
+
password: "string",
|
|
1934
|
+
slug: "string",
|
|
1935
|
+
uuid: "string",
|
|
1936
|
+
ip_address: "string",
|
|
1937
|
+
color: "string",
|
|
1938
|
+
rating: "number",
|
|
1939
|
+
geo_point: "{ lat: number; lng: number }",
|
|
1940
|
+
vector: "number[]",
|
|
1941
|
+
encrypted: "string"
|
|
1942
|
+
};
|
|
1943
|
+
function fieldTypeToTs(fieldType, multiple) {
|
|
1944
|
+
const base = FIELD_TYPE_MAP[fieldType] || "unknown";
|
|
1945
|
+
return multiple ? `${base}[]` : base;
|
|
1946
|
+
}
|
|
1947
|
+
function generateTypesFromConfig(config) {
|
|
1948
|
+
const lines = [
|
|
1949
|
+
"// Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
1950
|
+
`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1951
|
+
"",
|
|
1952
|
+
"import type { Data } from '@objectstack/spec';",
|
|
1953
|
+
""
|
|
1954
|
+
];
|
|
1955
|
+
const objects = [];
|
|
1956
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
1957
|
+
if (Array.isArray(rawObjects)) {
|
|
1958
|
+
objects.push(...rawObjects);
|
|
1959
|
+
} else if (typeof rawObjects === "object") {
|
|
1960
|
+
for (const val of Object.values(rawObjects)) {
|
|
1961
|
+
if (val && typeof val === "object") objects.push(val);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
if (objects.length === 0) {
|
|
1965
|
+
lines.push("// No objects found in configuration");
|
|
1966
|
+
return lines.join("\n") + "\n";
|
|
1967
|
+
}
|
|
1968
|
+
for (const obj of objects) {
|
|
1969
|
+
const name = String(obj.name || "unknown");
|
|
1970
|
+
const typeName = name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
1971
|
+
const fields = obj.fields ?? {};
|
|
1972
|
+
lines.push(`/** ${String(obj.label || typeName)} record type */`);
|
|
1973
|
+
lines.push(`export interface ${typeName}Record {`);
|
|
1974
|
+
lines.push(" id: string;");
|
|
1975
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
1976
|
+
const fType = String(fieldDef.type || "text");
|
|
1977
|
+
const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);
|
|
1978
|
+
const required = fieldDef.required ? "" : "?";
|
|
1979
|
+
if (fieldDef.label) {
|
|
1980
|
+
lines.push(` /** ${fieldDef.label} */`);
|
|
1981
|
+
}
|
|
1982
|
+
lines.push(` ${fieldName}${required}: ${tsType};`);
|
|
1983
|
+
}
|
|
1984
|
+
lines.push("}");
|
|
1985
|
+
lines.push("");
|
|
1986
|
+
}
|
|
1987
|
+
return lines.join("\n") + "\n";
|
|
1988
|
+
}
|
|
1989
|
+
var generateMetadataCommand = new Command11("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) => {
|
|
1808
1990
|
printHeader("Generate");
|
|
1809
1991
|
const generator = GENERATORS[type];
|
|
1810
1992
|
if (!generator) {
|
|
1811
1993
|
printError(`Unknown type: ${type}`);
|
|
1812
1994
|
console.log("");
|
|
1813
|
-
console.log(
|
|
1995
|
+
console.log(chalk10.bold(" Available types:"));
|
|
1814
1996
|
for (const [key, gen] of Object.entries(GENERATORS)) {
|
|
1815
|
-
console.log(` ${
|
|
1997
|
+
console.log(` ${chalk10.cyan(key.padEnd(12))} ${chalk10.dim(gen.description)}`);
|
|
1816
1998
|
}
|
|
1817
1999
|
console.log("");
|
|
1818
|
-
console.log(
|
|
1819
|
-
console.log(
|
|
1820
|
-
console.log(
|
|
2000
|
+
console.log(chalk10.dim(" Usage: objectstack generate <type> <name>"));
|
|
2001
|
+
console.log(chalk10.dim(" Example: objectstack generate object project"));
|
|
2002
|
+
console.log(chalk10.dim(" Alias: os g object project"));
|
|
1821
2003
|
process.exit(1);
|
|
1822
2004
|
}
|
|
1823
2005
|
const dir = options.dir || generator.defaultDir;
|
|
1824
2006
|
const fileName = `${toSnakeCase(name)}.ts`;
|
|
1825
|
-
const filePath =
|
|
1826
|
-
console.log(` ${
|
|
1827
|
-
console.log(` ${
|
|
1828
|
-
console.log(` ${
|
|
2007
|
+
const filePath = path9.join(process.cwd(), dir, fileName);
|
|
2008
|
+
console.log(` ${chalk10.dim("Type:")} ${chalk10.cyan(type)} \u2014 ${generator.description}`);
|
|
2009
|
+
console.log(` ${chalk10.dim("Name:")} ${chalk10.white(name)}`);
|
|
2010
|
+
console.log(` ${chalk10.dim("File:")} ${chalk10.white(path9.join(dir, fileName))}`);
|
|
1829
2011
|
console.log("");
|
|
1830
2012
|
if (options.dryRun) {
|
|
1831
2013
|
printInfo("Dry run \u2014 no files written");
|
|
1832
2014
|
console.log("");
|
|
1833
|
-
console.log(
|
|
1834
|
-
console.log(
|
|
2015
|
+
console.log(chalk10.dim(" Content:"));
|
|
2016
|
+
console.log(chalk10.dim(" " + "-".repeat(38)));
|
|
1835
2017
|
const content = generator.generate(name);
|
|
1836
2018
|
for (const line of content.split("\n")) {
|
|
1837
|
-
console.log(
|
|
2019
|
+
console.log(chalk10.dim(` ${line}`));
|
|
1838
2020
|
}
|
|
1839
2021
|
console.log("");
|
|
1840
2022
|
return;
|
|
1841
2023
|
}
|
|
1842
|
-
if (
|
|
2024
|
+
if (fs9.existsSync(filePath)) {
|
|
1843
2025
|
printError(`File already exists: ${filePath}`);
|
|
1844
2026
|
process.exit(1);
|
|
1845
2027
|
}
|
|
1846
2028
|
try {
|
|
1847
|
-
const fullDir =
|
|
1848
|
-
if (!
|
|
1849
|
-
|
|
2029
|
+
const fullDir = path9.dirname(filePath);
|
|
2030
|
+
if (!fs9.existsSync(fullDir)) {
|
|
2031
|
+
fs9.mkdirSync(fullDir, { recursive: true });
|
|
1850
2032
|
}
|
|
1851
2033
|
const content = generator.generate(name);
|
|
1852
|
-
|
|
1853
|
-
printSuccess(`Created ${
|
|
1854
|
-
const indexPath =
|
|
1855
|
-
if (
|
|
1856
|
-
const indexContent =
|
|
2034
|
+
fs9.writeFileSync(filePath, content);
|
|
2035
|
+
printSuccess(`Created ${path9.join(dir, fileName)}`);
|
|
2036
|
+
const indexPath = path9.join(process.cwd(), dir, "index.ts");
|
|
2037
|
+
if (fs9.existsSync(indexPath)) {
|
|
2038
|
+
const indexContent = fs9.readFileSync(indexPath, "utf-8");
|
|
1857
2039
|
const exportLine = `export { default as ${toCamelCase3(name)} } from './${toSnakeCase(name)}';`;
|
|
1858
2040
|
if (!indexContent.includes(toCamelCase3(name))) {
|
|
1859
|
-
|
|
2041
|
+
fs9.appendFileSync(indexPath, exportLine + "\n");
|
|
1860
2042
|
printSuccess(`Updated ${dir}/index.ts with export`);
|
|
1861
2043
|
}
|
|
1862
2044
|
} else {
|
|
1863
2045
|
const exportLine = `export { default as ${toCamelCase3(name)} } from './${toSnakeCase(name)}';
|
|
1864
2046
|
`;
|
|
1865
|
-
|
|
2047
|
+
fs9.writeFileSync(indexPath, exportLine);
|
|
1866
2048
|
printSuccess(`Created ${dir}/index.ts`);
|
|
1867
2049
|
}
|
|
1868
2050
|
console.log("");
|
|
1869
|
-
console.log(
|
|
2051
|
+
console.log(chalk10.dim(` Tip: Run \`objectstack validate\` to check your config`));
|
|
1870
2052
|
console.log("");
|
|
1871
2053
|
} catch (error) {
|
|
1872
2054
|
printError(error.message || String(error));
|
|
1873
2055
|
process.exit(1);
|
|
1874
2056
|
}
|
|
1875
2057
|
});
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
const
|
|
1886
|
-
if (
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2058
|
+
var generateTypesCommand = new Command11("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) => {
|
|
2059
|
+
printHeader("Generate Types");
|
|
2060
|
+
try {
|
|
2061
|
+
const { loadConfig: loadConfig2 } = await import("./config-A7BN6UIT.js");
|
|
2062
|
+
printInfo("Loading configuration...");
|
|
2063
|
+
const { config, absolutePath } = await loadConfig2(configPath);
|
|
2064
|
+
console.log(` ${chalk10.dim("Config:")} ${chalk10.white(absolutePath)}`);
|
|
2065
|
+
console.log(` ${chalk10.dim("Output:")} ${chalk10.white(options.output)}`);
|
|
2066
|
+
console.log("");
|
|
2067
|
+
const content = generateTypesFromConfig(config);
|
|
2068
|
+
if (options.dryRun) {
|
|
2069
|
+
printInfo("Dry run \u2014 no files written");
|
|
2070
|
+
console.log("");
|
|
2071
|
+
for (const line of content.split("\n")) {
|
|
2072
|
+
console.log(chalk10.dim(` ${line}`));
|
|
2073
|
+
}
|
|
2074
|
+
console.log("");
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
const outPath = path9.resolve(process.cwd(), options.output);
|
|
2078
|
+
const outDir = path9.dirname(outPath);
|
|
2079
|
+
if (!fs9.existsSync(outDir)) {
|
|
2080
|
+
fs9.mkdirSync(outDir, { recursive: true });
|
|
2081
|
+
}
|
|
2082
|
+
fs9.writeFileSync(outPath, content);
|
|
2083
|
+
printSuccess(`Generated types at ${options.output}`);
|
|
2084
|
+
console.log("");
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
printError(error.message || String(error));
|
|
2087
|
+
process.exit(1);
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
function generateClientFromConfig(config) {
|
|
2091
|
+
const lines = [
|
|
2092
|
+
"// Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
2093
|
+
`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2094
|
+
"",
|
|
2095
|
+
"import type { Data } from '@objectstack/spec';",
|
|
2096
|
+
""
|
|
2097
|
+
];
|
|
2098
|
+
const objects = [];
|
|
2099
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
2100
|
+
if (Array.isArray(rawObjects)) {
|
|
2101
|
+
objects.push(...rawObjects);
|
|
2102
|
+
} else if (typeof rawObjects === "object") {
|
|
2103
|
+
for (const val of Object.values(rawObjects)) {
|
|
2104
|
+
if (val && typeof val === "object") objects.push(val);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (objects.length === 0) {
|
|
2108
|
+
lines.push("// No objects found in configuration");
|
|
2109
|
+
return lines.join("\n") + "\n";
|
|
2110
|
+
}
|
|
2111
|
+
for (const obj of objects) {
|
|
2112
|
+
const name = String(obj.name || "unknown");
|
|
2113
|
+
const typeName = name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
2114
|
+
const fields = obj.fields ?? {};
|
|
2115
|
+
lines.push(`export interface ${typeName}Record {`);
|
|
2116
|
+
lines.push(" id: string;");
|
|
2117
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
2118
|
+
const fType = String(fieldDef.type || "text");
|
|
2119
|
+
const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);
|
|
2120
|
+
const required = fieldDef.required ? "" : "?";
|
|
2121
|
+
lines.push(` ${fieldName}${required}: ${tsType};`);
|
|
2122
|
+
}
|
|
2123
|
+
lines.push("}");
|
|
2124
|
+
lines.push("");
|
|
2125
|
+
}
|
|
2126
|
+
lines.push("export class ObjectStackClient {");
|
|
2127
|
+
lines.push(" constructor(private baseUrl: string, private headers: Record<string, string> = {}) {}");
|
|
2128
|
+
lines.push("");
|
|
2129
|
+
lines.push(" private async request<T>(method: string, path: string, body?: unknown): Promise<T> {");
|
|
2130
|
+
lines.push(" const res = await fetch(`${this.baseUrl}${path}`, {");
|
|
2131
|
+
lines.push(" method,");
|
|
2132
|
+
lines.push(" headers: { 'Content-Type': 'application/json', ...this.headers },");
|
|
2133
|
+
lines.push(" body: body ? JSON.stringify(body) : undefined,");
|
|
2134
|
+
lines.push(" });");
|
|
2135
|
+
lines.push(" if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);");
|
|
2136
|
+
lines.push(" return res.json() as Promise<T>;");
|
|
2137
|
+
lines.push(" }");
|
|
2138
|
+
for (const obj of objects) {
|
|
2139
|
+
const name = String(obj.name || "unknown");
|
|
2140
|
+
const typeName = name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
2141
|
+
const endpoint = `/api/${name}`;
|
|
2142
|
+
lines.push("");
|
|
2143
|
+
lines.push(` async list${typeName}(): Promise<${typeName}Record[]> {`);
|
|
2144
|
+
lines.push(` return this.request<${typeName}Record[]>('GET', '${endpoint}');`);
|
|
2145
|
+
lines.push(" }");
|
|
2146
|
+
lines.push("");
|
|
2147
|
+
lines.push(` async get${typeName}(id: string): Promise<${typeName}Record> {`);
|
|
2148
|
+
lines.push(` return this.request<${typeName}Record>('GET', '${endpoint}/\${id}');`);
|
|
2149
|
+
lines.push(" }");
|
|
2150
|
+
lines.push("");
|
|
2151
|
+
lines.push(` async create${typeName}(data: Omit<${typeName}Record, 'id'>): Promise<${typeName}Record> {`);
|
|
2152
|
+
lines.push(` return this.request<${typeName}Record>('POST', '${endpoint}', data);`);
|
|
2153
|
+
lines.push(" }");
|
|
2154
|
+
lines.push("");
|
|
2155
|
+
lines.push(` async update${typeName}(id: string, data: Partial<${typeName}Record>): Promise<${typeName}Record> {`);
|
|
2156
|
+
lines.push(` return this.request<${typeName}Record>('PATCH', '${endpoint}/\${id}', data);`);
|
|
2157
|
+
lines.push(" }");
|
|
2158
|
+
lines.push("");
|
|
2159
|
+
lines.push(` async delete${typeName}(id: string): Promise<void> {`);
|
|
2160
|
+
lines.push(` return this.request<void>('DELETE', '${endpoint}/\${id}');`);
|
|
2161
|
+
lines.push(" }");
|
|
2162
|
+
}
|
|
2163
|
+
lines.push("}");
|
|
2164
|
+
lines.push("");
|
|
2165
|
+
return lines.join("\n") + "\n";
|
|
2166
|
+
}
|
|
2167
|
+
var generateClientCommand = new Command11("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) => {
|
|
2168
|
+
printHeader("Generate Client SDK");
|
|
2169
|
+
try {
|
|
2170
|
+
const { loadConfig: loadConfig2 } = await import("./config-A7BN6UIT.js");
|
|
2171
|
+
const timer = createTimer();
|
|
2172
|
+
printInfo("Loading configuration...");
|
|
2173
|
+
const { config, absolutePath } = await loadConfig2(configPath);
|
|
2174
|
+
console.log(` ${chalk10.dim("Config:")} ${chalk10.white(absolutePath)}`);
|
|
2175
|
+
console.log(` ${chalk10.dim("Output:")} ${chalk10.white(options.output)}`);
|
|
2176
|
+
console.log("");
|
|
2177
|
+
printStep("Generating client SDK...");
|
|
2178
|
+
const content = generateClientFromConfig(config);
|
|
2179
|
+
if (options.dryRun) {
|
|
2180
|
+
printInfo("Dry run \u2014 no files written");
|
|
2181
|
+
console.log("");
|
|
2182
|
+
for (const line of content.split("\n")) {
|
|
2183
|
+
console.log(chalk10.dim(` ${line}`));
|
|
2184
|
+
}
|
|
2185
|
+
console.log("");
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
const outPath = path9.resolve(process.cwd(), options.output);
|
|
2189
|
+
const outDir = path9.dirname(outPath);
|
|
2190
|
+
if (!fs9.existsSync(outDir)) {
|
|
2191
|
+
fs9.mkdirSync(outDir, { recursive: true });
|
|
2192
|
+
}
|
|
2193
|
+
fs9.writeFileSync(outPath, content);
|
|
2194
|
+
printSuccess(`Generated client SDK at ${options.output} (${timer.display()})`);
|
|
2195
|
+
console.log("");
|
|
2196
|
+
} catch (error) {
|
|
2197
|
+
printError(error.message || String(error));
|
|
2198
|
+
process.exit(1);
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
var FIELD_TYPE_SQL_MAP = {
|
|
2202
|
+
text: "VARCHAR(255)",
|
|
2203
|
+
textarea: "TEXT",
|
|
2204
|
+
richtext: "TEXT",
|
|
2205
|
+
html: "TEXT",
|
|
2206
|
+
markdown: "TEXT",
|
|
2207
|
+
number: "DECIMAL(18,2)",
|
|
2208
|
+
integer: "INTEGER",
|
|
2209
|
+
currency: "DECIMAL(18,2)",
|
|
2210
|
+
percent: "DECIMAL(5,2)",
|
|
2211
|
+
boolean: "BOOLEAN",
|
|
2212
|
+
date: "DATE",
|
|
2213
|
+
datetime: "TIMESTAMP",
|
|
2214
|
+
time: "TIME",
|
|
2215
|
+
email: "VARCHAR(255)",
|
|
2216
|
+
phone: "VARCHAR(50)",
|
|
2217
|
+
url: "VARCHAR(2048)",
|
|
2218
|
+
select: "VARCHAR(255)",
|
|
2219
|
+
multiselect: "TEXT",
|
|
2220
|
+
lookup: "VARCHAR(36)",
|
|
2221
|
+
master_detail: "VARCHAR(36)",
|
|
2222
|
+
formula: "TEXT",
|
|
2223
|
+
autonumber: "SERIAL",
|
|
2224
|
+
json: "JSONB",
|
|
2225
|
+
file: "VARCHAR(2048)",
|
|
2226
|
+
image: "VARCHAR(2048)",
|
|
2227
|
+
password: "VARCHAR(255)",
|
|
2228
|
+
slug: "VARCHAR(255)",
|
|
2229
|
+
uuid: "UUID",
|
|
2230
|
+
ip_address: "VARCHAR(45)",
|
|
2231
|
+
color: "VARCHAR(7)",
|
|
2232
|
+
rating: "INTEGER",
|
|
2233
|
+
geo_point: "POINT",
|
|
2234
|
+
vector: "VECTOR",
|
|
2235
|
+
encrypted: "TEXT"
|
|
2236
|
+
};
|
|
2237
|
+
function fieldTypeToSql(fieldType) {
|
|
2238
|
+
return FIELD_TYPE_SQL_MAP[fieldType] || "TEXT";
|
|
2239
|
+
}
|
|
2240
|
+
function generateMigrationSql(config) {
|
|
2241
|
+
const lines = [
|
|
2242
|
+
"-- Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
2243
|
+
`-- Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2244
|
+
""
|
|
2245
|
+
];
|
|
2246
|
+
const objects = [];
|
|
2247
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
2248
|
+
if (Array.isArray(rawObjects)) {
|
|
2249
|
+
objects.push(...rawObjects);
|
|
2250
|
+
} else if (typeof rawObjects === "object") {
|
|
2251
|
+
for (const val of Object.values(rawObjects)) {
|
|
2252
|
+
if (val && typeof val === "object") objects.push(val);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (objects.length === 0) {
|
|
2256
|
+
lines.push("-- No objects found in configuration");
|
|
2257
|
+
return lines.join("\n") + "\n";
|
|
2258
|
+
}
|
|
2259
|
+
for (const obj of objects) {
|
|
2260
|
+
const tableName = String(obj.name || "unknown");
|
|
2261
|
+
const fields = obj.fields ?? {};
|
|
2262
|
+
lines.push(`CREATE TABLE IF NOT EXISTS "${tableName}" (`);
|
|
2263
|
+
lines.push(' "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),');
|
|
2264
|
+
const fieldLines = [];
|
|
2265
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
2266
|
+
const sqlType = fieldTypeToSql(String(fieldDef.type || "text"));
|
|
2267
|
+
const notNull = fieldDef.required ? " NOT NULL" : "";
|
|
2268
|
+
fieldLines.push(` "${fieldName}" ${sqlType}${notNull}`);
|
|
2269
|
+
}
|
|
2270
|
+
fieldLines.push(' "created_at" TIMESTAMP NOT NULL DEFAULT now()');
|
|
2271
|
+
fieldLines.push(' "updated_at" TIMESTAMP NOT NULL DEFAULT now()');
|
|
2272
|
+
lines.push(fieldLines.join(",\n"));
|
|
2273
|
+
lines.push(");");
|
|
2274
|
+
lines.push("");
|
|
2275
|
+
}
|
|
2276
|
+
return lines.join("\n") + "\n";
|
|
2277
|
+
}
|
|
2278
|
+
function generateMigrationTs(config) {
|
|
2279
|
+
const lines = [
|
|
2280
|
+
"// Auto-generated by ObjectStack CLI \u2014 do not edit manually",
|
|
2281
|
+
`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2282
|
+
"",
|
|
2283
|
+
"export async function up(db: any): Promise<void> {"
|
|
2284
|
+
];
|
|
2285
|
+
const objects = [];
|
|
2286
|
+
const rawObjects = config.objects ?? config.data?.objects ?? {};
|
|
2287
|
+
if (Array.isArray(rawObjects)) {
|
|
2288
|
+
objects.push(...rawObjects);
|
|
2289
|
+
} else if (typeof rawObjects === "object") {
|
|
2290
|
+
for (const val of Object.values(rawObjects)) {
|
|
2291
|
+
if (val && typeof val === "object") objects.push(val);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
if (objects.length === 0) {
|
|
2295
|
+
lines.push(" // No objects found in configuration");
|
|
2296
|
+
lines.push("}");
|
|
2297
|
+
lines.push("");
|
|
2298
|
+
lines.push("export async function down(db: any): Promise<void> {");
|
|
2299
|
+
lines.push(" // No objects found in configuration");
|
|
2300
|
+
lines.push("}");
|
|
2301
|
+
return lines.join("\n") + "\n";
|
|
2302
|
+
}
|
|
2303
|
+
for (const obj of objects) {
|
|
2304
|
+
const tableName = String(obj.name || "unknown");
|
|
2305
|
+
const fields = obj.fields ?? {};
|
|
2306
|
+
lines.push(` await db.schema.createTable('${tableName}', (table: any) => {`);
|
|
2307
|
+
lines.push(" table.uuid('id').primary().defaultTo(db.fn.uuid());");
|
|
2308
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
2309
|
+
const fType = String(fieldDef.type || "text");
|
|
2310
|
+
const required = fieldDef.required ? ".notNullable()" : ".nullable()";
|
|
2311
|
+
let colMethod;
|
|
2312
|
+
switch (fType) {
|
|
2313
|
+
case "text":
|
|
2314
|
+
case "email":
|
|
2315
|
+
case "phone":
|
|
2316
|
+
case "url":
|
|
2317
|
+
case "select":
|
|
2318
|
+
case "slug":
|
|
2319
|
+
case "password":
|
|
2320
|
+
case "color":
|
|
2321
|
+
case "ip_address":
|
|
2322
|
+
colMethod = `table.string('${fieldName}')`;
|
|
2323
|
+
break;
|
|
2324
|
+
case "textarea":
|
|
2325
|
+
case "richtext":
|
|
2326
|
+
case "html":
|
|
2327
|
+
case "markdown":
|
|
2328
|
+
case "formula":
|
|
2329
|
+
case "encrypted":
|
|
2330
|
+
colMethod = `table.text('${fieldName}')`;
|
|
2331
|
+
break;
|
|
2332
|
+
case "number":
|
|
2333
|
+
case "currency":
|
|
2334
|
+
case "percent":
|
|
2335
|
+
colMethod = `table.decimal('${fieldName}')`;
|
|
2336
|
+
break;
|
|
2337
|
+
case "integer":
|
|
2338
|
+
case "rating":
|
|
2339
|
+
colMethod = `table.integer('${fieldName}')`;
|
|
2340
|
+
break;
|
|
2341
|
+
case "boolean":
|
|
2342
|
+
colMethod = `table.boolean('${fieldName}')`;
|
|
2343
|
+
break;
|
|
2344
|
+
case "date":
|
|
2345
|
+
colMethod = `table.date('${fieldName}')`;
|
|
2346
|
+
break;
|
|
2347
|
+
case "datetime":
|
|
2348
|
+
colMethod = `table.timestamp('${fieldName}')`;
|
|
2349
|
+
break;
|
|
2350
|
+
case "time":
|
|
2351
|
+
colMethod = `table.time('${fieldName}')`;
|
|
2352
|
+
break;
|
|
2353
|
+
case "json":
|
|
2354
|
+
case "multiselect":
|
|
2355
|
+
colMethod = `table.jsonb('${fieldName}')`;
|
|
2356
|
+
break;
|
|
2357
|
+
case "uuid":
|
|
2358
|
+
case "lookup":
|
|
2359
|
+
case "master_detail":
|
|
2360
|
+
colMethod = `table.uuid('${fieldName}')`;
|
|
2361
|
+
break;
|
|
2362
|
+
default:
|
|
2363
|
+
colMethod = `table.text('${fieldName}')`;
|
|
2364
|
+
}
|
|
2365
|
+
lines.push(` ${colMethod}${required};`);
|
|
2366
|
+
}
|
|
2367
|
+
lines.push(" table.timestamps(true, true);");
|
|
2368
|
+
lines.push(" });");
|
|
2369
|
+
}
|
|
2370
|
+
lines.push("}");
|
|
2371
|
+
lines.push("");
|
|
2372
|
+
lines.push("export async function down(db: any): Promise<void> {");
|
|
2373
|
+
const tableNames = objects.map((o) => String(o.name || "unknown")).reverse();
|
|
2374
|
+
for (const tableName of tableNames) {
|
|
2375
|
+
lines.push(` await db.schema.dropTableIfExists('${tableName}');`);
|
|
2376
|
+
}
|
|
2377
|
+
lines.push("}");
|
|
2378
|
+
return lines.join("\n") + "\n";
|
|
2379
|
+
}
|
|
2380
|
+
var generateMigrationCommand = new Command11("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) => {
|
|
2381
|
+
printHeader("Generate Migration");
|
|
2382
|
+
try {
|
|
2383
|
+
const { loadConfig: loadConfig2 } = await import("./config-A7BN6UIT.js");
|
|
2384
|
+
const timer = createTimer();
|
|
2385
|
+
printInfo("Loading configuration...");
|
|
2386
|
+
const { config, absolutePath } = await loadConfig2(configPath);
|
|
2387
|
+
const ext = options.format === "sql" ? "sql" : "ts";
|
|
2388
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
|
2389
|
+
const defaultOutput = `migrations/${timestamp}_migration.${ext}`;
|
|
2390
|
+
const output = options.output || defaultOutput;
|
|
2391
|
+
console.log(` ${chalk10.dim("Config:")} ${chalk10.white(absolutePath)}`);
|
|
2392
|
+
console.log(` ${chalk10.dim("Format:")} ${chalk10.white(options.format)}`);
|
|
2393
|
+
console.log(` ${chalk10.dim("Output:")} ${chalk10.white(output)}`);
|
|
2394
|
+
console.log("");
|
|
2395
|
+
printStep("Generating migration...");
|
|
2396
|
+
const content = options.format === "sql" ? generateMigrationSql(config) : generateMigrationTs(config);
|
|
2397
|
+
if (options.dryRun) {
|
|
2398
|
+
printInfo("Dry run \u2014 no files written");
|
|
2399
|
+
console.log("");
|
|
2400
|
+
for (const line of content.split("\n")) {
|
|
2401
|
+
console.log(chalk10.dim(` ${line}`));
|
|
2402
|
+
}
|
|
2403
|
+
console.log("");
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
const outPath = path9.resolve(process.cwd(), output);
|
|
2407
|
+
const outDir = path9.dirname(outPath);
|
|
2408
|
+
if (!fs9.existsSync(outDir)) {
|
|
2409
|
+
fs9.mkdirSync(outDir, { recursive: true });
|
|
2410
|
+
}
|
|
2411
|
+
fs9.writeFileSync(outPath, content);
|
|
2412
|
+
printSuccess(`Generated migration at ${output} (${timer.display()})`);
|
|
2413
|
+
console.log("");
|
|
2414
|
+
} catch (error) {
|
|
2415
|
+
printError(error.message || String(error));
|
|
2416
|
+
process.exit(1);
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
var generateSchemaCommand = new Command11("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) => {
|
|
2420
|
+
printHeader("Generate Schema");
|
|
2421
|
+
try {
|
|
2422
|
+
const timer = createTimer();
|
|
2423
|
+
printStep("Loading ObjectStackDefinitionSchema...");
|
|
2424
|
+
const { z } = await import("zod");
|
|
2425
|
+
const { ObjectStackDefinitionSchema: ObjectStackDefinitionSchema3 } = await import("@objectstack/spec");
|
|
2426
|
+
printStep("Converting to JSON Schema...");
|
|
2427
|
+
const jsonSchema = z.toJSONSchema(ObjectStackDefinitionSchema3, {
|
|
2428
|
+
target: "draft-2020-12"
|
|
2429
|
+
});
|
|
2430
|
+
const schema = {
|
|
2431
|
+
...jsonSchema,
|
|
2432
|
+
$id: "https://schema.objectstack.io/objectstack.config.json",
|
|
2433
|
+
title: "ObjectStack Configuration",
|
|
2434
|
+
description: "JSON Schema for objectstack.config.ts \u2014 generated from ObjectStackDefinitionSchema"
|
|
2435
|
+
};
|
|
2436
|
+
const content = JSON.stringify(schema, null, 2) + "\n";
|
|
2437
|
+
if (options.dryRun) {
|
|
2438
|
+
printInfo("Dry run \u2014 no files written");
|
|
2439
|
+
console.log("");
|
|
2440
|
+
console.log(content);
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
const outPath = path9.resolve(process.cwd(), options.output);
|
|
2444
|
+
const outDir = path9.dirname(outPath);
|
|
2445
|
+
if (!fs9.existsSync(outDir)) {
|
|
2446
|
+
fs9.mkdirSync(outDir, { recursive: true });
|
|
2447
|
+
}
|
|
2448
|
+
fs9.writeFileSync(outPath, content);
|
|
2449
|
+
printSuccess(`Generated JSON Schema at ${options.output} (${timer.display()})`);
|
|
2450
|
+
console.log("");
|
|
2451
|
+
console.log(chalk10.dim(" Usage: Reference in your IDE or editor for autocomplete"));
|
|
2452
|
+
console.log(chalk10.dim(` Path: ${outPath}`));
|
|
2453
|
+
console.log("");
|
|
2454
|
+
} catch (error) {
|
|
2455
|
+
printError(error.message || String(error));
|
|
2456
|
+
process.exit(1);
|
|
2457
|
+
}
|
|
2458
|
+
});
|
|
2459
|
+
var generateCommand = new Command11("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) => {
|
|
2460
|
+
if (!type) {
|
|
2461
|
+
printHeader("Generate");
|
|
2462
|
+
console.log(chalk10.bold(" Sub-commands:"));
|
|
2463
|
+
console.log(` ${chalk10.cyan("types".padEnd(12))} Generate TypeScript type definitions from config`);
|
|
2464
|
+
console.log(` ${chalk10.cyan("client".padEnd(12))} Generate a type-safe client SDK from config`);
|
|
2465
|
+
console.log(` ${chalk10.cyan("migration".padEnd(12))} Generate database migration from schema`);
|
|
2466
|
+
console.log(` ${chalk10.cyan("schema".padEnd(12))} Generate JSON Schema for objectstack.config.ts (IDE autocomplete)`);
|
|
2467
|
+
console.log("");
|
|
2468
|
+
console.log(chalk10.bold(" Metadata types:"));
|
|
2469
|
+
for (const [key, gen] of Object.entries(GENERATORS)) {
|
|
2470
|
+
console.log(` ${chalk10.cyan(key.padEnd(12))} ${chalk10.dim(gen.description)}`);
|
|
2471
|
+
}
|
|
2472
|
+
console.log("");
|
|
2473
|
+
console.log(chalk10.dim(" Usage: objectstack generate <type> <name>"));
|
|
2474
|
+
console.log(chalk10.dim(" Usage: objectstack generate types [config]"));
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
if (!name) {
|
|
2478
|
+
printError("Missing required argument: <name>");
|
|
2479
|
+
console.log(chalk10.dim(" Usage: objectstack generate <type> <name>"));
|
|
2480
|
+
process.exit(1);
|
|
2481
|
+
}
|
|
2482
|
+
await generateMetadataCommand.parseAsync([type, name, ...process.argv.slice(4)], { from: "user" });
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
// src/commands/plugin.ts
|
|
2486
|
+
import { Command as Command12 } from "commander";
|
|
2487
|
+
import chalk11 from "chalk";
|
|
2488
|
+
import fs10 from "fs";
|
|
2489
|
+
import path10 from "path";
|
|
2490
|
+
function resolvePluginName(plugin) {
|
|
2491
|
+
if (typeof plugin === "string") return plugin;
|
|
2492
|
+
if (plugin && typeof plugin === "object") {
|
|
2493
|
+
const p = plugin;
|
|
2494
|
+
if (typeof p.name === "string") return p.name;
|
|
2495
|
+
if (p.constructor && p.constructor.name !== "Object") return p.constructor.name;
|
|
2496
|
+
}
|
|
2497
|
+
return "unknown";
|
|
2498
|
+
}
|
|
2499
|
+
function resolvePluginVersion(plugin) {
|
|
2500
|
+
if (plugin && typeof plugin === "object") {
|
|
2501
|
+
const p = plugin;
|
|
2502
|
+
if (typeof p.version === "string") return p.version;
|
|
2503
|
+
}
|
|
2504
|
+
return "-";
|
|
2505
|
+
}
|
|
2506
|
+
function resolvePluginType(plugin) {
|
|
1899
2507
|
if (plugin && typeof plugin === "object") {
|
|
1900
2508
|
const p = plugin;
|
|
1901
2509
|
if (typeof p.type === "string") return p.type;
|
|
@@ -1903,7 +2511,7 @@ function resolvePluginType(plugin) {
|
|
|
1903
2511
|
return "standard";
|
|
1904
2512
|
}
|
|
1905
2513
|
function readConfigText(configPath) {
|
|
1906
|
-
return
|
|
2514
|
+
return fs10.readFileSync(configPath, "utf-8");
|
|
1907
2515
|
}
|
|
1908
2516
|
function addPluginToConfig(configPath, packageName) {
|
|
1909
2517
|
let content = readConfigText(configPath);
|
|
@@ -1945,7 +2553,7 @@ function addPluginToConfig(configPath, packageName) {
|
|
|
1945
2553
|
$2`
|
|
1946
2554
|
);
|
|
1947
2555
|
}
|
|
1948
|
-
|
|
2556
|
+
fs10.writeFileSync(configPath, content);
|
|
1949
2557
|
}
|
|
1950
2558
|
function removePluginFromConfig(configPath, pluginName) {
|
|
1951
2559
|
let content = readConfigText(configPath);
|
|
@@ -1967,7 +2575,7 @@ function removePluginFromConfig(configPath, pluginName) {
|
|
|
1967
2575
|
content = content.replace(pattern, "\n");
|
|
1968
2576
|
}
|
|
1969
2577
|
content = content.replace(/plugins\s*:\s*\[\s*\],?\n?/g, "");
|
|
1970
|
-
|
|
2578
|
+
fs10.writeFileSync(configPath, content);
|
|
1971
2579
|
}
|
|
1972
2580
|
function escapeRegex(str) {
|
|
1973
2581
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -1999,31 +2607,31 @@ var listCommand = new Command12("list").alias("ls").description("List plugins de
|
|
|
1999
2607
|
if (plugins.length === 0 && devPlugins.length === 0) {
|
|
2000
2608
|
printInfo("No plugins configured");
|
|
2001
2609
|
console.log("");
|
|
2002
|
-
console.log(
|
|
2003
|
-
console.log(
|
|
2610
|
+
console.log(chalk11.dim(" Hint: Add plugins to your objectstack.config.ts"));
|
|
2611
|
+
console.log(chalk11.dim(" Or run: os plugin add <package-name>"));
|
|
2004
2612
|
console.log("");
|
|
2005
2613
|
return;
|
|
2006
2614
|
}
|
|
2007
2615
|
if (plugins.length > 0) {
|
|
2008
|
-
console.log(
|
|
2616
|
+
console.log(chalk11.bold(`
|
|
2009
2617
|
Plugins (${plugins.length}):`));
|
|
2010
2618
|
for (const plugin of plugins) {
|
|
2011
2619
|
const name = resolvePluginName(plugin);
|
|
2012
2620
|
const version = resolvePluginVersion(plugin);
|
|
2013
2621
|
const type = resolvePluginType(plugin);
|
|
2014
2622
|
console.log(
|
|
2015
|
-
` ${
|
|
2623
|
+
` ${chalk11.cyan("\u25CF")} ${chalk11.white(name)}` + (version !== "-" ? chalk11.dim(` v${version}`) : "") + (type !== "standard" ? chalk11.dim(` [${type}]`) : "")
|
|
2016
2624
|
);
|
|
2017
2625
|
}
|
|
2018
2626
|
}
|
|
2019
2627
|
if (devPlugins.length > 0) {
|
|
2020
|
-
console.log(
|
|
2628
|
+
console.log(chalk11.bold(`
|
|
2021
2629
|
Dev Plugins (${devPlugins.length}):`));
|
|
2022
2630
|
for (const plugin of devPlugins) {
|
|
2023
2631
|
const name = resolvePluginName(plugin);
|
|
2024
2632
|
const version = resolvePluginVersion(plugin);
|
|
2025
2633
|
console.log(
|
|
2026
|
-
` ${
|
|
2634
|
+
` ${chalk11.yellow("\u25CF")} ${chalk11.white(name)}` + (version !== "-" ? chalk11.dim(` v${version}`) : "") + chalk11.dim(" [dev]")
|
|
2027
2635
|
);
|
|
2028
2636
|
}
|
|
2029
2637
|
}
|
|
@@ -2047,9 +2655,9 @@ var infoSubCommand = new Command12("info").description("Show detailed informatio
|
|
|
2047
2655
|
if (!found) {
|
|
2048
2656
|
printError(`Plugin '${name}' not found in configuration`);
|
|
2049
2657
|
console.log("");
|
|
2050
|
-
console.log(
|
|
2658
|
+
console.log(chalk11.dim(" Available plugins:"));
|
|
2051
2659
|
for (const p of allPlugins) {
|
|
2052
|
-
console.log(
|
|
2660
|
+
console.log(chalk11.dim(` - ${resolvePluginName(p)}`));
|
|
2053
2661
|
}
|
|
2054
2662
|
console.log("");
|
|
2055
2663
|
process.exit(1);
|
|
@@ -2085,15 +2693,15 @@ var addCommand = new Command12("add").description("Add a plugin to objectstack.c
|
|
|
2085
2693
|
try {
|
|
2086
2694
|
const configPath = resolveConfigPath(options?.config);
|
|
2087
2695
|
printHeader("Add Plugin");
|
|
2088
|
-
console.log(` ${
|
|
2089
|
-
console.log(` ${
|
|
2696
|
+
console.log(` ${chalk11.dim("Package:")} ${chalk11.white(packageName)}`);
|
|
2697
|
+
console.log(` ${chalk11.dim("Config:")} ${chalk11.white(path10.relative(process.cwd(), configPath))}`);
|
|
2090
2698
|
console.log("");
|
|
2091
2699
|
addPluginToConfig(configPath, packageName);
|
|
2092
|
-
printSuccess(`Added ${
|
|
2700
|
+
printSuccess(`Added ${chalk11.cyan(packageName)} to config`);
|
|
2093
2701
|
console.log("");
|
|
2094
|
-
console.log(
|
|
2095
|
-
console.log(
|
|
2096
|
-
console.log(
|
|
2702
|
+
console.log(chalk11.dim(" Next steps:"));
|
|
2703
|
+
console.log(chalk11.dim(` 1. Install the package: pnpm add ${packageName}`));
|
|
2704
|
+
console.log(chalk11.dim(" 2. Run: os validate"));
|
|
2097
2705
|
console.log("");
|
|
2098
2706
|
} catch (error) {
|
|
2099
2707
|
printError(error.message || String(error));
|
|
@@ -2104,13 +2712,13 @@ var removeCommand = new Command12("remove").alias("rm").description("Remove a pl
|
|
|
2104
2712
|
try {
|
|
2105
2713
|
const configPath = resolveConfigPath(options?.config);
|
|
2106
2714
|
printHeader("Remove Plugin");
|
|
2107
|
-
console.log(` ${
|
|
2108
|
-
console.log(` ${
|
|
2715
|
+
console.log(` ${chalk11.dim("Plugin:")} ${chalk11.white(pluginName)}`);
|
|
2716
|
+
console.log(` ${chalk11.dim("Config:")} ${chalk11.white(path10.relative(process.cwd(), configPath))}`);
|
|
2109
2717
|
console.log("");
|
|
2110
2718
|
removePluginFromConfig(configPath, pluginName);
|
|
2111
|
-
printSuccess(`Removed ${
|
|
2719
|
+
printSuccess(`Removed ${chalk11.cyan(pluginName)} from config`);
|
|
2112
2720
|
console.log("");
|
|
2113
|
-
console.log(
|
|
2721
|
+
console.log(chalk11.dim(" Tip: Run `pnpm remove " + pluginName + "` to uninstall the package"));
|
|
2114
2722
|
console.log("");
|
|
2115
2723
|
} catch (error) {
|
|
2116
2724
|
printError(error.message || String(error));
|
|
@@ -2119,8 +2727,897 @@ var removeCommand = new Command12("remove").alias("rm").description("Remove a pl
|
|
|
2119
2727
|
});
|
|
2120
2728
|
var pluginCommand = new Command12("plugin").description("Manage plugins (list, info, add, remove)").addCommand(listCommand).addCommand(infoSubCommand).addCommand(addCommand).addCommand(removeCommand);
|
|
2121
2729
|
|
|
2122
|
-
// src/
|
|
2730
|
+
// src/commands/diff.ts
|
|
2731
|
+
import { Command as Command13 } from "commander";
|
|
2732
|
+
import chalk12 from "chalk";
|
|
2733
|
+
function getNames(items) {
|
|
2734
|
+
const map = /* @__PURE__ */ new Map();
|
|
2735
|
+
if (!Array.isArray(items)) return map;
|
|
2736
|
+
for (const item of items) {
|
|
2737
|
+
if (item?.name) map.set(item.name, item);
|
|
2738
|
+
}
|
|
2739
|
+
return map;
|
|
2740
|
+
}
|
|
2741
|
+
function getFieldNames(obj) {
|
|
2742
|
+
if (!obj?.fields || typeof obj.fields !== "object") return [];
|
|
2743
|
+
return Object.keys(obj.fields);
|
|
2744
|
+
}
|
|
2745
|
+
function getFieldType(obj, fieldName) {
|
|
2746
|
+
return obj?.fields?.[fieldName]?.type;
|
|
2747
|
+
}
|
|
2748
|
+
function isFieldRequired(obj, fieldName) {
|
|
2749
|
+
return obj?.fields?.[fieldName]?.required === true;
|
|
2750
|
+
}
|
|
2751
|
+
function diffNamedArrays(beforeItems, afterItems, category, detectFieldChanges) {
|
|
2752
|
+
const entries = [];
|
|
2753
|
+
const beforeMap = getNames(beforeItems);
|
|
2754
|
+
const afterMap = getNames(afterItems);
|
|
2755
|
+
for (const [name] of beforeMap) {
|
|
2756
|
+
if (!afterMap.has(name)) {
|
|
2757
|
+
entries.push({
|
|
2758
|
+
type: "removed",
|
|
2759
|
+
category,
|
|
2760
|
+
name,
|
|
2761
|
+
breaking: true
|
|
2762
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
for (const [name] of afterMap) {
|
|
2766
|
+
if (!beforeMap.has(name)) {
|
|
2767
|
+
entries.push({
|
|
2768
|
+
type: "added",
|
|
2769
|
+
category,
|
|
2770
|
+
name,
|
|
2771
|
+
breaking: false
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
if (detectFieldChanges) {
|
|
2776
|
+
for (const [name, beforeObj] of beforeMap) {
|
|
2777
|
+
const afterObj = afterMap.get(name);
|
|
2778
|
+
if (!afterObj) continue;
|
|
2779
|
+
const beforeFields = getFieldNames(beforeObj);
|
|
2780
|
+
const afterFields = getFieldNames(afterObj);
|
|
2781
|
+
const beforeSet = new Set(beforeFields);
|
|
2782
|
+
const afterSet = new Set(afterFields);
|
|
2783
|
+
for (const f of beforeFields) {
|
|
2784
|
+
if (!afterSet.has(f)) {
|
|
2785
|
+
entries.push({
|
|
2786
|
+
type: "removed",
|
|
2787
|
+
category: `${category}.${name}.fields`,
|
|
2788
|
+
name: f,
|
|
2789
|
+
breaking: true,
|
|
2790
|
+
detail: "field removed"
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
for (const f of afterFields) {
|
|
2795
|
+
if (!beforeSet.has(f)) {
|
|
2796
|
+
const breaking = isFieldRequired(afterObj, f);
|
|
2797
|
+
entries.push({
|
|
2798
|
+
type: "added",
|
|
2799
|
+
category: `${category}.${name}.fields`,
|
|
2800
|
+
name: f,
|
|
2801
|
+
breaking,
|
|
2802
|
+
detail: breaking ? "required field added" : "optional field added"
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
for (const f of beforeFields) {
|
|
2807
|
+
if (!afterSet.has(f)) continue;
|
|
2808
|
+
const oldType = getFieldType(beforeObj, f);
|
|
2809
|
+
const newType = getFieldType(afterObj, f);
|
|
2810
|
+
if (oldType && newType && oldType !== newType) {
|
|
2811
|
+
entries.push({
|
|
2812
|
+
type: "modified",
|
|
2813
|
+
category: `${category}.${name}.fields`,
|
|
2814
|
+
name: f,
|
|
2815
|
+
breaking: true,
|
|
2816
|
+
detail: `type changed: ${oldType} \u2192 ${newType}`
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
if (beforeObj.label !== afterObj.label) {
|
|
2821
|
+
entries.push({
|
|
2822
|
+
type: "modified",
|
|
2823
|
+
category,
|
|
2824
|
+
name,
|
|
2825
|
+
breaking: false,
|
|
2826
|
+
detail: `label changed: "${beforeObj.label ?? "(none)"}" \u2192 "${afterObj.label ?? "(none)"}"`
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
return entries;
|
|
2832
|
+
}
|
|
2833
|
+
var diffCommand = new Command13("diff").description("Compare two ObjectStack configurations and detect breaking changes").argument("[before]", 'Path to the "before" config file').argument("[after]", 'Path to the "after" config file').option("--before <path>", 'Path to the "before" config (alternative)').option("--after <path>", 'Path to the "after" config (alternative)').option("--json", "Output as JSON").option("--breaking-only", "Show only breaking changes").action(async (beforeArg, afterArg, options) => {
|
|
2834
|
+
const timer = createTimer();
|
|
2835
|
+
const beforePath = beforeArg || options.before;
|
|
2836
|
+
const afterPath = afterArg || options.after;
|
|
2837
|
+
if (!beforePath || !afterPath) {
|
|
2838
|
+
printError("Two config file paths are required.");
|
|
2839
|
+
console.log("");
|
|
2840
|
+
console.log(chalk12.dim(" Usage: objectstack diff <before> <after>"));
|
|
2841
|
+
console.log(chalk12.dim(" or: objectstack diff --before path1 --after path2"));
|
|
2842
|
+
process.exit(1);
|
|
2843
|
+
}
|
|
2844
|
+
if (!options.json) {
|
|
2845
|
+
printHeader("Diff");
|
|
2846
|
+
printStep("Loading configurations...");
|
|
2847
|
+
}
|
|
2848
|
+
try {
|
|
2849
|
+
const { config: beforeConfig } = await loadConfig(beforePath);
|
|
2850
|
+
const { config: afterConfig } = await loadConfig(afterPath);
|
|
2851
|
+
if (!options.json) {
|
|
2852
|
+
printInfo(`Before: ${chalk12.white(beforePath)}`);
|
|
2853
|
+
printInfo(`After: ${chalk12.white(afterPath)}`);
|
|
2854
|
+
}
|
|
2855
|
+
const allDiffs = [];
|
|
2856
|
+
allDiffs.push(...diffNamedArrays(beforeConfig.objects, afterConfig.objects, "objects", true));
|
|
2857
|
+
const simpleCats = [
|
|
2858
|
+
{ key: "views", label: "views" },
|
|
2859
|
+
{ key: "flows", label: "flows" },
|
|
2860
|
+
{ key: "agents", label: "agents" },
|
|
2861
|
+
{ key: "apps", label: "apps" },
|
|
2862
|
+
{ key: "dashboards", label: "dashboards" },
|
|
2863
|
+
{ key: "actions", label: "actions" },
|
|
2864
|
+
{ key: "workflows", label: "workflows" },
|
|
2865
|
+
{ key: "apis", label: "apis" },
|
|
2866
|
+
{ key: "roles", label: "roles" }
|
|
2867
|
+
];
|
|
2868
|
+
for (const cat of simpleCats) {
|
|
2869
|
+
allDiffs.push(
|
|
2870
|
+
...diffNamedArrays(beforeConfig[cat.key], afterConfig[cat.key], cat.label, false)
|
|
2871
|
+
);
|
|
2872
|
+
}
|
|
2873
|
+
const diffs = options.breakingOnly ? allDiffs.filter((d) => d.breaking) : allDiffs;
|
|
2874
|
+
const breakingCount = allDiffs.filter((d) => d.breaking).length;
|
|
2875
|
+
if (options.json) {
|
|
2876
|
+
console.log(JSON.stringify({
|
|
2877
|
+
before: beforePath,
|
|
2878
|
+
after: afterPath,
|
|
2879
|
+
total: diffs.length,
|
|
2880
|
+
breaking: breakingCount,
|
|
2881
|
+
changes: diffs,
|
|
2882
|
+
duration: timer.elapsed()
|
|
2883
|
+
}, null, 2));
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
console.log("");
|
|
2887
|
+
if (diffs.length === 0) {
|
|
2888
|
+
printSuccess(options.breakingOnly ? "No breaking changes detected." : "No changes detected.");
|
|
2889
|
+
console.log("");
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2893
|
+
for (const d of diffs) {
|
|
2894
|
+
const key = d.category;
|
|
2895
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
2896
|
+
grouped.get(key).push(d);
|
|
2897
|
+
}
|
|
2898
|
+
for (const [category, items] of grouped) {
|
|
2899
|
+
console.log(` ${chalk12.bold(category)}`);
|
|
2900
|
+
for (const item of items) {
|
|
2901
|
+
const icon = item.type === "added" ? "+" : item.type === "removed" ? "-" : "~";
|
|
2902
|
+
const color = item.type === "added" ? chalk12.green : item.type === "removed" ? chalk12.red : chalk12.yellow;
|
|
2903
|
+
const breakingTag = item.breaking ? chalk12.bgRed.white(" BREAKING ") + " " : "";
|
|
2904
|
+
const detail = item.detail ? chalk12.dim(` (${item.detail})`) : "";
|
|
2905
|
+
console.log(` ${color(icon)} ${breakingTag}${color(item.name)}${detail}`);
|
|
2906
|
+
}
|
|
2907
|
+
console.log("");
|
|
2908
|
+
}
|
|
2909
|
+
if (breakingCount > 0) {
|
|
2910
|
+
printError(`${breakingCount} breaking change(s) detected`);
|
|
2911
|
+
} else {
|
|
2912
|
+
printSuccess("No breaking changes");
|
|
2913
|
+
}
|
|
2914
|
+
console.log(chalk12.dim(` ${diffs.length} total change(s) in ${timer.display()}`));
|
|
2915
|
+
console.log("");
|
|
2916
|
+
} catch (error) {
|
|
2917
|
+
if (options.json) {
|
|
2918
|
+
console.log(JSON.stringify({ error: error.message }));
|
|
2919
|
+
process.exit(1);
|
|
2920
|
+
}
|
|
2921
|
+
console.log("");
|
|
2922
|
+
printError(error.message || String(error));
|
|
2923
|
+
process.exit(1);
|
|
2924
|
+
}
|
|
2925
|
+
});
|
|
2926
|
+
|
|
2927
|
+
// src/commands/lint.ts
|
|
2928
|
+
import { Command as Command14 } from "commander";
|
|
2929
|
+
import chalk13 from "chalk";
|
|
2930
|
+
var SNAKE_CASE_RE = /^[a-z][a-z0-9_]*$/;
|
|
2931
|
+
function checkSnakeCase(value, path12, label) {
|
|
2932
|
+
if (!SNAKE_CASE_RE.test(value)) {
|
|
2933
|
+
return {
|
|
2934
|
+
severity: "error",
|
|
2935
|
+
rule: "naming/snake-case",
|
|
2936
|
+
message: `${label} "${value}" must be snake_case`,
|
|
2937
|
+
path: path12,
|
|
2938
|
+
fix: value.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase().replace(/^_/, "").replace(/-/g, "_")
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
return null;
|
|
2942
|
+
}
|
|
2943
|
+
function checkLabelExists(item, path12, kind) {
|
|
2944
|
+
if (!item.label) {
|
|
2945
|
+
return {
|
|
2946
|
+
severity: "error",
|
|
2947
|
+
rule: "required/label",
|
|
2948
|
+
message: `${kind} "${item.name || "?"}" is missing a label`,
|
|
2949
|
+
path: path12
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
return null;
|
|
2953
|
+
}
|
|
2954
|
+
function checkLabelCase(label, path12) {
|
|
2955
|
+
if (label && label[0] !== label[0].toUpperCase()) {
|
|
2956
|
+
return {
|
|
2957
|
+
severity: "warning",
|
|
2958
|
+
rule: "convention/label-case",
|
|
2959
|
+
message: `Label "${label}" should start with an uppercase letter`,
|
|
2960
|
+
path: path12,
|
|
2961
|
+
fix: label.charAt(0).toUpperCase() + label.slice(1)
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
return null;
|
|
2965
|
+
}
|
|
2966
|
+
function lintConfig(config) {
|
|
2967
|
+
const issues = [];
|
|
2968
|
+
const push = (issue) => {
|
|
2969
|
+
if (issue) issues.push(issue);
|
|
2970
|
+
};
|
|
2971
|
+
const objects = Array.isArray(config.objects) ? config.objects : [];
|
|
2972
|
+
for (let i = 0; i < objects.length; i++) {
|
|
2973
|
+
const obj = objects[i];
|
|
2974
|
+
const objPath = `objects[${i}]`;
|
|
2975
|
+
if (obj.name) {
|
|
2976
|
+
push(checkSnakeCase(obj.name, `${objPath}.name`, "Object name"));
|
|
2977
|
+
}
|
|
2978
|
+
push(checkLabelExists(obj, `${objPath}.label`, "Object"));
|
|
2979
|
+
if (obj.label) {
|
|
2980
|
+
push(checkLabelCase(obj.label, `${objPath}.label`));
|
|
2981
|
+
}
|
|
2982
|
+
if (obj.fields && typeof obj.fields === "object") {
|
|
2983
|
+
const fieldNames = Object.keys(obj.fields);
|
|
2984
|
+
if (fieldNames.length === 0) {
|
|
2985
|
+
issues.push({
|
|
2986
|
+
severity: "warning",
|
|
2987
|
+
rule: "structure/empty-fields",
|
|
2988
|
+
message: `Object "${obj.name || "?"}" has an empty fields map`,
|
|
2989
|
+
path: `${objPath}.fields`
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
for (const fieldName of fieldNames) {
|
|
2993
|
+
const field = obj.fields[fieldName];
|
|
2994
|
+
const fieldPath = `${objPath}.fields.${fieldName}`;
|
|
2995
|
+
push(checkSnakeCase(fieldName, fieldPath, "Field name"));
|
|
2996
|
+
if (field && typeof field === "object") {
|
|
2997
|
+
push(checkLabelExists({ ...field, name: fieldName }, `${fieldPath}.label`, "Field"));
|
|
2998
|
+
if (field.label) {
|
|
2999
|
+
push(checkLabelCase(field.label, `${fieldPath}.label`));
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
} else if (!obj.fields) {
|
|
3004
|
+
issues.push({
|
|
3005
|
+
severity: "error",
|
|
3006
|
+
rule: "structure/no-fields",
|
|
3007
|
+
message: `Object "${obj.name || "?"}" has no fields defined`,
|
|
3008
|
+
path: `${objPath}.fields`
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
const views = Array.isArray(config.views) ? config.views : [];
|
|
3013
|
+
for (let i = 0; i < views.length; i++) {
|
|
3014
|
+
const view = views[i];
|
|
3015
|
+
const viewPath = `views[${i}]`;
|
|
3016
|
+
if (view.name) {
|
|
3017
|
+
push(checkSnakeCase(view.name, `${viewPath}.name`, "View name"));
|
|
3018
|
+
}
|
|
3019
|
+
push(checkLabelExists(view, `${viewPath}.label`, "View"));
|
|
3020
|
+
if (view.label) {
|
|
3021
|
+
push(checkLabelCase(view.label, `${viewPath}.label`));
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
const apps = Array.isArray(config.apps) ? config.apps : [];
|
|
3025
|
+
for (let i = 0; i < apps.length; i++) {
|
|
3026
|
+
const app = apps[i];
|
|
3027
|
+
const appPath = `apps[${i}]`;
|
|
3028
|
+
if (app.name) {
|
|
3029
|
+
push(checkSnakeCase(app.name, `${appPath}.name`, "App name"));
|
|
3030
|
+
}
|
|
3031
|
+
push(checkLabelExists(app, `${appPath}.label`, "App"));
|
|
3032
|
+
if (app.label) {
|
|
3033
|
+
push(checkLabelCase(app.label, `${appPath}.label`));
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
const flows = Array.isArray(config.flows) ? config.flows : [];
|
|
3037
|
+
for (let i = 0; i < flows.length; i++) {
|
|
3038
|
+
const flow = flows[i];
|
|
3039
|
+
const flowPath = `flows[${i}]`;
|
|
3040
|
+
if (flow.name) {
|
|
3041
|
+
push(checkSnakeCase(flow.name, `${flowPath}.name`, "Flow name"));
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
const agents = Array.isArray(config.agents) ? config.agents : [];
|
|
3045
|
+
for (let i = 0; i < agents.length; i++) {
|
|
3046
|
+
const agent = agents[i];
|
|
3047
|
+
const agentPath = `agents[${i}]`;
|
|
3048
|
+
if (agent.name) {
|
|
3049
|
+
push(checkSnakeCase(agent.name, `${agentPath}.name`, "Agent name"));
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
return issues;
|
|
3053
|
+
}
|
|
3054
|
+
var lintCommand = new Command14("lint").description("Check ObjectStack configuration for style and convention issues").argument("[config]", "Configuration file path").option("--json", "Output as JSON").option("--fix", "Show what would be fixed (dry-run)").action(async (configPath, options) => {
|
|
3055
|
+
const timer = createTimer();
|
|
3056
|
+
if (!options.json) {
|
|
3057
|
+
printHeader("Lint");
|
|
3058
|
+
printStep("Loading configuration...");
|
|
3059
|
+
}
|
|
3060
|
+
try {
|
|
3061
|
+
const { config, absolutePath } = await loadConfig(configPath);
|
|
3062
|
+
if (!options.json) {
|
|
3063
|
+
printInfo(`Config: ${chalk13.white(absolutePath)}`);
|
|
3064
|
+
}
|
|
3065
|
+
const issues = lintConfig(config);
|
|
3066
|
+
if (options.json) {
|
|
3067
|
+
const errors2 = issues.filter((i) => i.severity === "error");
|
|
3068
|
+
const warnings2 = issues.filter((i) => i.severity === "warning");
|
|
3069
|
+
const suggestions2 = issues.filter((i) => i.severity === "suggestion");
|
|
3070
|
+
console.log(JSON.stringify({
|
|
3071
|
+
passed: errors2.length === 0,
|
|
3072
|
+
total: issues.length,
|
|
3073
|
+
errors: errors2.length,
|
|
3074
|
+
warnings: warnings2.length,
|
|
3075
|
+
suggestions: suggestions2.length,
|
|
3076
|
+
issues,
|
|
3077
|
+
duration: timer.elapsed()
|
|
3078
|
+
}, null, 2));
|
|
3079
|
+
if (errors2.length > 0) process.exit(1);
|
|
3080
|
+
return;
|
|
3081
|
+
}
|
|
3082
|
+
console.log("");
|
|
3083
|
+
if (issues.length === 0) {
|
|
3084
|
+
printSuccess(`All checks passed ${chalk13.dim(`(${timer.display()})`)}`);
|
|
3085
|
+
console.log("");
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
3089
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
3090
|
+
const suggestions = issues.filter((i) => i.severity === "suggestion");
|
|
3091
|
+
const printIssue = (issue) => {
|
|
3092
|
+
const color = issue.severity === "error" ? chalk13.red : issue.severity === "warning" ? chalk13.yellow : chalk13.blue;
|
|
3093
|
+
const icon = issue.severity === "error" ? "\u2717" : issue.severity === "warning" ? "\u26A0" : "\u2139";
|
|
3094
|
+
console.log(` ${color(icon)} ${color(issue.message)}`);
|
|
3095
|
+
console.log(chalk13.dim(` ${issue.rule} at ${issue.path}`));
|
|
3096
|
+
if (options.fix && issue.fix) {
|
|
3097
|
+
console.log(chalk13.green(` \u2192 fix: ${issue.fix}`));
|
|
3098
|
+
}
|
|
3099
|
+
};
|
|
3100
|
+
if (errors.length > 0) {
|
|
3101
|
+
console.log(chalk13.bold.red(` Errors (${errors.length})`));
|
|
3102
|
+
errors.forEach(printIssue);
|
|
3103
|
+
console.log("");
|
|
3104
|
+
}
|
|
3105
|
+
if (warnings.length > 0) {
|
|
3106
|
+
console.log(chalk13.bold.yellow(` Warnings (${warnings.length})`));
|
|
3107
|
+
warnings.forEach(printIssue);
|
|
3108
|
+
console.log("");
|
|
3109
|
+
}
|
|
3110
|
+
if (suggestions.length > 0) {
|
|
3111
|
+
console.log(chalk13.bold.blue(` Suggestions (${suggestions.length})`));
|
|
3112
|
+
suggestions.forEach(printIssue);
|
|
3113
|
+
console.log("");
|
|
3114
|
+
}
|
|
3115
|
+
const parts = [];
|
|
3116
|
+
if (errors.length > 0) parts.push(chalk13.red(`${errors.length} error(s)`));
|
|
3117
|
+
if (warnings.length > 0) parts.push(chalk13.yellow(`${warnings.length} warning(s)`));
|
|
3118
|
+
if (suggestions.length > 0) parts.push(chalk13.blue(`${suggestions.length} suggestion(s)`));
|
|
3119
|
+
console.log(` ${parts.join(", ")} ${chalk13.dim(`(${timer.display()})`)}`);
|
|
3120
|
+
if (options.fix) {
|
|
3121
|
+
console.log("");
|
|
3122
|
+
printInfo("Dry-run mode: no files were modified.");
|
|
3123
|
+
}
|
|
3124
|
+
console.log("");
|
|
3125
|
+
if (errors.length > 0) process.exit(1);
|
|
3126
|
+
} catch (error) {
|
|
3127
|
+
if (options.json) {
|
|
3128
|
+
console.log(JSON.stringify({ error: error.message }));
|
|
3129
|
+
process.exit(1);
|
|
3130
|
+
}
|
|
3131
|
+
console.log("");
|
|
3132
|
+
printError(error.message || String(error));
|
|
3133
|
+
process.exit(1);
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
|
|
3137
|
+
// src/commands/explain.ts
|
|
3138
|
+
import { Command as Command15 } from "commander";
|
|
2123
3139
|
import chalk14 from "chalk";
|
|
3140
|
+
var SCHEMAS = {
|
|
3141
|
+
object: {
|
|
3142
|
+
name: "Object",
|
|
3143
|
+
description: "Defines a data entity in the ObjectStack data model. Objects contain fields, enable capabilities, and form the foundation of the metadata-driven platform.",
|
|
3144
|
+
required: [
|
|
3145
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" },
|
|
3146
|
+
{ name: "fields", type: "Record<string, Field>", description: "Map of field definitions" }
|
|
3147
|
+
],
|
|
3148
|
+
optional: [
|
|
3149
|
+
{ name: "label", type: "string", description: "Human-readable display name" },
|
|
3150
|
+
{ name: "pluralLabel", type: "string", description: "Plural display name" },
|
|
3151
|
+
{ name: "description", type: "string", description: "Documentation for the object" },
|
|
3152
|
+
{ name: "ownership", type: '"own" | "extend"', description: "Whether this object is owned or extended" },
|
|
3153
|
+
{ name: "enable", type: "ObjectCapabilities", description: "Feature flags (trackHistory, apiEnabled, etc.)" },
|
|
3154
|
+
{ name: "icon", type: "string", description: "Icon identifier for UI display" }
|
|
3155
|
+
],
|
|
3156
|
+
example: `{
|
|
3157
|
+
name: 'project_task',
|
|
3158
|
+
label: 'Project Task',
|
|
3159
|
+
fields: {
|
|
3160
|
+
title: { type: 'text', label: 'Title', required: true },
|
|
3161
|
+
status: { type: 'select', label: 'Status', options: ['open', 'closed'] },
|
|
3162
|
+
assigned_to: { type: 'lookup', label: 'Assigned To', reference: 'user' },
|
|
3163
|
+
},
|
|
3164
|
+
enable: { trackHistory: true, apiEnabled: true },
|
|
3165
|
+
}`,
|
|
3166
|
+
related: ["field", "view", "flow", "query"],
|
|
3167
|
+
docsPath: "data/object"
|
|
3168
|
+
},
|
|
3169
|
+
field: {
|
|
3170
|
+
name: "Field",
|
|
3171
|
+
description: "Defines a property on an Object. Fields have types that control validation, storage, and UI rendering.",
|
|
3172
|
+
required: [
|
|
3173
|
+
{ name: "type", type: "FieldType", description: "Field data type (text, number, boolean, select, lookup, etc.)" }
|
|
3174
|
+
],
|
|
3175
|
+
optional: [
|
|
3176
|
+
{ name: "label", type: "string", description: "Human-readable display name" },
|
|
3177
|
+
{ name: "required", type: "boolean", description: "Whether the field is mandatory" },
|
|
3178
|
+
{ name: "multiple", type: "boolean", description: "Whether the field holds an array of values" },
|
|
3179
|
+
{ name: "defaultValue", type: "any", description: "Default value for new records" },
|
|
3180
|
+
{ name: "maxLength", type: "number", description: "Maximum character length (text fields)" },
|
|
3181
|
+
{ name: "reference", type: "string", description: "Target object name (lookup fields)" },
|
|
3182
|
+
{ name: "options", type: "string[]", description: "Available choices (select fields)" }
|
|
3183
|
+
],
|
|
3184
|
+
example: `{
|
|
3185
|
+
type: 'text',
|
|
3186
|
+
label: 'Email Address',
|
|
3187
|
+
required: true,
|
|
3188
|
+
maxLength: 255,
|
|
3189
|
+
}`,
|
|
3190
|
+
related: ["object", "view", "query"],
|
|
3191
|
+
docsPath: "data/field"
|
|
3192
|
+
},
|
|
3193
|
+
view: {
|
|
3194
|
+
name: "View",
|
|
3195
|
+
description: "Defines how data is displayed and interacted with. Views can be list views (grid, kanban, calendar) or form views (simple, tabbed, wizard).",
|
|
3196
|
+
required: [
|
|
3197
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" },
|
|
3198
|
+
{ name: "object", type: "string", description: "Target object to display" },
|
|
3199
|
+
{ name: "type", type: '"list" | "form"', description: "View category" }
|
|
3200
|
+
],
|
|
3201
|
+
optional: [
|
|
3202
|
+
{ name: "label", type: "string", description: "Display name" },
|
|
3203
|
+
{ name: "layout", type: '"grid" | "kanban" | "calendar" | "gantt" | "simple" | "tabbed" | "wizard"', description: "Layout style" },
|
|
3204
|
+
{ name: "columns", type: "string[]", description: "Visible fields for list views" },
|
|
3205
|
+
{ name: "filters", type: "Filter[]", description: "Default query filters" },
|
|
3206
|
+
{ name: "sort", type: "SortConfig", description: "Default sort configuration" }
|
|
3207
|
+
],
|
|
3208
|
+
example: `{
|
|
3209
|
+
name: 'task_board',
|
|
3210
|
+
object: 'project_task',
|
|
3211
|
+
type: 'list',
|
|
3212
|
+
label: 'Task Board',
|
|
3213
|
+
layout: 'kanban',
|
|
3214
|
+
columns: ['title', 'status', 'assigned_to'],
|
|
3215
|
+
}`,
|
|
3216
|
+
related: ["object", "app", "action", "dashboard"],
|
|
3217
|
+
docsPath: "ui/view"
|
|
3218
|
+
},
|
|
3219
|
+
flow: {
|
|
3220
|
+
name: "Flow",
|
|
3221
|
+
description: "Visual logic orchestration for business processes. Flows can be auto-launched, screen-based, or scheduled.",
|
|
3222
|
+
required: [
|
|
3223
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" },
|
|
3224
|
+
{ name: "type", type: '"autolaunched" | "screen" | "schedule"', description: "Trigger type" }
|
|
3225
|
+
],
|
|
3226
|
+
optional: [
|
|
3227
|
+
{ name: "label", type: "string", description: "Display name" },
|
|
3228
|
+
{ name: "description", type: "string", description: "Documentation for the flow" },
|
|
3229
|
+
{ name: "trigger", type: "TriggerConfig", description: "Event that starts the flow" },
|
|
3230
|
+
{ name: "steps", type: "FlowStep[]", description: "Sequence of actions" },
|
|
3231
|
+
{ name: "variables", type: "Variable[]", description: "Flow-scoped variables" }
|
|
3232
|
+
],
|
|
3233
|
+
example: `{
|
|
3234
|
+
name: 'assign_on_create',
|
|
3235
|
+
type: 'autolaunched',
|
|
3236
|
+
label: 'Auto-Assign on Create',
|
|
3237
|
+
trigger: { object: 'project_task', event: 'afterInsert' },
|
|
3238
|
+
steps: [
|
|
3239
|
+
{ type: 'assignment', field: 'assigned_to', value: '$currentUser' },
|
|
3240
|
+
],
|
|
3241
|
+
}`,
|
|
3242
|
+
related: ["object", "trigger", "workflow", "agent"],
|
|
3243
|
+
docsPath: "automation/flow"
|
|
3244
|
+
},
|
|
3245
|
+
agent: {
|
|
3246
|
+
name: "Agent",
|
|
3247
|
+
description: "Autonomous AI actor that can perform tasks using tools, instructions, and context from the ObjectStack data model.",
|
|
3248
|
+
required: [
|
|
3249
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" },
|
|
3250
|
+
{ name: "role", type: "string", description: "Agent purpose description" }
|
|
3251
|
+
],
|
|
3252
|
+
optional: [
|
|
3253
|
+
{ name: "instructions", type: "string", description: "System prompt / behavioral instructions" },
|
|
3254
|
+
{ name: "tools", type: "ToolReference[]", description: "Tools the agent can invoke" },
|
|
3255
|
+
{ name: "model", type: "string", description: "LLM model to use" },
|
|
3256
|
+
{ name: "objects", type: "string[]", description: "Objects the agent can access" },
|
|
3257
|
+
{ name: "temperature", type: "number", description: "Creativity parameter (0-1)" }
|
|
3258
|
+
],
|
|
3259
|
+
example: `{
|
|
3260
|
+
name: 'support_agent',
|
|
3261
|
+
role: 'Customer Support Assistant',
|
|
3262
|
+
instructions: 'Help users resolve issues by searching the knowledge base.',
|
|
3263
|
+
tools: [{ name: 'query', object: 'knowledge_article' }],
|
|
3264
|
+
model: 'gpt-4o',
|
|
3265
|
+
}`,
|
|
3266
|
+
related: ["object", "flow", "query"],
|
|
3267
|
+
docsPath: "ai/agent"
|
|
3268
|
+
},
|
|
3269
|
+
app: {
|
|
3270
|
+
name: "App",
|
|
3271
|
+
description: "Application shell that groups navigation, branding, and views into a cohesive user experience.",
|
|
3272
|
+
required: [
|
|
3273
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" }
|
|
3274
|
+
],
|
|
3275
|
+
optional: [
|
|
3276
|
+
{ name: "label", type: "string", description: "Display name" },
|
|
3277
|
+
{ name: "description", type: "string", description: "App description" },
|
|
3278
|
+
{ name: "navigation", type: "NavItem[]", description: "Menu tree structure" },
|
|
3279
|
+
{ name: "logo", type: "string", description: "Logo URL or asset path" },
|
|
3280
|
+
{ name: "theme", type: "string", description: "Theme reference" },
|
|
3281
|
+
{ name: "defaultRoute", type: "string", description: "Landing page route" }
|
|
3282
|
+
],
|
|
3283
|
+
example: `{
|
|
3284
|
+
name: 'project_manager',
|
|
3285
|
+
label: 'Project Manager',
|
|
3286
|
+
navigation: [
|
|
3287
|
+
{ type: 'object', object: 'project_task', label: 'Tasks' },
|
|
3288
|
+
{ type: 'dashboard', dashboard: 'project_overview', label: 'Overview' },
|
|
3289
|
+
],
|
|
3290
|
+
}`,
|
|
3291
|
+
related: ["view", "dashboard", "action", "object"],
|
|
3292
|
+
docsPath: "ui/app"
|
|
3293
|
+
},
|
|
3294
|
+
query: {
|
|
3295
|
+
name: "Query",
|
|
3296
|
+
description: "Declarative data retrieval definition used for fetching and filtering records from objects.",
|
|
3297
|
+
required: [
|
|
3298
|
+
{ name: "object", type: "string", description: "Target object to query" }
|
|
3299
|
+
],
|
|
3300
|
+
optional: [
|
|
3301
|
+
{ name: "fields", type: "string[]", description: "Fields to select" },
|
|
3302
|
+
{ name: "filters", type: "Filter[]", description: "Where conditions" },
|
|
3303
|
+
{ name: "sort", type: "SortConfig[]", description: "Order by configuration" },
|
|
3304
|
+
{ name: "limit", type: "number", description: "Maximum records to return" },
|
|
3305
|
+
{ name: "offset", type: "number", description: "Pagination offset" }
|
|
3306
|
+
],
|
|
3307
|
+
example: `{
|
|
3308
|
+
object: 'project_task',
|
|
3309
|
+
fields: ['title', 'status', 'assigned_to'],
|
|
3310
|
+
filters: [{ field: 'status', operator: 'eq', value: 'open' }],
|
|
3311
|
+
sort: [{ field: 'created_at', direction: 'desc' }],
|
|
3312
|
+
limit: 50,
|
|
3313
|
+
}`,
|
|
3314
|
+
related: ["object", "field", "view"],
|
|
3315
|
+
docsPath: "data/query"
|
|
3316
|
+
},
|
|
3317
|
+
dashboard: {
|
|
3318
|
+
name: "Dashboard",
|
|
3319
|
+
description: "Grid-layout container for widgets that display aggregated data, charts, and key metrics.",
|
|
3320
|
+
required: [
|
|
3321
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" }
|
|
3322
|
+
],
|
|
3323
|
+
optional: [
|
|
3324
|
+
{ name: "label", type: "string", description: "Display name" },
|
|
3325
|
+
{ name: "widgets", type: "Widget[]", description: "Dashboard widget definitions" },
|
|
3326
|
+
{ name: "layout", type: "GridLayout", description: "Widget positioning" },
|
|
3327
|
+
{ name: "refreshInterval", type: "number", description: "Auto-refresh interval in seconds" }
|
|
3328
|
+
],
|
|
3329
|
+
example: `{
|
|
3330
|
+
name: 'project_overview',
|
|
3331
|
+
label: 'Project Overview',
|
|
3332
|
+
widgets: [
|
|
3333
|
+
{ type: 'chart', object: 'project_task', groupBy: 'status' },
|
|
3334
|
+
{ type: 'metric', object: 'project_task', aggregate: 'count' },
|
|
3335
|
+
],
|
|
3336
|
+
}`,
|
|
3337
|
+
related: ["app", "view", "object"],
|
|
3338
|
+
docsPath: "ui/dashboard"
|
|
3339
|
+
},
|
|
3340
|
+
action: {
|
|
3341
|
+
name: "Action",
|
|
3342
|
+
description: "User-triggered operation such as a button click, URL redirect, or screen flow launch.",
|
|
3343
|
+
required: [
|
|
3344
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" },
|
|
3345
|
+
{ name: "type", type: '"button" | "url" | "flow" | "api"', description: "Action type" }
|
|
3346
|
+
],
|
|
3347
|
+
optional: [
|
|
3348
|
+
{ name: "label", type: "string", description: "Display text" },
|
|
3349
|
+
{ name: "icon", type: "string", description: "Button icon" },
|
|
3350
|
+
{ name: "object", type: "string", description: "Target object context" },
|
|
3351
|
+
{ name: "flow", type: "string", description: "Flow to launch (for flow actions)" },
|
|
3352
|
+
{ name: "url", type: "string", description: "Target URL (for url actions)" },
|
|
3353
|
+
{ name: "confirmation", type: "string", description: "Confirmation dialog message" }
|
|
3354
|
+
],
|
|
3355
|
+
example: `{
|
|
3356
|
+
name: 'close_task',
|
|
3357
|
+
type: 'flow',
|
|
3358
|
+
label: 'Close Task',
|
|
3359
|
+
object: 'project_task',
|
|
3360
|
+
flow: 'close_task_flow',
|
|
3361
|
+
confirmation: 'Are you sure you want to close this task?',
|
|
3362
|
+
}`,
|
|
3363
|
+
related: ["flow", "view", "app"],
|
|
3364
|
+
docsPath: "ui/action"
|
|
3365
|
+
},
|
|
3366
|
+
workflow: {
|
|
3367
|
+
name: "Workflow",
|
|
3368
|
+
description: "State machine and approval process that governs record lifecycle transitions.",
|
|
3369
|
+
required: [
|
|
3370
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" },
|
|
3371
|
+
{ name: "object", type: "string", description: "Target object" }
|
|
3372
|
+
],
|
|
3373
|
+
optional: [
|
|
3374
|
+
{ name: "label", type: "string", description: "Display name" },
|
|
3375
|
+
{ name: "states", type: "State[]", description: "Defined workflow states" },
|
|
3376
|
+
{ name: "transitions", type: "Transition[]", description: "Allowed state transitions" },
|
|
3377
|
+
{ name: "approvers", type: "ApproverConfig", description: "Approval chain configuration" }
|
|
3378
|
+
],
|
|
3379
|
+
example: `{
|
|
3380
|
+
name: 'task_approval',
|
|
3381
|
+
object: 'project_task',
|
|
3382
|
+
label: 'Task Approval',
|
|
3383
|
+
states: ['draft', 'pending', 'approved', 'rejected'],
|
|
3384
|
+
transitions: [
|
|
3385
|
+
{ from: 'draft', to: 'pending', action: 'submit' },
|
|
3386
|
+
{ from: 'pending', to: 'approved', action: 'approve' },
|
|
3387
|
+
{ from: 'pending', to: 'rejected', action: 'reject' },
|
|
3388
|
+
],
|
|
3389
|
+
}`,
|
|
3390
|
+
related: ["object", "flow", "action"],
|
|
3391
|
+
docsPath: "automation/workflow"
|
|
3392
|
+
},
|
|
3393
|
+
trigger: {
|
|
3394
|
+
name: "Trigger",
|
|
3395
|
+
description: "Event-driven automation hook that fires when specific data events occur on an object.",
|
|
3396
|
+
required: [
|
|
3397
|
+
{ name: "name", type: "string (snake_case)", description: "Machine name identifier" },
|
|
3398
|
+
{ name: "object", type: "string", description: "Target object to monitor" },
|
|
3399
|
+
{ name: "event", type: '"beforeInsert" | "afterInsert" | "beforeUpdate" | "afterUpdate" | "beforeDelete" | "afterDelete"', description: "Event type" }
|
|
3400
|
+
],
|
|
3401
|
+
optional: [
|
|
3402
|
+
{ name: "label", type: "string", description: "Display name" },
|
|
3403
|
+
{ name: "condition", type: "FilterExpression", description: "Conditional guard" },
|
|
3404
|
+
{ name: "flow", type: "string", description: "Flow to execute" },
|
|
3405
|
+
{ name: "async", type: "boolean", description: "Whether to execute asynchronously" }
|
|
3406
|
+
],
|
|
3407
|
+
example: `{
|
|
3408
|
+
name: 'task_created_notify',
|
|
3409
|
+
object: 'project_task',
|
|
3410
|
+
event: 'afterInsert',
|
|
3411
|
+
label: 'Notify on Task Creation',
|
|
3412
|
+
flow: 'send_task_notification',
|
|
3413
|
+
async: true,
|
|
3414
|
+
}`,
|
|
3415
|
+
related: ["object", "flow", "workflow"],
|
|
3416
|
+
docsPath: "automation/trigger"
|
|
3417
|
+
}
|
|
3418
|
+
};
|
|
3419
|
+
var explainCommand = new Command15("explain").description("Display human-readable explanation of an ObjectStack schema").argument("[schema]", "Schema name (e.g., object, field, view, flow, agent, app)").option("--json", "Output as JSON").action(async (schemaName, options) => {
|
|
3420
|
+
if (!schemaName) {
|
|
3421
|
+
if (options.json) {
|
|
3422
|
+
console.log(JSON.stringify({
|
|
3423
|
+
schemas: Object.entries(SCHEMAS).map(([key, s]) => ({
|
|
3424
|
+
name: key,
|
|
3425
|
+
description: s.description
|
|
3426
|
+
}))
|
|
3427
|
+
}, null, 2));
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
printHeader("Available Schemas");
|
|
3431
|
+
console.log("");
|
|
3432
|
+
for (const [key, schema2] of Object.entries(SCHEMAS)) {
|
|
3433
|
+
const desc = schema2.description;
|
|
3434
|
+
console.log(` ${chalk14.bold.cyan(key.padEnd(12))} ${chalk14.dim(desc.length > 70 ? desc.slice(0, 70) + "..." : desc)}`);
|
|
3435
|
+
}
|
|
3436
|
+
console.log("");
|
|
3437
|
+
printInfo(`Run ${chalk14.white("objectstack explain <schema>")} for details.`);
|
|
3438
|
+
console.log("");
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
const schema = SCHEMAS[schemaName.toLowerCase()];
|
|
3442
|
+
if (!schema) {
|
|
3443
|
+
if (options.json) {
|
|
3444
|
+
console.log(JSON.stringify({ error: `Unknown schema: ${schemaName}` }));
|
|
3445
|
+
process.exit(1);
|
|
3446
|
+
}
|
|
3447
|
+
printError(`Unknown schema: "${schemaName}"`);
|
|
3448
|
+
console.log("");
|
|
3449
|
+
printInfo(`Available schemas: ${Object.keys(SCHEMAS).join(", ")}`);
|
|
3450
|
+
console.log("");
|
|
3451
|
+
process.exit(1);
|
|
3452
|
+
}
|
|
3453
|
+
if (options.json) {
|
|
3454
|
+
console.log(JSON.stringify(schema, null, 2));
|
|
3455
|
+
return;
|
|
3456
|
+
}
|
|
3457
|
+
printHeader(`Schema: ${schema.name}`);
|
|
3458
|
+
console.log("");
|
|
3459
|
+
console.log(` ${schema.description}`);
|
|
3460
|
+
console.log("");
|
|
3461
|
+
console.log(chalk14.bold(" Required Properties:"));
|
|
3462
|
+
for (const prop of schema.required) {
|
|
3463
|
+
console.log(` ${chalk14.green(prop.name.padEnd(18))} ${chalk14.dim(prop.type.padEnd(30))} ${prop.description}`);
|
|
3464
|
+
}
|
|
3465
|
+
if (schema.optional.length > 0) {
|
|
3466
|
+
console.log("");
|
|
3467
|
+
console.log(chalk14.bold(" Optional Properties:"));
|
|
3468
|
+
for (const prop of schema.optional) {
|
|
3469
|
+
console.log(` ${chalk14.yellow(prop.name.padEnd(18))} ${chalk14.dim(prop.type.padEnd(30))} ${prop.description}`);
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
console.log("");
|
|
3473
|
+
console.log(chalk14.bold(" Example:"));
|
|
3474
|
+
for (const line of schema.example.split("\n")) {
|
|
3475
|
+
console.log(chalk14.dim(` ${line}`));
|
|
3476
|
+
}
|
|
3477
|
+
console.log("");
|
|
3478
|
+
printKV(" Related", schema.related.map((r) => chalk14.cyan(r)).join(", "));
|
|
3479
|
+
printKV(" Docs", `https://objectstack.dev/docs/${schema.docsPath}`);
|
|
3480
|
+
console.log("");
|
|
3481
|
+
});
|
|
3482
|
+
|
|
3483
|
+
// src/commands/codemod.ts
|
|
3484
|
+
import { Command as Command16 } from "commander";
|
|
3485
|
+
import chalk15 from "chalk";
|
|
3486
|
+
import fs11 from "fs";
|
|
3487
|
+
import path11 from "path";
|
|
3488
|
+
var V2_TO_V3_TRANSFORMS = [
|
|
3489
|
+
{
|
|
3490
|
+
pattern: /\bEnhancedObjectKernel\b/g,
|
|
3491
|
+
replacement: "ObjectKernel",
|
|
3492
|
+
description: "EnhancedObjectKernel \u2192 ObjectKernel"
|
|
3493
|
+
},
|
|
3494
|
+
{
|
|
3495
|
+
pattern: /\bmax_length\b/g,
|
|
3496
|
+
replacement: "maxLength",
|
|
3497
|
+
description: "max_length \u2192 maxLength"
|
|
3498
|
+
},
|
|
3499
|
+
{
|
|
3500
|
+
pattern: /\breference_filters\b/g,
|
|
3501
|
+
replacement: "referenceFilters",
|
|
3502
|
+
description: "reference_filters \u2192 referenceFilters"
|
|
3503
|
+
},
|
|
3504
|
+
{
|
|
3505
|
+
pattern: /\bdefault_value\b/g,
|
|
3506
|
+
replacement: "defaultValue",
|
|
3507
|
+
description: "default_value \u2192 defaultValue"
|
|
3508
|
+
},
|
|
3509
|
+
{
|
|
3510
|
+
pattern: /\bmin_length\b/g,
|
|
3511
|
+
replacement: "minLength",
|
|
3512
|
+
description: "min_length \u2192 minLength"
|
|
3513
|
+
},
|
|
3514
|
+
{
|
|
3515
|
+
pattern: /\bunique_name\b/g,
|
|
3516
|
+
replacement: "uniqueName",
|
|
3517
|
+
description: "unique_name \u2192 uniqueName"
|
|
3518
|
+
},
|
|
3519
|
+
{
|
|
3520
|
+
pattern: /from\s+['"]@objectstack\/core\/enhanced['"]/g,
|
|
3521
|
+
replacement: "from '@objectstack/core'",
|
|
3522
|
+
description: "Update import from @objectstack/core/enhanced"
|
|
3523
|
+
},
|
|
3524
|
+
{
|
|
3525
|
+
pattern: /from\s+['"]@objectstack\/spec\/dist\/[^'"]+['"]/g,
|
|
3526
|
+
replacement: "from '@objectstack/spec'",
|
|
3527
|
+
description: "Update deep import from @objectstack/spec/dist/"
|
|
3528
|
+
}
|
|
3529
|
+
];
|
|
3530
|
+
function walkDir2(dir, ext) {
|
|
3531
|
+
const results = [];
|
|
3532
|
+
if (!fs11.existsSync(dir)) return results;
|
|
3533
|
+
const entries = fs11.readdirSync(dir, { withFileTypes: true });
|
|
3534
|
+
for (const entry of entries) {
|
|
3535
|
+
if (entry.name === "node_modules") continue;
|
|
3536
|
+
const fullPath = path11.join(dir, entry.name);
|
|
3537
|
+
if (entry.isDirectory()) {
|
|
3538
|
+
results.push(...walkDir2(fullPath, ext));
|
|
3539
|
+
} else if (entry.name.endsWith(ext)) {
|
|
3540
|
+
results.push(fullPath);
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
return results;
|
|
3544
|
+
}
|
|
3545
|
+
var v2ToV3Command = new Command16("v2-to-v3").description("Migrate ObjectStack v2 code patterns to v3").option("--dir <directory>", "Directory to scan", "src/").option("--dry-run", "Show changes without writing files").action(async (options) => {
|
|
3546
|
+
printHeader("Codemod: v2 \u2192 v3");
|
|
3547
|
+
const timer = createTimer();
|
|
3548
|
+
const dir = path11.resolve(process.cwd(), options.dir);
|
|
3549
|
+
if (!fs11.existsSync(dir)) {
|
|
3550
|
+
printError(`Directory not found: ${dir}`);
|
|
3551
|
+
process.exit(1);
|
|
3552
|
+
}
|
|
3553
|
+
console.log(` ${chalk15.dim("Directory:")} ${chalk15.white(options.dir)}`);
|
|
3554
|
+
console.log(` ${chalk15.dim("Dry run:")} ${chalk15.white(options.dryRun ? "yes" : "no")}`);
|
|
3555
|
+
console.log("");
|
|
3556
|
+
printStep("Scanning TypeScript files...");
|
|
3557
|
+
const files = walkDir2(dir, ".ts");
|
|
3558
|
+
if (files.length === 0) {
|
|
3559
|
+
printInfo("No .ts files found in directory");
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
printInfo(`Found ${files.length} TypeScript file(s)`);
|
|
3563
|
+
console.log("");
|
|
3564
|
+
let totalTransforms = 0;
|
|
3565
|
+
let filesModified = 0;
|
|
3566
|
+
const transformCounts = {};
|
|
3567
|
+
for (const file of files) {
|
|
3568
|
+
const original = fs11.readFileSync(file, "utf-8");
|
|
3569
|
+
let content = original;
|
|
3570
|
+
let fileTransforms = 0;
|
|
3571
|
+
for (const transform of V2_TO_V3_TRANSFORMS) {
|
|
3572
|
+
const matches = content.match(transform.pattern);
|
|
3573
|
+
if (matches) {
|
|
3574
|
+
const count = matches.length;
|
|
3575
|
+
content = content.replace(transform.pattern, transform.replacement);
|
|
3576
|
+
fileTransforms += count;
|
|
3577
|
+
transformCounts[transform.description] = (transformCounts[transform.description] || 0) + count;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
if (fileTransforms > 0) {
|
|
3581
|
+
const relPath = path11.relative(process.cwd(), file);
|
|
3582
|
+
filesModified++;
|
|
3583
|
+
totalTransforms += fileTransforms;
|
|
3584
|
+
if (options.dryRun) {
|
|
3585
|
+
printInfo(`${relPath} \u2014 ${fileTransforms} change(s)`);
|
|
3586
|
+
} else {
|
|
3587
|
+
fs11.writeFileSync(file, content);
|
|
3588
|
+
printSuccess(`${relPath} \u2014 ${fileTransforms} change(s)`);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
console.log("");
|
|
3593
|
+
if (totalTransforms === 0) {
|
|
3594
|
+
printSuccess("No v2 patterns found \u2014 code is already v3 compatible");
|
|
3595
|
+
} else {
|
|
3596
|
+
console.log(chalk15.bold(" Summary:"));
|
|
3597
|
+
for (const [desc, count] of Object.entries(transformCounts)) {
|
|
3598
|
+
console.log(` ${chalk15.dim(desc)}: ${chalk15.white(count)}`);
|
|
3599
|
+
}
|
|
3600
|
+
console.log("");
|
|
3601
|
+
if (options.dryRun) {
|
|
3602
|
+
printInfo(`Would modify ${filesModified} file(s) with ${totalTransforms} total change(s)`);
|
|
3603
|
+
console.log(chalk15.dim(" Run without --dry-run to apply changes"));
|
|
3604
|
+
} else {
|
|
3605
|
+
printSuccess(`Modified ${filesModified} file(s) with ${totalTransforms} total change(s) (${timer.display()})`);
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
console.log("");
|
|
3609
|
+
});
|
|
3610
|
+
var codemodCommand = new Command16("codemod").description("Run automated code transformations").addCommand(v2ToV3Command).action(() => {
|
|
3611
|
+
printHeader("Codemod");
|
|
3612
|
+
console.log(chalk15.bold(" Available codemods:"));
|
|
3613
|
+
console.log(` ${chalk15.cyan("v2-to-v3".padEnd(16))} Migrate ObjectStack v2 code patterns to v3`);
|
|
3614
|
+
console.log("");
|
|
3615
|
+
console.log(chalk15.dim(" Usage: objectstack codemod v2-to-v3 [--dir src/] [--dry-run]"));
|
|
3616
|
+
console.log("");
|
|
3617
|
+
});
|
|
3618
|
+
|
|
3619
|
+
// src/utils/plugin-commands.ts
|
|
3620
|
+
import chalk16 from "chalk";
|
|
2124
3621
|
async function loadPluginCommands(program2) {
|
|
2125
3622
|
let config;
|
|
2126
3623
|
try {
|
|
@@ -2164,7 +3661,7 @@ async function loadPluginCommands(program2) {
|
|
|
2164
3661
|
if (process.env.DEBUG) {
|
|
2165
3662
|
const message = error instanceof Error ? error.message : String(error);
|
|
2166
3663
|
console.error(
|
|
2167
|
-
|
|
3664
|
+
chalk16.yellow(` \u26A0 Failed to load CLI command '${contribution.name}' from plugin '${contribution.pluginName}': ${message}`)
|
|
2168
3665
|
);
|
|
2169
3666
|
}
|
|
2170
3667
|
}
|
|
@@ -2210,30 +3707,30 @@ function resolvePluginName2(plugin) {
|
|
|
2210
3707
|
var require2 = createRequire2(import.meta.url);
|
|
2211
3708
|
var pkg = require2("../package.json");
|
|
2212
3709
|
process.on("unhandledRejection", (err) => {
|
|
2213
|
-
console.error(
|
|
3710
|
+
console.error(chalk17.red(`
|
|
2214
3711
|
\u2717 Unhandled error: ${err?.message || err}`));
|
|
2215
3712
|
if (err?.stack && process.env.DEBUG) {
|
|
2216
|
-
console.error(
|
|
3713
|
+
console.error(chalk17.dim(err.stack));
|
|
2217
3714
|
}
|
|
2218
3715
|
process.exit(1);
|
|
2219
3716
|
});
|
|
2220
|
-
var program = new
|
|
3717
|
+
var program = new Command17();
|
|
2221
3718
|
program.name("objectstack").description("ObjectStack CLI \u2014 Build metadata-driven apps with the ObjectStack Protocol").version(pkg.version, "-v, --version").configureHelp({
|
|
2222
3719
|
sortSubcommands: false
|
|
2223
3720
|
}).addHelpText("before", `
|
|
2224
|
-
${
|
|
3721
|
+
${chalk17.bold.cyan("\u25C6 ObjectStack CLI")} ${chalk17.dim(`v${pkg.version}`)}
|
|
2225
3722
|
`).addHelpText("after", `
|
|
2226
|
-
${
|
|
2227
|
-
${
|
|
2228
|
-
${
|
|
2229
|
-
${
|
|
2230
|
-
${
|
|
2231
|
-
${
|
|
2232
|
-
${
|
|
2233
|
-
${
|
|
2234
|
-
|
|
2235
|
-
${
|
|
2236
|
-
${
|
|
3723
|
+
${chalk17.bold("Workflow:")}
|
|
3724
|
+
${chalk17.dim("$")} os init ${chalk17.dim("# Create a new project")}
|
|
3725
|
+
${chalk17.dim("$")} os generate object task ${chalk17.dim("# Add metadata")}
|
|
3726
|
+
${chalk17.dim("$")} os plugin add <package> ${chalk17.dim("# Add a plugin")}
|
|
3727
|
+
${chalk17.dim("$")} os validate ${chalk17.dim("# Check configuration")}
|
|
3728
|
+
${chalk17.dim("$")} os dev ${chalk17.dim("# Start dev server")}
|
|
3729
|
+
${chalk17.dim("$")} os studio ${chalk17.dim("# Dev server + Studio UI")}
|
|
3730
|
+
${chalk17.dim("$")} os compile ${chalk17.dim("# Build for production")}
|
|
3731
|
+
|
|
3732
|
+
${chalk17.dim("Aliases: objectstack | os")}
|
|
3733
|
+
${chalk17.dim("Docs: https://objectstack.dev")}
|
|
2237
3734
|
`);
|
|
2238
3735
|
program.addCommand(initCommand);
|
|
2239
3736
|
program.addCommand(devCommand);
|
|
@@ -2247,11 +3744,15 @@ program.addCommand(createCommand);
|
|
|
2247
3744
|
program.addCommand(pluginCommand);
|
|
2248
3745
|
program.addCommand(testCommand);
|
|
2249
3746
|
program.addCommand(doctorCommand);
|
|
3747
|
+
program.addCommand(lintCommand);
|
|
3748
|
+
program.addCommand(diffCommand);
|
|
3749
|
+
program.addCommand(explainCommand);
|
|
3750
|
+
program.addCommand(codemodCommand);
|
|
2250
3751
|
loadPluginCommands(program).then(() => {
|
|
2251
3752
|
program.parse(process.argv);
|
|
2252
3753
|
}).catch((err) => {
|
|
2253
3754
|
if (process.env.DEBUG) {
|
|
2254
|
-
console.error(
|
|
3755
|
+
console.error(chalk17.yellow(`
|
|
2255
3756
|
\u26A0 Plugin command loading failed: ${err?.message || err}`));
|
|
2256
3757
|
}
|
|
2257
3758
|
program.parse(process.argv);
|