@objectstack/cli 1.0.11 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +14 -9
- package/CHANGELOG.md +13 -0
- package/README.md +132 -13
- package/dist/bin.js +1488 -178
- package/dist/index.d.ts +97 -1
- package/dist/index.js +1938 -5
- package/package.json +9 -9
- package/src/bin.ts +53 -6
- package/src/commands/compile.ts +66 -39
- package/src/commands/create.ts +15 -11
- package/src/commands/dev.ts +12 -16
- package/src/commands/doctor.ts +13 -9
- package/src/commands/generate.ts +297 -0
- package/src/commands/info.ts +111 -0
- package/src/commands/init.ts +313 -0
- package/src/commands/serve.ts +134 -48
- package/src/commands/studio.ts +40 -0
- package/src/commands/test.ts +2 -2
- package/src/commands/validate.ts +130 -0
- package/src/index.ts +9 -0
- package/src/utils/config.ts +78 -0
- package/src/utils/console.ts +319 -0
- package/src/utils/format.ts +261 -0
- package/test/commands.test.ts +26 -1
- package/tsup.config.ts +18 -9
- package/dist/bin.d.ts +0 -2
- package/dist/chunk-2YXVEYO7.js +0 -64
package/dist/bin.js
CHANGED
|
@@ -1,29 +1,327 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
5
8
|
|
|
6
9
|
// src/bin.ts
|
|
7
10
|
import { createRequire } from "module";
|
|
8
|
-
import { Command as
|
|
11
|
+
import { Command as Command12 } from "commander";
|
|
12
|
+
import chalk13 from "chalk";
|
|
9
13
|
|
|
10
|
-
// src/commands/
|
|
14
|
+
// src/commands/compile.ts
|
|
11
15
|
import { Command } from "commander";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
16
|
+
import path2 from "path";
|
|
17
|
+
import fs2 from "fs";
|
|
18
|
+
import chalk3 from "chalk";
|
|
19
|
+
import { ObjectStackDefinitionSchema } from "@objectstack/spec";
|
|
20
|
+
|
|
21
|
+
// src/utils/config.ts
|
|
15
22
|
import path from "path";
|
|
16
|
-
|
|
23
|
+
import fs from "fs";
|
|
24
|
+
import chalk2 from "chalk";
|
|
25
|
+
import { bundleRequire } from "bundle-require";
|
|
26
|
+
|
|
27
|
+
// src/utils/format.ts
|
|
28
|
+
import chalk from "chalk";
|
|
29
|
+
function printHeader(title) {
|
|
17
30
|
console.log(chalk.bold(`
|
|
18
|
-
\
|
|
19
|
-
console.log(chalk.dim(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
\u25C6 ${title}`));
|
|
32
|
+
console.log(chalk.dim("\u2500".repeat(40)));
|
|
33
|
+
}
|
|
34
|
+
function printKV(key, value, icon) {
|
|
35
|
+
const prefix = icon ? `${icon} ` : " ";
|
|
36
|
+
console.log(`${prefix}${chalk.dim(key + ":")} ${chalk.white(String(value))}`);
|
|
37
|
+
}
|
|
38
|
+
function printSuccess(msg) {
|
|
39
|
+
console.log(chalk.green(` \u2713 ${msg}`));
|
|
40
|
+
}
|
|
41
|
+
function printWarning(msg) {
|
|
42
|
+
console.log(chalk.yellow(` \u26A0 ${msg}`));
|
|
43
|
+
}
|
|
44
|
+
function printError(msg) {
|
|
45
|
+
console.log(chalk.red(` \u2717 ${msg}`));
|
|
46
|
+
}
|
|
47
|
+
function printInfo(msg) {
|
|
48
|
+
console.log(chalk.blue(` \u2139 ${msg}`));
|
|
49
|
+
}
|
|
50
|
+
function printStep(msg) {
|
|
51
|
+
console.log(chalk.yellow(` \u2192 ${msg}`));
|
|
52
|
+
}
|
|
53
|
+
function createTimer() {
|
|
54
|
+
const start = Date.now();
|
|
55
|
+
return {
|
|
56
|
+
elapsed: () => Date.now() - start,
|
|
57
|
+
display: () => `${Date.now() - start}ms`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function formatZodErrors(error) {
|
|
61
|
+
const issues = error.issues || error.errors || [];
|
|
62
|
+
if (issues.length === 0) {
|
|
63
|
+
console.log(chalk.red(" Unknown validation error"));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const issue of issues) {
|
|
68
|
+
const topPath = issue.path?.[0] || "_root";
|
|
69
|
+
if (!grouped.has(String(topPath))) {
|
|
70
|
+
grouped.set(String(topPath), []);
|
|
71
|
+
}
|
|
72
|
+
grouped.get(String(topPath)).push(issue);
|
|
73
|
+
}
|
|
74
|
+
for (const [section, sectionIssues] of grouped) {
|
|
75
|
+
console.log(chalk.bold.red(`
|
|
76
|
+
${section}:`));
|
|
77
|
+
for (const issue of sectionIssues) {
|
|
78
|
+
const path11 = issue.path?.join(".") || "";
|
|
79
|
+
const code = issue.code || "";
|
|
80
|
+
const msg = issue.message || "";
|
|
81
|
+
console.log(chalk.red(` \u2717 ${path11}`));
|
|
82
|
+
console.log(chalk.dim(` ${code}: ${msg}`));
|
|
83
|
+
if (issue.expected) {
|
|
84
|
+
console.log(chalk.dim(` expected: ${chalk.green(issue.expected)}`));
|
|
85
|
+
}
|
|
86
|
+
if (issue.received) {
|
|
87
|
+
console.log(chalk.dim(` received: ${chalk.red(issue.received)}`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
console.log("");
|
|
92
|
+
console.log(chalk.dim(` ${issues.length} validation error(s) total`));
|
|
93
|
+
}
|
|
94
|
+
function collectMetadataStats(config) {
|
|
95
|
+
const count = (arr) => Array.isArray(arr) ? arr.length : 0;
|
|
96
|
+
let fields = 0;
|
|
97
|
+
if (Array.isArray(config.objects)) {
|
|
98
|
+
for (const obj of config.objects) {
|
|
99
|
+
if (obj.fields && typeof obj.fields === "object") {
|
|
100
|
+
fields += Object.keys(obj.fields).length;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
objects: count(config.objects),
|
|
106
|
+
objectExtensions: count(config.objectExtensions),
|
|
107
|
+
fields,
|
|
108
|
+
views: count(config.views),
|
|
109
|
+
pages: count(config.pages),
|
|
110
|
+
apps: count(config.apps),
|
|
111
|
+
dashboards: count(config.dashboards),
|
|
112
|
+
reports: count(config.reports),
|
|
113
|
+
actions: count(config.actions),
|
|
114
|
+
flows: count(config.flows),
|
|
115
|
+
workflows: count(config.workflows),
|
|
116
|
+
approvals: count(config.approvals),
|
|
117
|
+
agents: count(config.agents),
|
|
118
|
+
apis: count(config.apis),
|
|
119
|
+
roles: count(config.roles),
|
|
120
|
+
permissions: count(config.permissions),
|
|
121
|
+
themes: count(config.themes),
|
|
122
|
+
datasources: count(config.datasources),
|
|
123
|
+
translations: count(config.translations),
|
|
124
|
+
plugins: count(config.plugins),
|
|
125
|
+
devPlugins: count(config.devPlugins)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function printServerReady(opts) {
|
|
129
|
+
const base = `http://localhost:${opts.port}`;
|
|
130
|
+
console.log("");
|
|
131
|
+
console.log(chalk.bold.green(" \u2713 Server is ready"));
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(chalk.cyan(" \u279C") + chalk.bold(" API: ") + chalk.cyan(base + "/"));
|
|
134
|
+
if (opts.uiEnabled && opts.studioPath) {
|
|
135
|
+
console.log(chalk.cyan(" \u279C") + chalk.bold(" Console: ") + chalk.cyan(base + opts.studioPath + "/"));
|
|
136
|
+
}
|
|
137
|
+
console.log("");
|
|
138
|
+
console.log(chalk.dim(` Config: ${opts.configFile}`));
|
|
139
|
+
console.log(chalk.dim(` Mode: ${opts.isDev ? "development" : "production"}`));
|
|
140
|
+
console.log(chalk.dim(` Plugins: ${opts.pluginCount} loaded`));
|
|
141
|
+
if (opts.pluginNames && opts.pluginNames.length > 0) {
|
|
142
|
+
console.log(chalk.dim(` ${opts.pluginNames.join(", ")}`));
|
|
143
|
+
}
|
|
144
|
+
console.log("");
|
|
145
|
+
console.log(chalk.dim(" Press Ctrl+C to stop"));
|
|
146
|
+
console.log("");
|
|
147
|
+
}
|
|
148
|
+
function printMetadataStats(stats) {
|
|
149
|
+
const sections = [
|
|
150
|
+
{
|
|
151
|
+
label: "Data",
|
|
152
|
+
items: [
|
|
153
|
+
["Objects", stats.objects],
|
|
154
|
+
["Fields", stats.fields],
|
|
155
|
+
["Extensions", stats.objectExtensions],
|
|
156
|
+
["Datasources", stats.datasources]
|
|
157
|
+
]
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
label: "UI",
|
|
161
|
+
items: [
|
|
162
|
+
["Apps", stats.apps],
|
|
163
|
+
["Views", stats.views],
|
|
164
|
+
["Pages", stats.pages],
|
|
165
|
+
["Dashboards", stats.dashboards],
|
|
166
|
+
["Reports", stats.reports],
|
|
167
|
+
["Actions", stats.actions],
|
|
168
|
+
["Themes", stats.themes]
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
label: "Logic",
|
|
173
|
+
items: [
|
|
174
|
+
["Flows", stats.flows],
|
|
175
|
+
["Workflows", stats.workflows],
|
|
176
|
+
["Approvals", stats.approvals],
|
|
177
|
+
["Agents", stats.agents],
|
|
178
|
+
["APIs", stats.apis]
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
label: "Security",
|
|
183
|
+
items: [
|
|
184
|
+
["Roles", stats.roles],
|
|
185
|
+
["Permissions", stats.permissions]
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
for (const section of sections) {
|
|
190
|
+
const nonZero = section.items.filter(([, v]) => v > 0);
|
|
191
|
+
if (nonZero.length === 0) continue;
|
|
192
|
+
const line = nonZero.map(([k, v]) => `${chalk.white(v)} ${chalk.dim(k)}`).join(" ");
|
|
193
|
+
console.log(` ${chalk.bold(section.label + ":")} ${line}`);
|
|
194
|
+
}
|
|
195
|
+
if (stats.plugins > 0 || stats.devPlugins > 0) {
|
|
196
|
+
const parts = [];
|
|
197
|
+
if (stats.plugins > 0) parts.push(`${stats.plugins} plugins`);
|
|
198
|
+
if (stats.devPlugins > 0) parts.push(`${stats.devPlugins} devPlugins`);
|
|
199
|
+
console.log(` ${chalk.bold("Runtime:")} ${chalk.dim(parts.join(", "))}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/utils/config.ts
|
|
204
|
+
function resolveConfigPath(source) {
|
|
205
|
+
if (source) {
|
|
206
|
+
const abs = path.resolve(process.cwd(), source);
|
|
207
|
+
if (!fs.existsSync(abs)) {
|
|
208
|
+
printError(`Config file not found: ${chalk2.white(abs)}`);
|
|
209
|
+
console.log("");
|
|
210
|
+
console.log(chalk2.dim(" Hint: Run this command from a directory with objectstack.config.ts"));
|
|
211
|
+
console.log(chalk2.dim(" Or specify the path: objectstack <command> path/to/config.ts"));
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
return abs;
|
|
215
|
+
}
|
|
216
|
+
const candidates = [
|
|
217
|
+
"objectstack.config.ts",
|
|
218
|
+
"objectstack.config.js",
|
|
219
|
+
"objectstack.config.mjs"
|
|
220
|
+
];
|
|
221
|
+
for (const candidate of candidates) {
|
|
222
|
+
const abs = path.resolve(process.cwd(), candidate);
|
|
223
|
+
if (fs.existsSync(abs)) return abs;
|
|
224
|
+
}
|
|
225
|
+
printError("No objectstack.config.{ts,js,mjs} found in current directory");
|
|
226
|
+
console.log("");
|
|
227
|
+
console.log(chalk2.dim(" Hint: Run `objectstack init` to create a new project"));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
async function loadConfig(source) {
|
|
231
|
+
const absolutePath = resolveConfigPath(source);
|
|
232
|
+
const start = Date.now();
|
|
233
|
+
const { mod } = await bundleRequire({
|
|
234
|
+
filepath: absolutePath
|
|
235
|
+
});
|
|
236
|
+
const config = mod.default || mod;
|
|
237
|
+
if (!config) {
|
|
238
|
+
throw new Error(`No default export found in ${path.basename(absolutePath)}`);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
config,
|
|
242
|
+
absolutePath,
|
|
243
|
+
duration: Date.now() - start
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/commands/compile.ts
|
|
248
|
+
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) => {
|
|
249
|
+
const timer = createTimer();
|
|
250
|
+
if (!options.json) {
|
|
251
|
+
printHeader("Compile");
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
if (!options.json) printStep("Loading configuration...");
|
|
255
|
+
const { config, absolutePath, duration } = await loadConfig(configPath);
|
|
256
|
+
if (!options.json) {
|
|
257
|
+
printKV("Config", path2.relative(process.cwd(), absolutePath));
|
|
258
|
+
printKV("Load time", `${duration}ms`);
|
|
259
|
+
}
|
|
260
|
+
if (!options.json) printStep("Validating protocol compliance...");
|
|
261
|
+
const result = ObjectStackDefinitionSchema.safeParse(config);
|
|
262
|
+
if (!result.success) {
|
|
263
|
+
if (options.json) {
|
|
264
|
+
console.log(JSON.stringify({ success: false, errors: result.error.issues }));
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
console.log("");
|
|
268
|
+
printError("Validation failed");
|
|
269
|
+
formatZodErrors(result.error);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
if (!options.json) printStep("Writing artifact...");
|
|
273
|
+
const output = options.output;
|
|
274
|
+
const artifactPath = path2.resolve(process.cwd(), output);
|
|
275
|
+
const artifactDir = path2.dirname(artifactPath);
|
|
276
|
+
if (!fs2.existsSync(artifactDir)) {
|
|
277
|
+
fs2.mkdirSync(artifactDir, { recursive: true });
|
|
278
|
+
}
|
|
279
|
+
const jsonContent = JSON.stringify(result.data, null, 2);
|
|
280
|
+
fs2.writeFileSync(artifactPath, jsonContent);
|
|
281
|
+
const sizeKB = (jsonContent.length / 1024).toFixed(1);
|
|
282
|
+
const stats = collectMetadataStats(config);
|
|
283
|
+
if (options.json) {
|
|
284
|
+
console.log(JSON.stringify({
|
|
285
|
+
success: true,
|
|
286
|
+
output: artifactPath,
|
|
287
|
+
size: jsonContent.length,
|
|
288
|
+
stats,
|
|
289
|
+
duration: timer.elapsed()
|
|
290
|
+
}));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
console.log("");
|
|
294
|
+
printSuccess(`Build complete ${chalk3.dim(`(${timer.display()})`)}`);
|
|
295
|
+
console.log("");
|
|
296
|
+
printMetadataStats(stats);
|
|
297
|
+
console.log("");
|
|
298
|
+
printKV("Artifact", `${output} ${chalk3.dim(`(${sizeKB} KB`)})`);
|
|
299
|
+
console.log("");
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (options.json) {
|
|
302
|
+
console.log(JSON.stringify({ success: false, error: error.message }));
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
24
305
|
console.log("");
|
|
306
|
+
printError(error.message || String(error));
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// src/commands/dev.ts
|
|
312
|
+
import { Command as Command2 } from "commander";
|
|
313
|
+
import chalk4 from "chalk";
|
|
314
|
+
import { execSync, spawn } from "child_process";
|
|
315
|
+
import fs3 from "fs";
|
|
316
|
+
import path3 from "path";
|
|
317
|
+
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 Console UI at /_studio/").option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
|
|
318
|
+
printHeader("Development Mode");
|
|
319
|
+
const configPath = path3.resolve(process.cwd(), "objectstack.config.ts");
|
|
320
|
+
if (packageName === "all" && fs3.existsSync(configPath)) {
|
|
321
|
+
printKV("Config", configPath, "\u{1F4C2}");
|
|
322
|
+
printStep("Starting dev server...");
|
|
25
323
|
const binPath = process.argv[1];
|
|
26
|
-
const child = spawn(process.execPath, [binPath, "serve", "--dev", ...options.verbose ? ["--verbose"] : []], {
|
|
324
|
+
const child = spawn(process.execPath, [binPath, "serve", "--dev", ...options.ui ? ["--ui"] : [], ...options.verbose ? ["--verbose"] : []], {
|
|
27
325
|
stdio: "inherit",
|
|
28
326
|
env: { ...process.env, NODE_ENV: "development" }
|
|
29
327
|
});
|
|
@@ -31,45 +329,37 @@ var devCommand = new Command("dev").description("Start development mode for a pa
|
|
|
31
329
|
}
|
|
32
330
|
try {
|
|
33
331
|
const cwd = process.cwd();
|
|
34
|
-
const workspaceConfigPath =
|
|
35
|
-
const isWorkspaceRoot =
|
|
332
|
+
const workspaceConfigPath = path3.resolve(cwd, "pnpm-workspace.yaml");
|
|
333
|
+
const isWorkspaceRoot = fs3.existsSync(workspaceConfigPath);
|
|
36
334
|
if (packageName === "all" && !isWorkspaceRoot) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
console.error(chalk.yellow(` To start development mode, run this command in a directory with objectstack.config.ts`));
|
|
40
|
-
console.error(chalk.yellow(` OR run from the monorepo root to start all packages.`));
|
|
335
|
+
printError(`Config file not found in ${cwd}`);
|
|
336
|
+
console.error(chalk4.yellow(" Run in a directory with objectstack.config.ts, or from the monorepo root."));
|
|
41
337
|
process.exit(1);
|
|
42
338
|
}
|
|
43
339
|
const filter = packageName === "all" ? "" : `--filter ${packageName}`;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
console.log("");
|
|
340
|
+
printKV("Package", packageName === "all" ? "All packages" : packageName, "\u{1F4E6}");
|
|
341
|
+
printKV("Watch", "enabled", "\u{1F504}");
|
|
47
342
|
const command = `pnpm ${filter} dev`.trim();
|
|
48
|
-
console.log(
|
|
343
|
+
console.log(chalk4.dim(`$ ${command}`));
|
|
49
344
|
console.log("");
|
|
50
345
|
execSync(command, {
|
|
51
346
|
stdio: "inherit",
|
|
52
347
|
cwd
|
|
53
348
|
});
|
|
54
349
|
} catch (error) {
|
|
55
|
-
|
|
56
|
-
\u274C Development mode failed:`));
|
|
57
|
-
console.error(error.message || error);
|
|
350
|
+
printError(`Development mode failed: ${error.message || error}`);
|
|
58
351
|
process.exit(1);
|
|
59
352
|
}
|
|
60
353
|
});
|
|
61
354
|
|
|
62
355
|
// src/commands/doctor.ts
|
|
63
|
-
import { Command as
|
|
64
|
-
import
|
|
356
|
+
import { Command as Command3 } from "commander";
|
|
357
|
+
import chalk5 from "chalk";
|
|
65
358
|
import { execSync as execSync2 } from "child_process";
|
|
66
|
-
import
|
|
67
|
-
import
|
|
68
|
-
var doctorCommand = new
|
|
69
|
-
|
|
70
|
-
\u{1F3E5} ObjectStack Environment Health Check`));
|
|
71
|
-
console.log(chalk2.dim(`-----------------------------------------`));
|
|
72
|
-
console.log("");
|
|
359
|
+
import fs4 from "fs";
|
|
360
|
+
import path4 from "path";
|
|
361
|
+
var doctorCommand = new Command3("doctor").description("Check development environment health").option("-v, --verbose", "Show detailed information").action(async (options) => {
|
|
362
|
+
printHeader("Environment Health Check");
|
|
73
363
|
const results = [];
|
|
74
364
|
try {
|
|
75
365
|
const nodeVersion = process.version;
|
|
@@ -127,8 +417,8 @@ var doctorCommand = new Command2("doctor").description("Check development enviro
|
|
|
127
417
|
});
|
|
128
418
|
}
|
|
129
419
|
const cwd = process.cwd();
|
|
130
|
-
const nodeModulesPath =
|
|
131
|
-
if (
|
|
420
|
+
const nodeModulesPath = path4.join(cwd, "node_modules");
|
|
421
|
+
if (fs4.existsSync(nodeModulesPath)) {
|
|
132
422
|
results.push({
|
|
133
423
|
name: "Dependencies",
|
|
134
424
|
status: "ok",
|
|
@@ -142,8 +432,8 @@ var doctorCommand = new Command2("doctor").description("Check development enviro
|
|
|
142
432
|
fix: "Run: pnpm install"
|
|
143
433
|
});
|
|
144
434
|
}
|
|
145
|
-
const specDistPath =
|
|
146
|
-
if (
|
|
435
|
+
const specDistPath = path4.join(cwd, "packages/spec/dist");
|
|
436
|
+
if (fs4.existsSync(specDistPath)) {
|
|
147
437
|
results.push({
|
|
148
438
|
name: "@objectstack/spec",
|
|
149
439
|
status: "ok",
|
|
@@ -174,35 +464,41 @@ var doctorCommand = new Command2("doctor").description("Check development enviro
|
|
|
174
464
|
}
|
|
175
465
|
let hasErrors = false;
|
|
176
466
|
let hasWarnings = false;
|
|
467
|
+
console.log("");
|
|
177
468
|
results.forEach((result) => {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (result.
|
|
182
|
-
|
|
469
|
+
const padded = result.name.padEnd(20);
|
|
470
|
+
if (result.status === "ok") {
|
|
471
|
+
printSuccess(`${padded} ${result.message}`);
|
|
472
|
+
} else if (result.status === "warning") {
|
|
473
|
+
printWarning(`${padded} ${result.message}`);
|
|
474
|
+
} else {
|
|
475
|
+
printError(`${padded} ${result.message}`);
|
|
476
|
+
}
|
|
477
|
+
if (result.fix && (options.verbose || result.status === "error")) {
|
|
478
|
+
console.log(chalk5.dim(` \u2192 ${result.fix}`));
|
|
183
479
|
}
|
|
184
480
|
if (result.status === "error") hasErrors = true;
|
|
185
481
|
if (result.status === "warning") hasWarnings = true;
|
|
186
482
|
});
|
|
187
483
|
console.log("");
|
|
188
484
|
if (hasErrors) {
|
|
189
|
-
console.log(
|
|
190
|
-
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(
|
|
485
|
+
console.log(chalk5.red("\u274C Some critical issues found. Please fix them before continuing."));
|
|
486
|
+
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(chalk5.dim(` ${r.fix}`)));
|
|
191
487
|
process.exit(1);
|
|
192
488
|
} else if (hasWarnings) {
|
|
193
|
-
console.log(
|
|
194
|
-
console.log(
|
|
489
|
+
console.log(chalk5.yellow("\u26A0\uFE0F Environment is functional but has some warnings."));
|
|
490
|
+
console.log(chalk5.dim(" Run with --verbose to see fix suggestions."));
|
|
195
491
|
} else {
|
|
196
|
-
console.log(
|
|
492
|
+
console.log(chalk5.green("\u2705 Environment is healthy and ready for development!"));
|
|
197
493
|
}
|
|
198
494
|
console.log("");
|
|
199
495
|
});
|
|
200
496
|
|
|
201
497
|
// src/commands/create.ts
|
|
202
|
-
import { Command as
|
|
203
|
-
import
|
|
204
|
-
import
|
|
205
|
-
import
|
|
498
|
+
import { Command as Command4 } from "commander";
|
|
499
|
+
import chalk6 from "chalk";
|
|
500
|
+
import fs5 from "fs";
|
|
501
|
+
import path5 from "path";
|
|
206
502
|
var templates = {
|
|
207
503
|
plugin: {
|
|
208
504
|
description: "Create a new ObjectStack plugin",
|
|
@@ -223,12 +519,12 @@ var templates = {
|
|
|
223
519
|
license: "MIT",
|
|
224
520
|
dependencies: {
|
|
225
521
|
"@objectstack/spec": "workspace:*",
|
|
226
|
-
zod: "^3.
|
|
522
|
+
zod: "^4.3.6"
|
|
227
523
|
},
|
|
228
524
|
devDependencies: {
|
|
229
|
-
"@types/node": "^
|
|
230
|
-
typescript: "^5.
|
|
231
|
-
vitest: "^
|
|
525
|
+
"@types/node": "^22.0.0",
|
|
526
|
+
typescript: "^5.8.0",
|
|
527
|
+
vitest: "^4.0.0"
|
|
232
528
|
}
|
|
233
529
|
}),
|
|
234
530
|
"tsconfig.json": () => ({
|
|
@@ -306,17 +602,22 @@ MIT
|
|
|
306
602
|
dependencies: {
|
|
307
603
|
"@objectstack/spec": "workspace:*",
|
|
308
604
|
"@objectstack/cli": "workspace:*",
|
|
309
|
-
zod: "^3.
|
|
605
|
+
zod: "^4.3.6"
|
|
310
606
|
},
|
|
311
607
|
devDependencies: {
|
|
312
|
-
"@types/node": "^
|
|
608
|
+
"@types/node": "^22.0.0",
|
|
313
609
|
tsx: "^4.21.0",
|
|
314
|
-
typescript: "^5.
|
|
315
|
-
vitest: "^
|
|
610
|
+
typescript: "^5.8.0",
|
|
611
|
+
vitest: "^4.0.0"
|
|
316
612
|
}
|
|
317
613
|
}),
|
|
318
614
|
"objectstack.config.ts": (name) => `import { defineStack } from '@objectstack/spec';
|
|
319
615
|
|
|
616
|
+
// Barrel imports \u2014 add more as you create new type folders
|
|
617
|
+
// import * as objects from './src/objects';
|
|
618
|
+
// import * as actions from './src/actions';
|
|
619
|
+
// import * as apps from './src/apps';
|
|
620
|
+
|
|
320
621
|
export default defineStack({
|
|
321
622
|
manifest: {
|
|
322
623
|
name: '${name}',
|
|
@@ -325,12 +626,11 @@ export default defineStack({
|
|
|
325
626
|
},
|
|
326
627
|
|
|
327
628
|
objects: [
|
|
328
|
-
//
|
|
329
|
-
// { name: 'my_object', fields: { ... } }
|
|
629
|
+
// Object.values(objects), // Uncomment after creating src/objects/index.ts
|
|
330
630
|
],
|
|
331
631
|
|
|
332
632
|
apps: [
|
|
333
|
-
//
|
|
633
|
+
// Object.values(apps), // Uncomment after creating src/apps/index.ts
|
|
334
634
|
],
|
|
335
635
|
});
|
|
336
636
|
`,
|
|
@@ -372,81 +672,289 @@ pnpm dev
|
|
|
372
672
|
function toCamelCase(str) {
|
|
373
673
|
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
374
674
|
}
|
|
375
|
-
var createCommand = new
|
|
376
|
-
console.log(
|
|
675
|
+
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) => {
|
|
676
|
+
console.log(chalk6.bold(`
|
|
377
677
|
\u{1F4E6} ObjectStack Project Creator`));
|
|
378
|
-
console.log(
|
|
678
|
+
console.log(chalk6.dim(`-------------------------------`));
|
|
379
679
|
if (!templates[type]) {
|
|
380
|
-
console.error(
|
|
680
|
+
console.error(chalk6.red(`
|
|
381
681
|
\u274C Unknown type: ${type}`));
|
|
382
|
-
console.log(
|
|
682
|
+
console.log(chalk6.dim("Available types: plugin, example"));
|
|
383
683
|
process.exit(1);
|
|
384
684
|
}
|
|
385
685
|
if (!name) {
|
|
386
|
-
console.error(
|
|
387
|
-
console.log(
|
|
686
|
+
console.error(chalk6.red("\n\u274C Project name is required"));
|
|
687
|
+
console.log(chalk6.dim(`Usage: objectstack create ${type} <name>`));
|
|
388
688
|
process.exit(1);
|
|
389
689
|
}
|
|
390
690
|
const template = templates[type];
|
|
391
691
|
const cwd = process.cwd();
|
|
392
692
|
let targetDir;
|
|
393
693
|
if (options?.dir) {
|
|
394
|
-
targetDir =
|
|
694
|
+
targetDir = path5.resolve(cwd, options.dir);
|
|
395
695
|
} else {
|
|
396
696
|
const baseDir = type === "plugin" ? "packages/plugins" : "examples";
|
|
397
697
|
const projectName = type === "plugin" ? `plugin-${name}` : name;
|
|
398
|
-
targetDir =
|
|
698
|
+
targetDir = path5.join(cwd, baseDir, projectName);
|
|
399
699
|
}
|
|
400
|
-
if (
|
|
401
|
-
console.error(
|
|
700
|
+
if (fs5.existsSync(targetDir)) {
|
|
701
|
+
console.error(chalk6.red(`
|
|
402
702
|
\u274C Directory already exists: ${targetDir}`));
|
|
403
703
|
process.exit(1);
|
|
404
704
|
}
|
|
405
|
-
console.log(`\u{1F4C1} Creating ${type}: ${
|
|
406
|
-
console.log(`\u{1F4C2} Location: ${
|
|
705
|
+
console.log(`\u{1F4C1} Creating ${type}: ${chalk6.blue(name)}`);
|
|
706
|
+
console.log(`\u{1F4C2} Location: ${chalk6.dim(targetDir)}`);
|
|
407
707
|
console.log("");
|
|
408
708
|
try {
|
|
409
|
-
|
|
709
|
+
fs5.mkdirSync(targetDir, { recursive: true });
|
|
410
710
|
for (const [filePath, contentFn] of Object.entries(template.files)) {
|
|
411
|
-
const fullPath =
|
|
412
|
-
const dir =
|
|
413
|
-
if (!
|
|
414
|
-
|
|
711
|
+
const fullPath = path5.join(targetDir, filePath);
|
|
712
|
+
const dir = path5.dirname(fullPath);
|
|
713
|
+
if (!fs5.existsSync(dir)) {
|
|
714
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
415
715
|
}
|
|
416
716
|
const content = contentFn(name);
|
|
417
717
|
const fileContent = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
418
|
-
|
|
419
|
-
console.log(
|
|
718
|
+
fs5.writeFileSync(fullPath, fileContent);
|
|
719
|
+
console.log(chalk6.green(`\u2713 Created ${filePath}`));
|
|
420
720
|
}
|
|
421
721
|
console.log("");
|
|
422
|
-
console.log(
|
|
722
|
+
console.log(chalk6.green("\u2705 Project created successfully!"));
|
|
423
723
|
console.log("");
|
|
424
|
-
console.log(
|
|
425
|
-
console.log(
|
|
426
|
-
console.log(
|
|
427
|
-
console.log(
|
|
724
|
+
console.log(chalk6.bold("Next steps:"));
|
|
725
|
+
console.log(chalk6.dim(` cd ${path5.relative(cwd, targetDir)}`));
|
|
726
|
+
console.log(chalk6.dim(" pnpm install"));
|
|
727
|
+
console.log(chalk6.dim(" pnpm build"));
|
|
428
728
|
console.log("");
|
|
429
729
|
} catch (error) {
|
|
430
|
-
console.error(
|
|
730
|
+
console.error(chalk6.red("\n\u274C Failed to create project:"));
|
|
431
731
|
console.error(error.message || error);
|
|
432
|
-
if (
|
|
433
|
-
|
|
732
|
+
if (fs5.existsSync(targetDir)) {
|
|
733
|
+
fs5.rmSync(targetDir, { recursive: true });
|
|
434
734
|
}
|
|
435
735
|
process.exit(1);
|
|
436
736
|
}
|
|
437
737
|
});
|
|
438
738
|
|
|
439
739
|
// src/commands/serve.ts
|
|
440
|
-
import { Command as
|
|
441
|
-
import
|
|
442
|
-
import
|
|
740
|
+
import { Command as Command5 } from "commander";
|
|
741
|
+
import path7 from "path";
|
|
742
|
+
import fs7 from "fs";
|
|
743
|
+
import net2 from "net";
|
|
744
|
+
import chalk7 from "chalk";
|
|
745
|
+
import { bundleRequire as bundleRequire2 } from "bundle-require";
|
|
746
|
+
|
|
747
|
+
// src/utils/console.ts
|
|
748
|
+
import path6 from "path";
|
|
749
|
+
import fs6 from "fs";
|
|
443
750
|
import net from "net";
|
|
444
|
-
import
|
|
445
|
-
|
|
751
|
+
import { spawn as spawn2 } from "child_process";
|
|
752
|
+
var STUDIO_PATH = "/_studio";
|
|
753
|
+
var VITE_PORT_START = 24678;
|
|
754
|
+
function resolveConsolePath() {
|
|
755
|
+
const cwd = process.cwd();
|
|
756
|
+
const candidates = [
|
|
757
|
+
path6.resolve(cwd, "apps/console"),
|
|
758
|
+
path6.resolve(cwd, "../../apps/console"),
|
|
759
|
+
path6.resolve(cwd, "../apps/console")
|
|
760
|
+
];
|
|
761
|
+
for (const candidate of candidates) {
|
|
762
|
+
const pkgPath = path6.join(candidate, "package.json");
|
|
763
|
+
if (fs6.existsSync(pkgPath)) {
|
|
764
|
+
try {
|
|
765
|
+
const pkg2 = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
|
|
766
|
+
if (pkg2.name === "@objectstack/console") return candidate;
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
const { createRequire: createRequire2 } = __require("module");
|
|
773
|
+
const req = createRequire2(import.meta.url);
|
|
774
|
+
const resolved = req.resolve("@objectstack/console/package.json");
|
|
775
|
+
return path6.dirname(resolved);
|
|
776
|
+
} catch {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function hasConsoleDist(consolePath) {
|
|
781
|
+
return fs6.existsSync(path6.join(consolePath, "dist", "index.html"));
|
|
782
|
+
}
|
|
783
|
+
function findAvailablePort(start = VITE_PORT_START) {
|
|
784
|
+
return new Promise((resolve, reject) => {
|
|
785
|
+
const server = net.createServer();
|
|
786
|
+
server.once("error", () => {
|
|
787
|
+
findAvailablePort(start + 1).then(resolve, reject);
|
|
788
|
+
});
|
|
789
|
+
server.once("listening", () => {
|
|
790
|
+
server.close(() => resolve(start));
|
|
791
|
+
});
|
|
792
|
+
server.listen(start);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
async function spawnViteDevServer(consolePath, options = {}) {
|
|
796
|
+
const vitePort = await findAvailablePort(VITE_PORT_START);
|
|
797
|
+
const viteBinCandidates = [
|
|
798
|
+
path6.join(consolePath, "node_modules", ".bin", "vite"),
|
|
799
|
+
path6.join(consolePath, "..", "..", "node_modules", ".bin", "vite")
|
|
800
|
+
];
|
|
801
|
+
let viteBin = null;
|
|
802
|
+
for (const candidate of viteBinCandidates) {
|
|
803
|
+
if (fs6.existsSync(candidate)) {
|
|
804
|
+
viteBin = candidate;
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const command = viteBin || "npx";
|
|
809
|
+
const args = viteBin ? ["--port", String(vitePort), "--strictPort"] : ["vite", "--port", String(vitePort), "--strictPort"];
|
|
810
|
+
const child = spawn2(command, args, {
|
|
811
|
+
cwd: consolePath,
|
|
812
|
+
env: {
|
|
813
|
+
...process.env,
|
|
814
|
+
VITE_BASE: `${STUDIO_PATH}/`,
|
|
815
|
+
VITE_PORT: String(vitePort),
|
|
816
|
+
VITE_HMR_PORT: String(vitePort),
|
|
817
|
+
VITE_RUNTIME_MODE: "server",
|
|
818
|
+
VITE_SERVER_URL: "",
|
|
819
|
+
// Same-origin API
|
|
820
|
+
NODE_ENV: "development"
|
|
821
|
+
},
|
|
822
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
823
|
+
});
|
|
824
|
+
let stderr = "";
|
|
825
|
+
child.stderr?.on("data", (data) => {
|
|
826
|
+
stderr += data.toString();
|
|
827
|
+
});
|
|
828
|
+
await new Promise((resolve, reject) => {
|
|
829
|
+
const timeout = setTimeout(() => {
|
|
830
|
+
child.kill();
|
|
831
|
+
reject(new Error(`Vite dev server timed out after 30 s.
|
|
832
|
+
${stderr}`));
|
|
833
|
+
}, 3e4);
|
|
834
|
+
child.stdout?.on("data", (data) => {
|
|
835
|
+
const output = data.toString();
|
|
836
|
+
if (output.includes("Local:") || output.includes("ready in")) {
|
|
837
|
+
clearTimeout(timeout);
|
|
838
|
+
resolve();
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
child.on("error", (err) => {
|
|
842
|
+
clearTimeout(timeout);
|
|
843
|
+
reject(err);
|
|
844
|
+
});
|
|
845
|
+
child.on("exit", (code) => {
|
|
846
|
+
if (code !== 0 && code !== null) {
|
|
847
|
+
clearTimeout(timeout);
|
|
848
|
+
reject(new Error(`Vite exited with code ${code}.
|
|
849
|
+
${stderr}`));
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
return { port: vitePort, process: child };
|
|
854
|
+
}
|
|
855
|
+
function createConsoleProxyPlugin(vitePort) {
|
|
856
|
+
return {
|
|
857
|
+
name: "com.objectstack.console-proxy",
|
|
858
|
+
init: async () => {
|
|
859
|
+
},
|
|
860
|
+
start: async (ctx) => {
|
|
861
|
+
const httpServer = ctx.getService?.("http.server");
|
|
862
|
+
if (!httpServer?.getRawApp) {
|
|
863
|
+
ctx.logger?.warn?.("Console proxy: http.server service not found \u2014 skipping");
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const app = httpServer.getRawApp();
|
|
867
|
+
app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
868
|
+
app.all(`${STUDIO_PATH}/*`, async (c) => {
|
|
869
|
+
const targetUrl = `http://localhost:${vitePort}${c.req.path}`;
|
|
870
|
+
try {
|
|
871
|
+
const headers = new Headers(c.req.raw.headers);
|
|
872
|
+
headers.delete("host");
|
|
873
|
+
const isBodyAllowed = !["GET", "HEAD"].includes(c.req.method);
|
|
874
|
+
const resp = await fetch(targetUrl, {
|
|
875
|
+
method: c.req.method,
|
|
876
|
+
headers,
|
|
877
|
+
body: isBodyAllowed ? c.req.raw.body : void 0,
|
|
878
|
+
// @ts-expect-error — duplex required for streaming request body
|
|
879
|
+
duplex: isBodyAllowed ? "half" : void 0
|
|
880
|
+
});
|
|
881
|
+
return new Response(resp.body, {
|
|
882
|
+
status: resp.status,
|
|
883
|
+
headers: resp.headers
|
|
884
|
+
});
|
|
885
|
+
} catch {
|
|
886
|
+
return c.text("Console dev server is starting\u2026", 502);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
function createConsoleStaticPlugin(distPath) {
|
|
893
|
+
return {
|
|
894
|
+
name: "com.objectstack.console-static",
|
|
895
|
+
init: async () => {
|
|
896
|
+
},
|
|
897
|
+
start: async (ctx) => {
|
|
898
|
+
const httpServer = ctx.getService?.("http.server");
|
|
899
|
+
if (!httpServer?.getRawApp) {
|
|
900
|
+
ctx.logger?.warn?.("Console static: http.server service not found \u2014 skipping");
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const app = httpServer.getRawApp();
|
|
904
|
+
const absoluteDist = path6.resolve(distPath);
|
|
905
|
+
const indexPath = path6.join(absoluteDist, "index.html");
|
|
906
|
+
if (!fs6.existsSync(indexPath)) {
|
|
907
|
+
ctx.logger?.warn?.(`Console static: dist not found at ${absoluteDist}`);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
911
|
+
app.get(`${STUDIO_PATH}/*`, async (c) => {
|
|
912
|
+
const reqPath = c.req.path.substring(STUDIO_PATH.length) || "/";
|
|
913
|
+
const filePath = path6.join(absoluteDist, reqPath);
|
|
914
|
+
if (!filePath.startsWith(absoluteDist)) {
|
|
915
|
+
return c.text("Forbidden", 403);
|
|
916
|
+
}
|
|
917
|
+
if (fs6.existsSync(filePath) && fs6.statSync(filePath).isFile()) {
|
|
918
|
+
const content = fs6.readFileSync(filePath);
|
|
919
|
+
return new Response(content, {
|
|
920
|
+
headers: { "content-type": mimeType(filePath) }
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
const html = fs6.readFileSync(indexPath);
|
|
924
|
+
return new Response(html, {
|
|
925
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
var MIME_TYPES = {
|
|
932
|
+
".html": "text/html; charset=utf-8",
|
|
933
|
+
".js": "application/javascript; charset=utf-8",
|
|
934
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
935
|
+
".css": "text/css; charset=utf-8",
|
|
936
|
+
".json": "application/json; charset=utf-8",
|
|
937
|
+
".svg": "image/svg+xml",
|
|
938
|
+
".png": "image/png",
|
|
939
|
+
".jpg": "image/jpeg",
|
|
940
|
+
".jpeg": "image/jpeg",
|
|
941
|
+
".gif": "image/gif",
|
|
942
|
+
".ico": "image/x-icon",
|
|
943
|
+
".woff": "font/woff",
|
|
944
|
+
".woff2": "font/woff2",
|
|
945
|
+
".ttf": "font/ttf",
|
|
946
|
+
".map": "application/json"
|
|
947
|
+
};
|
|
948
|
+
function mimeType(filePath) {
|
|
949
|
+
const ext = path6.extname(filePath).toLowerCase();
|
|
950
|
+
return MIME_TYPES[ext] || "application/octet-stream";
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/commands/serve.ts
|
|
446
954
|
var getAvailablePort = async (startPort) => {
|
|
447
955
|
const isPortAvailable = (port2) => {
|
|
448
956
|
return new Promise((resolve) => {
|
|
449
|
-
const server =
|
|
957
|
+
const server = net2.createServer();
|
|
450
958
|
server.once("error", (err) => {
|
|
451
959
|
resolve(false);
|
|
452
960
|
});
|
|
@@ -465,7 +973,7 @@ var getAvailablePort = async (startPort) => {
|
|
|
465
973
|
}
|
|
466
974
|
return port;
|
|
467
975
|
};
|
|
468
|
-
var serveCommand = new
|
|
976
|
+
var serveCommand = new Command5("serve").description("Start ObjectStack server with plugins from configuration").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").option("--dev", "Run in development mode (load devPlugins)").option("--ui", "Enable Console UI at /_studio/").option("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
|
|
469
977
|
let port = parseInt(options.port);
|
|
470
978
|
try {
|
|
471
979
|
const availablePort = await getAvailablePort(port);
|
|
@@ -474,36 +982,59 @@ var serveCommand = new Command4("serve").description("Start ObjectStack server w
|
|
|
474
982
|
}
|
|
475
983
|
} catch (e) {
|
|
476
984
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
console.log(
|
|
483
|
-
} else {
|
|
484
|
-
console.log(`\u{1F310} Port: ${chalk4.blue(port)}`);
|
|
485
|
-
}
|
|
486
|
-
console.log("");
|
|
487
|
-
const absolutePath = path4.resolve(process.cwd(), configPath);
|
|
488
|
-
if (!fs4.existsSync(absolutePath)) {
|
|
489
|
-
console.error(chalk4.red(`
|
|
490
|
-
\u274C Configuration file not found: ${absolutePath}`));
|
|
985
|
+
const isDev = options.dev || process.env.NODE_ENV === "development";
|
|
986
|
+
const absolutePath = path7.resolve(process.cwd(), configPath);
|
|
987
|
+
const relativeConfig = path7.relative(process.cwd(), absolutePath);
|
|
988
|
+
if (!fs7.existsSync(absolutePath)) {
|
|
989
|
+
printError(`Configuration file not found: ${absolutePath}`);
|
|
990
|
+
console.log(chalk7.dim(" Hint: Run `objectstack init` to create a new project"));
|
|
491
991
|
process.exit(1);
|
|
492
992
|
}
|
|
993
|
+
console.log("");
|
|
994
|
+
console.log(chalk7.dim(` Loading ${relativeConfig}...`));
|
|
995
|
+
const loadedPlugins = [];
|
|
996
|
+
const shortPluginName = (raw) => {
|
|
997
|
+
if (raw.includes("objectql")) return "ObjectQL";
|
|
998
|
+
if (raw.includes("driver") && raw.includes("memory")) return "MemoryDriver";
|
|
999
|
+
if (raw.startsWith("plugin.app.")) return raw.replace("plugin.app.", "").split(".").pop() || raw;
|
|
1000
|
+
if (raw.includes("hono")) return "HonoServer";
|
|
1001
|
+
return raw;
|
|
1002
|
+
};
|
|
1003
|
+
const trackPlugin = (name) => {
|
|
1004
|
+
loadedPlugins.push(shortPluginName(name));
|
|
1005
|
+
};
|
|
1006
|
+
const originalConsoleLog = console.log;
|
|
1007
|
+
const originalConsoleDebug = console.debug;
|
|
1008
|
+
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
1009
|
+
let bootQuiet = false;
|
|
1010
|
+
const restoreOutput = () => {
|
|
1011
|
+
bootQuiet = false;
|
|
1012
|
+
process.stdout.write = origStdoutWrite;
|
|
1013
|
+
console.log = originalConsoleLog;
|
|
1014
|
+
console.debug = originalConsoleDebug;
|
|
1015
|
+
};
|
|
1016
|
+
const portShifted = parseInt(options.port) !== port;
|
|
493
1017
|
try {
|
|
494
|
-
|
|
495
|
-
|
|
1018
|
+
bootQuiet = true;
|
|
1019
|
+
process.stdout.write = (chunk, ...rest) => {
|
|
1020
|
+
if (bootQuiet) return true;
|
|
1021
|
+
return origStdoutWrite(chunk, ...rest);
|
|
1022
|
+
};
|
|
1023
|
+
console.log = (...args) => {
|
|
1024
|
+
if (!bootQuiet) originalConsoleLog(...args);
|
|
1025
|
+
};
|
|
1026
|
+
console.debug = (...args) => {
|
|
1027
|
+
if (!bootQuiet) originalConsoleDebug(...args);
|
|
1028
|
+
};
|
|
1029
|
+
const { mod } = await bundleRequire2({
|
|
496
1030
|
filepath: absolutePath
|
|
497
1031
|
});
|
|
498
1032
|
const config = mod.default || mod;
|
|
499
1033
|
if (!config) {
|
|
500
|
-
throw new Error(`
|
|
1034
|
+
throw new Error(`No default export found in ${configPath}`);
|
|
501
1035
|
}
|
|
502
|
-
console.log(chalk4.green(`\u2713 Configuration loaded`));
|
|
503
1036
|
const { Runtime } = await import("@objectstack/runtime");
|
|
504
|
-
|
|
505
|
-
const isDev = options.dev || process.env.NODE_ENV === "development";
|
|
506
|
-
const loggerConfig = isDev ? { format: "pretty" } : void 0;
|
|
1037
|
+
const loggerConfig = { level: "silent" };
|
|
507
1038
|
const runtime = new Runtime({
|
|
508
1039
|
kernel: {
|
|
509
1040
|
logger: loggerConfig
|
|
@@ -512,48 +1043,40 @@ var serveCommand = new Command4("serve").description("Start ObjectStack server w
|
|
|
512
1043
|
const kernel = runtime.getKernel();
|
|
513
1044
|
let plugins = config.plugins || [];
|
|
514
1045
|
if (options.dev && config.devPlugins) {
|
|
515
|
-
console.log(chalk4.blue(`\u{1F4E6} Loading development plugins...`));
|
|
516
1046
|
plugins = [...plugins, ...config.devPlugins];
|
|
517
1047
|
}
|
|
518
1048
|
const hasObjectQL = plugins.some((p) => p.name?.includes("objectql") || p.constructor?.name?.includes("ObjectQL"));
|
|
519
1049
|
if (config.objects && !hasObjectQL) {
|
|
520
1050
|
try {
|
|
521
|
-
console.log(chalk4.dim(` Auto-injecting ObjectQL Engine...`));
|
|
522
1051
|
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
523
1052
|
await kernel.use(new ObjectQLPlugin());
|
|
524
|
-
|
|
1053
|
+
trackPlugin("ObjectQL");
|
|
525
1054
|
} catch (e) {
|
|
526
|
-
console.warn(chalk4.yellow(` \u26A0 Could not auto-load ObjectQL: ${e.message}`));
|
|
527
1055
|
}
|
|
528
1056
|
}
|
|
529
1057
|
const hasDriver = plugins.some((p) => p.name?.includes("driver") || p.constructor?.name?.includes("Driver"));
|
|
530
1058
|
if (isDev && !hasDriver && config.objects) {
|
|
531
1059
|
try {
|
|
532
|
-
console.log(chalk4.dim(` Auto-injecting Memory Driver (Dev Mode)...`));
|
|
533
1060
|
const { DriverPlugin } = await import("@objectstack/runtime");
|
|
534
1061
|
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
535
1062
|
await kernel.use(new DriverPlugin(new InMemoryDriver()));
|
|
536
|
-
|
|
1063
|
+
trackPlugin("MemoryDriver");
|
|
537
1064
|
} catch (e) {
|
|
538
|
-
console.log(chalk4.dim(` \u2139 No default driver loaded: ${e.message}`));
|
|
539
1065
|
}
|
|
540
1066
|
}
|
|
541
1067
|
if (config.objects || config.manifest || config.apps) {
|
|
542
1068
|
try {
|
|
543
1069
|
const { AppPlugin } = await import("@objectstack/runtime");
|
|
544
1070
|
await kernel.use(new AppPlugin(config));
|
|
545
|
-
|
|
1071
|
+
trackPlugin("App");
|
|
546
1072
|
} catch (e) {
|
|
547
|
-
console.warn(chalk4.yellow(` \u26A0 Could not auto-load AppPlugin: ${e.message}`));
|
|
548
1073
|
}
|
|
549
1074
|
}
|
|
550
1075
|
if (plugins.length > 0) {
|
|
551
|
-
console.log(chalk4.yellow(`\u{1F4E6} Loading ${plugins.length} plugin(s)...`));
|
|
552
1076
|
for (const plugin of plugins) {
|
|
553
1077
|
try {
|
|
554
1078
|
let pluginToLoad = plugin;
|
|
555
1079
|
if (typeof plugin === "string") {
|
|
556
|
-
console.log(chalk4.dim(` Trying to resolve plugin: ${plugin}`));
|
|
557
1080
|
try {
|
|
558
1081
|
const imported = await import(plugin);
|
|
559
1082
|
pluginToLoad = imported.default || imported;
|
|
@@ -563,9 +1086,9 @@ var serveCommand = new Command4("serve").description("Start ObjectStack server w
|
|
|
563
1086
|
}
|
|
564
1087
|
await kernel.use(pluginToLoad);
|
|
565
1088
|
const pluginName = plugin.name || plugin.constructor?.name || "unnamed";
|
|
566
|
-
|
|
1089
|
+
trackPlugin(pluginName);
|
|
567
1090
|
} catch (e) {
|
|
568
|
-
console.error(
|
|
1091
|
+
console.error(chalk7.red(` \u2717 Failed to load plugin: ${e.message}`));
|
|
569
1092
|
}
|
|
570
1093
|
}
|
|
571
1094
|
}
|
|
@@ -574,62 +1097,120 @@ var serveCommand = new Command4("serve").description("Start ObjectStack server w
|
|
|
574
1097
|
const { HonoServerPlugin } = await import("@objectstack/plugin-hono-server");
|
|
575
1098
|
const serverPlugin = new HonoServerPlugin({ port });
|
|
576
1099
|
await kernel.use(serverPlugin);
|
|
577
|
-
|
|
1100
|
+
trackPlugin("HonoServer");
|
|
578
1101
|
} catch (e) {
|
|
579
|
-
console.warn(
|
|
1102
|
+
console.warn(chalk7.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
let viteProcess = null;
|
|
1106
|
+
if (options.ui) {
|
|
1107
|
+
const consolePath = resolveConsolePath();
|
|
1108
|
+
if (!consolePath) {
|
|
1109
|
+
console.warn(chalk7.yellow(` \u26A0 @objectstack/console not found \u2014 skipping UI`));
|
|
1110
|
+
} else if (isDev) {
|
|
1111
|
+
try {
|
|
1112
|
+
const result = await spawnViteDevServer(consolePath, { serverPort: port });
|
|
1113
|
+
viteProcess = result.process;
|
|
1114
|
+
await kernel.use(createConsoleProxyPlugin(result.port));
|
|
1115
|
+
trackPlugin("ConsoleUI");
|
|
1116
|
+
} catch (e) {
|
|
1117
|
+
console.warn(chalk7.yellow(` \u26A0 Console UI failed to start: ${e.message}`));
|
|
1118
|
+
}
|
|
1119
|
+
} else {
|
|
1120
|
+
const distPath = path7.join(consolePath, "dist");
|
|
1121
|
+
if (hasConsoleDist(consolePath)) {
|
|
1122
|
+
await kernel.use(createConsoleStaticPlugin(distPath));
|
|
1123
|
+
trackPlugin("ConsoleUI");
|
|
1124
|
+
} else {
|
|
1125
|
+
console.warn(chalk7.yellow(` \u26A0 Console dist not found \u2014 run "pnpm --filter @objectstack/console build" first`));
|
|
1126
|
+
}
|
|
580
1127
|
}
|
|
581
1128
|
}
|
|
582
|
-
console.log(chalk4.yellow(`
|
|
583
|
-
\u{1F680} Starting ObjectStack...`));
|
|
584
1129
|
await runtime.start();
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
1130
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1131
|
+
restoreOutput();
|
|
1132
|
+
printServerReady({
|
|
1133
|
+
port,
|
|
1134
|
+
configFile: relativeConfig,
|
|
1135
|
+
isDev,
|
|
1136
|
+
pluginCount: loadedPlugins.length,
|
|
1137
|
+
pluginNames: loadedPlugins,
|
|
1138
|
+
uiEnabled: !!options.ui,
|
|
1139
|
+
studioPath: STUDIO_PATH
|
|
1140
|
+
});
|
|
589
1141
|
process.on("SIGINT", async () => {
|
|
590
|
-
console.
|
|
1142
|
+
console.warn(chalk7.yellow(`
|
|
591
1143
|
|
|
592
1144
|
\u23F9 Stopping server...`));
|
|
1145
|
+
if (viteProcess) {
|
|
1146
|
+
viteProcess.kill();
|
|
1147
|
+
viteProcess = null;
|
|
1148
|
+
}
|
|
593
1149
|
await runtime.getKernel().shutdown();
|
|
594
|
-
console.log(
|
|
1150
|
+
console.log(chalk7.green(`\u2705 Server stopped`));
|
|
595
1151
|
process.exit(0);
|
|
596
1152
|
});
|
|
597
1153
|
} catch (error) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
console.error(error.stack);
|
|
1154
|
+
restoreOutput();
|
|
1155
|
+
console.log("");
|
|
1156
|
+
printError(error.message || String(error));
|
|
1157
|
+
if (process.env.DEBUG) console.error(chalk7.dim(error.stack));
|
|
602
1158
|
process.exit(1);
|
|
603
1159
|
}
|
|
604
1160
|
});
|
|
605
1161
|
|
|
1162
|
+
// src/commands/studio.ts
|
|
1163
|
+
import { Command as Command6 } from "commander";
|
|
1164
|
+
import { spawn as spawn3 } from "child_process";
|
|
1165
|
+
var studioCommand = new Command6("studio").description("Launch Console UI with development server").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").action(async (configPath, options) => {
|
|
1166
|
+
printHeader("Studio");
|
|
1167
|
+
printKV("Mode", "dev + ui", "\u{1F3A8}");
|
|
1168
|
+
printStep("Delegating to serve --dev --ui \u2026");
|
|
1169
|
+
console.log("");
|
|
1170
|
+
const binPath = process.argv[1];
|
|
1171
|
+
const args = [
|
|
1172
|
+
binPath,
|
|
1173
|
+
"serve",
|
|
1174
|
+
configPath,
|
|
1175
|
+
"--dev",
|
|
1176
|
+
"--ui",
|
|
1177
|
+
"--port",
|
|
1178
|
+
options.port
|
|
1179
|
+
];
|
|
1180
|
+
const child = spawn3(process.execPath, args, {
|
|
1181
|
+
stdio: "inherit",
|
|
1182
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
1183
|
+
});
|
|
1184
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
1185
|
+
});
|
|
1186
|
+
|
|
606
1187
|
// src/commands/test.ts
|
|
607
|
-
import { Command as
|
|
608
|
-
import
|
|
609
|
-
import
|
|
610
|
-
import
|
|
1188
|
+
import { Command as Command7 } from "commander";
|
|
1189
|
+
import chalk8 from "chalk";
|
|
1190
|
+
import path8 from "path";
|
|
1191
|
+
import fs8 from "fs";
|
|
611
1192
|
import { QA as CoreQA } from "@objectstack/core";
|
|
612
|
-
var testCommand = new
|
|
613
|
-
console.log(
|
|
1193
|
+
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) => {
|
|
1194
|
+
console.log(chalk8.bold(`
|
|
614
1195
|
\u{1F9EA} ObjectStack Quality Protocol Runner`));
|
|
615
|
-
console.log(
|
|
616
|
-
console.log(`Target: ${
|
|
1196
|
+
console.log(chalk8.dim(`-------------------------------------`));
|
|
1197
|
+
console.log(`Target: ${chalk8.blue(options.url)}`);
|
|
617
1198
|
const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
|
|
618
1199
|
const runner = new CoreQA.TestRunner(adapter);
|
|
619
1200
|
const cwd = process.cwd();
|
|
620
1201
|
const testFiles = [];
|
|
621
|
-
if (
|
|
1202
|
+
if (fs8.existsSync(filesPattern)) {
|
|
622
1203
|
testFiles.push(filesPattern);
|
|
623
1204
|
} else {
|
|
624
|
-
const dir =
|
|
625
|
-
const ext =
|
|
626
|
-
if (
|
|
627
|
-
const files =
|
|
628
|
-
files.forEach((f) => testFiles.push(
|
|
1205
|
+
const dir = path8.dirname(filesPattern);
|
|
1206
|
+
const ext = path8.extname(filesPattern);
|
|
1207
|
+
if (fs8.existsSync(dir)) {
|
|
1208
|
+
const files = fs8.readdirSync(dir).filter((f) => f.endsWith(ext) || f.endsWith(".json"));
|
|
1209
|
+
files.forEach((f) => testFiles.push(path8.join(dir, f)));
|
|
629
1210
|
}
|
|
630
1211
|
}
|
|
631
1212
|
if (testFiles.length === 0) {
|
|
632
|
-
console.warn(
|
|
1213
|
+
console.warn(chalk8.yellow(`No test files found matching: ${filesPattern}`));
|
|
633
1214
|
return;
|
|
634
1215
|
}
|
|
635
1216
|
console.log(`Found ${testFiles.length} test suites.`);
|
|
@@ -637,19 +1218,19 @@ var testCommand = new Command5("test:run").description("Run Quality Protocol tes
|
|
|
637
1218
|
let totalFailed = 0;
|
|
638
1219
|
for (const file of testFiles) {
|
|
639
1220
|
console.log(`
|
|
640
|
-
\u{1F4C4} Running suite: ${
|
|
1221
|
+
\u{1F4C4} Running suite: ${chalk8.bold(path8.basename(file))}`);
|
|
641
1222
|
try {
|
|
642
|
-
const content =
|
|
1223
|
+
const content = fs8.readFileSync(file, "utf-8");
|
|
643
1224
|
const suite = JSON.parse(content);
|
|
644
1225
|
const results = await runner.runSuite(suite);
|
|
645
1226
|
for (const result of results) {
|
|
646
1227
|
const icon = result.passed ? "\u2705" : "\u274C";
|
|
647
1228
|
console.log(` ${icon} Scenario: ${result.scenarioId} (${result.duration}ms)`);
|
|
648
1229
|
if (!result.passed) {
|
|
649
|
-
console.error(
|
|
1230
|
+
console.error(chalk8.red(` Error: ${result.error}`));
|
|
650
1231
|
result.steps.forEach((step) => {
|
|
651
1232
|
if (!step.passed) {
|
|
652
|
-
console.error(
|
|
1233
|
+
console.error(chalk8.red(` Step Failed: ${step.stepName}`));
|
|
653
1234
|
if (step.output) console.error(` Output:`, step.output);
|
|
654
1235
|
if (step.error) console.error(` Error:`, step.error);
|
|
655
1236
|
}
|
|
@@ -660,30 +1241,759 @@ var testCommand = new Command5("test:run").description("Run Quality Protocol tes
|
|
|
660
1241
|
}
|
|
661
1242
|
}
|
|
662
1243
|
} catch (e) {
|
|
663
|
-
console.error(
|
|
1244
|
+
console.error(chalk8.red(`Failed to load or run suite ${file}: ${e}`));
|
|
664
1245
|
totalFailed++;
|
|
665
1246
|
}
|
|
666
1247
|
}
|
|
667
|
-
console.log(
|
|
1248
|
+
console.log(chalk8.dim(`
|
|
668
1249
|
-------------------------------------`));
|
|
669
1250
|
if (totalFailed > 0) {
|
|
670
|
-
console.log(
|
|
1251
|
+
console.log(chalk8.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
|
|
671
1252
|
process.exit(1);
|
|
672
1253
|
} else {
|
|
673
|
-
console.log(
|
|
1254
|
+
console.log(chalk8.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
|
|
674
1255
|
process.exit(0);
|
|
675
1256
|
}
|
|
676
1257
|
});
|
|
677
1258
|
|
|
1259
|
+
// src/commands/validate.ts
|
|
1260
|
+
import { Command as Command8 } from "commander";
|
|
1261
|
+
import chalk9 from "chalk";
|
|
1262
|
+
import { ObjectStackDefinitionSchema as ObjectStackDefinitionSchema2 } from "@objectstack/spec";
|
|
1263
|
+
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) => {
|
|
1264
|
+
const timer = createTimer();
|
|
1265
|
+
if (!options.json) {
|
|
1266
|
+
printHeader("Validate");
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
if (!options.json) printStep("Loading configuration...");
|
|
1270
|
+
const { config, absolutePath, duration } = await loadConfig(configPath);
|
|
1271
|
+
if (!options.json) {
|
|
1272
|
+
printKV("Config", absolutePath);
|
|
1273
|
+
printKV("Load time", `${duration}ms`);
|
|
1274
|
+
}
|
|
1275
|
+
if (!options.json) printStep("Validating against ObjectStack Protocol...");
|
|
1276
|
+
const result = ObjectStackDefinitionSchema2.safeParse(config);
|
|
1277
|
+
if (!result.success) {
|
|
1278
|
+
if (options.json) {
|
|
1279
|
+
console.log(JSON.stringify({
|
|
1280
|
+
valid: false,
|
|
1281
|
+
errors: result.error.issues,
|
|
1282
|
+
duration: timer.elapsed()
|
|
1283
|
+
}, null, 2));
|
|
1284
|
+
process.exit(1);
|
|
1285
|
+
}
|
|
1286
|
+
console.log("");
|
|
1287
|
+
printError("Validation failed");
|
|
1288
|
+
formatZodErrors(result.error);
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
}
|
|
1291
|
+
const stats = collectMetadataStats(config);
|
|
1292
|
+
if (options.json) {
|
|
1293
|
+
console.log(JSON.stringify({
|
|
1294
|
+
valid: true,
|
|
1295
|
+
manifest: config.manifest,
|
|
1296
|
+
stats,
|
|
1297
|
+
duration: timer.elapsed()
|
|
1298
|
+
}, null, 2));
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const warnings = [];
|
|
1302
|
+
if (stats.objects === 0) {
|
|
1303
|
+
warnings.push("No objects defined \u2014 this stack has no data model");
|
|
1304
|
+
}
|
|
1305
|
+
if (stats.apps === 0 && stats.plugins === 0) {
|
|
1306
|
+
warnings.push("No apps or plugins defined \u2014 this stack may not do much");
|
|
1307
|
+
}
|
|
1308
|
+
if (!config.manifest?.id) {
|
|
1309
|
+
warnings.push("Missing manifest.id \u2014 required for deployment");
|
|
1310
|
+
}
|
|
1311
|
+
if (!config.manifest?.namespace) {
|
|
1312
|
+
warnings.push("Missing manifest.namespace \u2014 required for multi-app hosting");
|
|
1313
|
+
}
|
|
1314
|
+
console.log("");
|
|
1315
|
+
printSuccess(`Validation passed ${chalk9.dim(`(${timer.display()})`)}`);
|
|
1316
|
+
console.log("");
|
|
1317
|
+
if (config.manifest) {
|
|
1318
|
+
console.log(` ${chalk9.bold(config.manifest.name || config.manifest.id || "Unnamed")} ${chalk9.dim(`v${config.manifest.version || "0.0.0"}`)}`);
|
|
1319
|
+
if (config.manifest.description) {
|
|
1320
|
+
console.log(chalk9.dim(` ${config.manifest.description}`));
|
|
1321
|
+
}
|
|
1322
|
+
console.log("");
|
|
1323
|
+
}
|
|
1324
|
+
printMetadataStats(stats);
|
|
1325
|
+
if (warnings.length > 0) {
|
|
1326
|
+
console.log("");
|
|
1327
|
+
for (const w of warnings) {
|
|
1328
|
+
console.log(chalk9.yellow(` \u26A0 ${w}`));
|
|
1329
|
+
}
|
|
1330
|
+
if (options.strict) {
|
|
1331
|
+
console.log("");
|
|
1332
|
+
printError("Strict mode: warnings treated as errors");
|
|
1333
|
+
process.exit(1);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
console.log("");
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
if (options.json) {
|
|
1339
|
+
console.log(JSON.stringify({
|
|
1340
|
+
valid: false,
|
|
1341
|
+
error: error.message,
|
|
1342
|
+
duration: timer.elapsed()
|
|
1343
|
+
}, null, 2));
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
console.log("");
|
|
1347
|
+
printError(error.message || String(error));
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
// src/commands/init.ts
|
|
1353
|
+
import { Command as Command9 } from "commander";
|
|
1354
|
+
import chalk10 from "chalk";
|
|
1355
|
+
import fs9 from "fs";
|
|
1356
|
+
import path9 from "path";
|
|
1357
|
+
var TEMPLATES = {
|
|
1358
|
+
app: {
|
|
1359
|
+
description: "Full application with objects, views, and actions",
|
|
1360
|
+
dependencies: {
|
|
1361
|
+
"@objectstack/spec": "workspace:*",
|
|
1362
|
+
"@objectstack/runtime": "workspace:^",
|
|
1363
|
+
"@objectstack/objectql": "workspace:^",
|
|
1364
|
+
"@objectstack/driver-memory": "workspace:^"
|
|
1365
|
+
},
|
|
1366
|
+
devDependencies: {
|
|
1367
|
+
"@objectstack/cli": "workspace:*",
|
|
1368
|
+
"typescript": "^5.3.0"
|
|
1369
|
+
},
|
|
1370
|
+
scripts: {
|
|
1371
|
+
dev: "objectstack dev",
|
|
1372
|
+
start: "objectstack serve",
|
|
1373
|
+
build: "objectstack compile",
|
|
1374
|
+
validate: "objectstack validate",
|
|
1375
|
+
typecheck: "tsc --noEmit"
|
|
1376
|
+
},
|
|
1377
|
+
configContent: (name) => `import { defineStack } from '@objectstack/spec';
|
|
1378
|
+
import * as objects from './src/objects';
|
|
1379
|
+
|
|
1380
|
+
export default defineStack({
|
|
1381
|
+
manifest: {
|
|
1382
|
+
id: 'com.example.${name}',
|
|
1383
|
+
namespace: '${name}',
|
|
1384
|
+
version: '0.1.0',
|
|
1385
|
+
type: 'app',
|
|
1386
|
+
name: '${toTitleCase(name)}',
|
|
1387
|
+
description: '${toTitleCase(name)} application built with ObjectStack',
|
|
1388
|
+
},
|
|
1389
|
+
|
|
1390
|
+
objects: Object.values(objects),
|
|
1391
|
+
});
|
|
1392
|
+
`,
|
|
1393
|
+
srcFiles: {
|
|
1394
|
+
"src/objects/index.ts": (name) => `export { default as ${toCamelCase2(name)} } from './${name}';
|
|
1395
|
+
`,
|
|
1396
|
+
"src/objects/__name__.ts": (name) => `import { Data } from '@objectstack/spec';
|
|
1397
|
+
|
|
1398
|
+
const ${toCamelCase2(name)}: Data.Object = {
|
|
1399
|
+
name: '${name}',
|
|
1400
|
+
label: '${toTitleCase(name)}',
|
|
1401
|
+
ownership: 'own',
|
|
1402
|
+
fields: {
|
|
1403
|
+
name: {
|
|
1404
|
+
type: 'text',
|
|
1405
|
+
label: 'Name',
|
|
1406
|
+
required: true,
|
|
1407
|
+
},
|
|
1408
|
+
description: {
|
|
1409
|
+
type: 'textarea',
|
|
1410
|
+
label: 'Description',
|
|
1411
|
+
},
|
|
1412
|
+
status: {
|
|
1413
|
+
type: 'select',
|
|
1414
|
+
label: 'Status',
|
|
1415
|
+
options: [
|
|
1416
|
+
{ label: 'Draft', value: 'draft' },
|
|
1417
|
+
{ label: 'Active', value: 'active' },
|
|
1418
|
+
{ label: 'Archived', value: 'archived' },
|
|
1419
|
+
],
|
|
1420
|
+
defaultValue: 'draft',
|
|
1421
|
+
},
|
|
1422
|
+
},
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
export default ${toCamelCase2(name)};
|
|
1426
|
+
`
|
|
1427
|
+
}
|
|
1428
|
+
},
|
|
1429
|
+
plugin: {
|
|
1430
|
+
description: "Reusable plugin with objects and extensions",
|
|
1431
|
+
dependencies: {
|
|
1432
|
+
"@objectstack/spec": "workspace:*"
|
|
1433
|
+
},
|
|
1434
|
+
devDependencies: {
|
|
1435
|
+
"typescript": "^5.3.0",
|
|
1436
|
+
"vitest": "^4.0.18"
|
|
1437
|
+
},
|
|
1438
|
+
scripts: {
|
|
1439
|
+
build: "objectstack compile",
|
|
1440
|
+
validate: "objectstack validate",
|
|
1441
|
+
test: "vitest run",
|
|
1442
|
+
typecheck: "tsc --noEmit"
|
|
1443
|
+
},
|
|
1444
|
+
configContent: (name) => `import { defineStack } from '@objectstack/spec';
|
|
1445
|
+
import * as objects from './src/objects';
|
|
1446
|
+
|
|
1447
|
+
export default defineStack({
|
|
1448
|
+
manifest: {
|
|
1449
|
+
id: 'com.objectstack.plugin-${name}',
|
|
1450
|
+
namespace: 'plugin_${name}',
|
|
1451
|
+
version: '0.1.0',
|
|
1452
|
+
type: 'plugin',
|
|
1453
|
+
name: '${toTitleCase(name)} Plugin',
|
|
1454
|
+
description: 'ObjectStack Plugin: ${toTitleCase(name)}',
|
|
1455
|
+
},
|
|
1456
|
+
|
|
1457
|
+
objects: Object.values(objects),
|
|
1458
|
+
});
|
|
1459
|
+
`,
|
|
1460
|
+
srcFiles: {
|
|
1461
|
+
"src/objects/index.ts": (name) => `export { default as ${toCamelCase2(name)} } from './${name}';
|
|
1462
|
+
`,
|
|
1463
|
+
"src/objects/__name__.ts": (name) => `import { Data } from '@objectstack/spec';
|
|
1464
|
+
|
|
1465
|
+
const ${toCamelCase2(name)}: Data.Object = {
|
|
1466
|
+
name: '${name}',
|
|
1467
|
+
label: '${toTitleCase(name)}',
|
|
1468
|
+
ownership: 'own',
|
|
1469
|
+
fields: {
|
|
1470
|
+
name: {
|
|
1471
|
+
type: 'text',
|
|
1472
|
+
label: 'Name',
|
|
1473
|
+
required: true,
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
};
|
|
1477
|
+
|
|
1478
|
+
export default ${toCamelCase2(name)};
|
|
1479
|
+
`
|
|
1480
|
+
}
|
|
1481
|
+
},
|
|
1482
|
+
empty: {
|
|
1483
|
+
description: "Minimal project with just a config file",
|
|
1484
|
+
dependencies: {
|
|
1485
|
+
"@objectstack/spec": "workspace:*"
|
|
1486
|
+
},
|
|
1487
|
+
devDependencies: {
|
|
1488
|
+
"@objectstack/cli": "workspace:*",
|
|
1489
|
+
"typescript": "^5.3.0"
|
|
1490
|
+
},
|
|
1491
|
+
scripts: {
|
|
1492
|
+
build: "objectstack compile",
|
|
1493
|
+
validate: "objectstack validate",
|
|
1494
|
+
typecheck: "tsc --noEmit"
|
|
1495
|
+
},
|
|
1496
|
+
configContent: (name) => `import { defineStack } from '@objectstack/spec';
|
|
1497
|
+
|
|
1498
|
+
export default defineStack({
|
|
1499
|
+
manifest: {
|
|
1500
|
+
id: 'com.example.${name}',
|
|
1501
|
+
namespace: '${name}',
|
|
1502
|
+
version: '0.1.0',
|
|
1503
|
+
type: 'app',
|
|
1504
|
+
name: '${toTitleCase(name)}',
|
|
1505
|
+
description: '',
|
|
1506
|
+
},
|
|
1507
|
+
});
|
|
1508
|
+
`,
|
|
1509
|
+
srcFiles: {}
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
function toCamelCase2(str) {
|
|
1513
|
+
return str.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
|
|
1514
|
+
}
|
|
1515
|
+
function toTitleCase(str) {
|
|
1516
|
+
return str.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1517
|
+
}
|
|
1518
|
+
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) => {
|
|
1519
|
+
printHeader("Init");
|
|
1520
|
+
const cwd = process.cwd();
|
|
1521
|
+
const projectName = name || path9.basename(cwd);
|
|
1522
|
+
const template = TEMPLATES[options.template];
|
|
1523
|
+
if (!template) {
|
|
1524
|
+
printError(`Unknown template: ${options.template}`);
|
|
1525
|
+
console.log(chalk10.dim(` Available: ${Object.keys(TEMPLATES).join(", ")}`));
|
|
1526
|
+
process.exit(1);
|
|
1527
|
+
}
|
|
1528
|
+
if (fs9.existsSync(path9.join(cwd, "objectstack.config.ts"))) {
|
|
1529
|
+
printError("objectstack.config.ts already exists in this directory");
|
|
1530
|
+
console.log(chalk10.dim(" Use `objectstack generate` to add metadata to an existing project"));
|
|
1531
|
+
process.exit(1);
|
|
1532
|
+
}
|
|
1533
|
+
printKV("Project", projectName);
|
|
1534
|
+
printKV("Template", `${options.template} \u2014 ${template.description}`);
|
|
1535
|
+
printKV("Directory", cwd);
|
|
1536
|
+
console.log("");
|
|
1537
|
+
const createdFiles = [];
|
|
1538
|
+
try {
|
|
1539
|
+
const pkgPath = path9.join(cwd, "package.json");
|
|
1540
|
+
if (!fs9.existsSync(pkgPath)) {
|
|
1541
|
+
const pkg2 = {
|
|
1542
|
+
name: projectName,
|
|
1543
|
+
version: "0.1.0",
|
|
1544
|
+
private: true,
|
|
1545
|
+
type: "module",
|
|
1546
|
+
scripts: template.scripts,
|
|
1547
|
+
dependencies: template.dependencies,
|
|
1548
|
+
devDependencies: template.devDependencies
|
|
1549
|
+
};
|
|
1550
|
+
fs9.writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + "\n");
|
|
1551
|
+
createdFiles.push("package.json");
|
|
1552
|
+
} else {
|
|
1553
|
+
printInfo("package.json already exists, skipping");
|
|
1554
|
+
}
|
|
1555
|
+
const configContent = template.configContent(projectName);
|
|
1556
|
+
fs9.writeFileSync(path9.join(cwd, "objectstack.config.ts"), configContent);
|
|
1557
|
+
createdFiles.push("objectstack.config.ts");
|
|
1558
|
+
const tsconfigPath = path9.join(cwd, "tsconfig.json");
|
|
1559
|
+
if (!fs9.existsSync(tsconfigPath)) {
|
|
1560
|
+
const tsconfig = {
|
|
1561
|
+
compilerOptions: {
|
|
1562
|
+
target: "ES2022",
|
|
1563
|
+
module: "ESNext",
|
|
1564
|
+
moduleResolution: "bundler",
|
|
1565
|
+
strict: true,
|
|
1566
|
+
esModuleInterop: true,
|
|
1567
|
+
skipLibCheck: true,
|
|
1568
|
+
outDir: "dist",
|
|
1569
|
+
rootDir: ".",
|
|
1570
|
+
declaration: true
|
|
1571
|
+
},
|
|
1572
|
+
include: ["*.ts", "src/**/*"],
|
|
1573
|
+
exclude: ["dist", "node_modules"]
|
|
1574
|
+
};
|
|
1575
|
+
fs9.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
1576
|
+
createdFiles.push("tsconfig.json");
|
|
1577
|
+
}
|
|
1578
|
+
for (const [filePath, contentFn] of Object.entries(template.srcFiles)) {
|
|
1579
|
+
const resolvedPath = filePath.replace("__name__", projectName);
|
|
1580
|
+
const fullPath = path9.join(cwd, resolvedPath);
|
|
1581
|
+
const dir = path9.dirname(fullPath);
|
|
1582
|
+
if (!fs9.existsSync(dir)) {
|
|
1583
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1584
|
+
}
|
|
1585
|
+
fs9.writeFileSync(fullPath, contentFn(projectName));
|
|
1586
|
+
createdFiles.push(resolvedPath);
|
|
1587
|
+
}
|
|
1588
|
+
const gitignorePath = path9.join(cwd, ".gitignore");
|
|
1589
|
+
if (!fs9.existsSync(gitignorePath)) {
|
|
1590
|
+
fs9.writeFileSync(gitignorePath, `node_modules/
|
|
1591
|
+
dist/
|
|
1592
|
+
*.tsbuildinfo
|
|
1593
|
+
`);
|
|
1594
|
+
createdFiles.push(".gitignore");
|
|
1595
|
+
}
|
|
1596
|
+
console.log(chalk10.bold(" Created files:"));
|
|
1597
|
+
for (const f of createdFiles) {
|
|
1598
|
+
console.log(chalk10.green(` + ${f}`));
|
|
1599
|
+
}
|
|
1600
|
+
console.log("");
|
|
1601
|
+
if (options.install !== false) {
|
|
1602
|
+
printStep("Installing dependencies...");
|
|
1603
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
1604
|
+
try {
|
|
1605
|
+
execSync3("pnpm install", { stdio: "inherit", cwd });
|
|
1606
|
+
} catch {
|
|
1607
|
+
printWarning2("Dependency installation failed. Run `pnpm install` manually.");
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
printSuccess("Project initialized!");
|
|
1611
|
+
console.log("");
|
|
1612
|
+
console.log(chalk10.bold(" Next steps:"));
|
|
1613
|
+
console.log(chalk10.dim(" objectstack validate # Check configuration"));
|
|
1614
|
+
console.log(chalk10.dim(" objectstack dev # Start development server"));
|
|
1615
|
+
console.log(chalk10.dim(" objectstack generate # Add objects, views, etc."));
|
|
1616
|
+
console.log("");
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
printError(error.message || String(error));
|
|
1619
|
+
process.exit(1);
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
function printWarning2(msg) {
|
|
1623
|
+
console.log(chalk10.yellow(` \u26A0 ${msg}`));
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/commands/info.ts
|
|
1627
|
+
import { Command as Command10 } from "commander";
|
|
1628
|
+
import chalk11 from "chalk";
|
|
1629
|
+
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) => {
|
|
1630
|
+
const timer = createTimer();
|
|
1631
|
+
if (!options.json) {
|
|
1632
|
+
printHeader("Info");
|
|
1633
|
+
}
|
|
1634
|
+
try {
|
|
1635
|
+
const { config, absolutePath, duration } = await loadConfig(configPath);
|
|
1636
|
+
const stats = collectMetadataStats(config);
|
|
1637
|
+
if (options.json) {
|
|
1638
|
+
console.log(JSON.stringify({
|
|
1639
|
+
config: absolutePath,
|
|
1640
|
+
manifest: config.manifest || null,
|
|
1641
|
+
stats,
|
|
1642
|
+
objects: (config.objects || []).map((o) => ({
|
|
1643
|
+
name: o.name,
|
|
1644
|
+
label: o.label,
|
|
1645
|
+
fields: o.fields ? Object.keys(o.fields).length : 0
|
|
1646
|
+
})),
|
|
1647
|
+
loadTime: duration
|
|
1648
|
+
}, null, 2));
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
if (config.manifest) {
|
|
1652
|
+
const m = config.manifest;
|
|
1653
|
+
console.log("");
|
|
1654
|
+
console.log(` ${chalk11.bold(m.name || m.id || "Unnamed")} ${chalk11.dim(`v${m.version || "0.0.0"}`)}`);
|
|
1655
|
+
if (m.id) console.log(chalk11.dim(` ${m.id}`));
|
|
1656
|
+
if (m.description) console.log(chalk11.dim(` ${m.description}`));
|
|
1657
|
+
if (m.namespace) printKV(" Namespace", m.namespace);
|
|
1658
|
+
if (m.type) printKV(" Type", m.type);
|
|
1659
|
+
}
|
|
1660
|
+
console.log("");
|
|
1661
|
+
printMetadataStats(stats);
|
|
1662
|
+
if (config.objects && config.objects.length > 0) {
|
|
1663
|
+
console.log("");
|
|
1664
|
+
console.log(chalk11.bold(" Objects:"));
|
|
1665
|
+
for (const obj of config.objects) {
|
|
1666
|
+
const fieldCount = obj.fields ? Object.keys(obj.fields).length : 0;
|
|
1667
|
+
const ownership = obj.ownership || "own";
|
|
1668
|
+
console.log(
|
|
1669
|
+
` ${chalk11.cyan(obj.name || "?")}` + chalk11.dim(` (${fieldCount} fields, ${ownership})`) + (obj.label ? chalk11.dim(` \u2014 ${obj.label}`) : "")
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
if (config.agents && config.agents.length > 0) {
|
|
1674
|
+
console.log("");
|
|
1675
|
+
console.log(chalk11.bold(" Agents:"));
|
|
1676
|
+
for (const agent of config.agents) {
|
|
1677
|
+
console.log(
|
|
1678
|
+
` ${chalk11.magenta(agent.name || "?")}` + (agent.role ? chalk11.dim(` \u2014 ${agent.role}`) : "")
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
if (config.apps && config.apps.length > 0) {
|
|
1683
|
+
console.log("");
|
|
1684
|
+
console.log(chalk11.bold(" Apps:"));
|
|
1685
|
+
for (const app of config.apps) {
|
|
1686
|
+
console.log(
|
|
1687
|
+
` ${chalk11.green(app.name || "?")}` + (app.label ? chalk11.dim(` \u2014 ${app.label}`) : "")
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
console.log("");
|
|
1692
|
+
console.log(chalk11.dim(` Loaded in ${duration}ms`));
|
|
1693
|
+
console.log("");
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
if (options.json) {
|
|
1696
|
+
console.log(JSON.stringify({ error: error.message }));
|
|
1697
|
+
process.exit(1);
|
|
1698
|
+
}
|
|
1699
|
+
console.log("");
|
|
1700
|
+
printError(error.message || String(error));
|
|
1701
|
+
process.exit(1);
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
// src/commands/generate.ts
|
|
1706
|
+
import { Command as Command11 } from "commander";
|
|
1707
|
+
import chalk12 from "chalk";
|
|
1708
|
+
import fs10 from "fs";
|
|
1709
|
+
import path10 from "path";
|
|
1710
|
+
var GENERATORS = {
|
|
1711
|
+
object: {
|
|
1712
|
+
description: "Business data object",
|
|
1713
|
+
defaultDir: "src/objects",
|
|
1714
|
+
generate: (name) => `import { Data } from '@objectstack/spec';
|
|
1715
|
+
|
|
1716
|
+
/**
|
|
1717
|
+
* ${toTitleCase2(name)} Object
|
|
1718
|
+
*/
|
|
1719
|
+
const ${toCamelCase3(name)}: Data.Object = {
|
|
1720
|
+
name: '${toSnakeCase(name)}',
|
|
1721
|
+
label: '${toTitleCase2(name)}',
|
|
1722
|
+
pluralLabel: '${toTitleCase2(name)}s',
|
|
1723
|
+
ownership: 'own',
|
|
1724
|
+
fields: {
|
|
1725
|
+
name: {
|
|
1726
|
+
type: 'text',
|
|
1727
|
+
label: 'Name',
|
|
1728
|
+
required: true,
|
|
1729
|
+
maxLength: 255,
|
|
1730
|
+
},
|
|
1731
|
+
description: {
|
|
1732
|
+
type: 'textarea',
|
|
1733
|
+
label: 'Description',
|
|
1734
|
+
},
|
|
1735
|
+
},
|
|
1736
|
+
};
|
|
1737
|
+
|
|
1738
|
+
export default ${toCamelCase3(name)};
|
|
1739
|
+
`
|
|
1740
|
+
},
|
|
1741
|
+
view: {
|
|
1742
|
+
description: "List or form view",
|
|
1743
|
+
defaultDir: "src/views",
|
|
1744
|
+
generate: (name) => `import { UI } from '@objectstack/spec';
|
|
1745
|
+
|
|
1746
|
+
/**
|
|
1747
|
+
* ${toTitleCase2(name)} List View
|
|
1748
|
+
*/
|
|
1749
|
+
const ${toCamelCase3(name)}ListView: UI.View = {
|
|
1750
|
+
name: '${toSnakeCase(name)}_list',
|
|
1751
|
+
label: '${toTitleCase2(name)} List',
|
|
1752
|
+
type: 'list',
|
|
1753
|
+
objectName: '${toSnakeCase(name)}',
|
|
1754
|
+
list: {
|
|
1755
|
+
type: 'grid',
|
|
1756
|
+
columns: [
|
|
1757
|
+
{ field: 'name', width: 200 },
|
|
1758
|
+
],
|
|
1759
|
+
defaultSort: { field: 'name', direction: 'asc' },
|
|
1760
|
+
pageSize: 25,
|
|
1761
|
+
},
|
|
1762
|
+
};
|
|
1763
|
+
|
|
1764
|
+
export default ${toCamelCase3(name)}ListView;
|
|
1765
|
+
`
|
|
1766
|
+
},
|
|
1767
|
+
action: {
|
|
1768
|
+
description: "Button or batch action",
|
|
1769
|
+
defaultDir: "src/actions",
|
|
1770
|
+
generate: (name) => `import { UI } from '@objectstack/spec';
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* ${toTitleCase2(name)} Action
|
|
1774
|
+
*/
|
|
1775
|
+
const ${toCamelCase3(name)}Action: UI.Action = {
|
|
1776
|
+
name: '${toSnakeCase(name)}',
|
|
1777
|
+
label: '${toTitleCase2(name)}',
|
|
1778
|
+
type: 'custom',
|
|
1779
|
+
objectName: '${toSnakeCase(name)}',
|
|
1780
|
+
handler: {
|
|
1781
|
+
type: 'flow',
|
|
1782
|
+
target: '${toSnakeCase(name)}_flow',
|
|
1783
|
+
},
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
export default ${toCamelCase3(name)}Action;
|
|
1787
|
+
`
|
|
1788
|
+
},
|
|
1789
|
+
flow: {
|
|
1790
|
+
description: "Automation flow",
|
|
1791
|
+
defaultDir: "src/flows",
|
|
1792
|
+
generate: (name) => `import { Automation } from '@objectstack/spec';
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* ${toTitleCase2(name)} Flow
|
|
1796
|
+
*/
|
|
1797
|
+
const ${toCamelCase3(name)}Flow: Automation.Flow = {
|
|
1798
|
+
name: '${toSnakeCase(name)}_flow',
|
|
1799
|
+
label: '${toTitleCase2(name)} Flow',
|
|
1800
|
+
type: 'autolaunched',
|
|
1801
|
+
status: 'draft',
|
|
1802
|
+
trigger: {
|
|
1803
|
+
type: 'record_change',
|
|
1804
|
+
object: '${toSnakeCase(name)}',
|
|
1805
|
+
events: ['after_insert', 'after_update'],
|
|
1806
|
+
},
|
|
1807
|
+
nodes: [
|
|
1808
|
+
{
|
|
1809
|
+
id: 'start',
|
|
1810
|
+
type: 'start',
|
|
1811
|
+
name: 'Start',
|
|
1812
|
+
next: 'end',
|
|
1813
|
+
},
|
|
1814
|
+
],
|
|
1815
|
+
};
|
|
1816
|
+
|
|
1817
|
+
export default ${toCamelCase3(name)}Flow;
|
|
1818
|
+
`
|
|
1819
|
+
},
|
|
1820
|
+
agent: {
|
|
1821
|
+
description: "AI agent",
|
|
1822
|
+
defaultDir: "src/agents",
|
|
1823
|
+
generate: (name) => `import { AI } from '@objectstack/spec';
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* ${toTitleCase2(name)} Agent
|
|
1827
|
+
*/
|
|
1828
|
+
const ${toCamelCase3(name)}Agent: AI.Agent = {
|
|
1829
|
+
name: '${toSnakeCase(name)}_agent',
|
|
1830
|
+
label: '${toTitleCase2(name)} Agent',
|
|
1831
|
+
role: '${toTitleCase2(name)} assistant',
|
|
1832
|
+
instructions: 'You are a helpful ${toTitleCase2(name).toLowerCase()} assistant.',
|
|
1833
|
+
model: {
|
|
1834
|
+
provider: 'openai',
|
|
1835
|
+
model: 'gpt-4o',
|
|
1836
|
+
},
|
|
1837
|
+
tools: [],
|
|
1838
|
+
};
|
|
1839
|
+
|
|
1840
|
+
export default ${toCamelCase3(name)}Agent;
|
|
1841
|
+
`
|
|
1842
|
+
},
|
|
1843
|
+
dashboard: {
|
|
1844
|
+
description: "Analytics dashboard",
|
|
1845
|
+
defaultDir: "src/dashboards",
|
|
1846
|
+
generate: (name) => `import { UI } from '@objectstack/spec';
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* ${toTitleCase2(name)} Dashboard
|
|
1850
|
+
*/
|
|
1851
|
+
const ${toCamelCase3(name)}Dashboard: UI.Dashboard = {
|
|
1852
|
+
name: '${toSnakeCase(name)}_dashboard',
|
|
1853
|
+
label: '${toTitleCase2(name)} Dashboard',
|
|
1854
|
+
widgets: [],
|
|
1855
|
+
};
|
|
1856
|
+
|
|
1857
|
+
export default ${toCamelCase3(name)}Dashboard;
|
|
1858
|
+
`
|
|
1859
|
+
},
|
|
1860
|
+
app: {
|
|
1861
|
+
description: "Application navigation",
|
|
1862
|
+
defaultDir: "src/apps",
|
|
1863
|
+
generate: (name) => `import { UI } from '@objectstack/spec';
|
|
1864
|
+
|
|
1865
|
+
/**
|
|
1866
|
+
* ${toTitleCase2(name)} App
|
|
1867
|
+
*/
|
|
1868
|
+
const ${toCamelCase3(name)}App: UI.App = {
|
|
1869
|
+
name: '${toSnakeCase(name)}_app',
|
|
1870
|
+
label: '${toTitleCase2(name)}',
|
|
1871
|
+
navigation: {
|
|
1872
|
+
type: 'sidebar',
|
|
1873
|
+
items: [],
|
|
1874
|
+
},
|
|
1875
|
+
};
|
|
1876
|
+
|
|
1877
|
+
export default ${toCamelCase3(name)}App;
|
|
1878
|
+
`
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
function toCamelCase3(str) {
|
|
1882
|
+
return str.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
|
|
1883
|
+
}
|
|
1884
|
+
function toTitleCase2(str) {
|
|
1885
|
+
return str.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1886
|
+
}
|
|
1887
|
+
function toSnakeCase(str) {
|
|
1888
|
+
return str.replace(/[-]/g, "_").replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`).replace(/^_/, "");
|
|
1889
|
+
}
|
|
1890
|
+
var generateCommand = new Command11("generate").alias("g").description("Generate metadata files (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) => {
|
|
1891
|
+
printHeader("Generate");
|
|
1892
|
+
const generator = GENERATORS[type];
|
|
1893
|
+
if (!generator) {
|
|
1894
|
+
printError(`Unknown type: ${type}`);
|
|
1895
|
+
console.log("");
|
|
1896
|
+
console.log(chalk12.bold(" Available types:"));
|
|
1897
|
+
for (const [key, gen] of Object.entries(GENERATORS)) {
|
|
1898
|
+
console.log(` ${chalk12.cyan(key.padEnd(12))} ${chalk12.dim(gen.description)}`);
|
|
1899
|
+
}
|
|
1900
|
+
console.log("");
|
|
1901
|
+
console.log(chalk12.dim(" Usage: objectstack generate <type> <name>"));
|
|
1902
|
+
console.log(chalk12.dim(" Example: objectstack generate object project"));
|
|
1903
|
+
console.log(chalk12.dim(" Alias: os g object project"));
|
|
1904
|
+
process.exit(1);
|
|
1905
|
+
}
|
|
1906
|
+
const dir = options.dir || generator.defaultDir;
|
|
1907
|
+
const fileName = `${toSnakeCase(name)}.ts`;
|
|
1908
|
+
const filePath = path10.join(process.cwd(), dir, fileName);
|
|
1909
|
+
console.log(` ${chalk12.dim("Type:")} ${chalk12.cyan(type)} \u2014 ${generator.description}`);
|
|
1910
|
+
console.log(` ${chalk12.dim("Name:")} ${chalk12.white(name)}`);
|
|
1911
|
+
console.log(` ${chalk12.dim("File:")} ${chalk12.white(path10.join(dir, fileName))}`);
|
|
1912
|
+
console.log("");
|
|
1913
|
+
if (options.dryRun) {
|
|
1914
|
+
printInfo("Dry run \u2014 no files written");
|
|
1915
|
+
console.log("");
|
|
1916
|
+
console.log(chalk12.dim(" Content:"));
|
|
1917
|
+
console.log(chalk12.dim(" " + "-".repeat(38)));
|
|
1918
|
+
const content = generator.generate(name);
|
|
1919
|
+
for (const line of content.split("\n")) {
|
|
1920
|
+
console.log(chalk12.dim(` ${line}`));
|
|
1921
|
+
}
|
|
1922
|
+
console.log("");
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
if (fs10.existsSync(filePath)) {
|
|
1926
|
+
printError(`File already exists: ${filePath}`);
|
|
1927
|
+
process.exit(1);
|
|
1928
|
+
}
|
|
1929
|
+
try {
|
|
1930
|
+
const fullDir = path10.dirname(filePath);
|
|
1931
|
+
if (!fs10.existsSync(fullDir)) {
|
|
1932
|
+
fs10.mkdirSync(fullDir, { recursive: true });
|
|
1933
|
+
}
|
|
1934
|
+
const content = generator.generate(name);
|
|
1935
|
+
fs10.writeFileSync(filePath, content);
|
|
1936
|
+
printSuccess(`Created ${path10.join(dir, fileName)}`);
|
|
1937
|
+
const indexPath = path10.join(process.cwd(), dir, "index.ts");
|
|
1938
|
+
if (fs10.existsSync(indexPath)) {
|
|
1939
|
+
const indexContent = fs10.readFileSync(indexPath, "utf-8");
|
|
1940
|
+
const exportLine = `export { default as ${toCamelCase3(name)} } from './${toSnakeCase(name)}';`;
|
|
1941
|
+
if (!indexContent.includes(toCamelCase3(name))) {
|
|
1942
|
+
fs10.appendFileSync(indexPath, exportLine + "\n");
|
|
1943
|
+
printSuccess(`Updated ${dir}/index.ts with export`);
|
|
1944
|
+
}
|
|
1945
|
+
} else {
|
|
1946
|
+
const exportLine = `export { default as ${toCamelCase3(name)} } from './${toSnakeCase(name)}';
|
|
1947
|
+
`;
|
|
1948
|
+
fs10.writeFileSync(indexPath, exportLine);
|
|
1949
|
+
printSuccess(`Created ${dir}/index.ts`);
|
|
1950
|
+
}
|
|
1951
|
+
console.log("");
|
|
1952
|
+
console.log(chalk12.dim(` Tip: Run \`objectstack validate\` to check your config`));
|
|
1953
|
+
console.log("");
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
printError(error.message || String(error));
|
|
1956
|
+
process.exit(1);
|
|
1957
|
+
}
|
|
1958
|
+
});
|
|
1959
|
+
|
|
678
1960
|
// src/bin.ts
|
|
679
1961
|
var require2 = createRequire(import.meta.url);
|
|
680
1962
|
var pkg = require2("../package.json");
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1963
|
+
process.on("unhandledRejection", (err) => {
|
|
1964
|
+
console.error(chalk13.red(`
|
|
1965
|
+
\u2717 Unhandled error: ${err?.message || err}`));
|
|
1966
|
+
if (err?.stack && process.env.DEBUG) {
|
|
1967
|
+
console.error(chalk13.dim(err.stack));
|
|
1968
|
+
}
|
|
1969
|
+
process.exit(1);
|
|
1970
|
+
});
|
|
1971
|
+
var program = new Command12();
|
|
1972
|
+
program.name("objectstack").description("ObjectStack CLI \u2014 Build metadata-driven apps with the ObjectStack Protocol").version(pkg.version, "-v, --version").configureHelp({
|
|
1973
|
+
sortSubcommands: false
|
|
1974
|
+
}).addHelpText("before", `
|
|
1975
|
+
${chalk13.bold.cyan("\u25C6 ObjectStack CLI")} ${chalk13.dim(`v${pkg.version}`)}
|
|
1976
|
+
`).addHelpText("after", `
|
|
1977
|
+
${chalk13.bold("Workflow:")}
|
|
1978
|
+
${chalk13.dim("$")} os init ${chalk13.dim("# Create a new project")}
|
|
1979
|
+
${chalk13.dim("$")} os generate object task ${chalk13.dim("# Add metadata")}
|
|
1980
|
+
${chalk13.dim("$")} os validate ${chalk13.dim("# Check configuration")}
|
|
1981
|
+
${chalk13.dim("$")} os dev ${chalk13.dim("# Start dev server")}
|
|
1982
|
+
${chalk13.dim("$")} os studio ${chalk13.dim("# Dev server + Console UI")}
|
|
1983
|
+
${chalk13.dim("$")} os compile ${chalk13.dim("# Build for production")}
|
|
1984
|
+
|
|
1985
|
+
${chalk13.dim("Aliases: objectstack | os")}
|
|
1986
|
+
${chalk13.dim("Docs: https://objectstack.dev")}
|
|
1987
|
+
`);
|
|
1988
|
+
program.addCommand(initCommand);
|
|
685
1989
|
program.addCommand(devCommand);
|
|
686
|
-
program.addCommand(
|
|
1990
|
+
program.addCommand(serveCommand);
|
|
1991
|
+
program.addCommand(studioCommand);
|
|
1992
|
+
program.addCommand(compileCommand);
|
|
1993
|
+
program.addCommand(validateCommand);
|
|
1994
|
+
program.addCommand(infoCommand);
|
|
1995
|
+
program.addCommand(generateCommand);
|
|
687
1996
|
program.addCommand(createCommand);
|
|
688
1997
|
program.addCommand(testCommand);
|
|
1998
|
+
program.addCommand(doctorCommand);
|
|
689
1999
|
program.parse(process.argv);
|