@objectstack/cli 2.0.7 → 3.0.1

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