@objectstack/cli 1.0.11 → 1.0.12

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/dist/bin.js CHANGED
@@ -1,29 +1,327 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- compileCommand
4
- } from "./chunk-2YXVEYO7.js";
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 Command6 } from "commander";
11
+ import { Command as Command12 } from "commander";
12
+ import chalk13 from "chalk";
9
13
 
10
- // src/commands/dev.ts
14
+ // src/commands/compile.ts
11
15
  import { Command } from "commander";
12
- import chalk from "chalk";
13
- import { execSync, spawn } from "child_process";
14
- import fs from "fs";
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
- var devCommand = new Command("dev").description("Start development mode for a package").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
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
- \u{1F680} ObjectStack Development Mode`));
19
- console.log(chalk.dim(`-------------------------------`));
20
- const configPath = path.resolve(process.cwd(), "objectstack.config.ts");
21
- if (packageName === "all" && fs.existsSync(configPath)) {
22
- console.log(chalk.blue(`\u{1F4C2} Detected package config: ${configPath}`));
23
- console.log(chalk.green(`\u26A1 Starting Dev Server...`));
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 = path.resolve(cwd, "pnpm-workspace.yaml");
35
- const isWorkspaceRoot = fs.existsSync(workspaceConfigPath);
332
+ const workspaceConfigPath = path3.resolve(cwd, "pnpm-workspace.yaml");
333
+ const isWorkspaceRoot = fs3.existsSync(workspaceConfigPath);
36
334
  if (packageName === "all" && !isWorkspaceRoot) {
37
- console.error(chalk.red(`
38
- \u274C Configuration file (objectstack.config.ts) not found in ${cwd}`));
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
- console.log(`\u{1F4E6} Package: ${chalk.blue(packageName === "all" ? "All packages" : packageName)}`);
45
- console.log(`\u{1F504} Watch mode: ${chalk.green("enabled")}`);
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(chalk.dim(`$ ${command}`));
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
- console.error(chalk.red(`
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 Command2 } from "commander";
64
- import chalk2 from "chalk";
356
+ import { Command as Command3 } from "commander";
357
+ import chalk5 from "chalk";
65
358
  import { execSync as execSync2 } from "child_process";
66
- import fs2 from "fs";
67
- import path2 from "path";
68
- var doctorCommand = new Command2("doctor").description("Check development environment health").option("-v, --verbose", "Show detailed information").action(async (options) => {
69
- console.log(chalk2.bold(`
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 = path2.join(cwd, "node_modules");
131
- if (fs2.existsSync(nodeModulesPath)) {
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 = path2.join(cwd, "packages/spec/dist");
146
- if (fs2.existsSync(specDistPath)) {
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 icon = result.status === "ok" ? "\u2713" : result.status === "warning" ? "\u26A0" : "\u2717";
179
- const color = result.status === "ok" ? chalk2.green : result.status === "warning" ? chalk2.yellow : chalk2.red;
180
- console.log(color(`${icon} ${result.name.padEnd(20)} ${result.message}`));
181
- if (result.fix && options.verbose) {
182
- console.log(chalk2.dim(` \u2192 ${result.fix}`));
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(chalk2.red("\u274C Some critical issues found. Please fix them before continuing."));
190
- results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(chalk2.dim(` ${r.fix}`)));
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(chalk2.yellow("\u26A0\uFE0F Environment is functional but has some warnings."));
194
- console.log(chalk2.dim(" Run with --verbose to see fix suggestions."));
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(chalk2.green("\u2705 Environment is healthy and ready for development!"));
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 Command3 } from "commander";
203
- import chalk3 from "chalk";
204
- import fs3 from "fs";
205
- import path3 from "path";
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.22.4"
522
+ zod: "^4.3.6"
227
523
  },
228
524
  devDependencies: {
229
- "@types/node": "^20.10.0",
230
- typescript: "^5.3.0",
231
- vitest: "^2.1.8"
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.22.4"
605
+ zod: "^4.3.6"
310
606
  },
311
607
  devDependencies: {
312
- "@types/node": "^20.10.0",
608
+ "@types/node": "^22.0.0",
313
609
  tsx: "^4.21.0",
314
- typescript: "^5.3.0",
315
- vitest: "^2.1.8"
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
- // Define your data objects here
329
- // { name: 'my_object', fields: { ... } }
629
+ // Object.values(objects), // Uncomment after creating src/objects/index.ts
330
630
  ],
331
631
 
332
632
  apps: [
333
- // Define your apps here
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 Command3("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) => {
376
- console.log(chalk3.bold(`
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(chalk3.dim(`-------------------------------`));
678
+ console.log(chalk6.dim(`-------------------------------`));
379
679
  if (!templates[type]) {
380
- console.error(chalk3.red(`
680
+ console.error(chalk6.red(`
381
681
  \u274C Unknown type: ${type}`));
382
- console.log(chalk3.dim("Available types: plugin, example"));
682
+ console.log(chalk6.dim("Available types: plugin, example"));
383
683
  process.exit(1);
384
684
  }
385
685
  if (!name) {
386
- console.error(chalk3.red("\n\u274C Project name is required"));
387
- console.log(chalk3.dim(`Usage: objectstack create ${type} <name>`));
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 = path3.resolve(cwd, options.dir);
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 = path3.join(cwd, baseDir, projectName);
698
+ targetDir = path5.join(cwd, baseDir, projectName);
399
699
  }
400
- if (fs3.existsSync(targetDir)) {
401
- console.error(chalk3.red(`
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}: ${chalk3.blue(name)}`);
406
- console.log(`\u{1F4C2} Location: ${chalk3.dim(targetDir)}`);
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
- fs3.mkdirSync(targetDir, { recursive: true });
709
+ fs5.mkdirSync(targetDir, { recursive: true });
410
710
  for (const [filePath, contentFn] of Object.entries(template.files)) {
411
- const fullPath = path3.join(targetDir, filePath);
412
- const dir = path3.dirname(fullPath);
413
- if (!fs3.existsSync(dir)) {
414
- fs3.mkdirSync(dir, { recursive: true });
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
- fs3.writeFileSync(fullPath, fileContent);
419
- console.log(chalk3.green(`\u2713 Created ${filePath}`));
718
+ fs5.writeFileSync(fullPath, fileContent);
719
+ console.log(chalk6.green(`\u2713 Created ${filePath}`));
420
720
  }
421
721
  console.log("");
422
- console.log(chalk3.green("\u2705 Project created successfully!"));
722
+ console.log(chalk6.green("\u2705 Project created successfully!"));
423
723
  console.log("");
424
- console.log(chalk3.bold("Next steps:"));
425
- console.log(chalk3.dim(` cd ${path3.relative(cwd, targetDir)}`));
426
- console.log(chalk3.dim(" pnpm install"));
427
- console.log(chalk3.dim(" pnpm build"));
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(chalk3.red("\n\u274C Failed to create project:"));
730
+ console.error(chalk6.red("\n\u274C Failed to create project:"));
431
731
  console.error(error.message || error);
432
- if (fs3.existsSync(targetDir)) {
433
- fs3.rmSync(targetDir, { recursive: true });
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 Command4 } from "commander";
441
- import path4 from "path";
442
- import fs4 from "fs";
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 chalk4 from "chalk";
445
- import { bundleRequire } from "bundle-require";
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 = net.createServer();
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 Command4("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("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
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
- console.log(chalk4.bold(`
478
- \u{1F680} ObjectStack Server`));
479
- console.log(chalk4.dim(`------------------------`));
480
- console.log(`\u{1F4C2} Config: ${chalk4.blue(configPath)}`);
481
- if (parseInt(options.port) !== port) {
482
- console.log(`\u{1F310} Port: ${chalk4.blue(port)} ${chalk4.yellow(`(requested: ${options.port} in use)`)}`);
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
- console.log(chalk4.yellow(`\u{1F4E6} Loading configuration...`));
495
- const { mod } = await bundleRequire({
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(`Default export not found in ${configPath}`);
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
- console.log(chalk4.yellow(`\u{1F527} Initializing ObjectStack runtime...`));
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
- console.log(chalk4.green(` \u2713 Registered ObjectQL Plugin (auto-detected)`));
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
- console.log(chalk4.green(` \u2713 Registered Memory Driver (auto-detected)`));
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
- console.log(chalk4.green(` \u2713 Registered App Plugin (auto-detected)`));
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
- console.log(chalk4.green(` \u2713 Registered plugin: ${pluginName}`));
1089
+ trackPlugin(pluginName);
567
1090
  } catch (e) {
568
- console.error(chalk4.red(` \u2717 Failed to register plugin: ${e.message}`));
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
- console.log(chalk4.green(` \u2713 Registered HTTP server plugin (port: ${port})`));
1100
+ trackPlugin("HonoServer");
578
1101
  } catch (e) {
579
- console.warn(chalk4.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
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
- console.log(chalk4.green(`
586
- \u2705 ObjectStack server is running!`));
587
- console.log(chalk4.dim(` Press Ctrl+C to stop
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.log(chalk4.yellow(`
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(chalk4.green(`\u2705 Server stopped`));
1150
+ console.log(chalk7.green(`\u2705 Server stopped`));
595
1151
  process.exit(0);
596
1152
  });
597
1153
  } catch (error) {
598
- console.error(chalk4.red(`
599
- \u274C Server Error:`));
600
- console.error(error.message || error);
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 Command5 } from "commander";
608
- import chalk5 from "chalk";
609
- import path5 from "path";
610
- import fs5 from "fs";
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 Command5("test:run").description("Run Quality Protocol test scenarios").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) => {
613
- console.log(chalk5.bold(`
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(chalk5.dim(`-------------------------------------`));
616
- console.log(`Target: ${chalk5.blue(options.url)}`);
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 (fs5.existsSync(filesPattern)) {
1202
+ if (fs8.existsSync(filesPattern)) {
622
1203
  testFiles.push(filesPattern);
623
1204
  } else {
624
- const dir = path5.dirname(filesPattern);
625
- const ext = path5.extname(filesPattern);
626
- if (fs5.existsSync(dir)) {
627
- const files = fs5.readdirSync(dir).filter((f) => f.endsWith(ext) || f.endsWith(".json"));
628
- files.forEach((f) => testFiles.push(path5.join(dir, f)));
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(chalk5.yellow(`No test files found matching: ${filesPattern}`));
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: ${chalk5.bold(path5.basename(file))}`);
1221
+ \u{1F4C4} Running suite: ${chalk8.bold(path8.basename(file))}`);
641
1222
  try {
642
- const content = fs5.readFileSync(file, "utf-8");
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(chalk5.red(` Error: ${result.error}`));
1230
+ console.error(chalk8.red(` Error: ${result.error}`));
650
1231
  result.steps.forEach((step) => {
651
1232
  if (!step.passed) {
652
- console.error(chalk5.red(` Step Failed: ${step.stepName}`));
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(chalk5.red(`Failed to load or run suite ${file}: ${e}`));
1244
+ console.error(chalk8.red(`Failed to load or run suite ${file}: ${e}`));
664
1245
  totalFailed++;
665
1246
  }
666
1247
  }
667
- console.log(chalk5.dim(`
1248
+ console.log(chalk8.dim(`
668
1249
  -------------------------------------`));
669
1250
  if (totalFailed > 0) {
670
- console.log(chalk5.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
1251
+ console.log(chalk8.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
671
1252
  process.exit(1);
672
1253
  } else {
673
- console.log(chalk5.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
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
- var program = new Command6();
682
- program.name("objectstack").description("CLI for ObjectStack Protocol - Development Tools for Microkernel Architecture").version(pkg.version);
683
- program.addCommand(compileCommand);
684
- program.addCommand(serveCommand);
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(doctorCommand);
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);