@object-ui/cli 17.3.0 → 17.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,60 @@
1
1
  // src/commands/serve.ts
2
- import { createServer } from "vite";
2
+ import { createServer, createLogger } from "vite";
3
3
  import react from "@vitejs/plugin-react";
4
- import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
5
- import { join as join2, resolve, relative } from "path";
6
- import chalk2 from "chalk";
4
+ import { mkdirSync as mkdirSync2 } from "fs";
5
+ import { join as join5 } from "path";
6
+ import chalk3 from "chalk";
7
7
  import { execSync } from "child_process";
8
8
 
9
9
  // src/utils/app-generator.ts
10
- import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, existsSync } from "fs";
11
- import { join } from "path";
10
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, readdirSync, statSync, existsSync as existsSync2 } from "fs";
11
+ import { dirname as dirname2, join as join2 } from "path";
12
12
  import chalk from "chalk";
13
13
  import * as yaml from "js-yaml";
14
+
15
+ // src/utils/scaffold-dependencies.ts
16
+ import { existsSync, readFileSync } from "fs";
17
+ import { dirname, join } from "path";
18
+ import { fileURLToPath } from "url";
19
+ var CLI_PACKAGE_NAME = "@object-ui/cli";
20
+ var cachedCliVersion;
21
+ function cliVersion() {
22
+ if (cachedCliVersion !== void 0) return cachedCliVersion;
23
+ let dir = dirname(fileURLToPath(import.meta.url));
24
+ for (; ; ) {
25
+ const candidate = join(dir, "package.json");
26
+ if (existsSync(candidate)) {
27
+ const manifest = JSON.parse(readFileSync(candidate, "utf-8"));
28
+ if (manifest.name === CLI_PACKAGE_NAME && typeof manifest.version === "string") {
29
+ cachedCliVersion = manifest.version;
30
+ return cachedCliVersion;
31
+ }
32
+ }
33
+ const parent = dirname(dir);
34
+ if (parent === dir) break;
35
+ dir = parent;
36
+ }
37
+ throw new Error(
38
+ `Could not locate the ${CLI_PACKAGE_NAME} manifest to version the generated app's platform dependencies against. Refusing to write a package.json with a guessed range.`
39
+ );
40
+ }
41
+ function platformPackageRange() {
42
+ return `^${cliVersion()}`;
43
+ }
44
+ var REACT_RANGE = "19.2.8";
45
+ var SCAFFOLD_DEV_DEPENDENCIES = {
46
+ "@tailwindcss/postcss": "^4.3.3",
47
+ "@types/react": "19.2.18",
48
+ "@types/react-dom": "19.2.4",
49
+ "@vitejs/plugin-react": "^6.0.5",
50
+ autoprefixer: "^10.5.4",
51
+ postcss: "^8.5.26",
52
+ tailwindcss: "^4.3.3",
53
+ typescript: "^6.0.3",
54
+ vite: "^8.2.1"
55
+ };
56
+
57
+ // src/utils/app-generator.ts
14
58
  function isSupportedSchemaFile(filename) {
15
59
  return filename.endsWith(".json") || filename.endsWith(".yml") || filename.endsWith(".yaml");
16
60
  }
@@ -18,7 +62,7 @@ function getBaseFileName(filename) {
18
62
  return filename.replace(/\.(json|yml|yaml)$/, "");
19
63
  }
20
64
  function parseSchemaFile(filePath) {
21
- const content = readFileSync(filePath, "utf-8");
65
+ const content = readFileSync2(filePath, "utf-8");
22
66
  if (filePath.endsWith(".json")) {
23
67
  return JSON.parse(content);
24
68
  } else if (filePath.endsWith(".yml") || filePath.endsWith(".yaml")) {
@@ -31,7 +75,7 @@ function scanPagesDirectory(pagesDir) {
31
75
  const scanDir = (dir, routePrefix = "") => {
32
76
  const entries = readdirSync(dir);
33
77
  for (const entry of entries) {
34
- const fullPath = join(dir, entry);
78
+ const fullPath = join2(dir, entry);
35
79
  const stat = statSync(fullPath);
36
80
  if (stat.isDirectory()) {
37
81
  const newPrefix = routePrefix + "/" + entry;
@@ -73,7 +117,255 @@ function scanPagesDirectory(pagesDir) {
73
117
  });
74
118
  return routes;
75
119
  }
120
+ var PLATFORM_RUNTIME_PACKAGES = [
121
+ "@object-ui/react",
122
+ "@object-ui/components",
123
+ "@object-ui/plugin-charts",
124
+ "@object-ui/plugin-editor",
125
+ "@object-ui/plugin-kanban",
126
+ "@object-ui/plugin-markdown",
127
+ "@object-ui/plugin-form",
128
+ "@object-ui/plugin-grid",
129
+ "@object-ui/plugin-view"
130
+ ];
131
+ function buildAppDependencies() {
132
+ const range = platformPackageRange();
133
+ return {
134
+ react: REACT_RANGE,
135
+ "react-dom": REACT_RANGE,
136
+ ...Object.fromEntries(PLATFORM_RUNTIME_PACKAGES.map((name) => [name, range]))
137
+ };
138
+ }
139
+ function buildRoutedAppDependencies() {
140
+ const range = platformPackageRange();
141
+ return {
142
+ react: REACT_RANGE,
143
+ "react-dom": REACT_RANGE,
144
+ "react-router-dom": "^7.18.2",
145
+ "lucide-react": "^1.31.0",
146
+ ...Object.fromEntries(PLATFORM_RUNTIME_PACKAGES.map((name) => [name, range]))
147
+ };
148
+ }
149
+ var APP_VITE_ENV_D_TS = `/// <reference types="vite/client" />
150
+ `;
151
+ var APP_TSCONFIG = {
152
+ compilerOptions: {
153
+ target: "ES2020",
154
+ useDefineForClassFields: true,
155
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
156
+ module: "ESNext",
157
+ skipLibCheck: true,
158
+ moduleResolution: "bundler",
159
+ allowImportingTsExtensions: true,
160
+ resolveJsonModule: true,
161
+ isolatedModules: true,
162
+ noEmit: true,
163
+ jsx: "react-jsx",
164
+ strict: true,
165
+ noUnusedLocals: true,
166
+ noUnusedParameters: true,
167
+ noFallthroughCasesInSwitch: true
168
+ },
169
+ include: ["src"]
170
+ };
171
+ var APP_POSTCSS_CONFIG = `export default {
172
+ plugins: {
173
+ '@tailwindcss/postcss': {},
174
+ autoprefixer: {},
175
+ },
176
+ };`;
177
+ function currentContext() {
178
+ const cwd = process.cwd();
179
+ return { cwd, isMonorepo: existsSync2(join2(cwd, "pnpm-workspace.yaml")) };
180
+ }
181
+ function buildAppPackageJson(context) {
182
+ return {
183
+ name: "objectui-temp-app",
184
+ private: true,
185
+ type: "module",
186
+ // In monorepo, we use root node_modules, so we don't need dependencies here
187
+ dependencies: context.isMonorepo ? {} : buildAppDependencies(),
188
+ devDependencies: context.isMonorepo ? {} : { ...SCAFFOLD_DEV_DEPENDENCIES }
189
+ };
190
+ }
191
+ function buildRoutedAppPackageJson() {
192
+ return {
193
+ name: "objectui-temp-app",
194
+ private: true,
195
+ type: "module",
196
+ dependencies: buildRoutedAppDependencies(),
197
+ devDependencies: { ...SCAFFOLD_DEV_DEPENDENCIES }
198
+ };
199
+ }
200
+ function buildAppSourceDirectives(context) {
201
+ const sources = [`@source '../index.html';`, `@source '../src/**/*.{js,ts,jsx,tsx,json}';`];
202
+ if (context.isMonorepo) {
203
+ sources.push(`@source '${join2(context.cwd, "packages/components/src/**/*.{ts,tsx}")}';`);
204
+ sources.push(`@source '${join2(context.cwd, "packages/plugin-*/src/**/*.{ts,tsx}")}';`);
205
+ } else {
206
+ sources.push(`@source '../node_modules/@object-ui/*/dist/**/*.js';`);
207
+ }
208
+ return sources;
209
+ }
210
+ var APP_THEME_TOKENS = `@theme {
211
+ /* Border radius tokens */
212
+ --radius-lg: var(--radius);
213
+ --radius-md: calc(var(--radius) - 2px);
214
+ --radius-sm: calc(var(--radius) - 4px);
215
+
216
+ /* Color tokens mapped to the CSS variables declared below */
217
+ --color-border: hsl(var(--border));
218
+ --color-input: hsl(var(--input));
219
+ --color-ring: hsl(var(--ring));
220
+ --color-background: hsl(var(--background));
221
+ --color-foreground: hsl(var(--foreground));
222
+ --color-primary: hsl(var(--primary));
223
+ --color-primary-foreground: hsl(var(--primary-foreground));
224
+ --color-secondary: hsl(var(--secondary));
225
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
226
+ --color-destructive: hsl(var(--destructive));
227
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
228
+ --color-muted: hsl(var(--muted));
229
+ --color-muted-foreground: hsl(var(--muted-foreground));
230
+ --color-accent: hsl(var(--accent));
231
+ --color-accent-foreground: hsl(var(--accent-foreground));
232
+ --color-popover: hsl(var(--popover));
233
+ --color-popover-foreground: hsl(var(--popover-foreground));
234
+ --color-card: hsl(var(--card));
235
+ --color-card-foreground: hsl(var(--card-foreground));
236
+ --color-sidebar: hsl(var(--sidebar));
237
+ --color-sidebar-foreground: hsl(var(--sidebar-foreground));
238
+ --color-sidebar-primary: hsl(var(--sidebar-primary));
239
+ --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
240
+ --color-sidebar-accent: hsl(var(--sidebar-accent));
241
+ --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
242
+ --color-sidebar-border: hsl(var(--sidebar-border));
243
+ --color-sidebar-ring: hsl(var(--sidebar-ring));
244
+
245
+ /* Chart colors */
246
+ --color-chart-1: hsl(var(--chart-1));
247
+ --color-chart-2: hsl(var(--chart-2));
248
+ --color-chart-3: hsl(var(--chart-3));
249
+ --color-chart-4: hsl(var(--chart-4));
250
+ --color-chart-5: hsl(var(--chart-5));
251
+ }`;
252
+ var APP_THEME_VARIABLES = `:root {
253
+ color-scheme: light;
254
+
255
+ --background: 0 0% 100%;
256
+ --foreground: 222.2 84% 4.9%;
257
+ --card: 0 0% 100%;
258
+ --card-foreground: 222.2 84% 4.9%;
259
+ --popover: 0 0% 100%;
260
+ --popover-foreground: 222.2 84% 4.9%;
261
+ --primary: 222.2 47.4% 11.2%;
262
+ --primary-foreground: 210 40% 98%;
263
+ --secondary: 210 40% 96.1%;
264
+ --secondary-foreground: 222.2 47.4% 11.2%;
265
+ --muted: 210 40% 96.1%;
266
+ --muted-foreground: 215.4 16.3% 46.9%;
267
+ --accent: 210 40% 96.1%;
268
+ --accent-foreground: 222.2 47.4% 11.2%;
269
+ --destructive: 0 84.2% 60.2%;
270
+ --destructive-foreground: 210 40% 98%;
271
+ --border: 214.3 31.8% 91.4%;
272
+ --input: 214.3 31.8% 91.4%;
273
+ --ring: 222.2 84% 4.9%;
274
+ --radius: 0.5rem;
275
+ --chart-1: 12 76% 61%;
276
+ --chart-2: 173 58% 39%;
277
+ --chart-3: 197 37% 24%;
278
+ --chart-4: 43 74% 66%;
279
+ --chart-5: 27 87% 67%;
280
+
281
+ /* Sidebar colors */
282
+ --sidebar: 0 0% 98%;
283
+ --sidebar-foreground: 240 5.3% 26.1%;
284
+ --sidebar-primary: 240 5.9% 10%;
285
+ --sidebar-primary-foreground: 0 0% 98%;
286
+ --sidebar-accent: 240 4.8% 95.9%;
287
+ --sidebar-accent-foreground: 240 5.9% 10%;
288
+ --sidebar-border: 220 13% 91%;
289
+ --sidebar-ring: 217.2 91.2% 59.8%;
290
+ }
291
+
292
+ .dark {
293
+ color-scheme: dark;
294
+
295
+ --background: 222.2 84% 4.9%;
296
+ --foreground: 210 40% 98%;
297
+ --card: 222.2 84% 4.9%;
298
+ --card-foreground: 210 40% 98%;
299
+ --popover: 222.2 84% 4.9%;
300
+ --popover-foreground: 210 40% 98%;
301
+ --primary: 210 40% 98%;
302
+ --primary-foreground: 222.2 47.4% 11.2%;
303
+ --secondary: 217.2 32.6% 17.5%;
304
+ --secondary-foreground: 210 40% 98%;
305
+ --muted: 217.2 32.6% 17.5%;
306
+ --muted-foreground: 215 20.2% 65.1%;
307
+ --accent: 217.2 32.6% 17.5%;
308
+ --accent-foreground: 210 40% 98%;
309
+ --destructive: 0 62.8% 30.6%;
310
+ --destructive-foreground: 210 40% 98%;
311
+ --border: 217.2 32.6% 17.5%;
312
+ --input: 217.2 32.6% 17.5%;
313
+ --ring: 212.7 26.8% 83.9%;
314
+ --chart-1: 220 70% 50%;
315
+ --chart-2: 160 60% 45%;
316
+ --chart-3: 30 80% 55%;
317
+ --chart-4: 280 65% 60%;
318
+ --chart-5: 340 75% 55%;
319
+
320
+ /* Sidebar colors for dark mode */
321
+ --sidebar: 240 5.9% 10%;
322
+ --sidebar-foreground: 240 4.8% 95.9%;
323
+ --sidebar-primary: 224.3 76.3% 48%;
324
+ --sidebar-primary-foreground: 0 0% 100%;
325
+ --sidebar-accent: 240 3.7% 15.9%;
326
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
327
+ --sidebar-border: 240 3.7% 15.9%;
328
+ --sidebar-ring: 217.2 91.2% 59.8%;
329
+ }`;
330
+ function buildAppIndexCss(context) {
331
+ return `@import 'tailwindcss';
332
+
333
+ /* Class-based dark variant: follow \`.dark\` on <html> \u2014 the routed app's
334
+ \`src/theme-provider.tsx\` toggles exactly that class \u2014 instead of the OS
335
+ \`prefers-color-scheme\`. Same spelling as packages/components/src/index.css. */
336
+ @custom-variant dark (&:where(.dark, .dark *));
337
+
338
+ /* Sources to scan for utility classes (v4's replacement for v3 \`content\`) */
339
+ ${buildAppSourceDirectives(context).join("\n")}
340
+
341
+ ${APP_THEME_TOKENS}
342
+
343
+ ${APP_THEME_VARIABLES}
344
+
345
+ * {
346
+ border-color: hsl(var(--border));
347
+ }
348
+
349
+ body {
350
+ background-color: hsl(var(--background));
351
+ color: hsl(var(--foreground));
352
+ font-family: var(--font-sans);
353
+ -webkit-font-smoothing: antialiased;
354
+ -moz-osx-font-smoothing: grayscale;
355
+ min-height: 100vh;
356
+ }`;
357
+ }
358
+ function writeGeneratedFiles(tmpDir, files) {
359
+ for (const [relativePath, contents] of Object.entries(files)) {
360
+ const target = join2(tmpDir, relativePath);
361
+ mkdirSync(dirname2(target), { recursive: true });
362
+ writeFileSync(target, contents);
363
+ }
364
+ }
76
365
  function createTempApp(tmpDir, schema) {
366
+ writeGeneratedFiles(tmpDir, buildAppFiles(schema, currentContext()));
367
+ }
368
+ function buildAppFiles(schema, context) {
77
369
  const html = `<!DOCTYPE html>
78
370
  <html lang="en">
79
371
  <head>
@@ -86,9 +378,6 @@ function createTempApp(tmpDir, schema) {
86
378
  <script type="module" src="/src/main.tsx"></script>
87
379
  </body>
88
380
  </html>`;
89
- writeFileSync(join(tmpDir, "index.html"), html);
90
- const srcDir = join(tmpDir, "src");
91
- mkdirSync(srcDir, { recursive: true });
92
381
  const mainTsx = `import React from 'react';
93
382
  import ReactDOM from 'react-dom/client';
94
383
  import App from './App';
@@ -99,7 +388,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
99
388
  <App />
100
389
  </React.StrictMode>
101
390
  );`;
102
- writeFileSync(join(srcDir, "main.tsx"), mainTsx);
103
391
  const appTsx = `import { SchemaRenderer } from '@object-ui/react';
104
392
  import '@object-ui/components';
105
393
  import '@object-ui/plugin-charts';
@@ -117,256 +405,22 @@ function App() {
117
405
  }
118
406
 
119
407
  export default App;`;
120
- writeFileSync(join(srcDir, "App.tsx"), appTsx);
121
- const indexCss = `@tailwind base;
122
- @tailwind components;
123
- @tailwind utilities;
124
-
125
- @layer base {
126
- :root {
127
- --background: 0 0% 100%;
128
- --foreground: 222.2 84% 4.9%;
129
- --card: 0 0% 100%;
130
- --card-foreground: 222.2 84% 4.9%;
131
- --popover: 0 0% 100%;
132
- --popover-foreground: 222.2 84% 4.9%;
133
- --primary: 222.2 47.4% 11.2%;
134
- --primary-foreground: 210 40% 98%;
135
- --secondary: 210 40% 96.1%;
136
- --secondary-foreground: 222.2 47.4% 11.2%;
137
- --muted: 210 40% 96.1%;
138
- --muted-foreground: 215.4 16.3% 46.9%;
139
- --accent: 210 40% 96.1%;
140
- --accent-foreground: 222.2 47.4% 11.2%;
141
- --destructive: 0 84.2% 60.2%;
142
- --destructive-foreground: 210 40% 98%;
143
- --border: 214.3 31.8% 91.4%;
144
- --input: 214.3 31.8% 91.4%;
145
- --ring: 222.2 84% 4.9%;
146
- --radius: 0.5rem;
147
- --chart-1: 12 76% 61%;
148
- --chart-2: 173 58% 39%;
149
- --chart-3: 197 37% 24%;
150
- --chart-4: 43 74% 66%;
151
- --chart-5: 27 87% 67%;
152
- }
153
-
154
- .dark {
155
- --background: 222.2 84% 4.9%;
156
- --foreground: 210 40% 98%;
157
- --card: 222.2 84% 4.9%;
158
- --card-foreground: 210 40% 98%;
159
- --popover: 222.2 84% 4.9%;
160
- --popover-foreground: 210 40% 98%;
161
- --primary: 210 40% 98%;
162
- --primary-foreground: 222.2 47.4% 11.2%;
163
- --secondary: 217.2 32.6% 17.5%;
164
- --secondary-foreground: 210 40% 98%;
165
- --muted: 217.2 32.6% 17.5%;
166
- --muted-foreground: 215 20.2% 65.1%;
167
- --accent: 217.2 32.6% 17.5%;
168
- --accent-foreground: 210 40% 98%;
169
- --destructive: 0 62.8% 30.6%;
170
- --destructive-foreground: 210 40% 98%;
171
- --border: 217.2 32.6% 17.5%;
172
- --input: 217.2 32.6% 17.5%;
173
- --ring: 212.7 26.8% 83.9%;
174
- --chart-1: 220 70% 50%;
175
- --chart-2: 160 60% 45%;
176
- --chart-3: 30 80% 55%;
177
- --chart-4: 280 65% 60%;
178
- --chart-5: 340 75% 55%;
179
- }
180
- }
181
-
182
- @layer base {
183
- * {
184
- @apply border-border;
185
- }
186
- body {
187
- @apply bg-background text-foreground;
188
- }
189
- }`;
190
- writeFileSync(join(srcDir, "index.css"), indexCss);
191
- const tailwindConfig = `/** @type {import('tailwindcss').Config} */
192
- export default {
193
- darkMode: ['class'],
194
- content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
195
- theme: {
196
- extend: {
197
- borderRadius: {
198
- lg: 'var(--radius)',
199
- md: 'calc(var(--radius) - 2px)',
200
- sm: 'calc(var(--radius) - 4px)',
201
- },
202
- colors: {
203
- background: 'hsl(var(--background))',
204
- foreground: 'hsl(var(--foreground))',
205
- card: {
206
- DEFAULT: 'hsl(var(--card))',
207
- foreground: 'hsl(var(--card-foreground))',
208
- },
209
- popover: {
210
- DEFAULT: 'hsl(var(--popover))',
211
- foreground: 'hsl(var(--popover-foreground))',
212
- },
213
- primary: {
214
- DEFAULT: 'hsl(var(--primary))',
215
- foreground: 'hsl(var(--primary-foreground))',
216
- },
217
- secondary: {
218
- DEFAULT: 'hsl(var(--secondary))',
219
- foreground: 'hsl(var(--secondary-foreground))',
220
- },
221
- muted: {
222
- DEFAULT: 'hsl(var(--muted))',
223
- foreground: 'hsl(var(--muted-foreground))',
224
- },
225
- accent: {
226
- DEFAULT: 'hsl(var(--accent))',
227
- foreground: 'hsl(var(--accent-foreground))',
228
- },
229
- destructive: {
230
- DEFAULT: 'hsl(var(--destructive))',
231
- foreground: 'hsl(var(--destructive-foreground))',
232
- },
233
- border: 'hsl(var(--border))',
234
- input: 'hsl(var(--input))',
235
- ring: 'hsl(var(--ring))',
236
- chart: {
237
- 1: 'hsl(var(--chart-1))',
238
- 2: 'hsl(var(--chart-2))',
239
- 3: 'hsl(var(--chart-3))',
240
- 4: 'hsl(var(--chart-4))',
241
- 5: 'hsl(var(--chart-5))',
242
- },
243
- },
244
- },
245
- },
246
- plugins: [],
247
- };`;
248
- const cwd = process.cwd();
249
- const isMonorepo = existsSync(join(cwd, "pnpm-workspace.yaml"));
250
- const contentPaths = ["'./index.html'", "'./src/**/*.{js,ts,jsx,tsx,json}'"];
251
- if (isMonorepo) {
252
- const componentsPath = join(cwd, "packages/components/src/**/*.{ts,tsx}");
253
- const pluginsPath = join(cwd, "packages/plugin-*/src/**/*.{ts,tsx}");
254
- contentPaths.push(`'${componentsPath}'`);
255
- contentPaths.push(`'${pluginsPath}'`);
256
- }
257
- const finalTailwindConfig = `/** @type {import('tailwindcss').Config} */
258
- export default {
259
- darkMode: ['class'],
260
- content: [${contentPaths.join(", ")}],
261
- theme: {
262
- extend: {
263
- borderRadius: {
264
- lg: 'var(--radius)',
265
- md: 'calc(var(--radius) - 2px)',
266
- sm: 'calc(var(--radius) - 4px)',
267
- },
268
- colors: {
269
- background: 'hsl(var(--background))',
270
- foreground: 'hsl(var(--foreground))',
271
- card: {
272
- DEFAULT: 'hsl(var(--card))',
273
- foreground: 'hsl(var(--card-foreground))',
274
- },
275
- popover: {
276
- DEFAULT: 'hsl(var(--popover))',
277
- foreground: 'hsl(var(--popover-foreground))',
278
- },
279
- primary: {
280
- DEFAULT: 'hsl(var(--primary))',
281
- foreground: 'hsl(var(--primary-foreground))',
282
- },
283
- secondary: {
284
- DEFAULT: 'hsl(var(--secondary))',
285
- foreground: 'hsl(var(--secondary-foreground))',
286
- },
287
- muted: {
288
- DEFAULT: 'hsl(var(--muted))',
289
- foreground: 'hsl(var(--muted-foreground))',
290
- },
291
- accent: {
292
- DEFAULT: 'hsl(var(--accent))',
293
- foreground: 'hsl(var(--accent-foreground))',
294
- },
295
- destructive: {
296
- DEFAULT: 'hsl(var(--destructive))',
297
- foreground: 'hsl(var(--destructive-foreground))',
298
- },
299
- border: 'hsl(var(--border))',
300
- input: 'hsl(var(--input))',
301
- ring: 'hsl(var(--ring))',
302
- chart: {
303
- 1: 'hsl(var(--chart-1))',
304
- 2: 'hsl(var(--chart-2))',
305
- 3: 'hsl(var(--chart-3))',
306
- 4: 'hsl(var(--chart-4))',
307
- 5: 'hsl(var(--chart-5))',
308
- },
309
- },
310
- },
311
- },
312
- plugins: [],
313
- };`;
314
- writeFileSync(join(tmpDir, "tailwind.config.js"), finalTailwindConfig);
315
- const finalPostcssConfig = `export default {
316
- plugins: {
317
- tailwindcss: {},
318
- autoprefixer: {},
319
- },
320
- };`;
321
- writeFileSync(join(tmpDir, "postcss.config.js"), finalPostcssConfig);
322
- const baseDependencies = {
323
- react: "^18.3.1",
324
- "react-dom": "^18.3.1",
325
- "@object-ui/react": "^0.1.0",
326
- "@object-ui/components": "^0.1.0"
327
- };
328
- const baseDevDependencies = {
329
- "@types/react": "^18.3.12",
330
- "@types/react-dom": "^18.3.1",
331
- "@vitejs/plugin-react": "^4.2.1",
332
- autoprefixer: "^10.4.23",
333
- postcss: "^8.5.6",
334
- tailwindcss: "^3.4.19",
335
- typescript: "~5.7.3",
336
- vite: "^5.0.0"
337
- };
338
- const packageJson = {
339
- name: "objectui-temp-app",
340
- private: true,
341
- type: "module",
342
- // In monorepo, we use root node_modules, so we don't need dependencies here
343
- dependencies: isMonorepo ? {} : baseDependencies,
344
- devDependencies: isMonorepo ? {} : baseDevDependencies
345
- };
346
- writeFileSync(join(tmpDir, "package.json"), JSON.stringify(packageJson, null, 2));
347
- const tsconfig = {
348
- compilerOptions: {
349
- target: "ES2020",
350
- useDefineForClassFields: true,
351
- lib: ["ES2020", "DOM", "DOM.Iterable"],
352
- module: "ESNext",
353
- skipLibCheck: true,
354
- moduleResolution: "bundler",
355
- allowImportingTsExtensions: true,
356
- resolveJsonModule: true,
357
- isolatedModules: true,
358
- noEmit: true,
359
- jsx: "react-jsx",
360
- strict: true,
361
- noUnusedLocals: true,
362
- noUnusedParameters: true,
363
- noFallthroughCasesInSwitch: true
364
- },
365
- include: ["src"]
408
+ return {
409
+ "index.html": html,
410
+ "src/main.tsx": mainTsx,
411
+ "src/App.tsx": appTsx,
412
+ "src/vite-env.d.ts": APP_VITE_ENV_D_TS,
413
+ "src/index.css": buildAppIndexCss(context),
414
+ "postcss.config.js": APP_POSTCSS_CONFIG,
415
+ "package.json": JSON.stringify(buildAppPackageJson(context), null, 2),
416
+ "tsconfig.json": JSON.stringify(APP_TSCONFIG, null, 2)
366
417
  };
367
- writeFileSync(join(tmpDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
368
418
  }
369
419
  function createTempAppWithRouting(tmpDir, routes, appConfig) {
420
+ writeGeneratedFiles(tmpDir, buildRoutedAppFiles(routes, appConfig, currentContext()));
421
+ }
422
+ function buildRoutedAppFiles(routes, appConfig, context) {
423
+ const files = {};
370
424
  const html = `<!DOCTYPE html>
371
425
  <html lang="en">
372
426
  <head>
@@ -379,20 +433,13 @@ function createTempAppWithRouting(tmpDir, routes, appConfig) {
379
433
  <script type="module" src="/src/main.tsx"></script>
380
434
  </body>
381
435
  </html>`;
382
- writeFileSync(join(tmpDir, "index.html"), html);
383
- const srcDir = join(tmpDir, "src");
384
- mkdirSync(srcDir, { recursive: true });
385
- const schemasDir = join(srcDir, "schemas");
386
- mkdirSync(schemasDir, { recursive: true });
436
+ files["index.html"] = html;
387
437
  const schemaImports = [];
388
438
  const routeComponents = [];
389
439
  routes.forEach((route, index) => {
390
440
  const schemaVarName = `schema${index}`;
391
441
  const schemaFileName = `page${index}.json`;
392
- writeFileSync(
393
- join(schemasDir, schemaFileName),
394
- JSON.stringify(route.schema, null, 2)
395
- );
442
+ files[`src/schemas/${schemaFileName}`] = JSON.stringify(route.schema, null, 2);
396
443
  schemaImports.push(`import ${schemaVarName} from './schemas/${schemaFileName}';`);
397
444
  routeComponents.push(` <Route path="${route.path}" element={<SchemaRenderer schema={${schemaVarName}} />} />`);
398
445
  });
@@ -469,7 +516,7 @@ export const useTheme = () => {
469
516
 
470
517
  return context
471
518
  }`;
472
- writeFileSync(join(srcDir, "theme-provider.tsx"), themeProviderTsx);
519
+ files["src/theme-provider.tsx"] = themeProviderTsx;
473
520
  const mainTsx = `import React from 'react';
474
521
  import ReactDOM from 'react-dom/client';
475
522
  import App from './App';
@@ -483,19 +530,18 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
483
530
  </ThemeProvider>
484
531
  </React.StrictMode>
485
532
  );`;
486
- writeFileSync(join(srcDir, "main.tsx"), mainTsx);
533
+ files["src/main.tsx"] = mainTsx;
487
534
  let layoutImport = "";
488
535
  let layoutWrapperStart = "";
489
536
  let layoutWrapperEnd = "";
490
537
  if (appConfig) {
491
538
  const layoutCode = `
539
+ import type { ComponentType, ReactNode } from 'react';
492
540
  import { Link, useLocation } from 'react-router-dom';
493
541
  import * as LucideIcons from 'lucide-react';
494
542
  import { Moon, Sun } from "lucide-react"
495
543
  import { useTheme } from "./theme-provider"
496
- import {
497
- cn,
498
- Button,
544
+ import {
499
545
  DropdownMenu,
500
546
  DropdownMenuContent,
501
547
  DropdownMenuItem,
@@ -507,8 +553,6 @@ import {
507
553
  SidebarHeader,
508
554
  SidebarRail,
509
555
  SidebarGroup,
510
- SidebarGroupContent,
511
- SidebarGroupLabel,
512
556
  SidebarMenu,
513
557
  SidebarMenuItem,
514
558
  SidebarMenuButton,
@@ -521,11 +565,36 @@ import {
521
565
  Collapsible,
522
566
  CollapsibleTrigger,
523
567
  CollapsibleContent
524
- } from '@object-ui/components';
568
+ } from '@object-ui/components';
569
+
570
+ /** One entry of \`app.json\`'s \`menu\`, as this layout reads it. */
571
+ type MenuItem = {
572
+ label?: string;
573
+ path?: string;
574
+ icon?: string;
575
+ children?: MenuItem[];
576
+ };
577
+
578
+ /** The \`app.json\` fields this layout renders. */
579
+ type AppConfig = {
580
+ title?: string;
581
+ logo?: string;
582
+ menu?: MenuItem[];
583
+ };
584
+
585
+ // Lucide publishes every icon as a named export, and a menu entry names one as
586
+ // a string \u2014 so the lookup is a runtime index into the namespace object, which
587
+ // has no index signature. Narrowing it to the component-by-name shape this file
588
+ // actually uses keeps that honest without an \`any\` or a \`@ts-expect-error\`; the
589
+ // \`undefined\` arm is real, since the namespace also exports helpers that are not
590
+ // components and a schema may name an icon that does not exist.
591
+ const lucideIcons = LucideIcons as unknown as Record<
592
+ string,
593
+ ComponentType<{ className?: string }> | undefined
594
+ >;
525
595
 
526
- const DynamicIcon = ({ name, className }) => {
527
- // @ts-expect-error - Dynamic icon lookup from Lucide icons
528
- const Icon = LucideIcons[name];
596
+ const DynamicIcon = ({ name, className }: { name: string; className?: string }) => {
597
+ const Icon = lucideIcons[name];
529
598
  if (!Icon) return null;
530
599
  return <Icon className={className} />;
531
600
  };
@@ -566,7 +635,7 @@ export function ModeToggle() {
566
635
  )
567
636
  }
568
637
 
569
- const AppLayout = ({ app, children }) => {
638
+ const AppLayout = ({ app, children }: { app: AppConfig; children: ReactNode }) => {
570
639
  const location = useLocation();
571
640
  const menu = app.menu || [];
572
641
 
@@ -668,13 +737,13 @@ const AppLayout = ({ app, children }) => {
668
737
 
669
738
  export default AppLayout;
670
739
  `;
671
- writeFileSync(join(srcDir, "Layout.tsx"), layoutCode);
740
+ files["src/Layout.tsx"] = layoutCode;
672
741
  layoutImport = `import AppLayout from './Layout';
673
742
  const appConfig = ${JSON.stringify(appConfig)};`;
674
743
  layoutWrapperStart = `<AppLayout app={appConfig}>`;
675
744
  layoutWrapperEnd = `</AppLayout>`;
676
745
  }
677
- const appTsx = `import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
746
+ const appTsx = `import { BrowserRouter, Routes, Route } from 'react-router-dom';
678
747
  import { SchemaRenderer } from '@object-ui/react';
679
748
  import '@object-ui/components';
680
749
  import '@object-ui/plugin-charts';
@@ -700,257 +769,313 @@ ${routeComponents.join("\n")}
700
769
  }
701
770
 
702
771
  export default App;`;
703
- writeFileSync(join(srcDir, "App.tsx"), appTsx);
704
- const indexCss = `@tailwind base;
705
- @tailwind components;
706
- @tailwind utilities;
707
-
708
- @layer base {
709
- :root {
710
- --background: 0 0% 100%;
711
- --foreground: 222.2 84% 4.9%;
712
- --card: 0 0% 100%;
713
- --card-foreground: 222.2 84% 4.9%;
714
- --popover: 0 0% 100%;
715
- --popover-foreground: 222.2 84% 4.9%;
716
- --primary: 222.2 47.4% 11.2%;
717
- --primary-foreground: 210 40% 98%;
718
- --secondary: 210 40% 96.1%;
719
- --secondary-foreground: 222.2 47.4% 11.2%;
720
- --muted: 210 40% 96.1%;
721
- --muted-foreground: 215.4 16.3% 46.9%;
722
- --accent: 210 40% 96.1%;
723
- --accent-foreground: 222.2 47.4% 11.2%;
724
- --destructive: 0 84.2% 60.2%;
725
- --destructive-foreground: 210 40% 98%;
726
- --border: 214.3 31.8% 91.4%;
727
- --input: 214.3 31.8% 91.4%;
728
- --ring: 222.2 84% 4.9%;
729
- --radius: 0.5rem;
730
- --chart-1: 12 76% 61%;
731
- --chart-2: 173 58% 39%;
732
- --chart-3: 197 37% 24%;
733
- --chart-4: 43 74% 66%;
734
- --chart-5: 27 87% 67%;
735
- }
772
+ files["src/App.tsx"] = appTsx;
773
+ files["src/vite-env.d.ts"] = APP_VITE_ENV_D_TS;
774
+ files["src/index.css"] = buildAppIndexCss(context);
775
+ files["postcss.config.js"] = APP_POSTCSS_CONFIG;
776
+ files["package.json"] = JSON.stringify(buildRoutedAppPackageJson(), null, 2);
777
+ files["tsconfig.json"] = JSON.stringify(APP_TSCONFIG, null, 2);
778
+ return files;
779
+ }
736
780
 
737
- .dark {
738
- --background: 222.2 84% 4.9%;
739
- --foreground: 210 40% 98%;
740
- --card: 222.2 84% 4.9%;
741
- --card-foreground: 210 40% 98%;
742
- --popover: 222.2 84% 4.9%;
743
- --popover-foreground: 210 40% 98%;
744
- --primary: 210 40% 98%;
745
- --primary-foreground: 222.2 47.4% 11.2%;
746
- --secondary: 217.2 32.6% 17.5%;
747
- --secondary-foreground: 210 40% 98%;
748
- --muted: 217.2 32.6% 17.5%;
749
- --muted-foreground: 215 20.2% 65.1%;
750
- --accent: 217.2 32.6% 17.5%;
751
- --accent-foreground: 210 40% 98%;
752
- --destructive: 0 62.8% 30.6%;
753
- --destructive-foreground: 210 40% 98%;
754
- --border: 217.2 32.6% 17.5%;
755
- --input: 217.2 32.6% 17.5%;
756
- --ring: 212.7 26.8% 83.9%;
757
- --chart-1: 220 70% 50%;
758
- --chart-2: 160 60% 45%;
759
- --chart-3: 30 80% 55%;
760
- --chart-4: 280 65% 60%;
761
- --chart-5: 340 75% 55%;
781
+ // src/utils/project-source.ts
782
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
783
+ import { basename, dirname as dirname3, join as join3, relative, resolve } from "path";
784
+ import chalk2 from "chalk";
785
+ function readAppConfig(path, warnings) {
786
+ try {
787
+ return parseSchemaFile(path);
788
+ } catch (error) {
789
+ warnings.push(
790
+ `Failed to parse app config ${path}: ${error instanceof Error ? error.message : error}`
791
+ );
792
+ return null;
762
793
  }
763
794
  }
764
-
765
- @layer base {
766
- * {
767
- @apply border-border;
795
+ function resolveProjectSource(cwd, schemaArg) {
796
+ const warnings = [];
797
+ const absoluteSchemaPath = resolve(cwd, schemaArg);
798
+ if (existsSync3(absoluteSchemaPath)) {
799
+ const argumentStats = statSync2(absoluteSchemaPath);
800
+ if (argumentStats.isFile()) {
801
+ const projectRoot = dirname3(absoluteSchemaPath);
802
+ const pagesDir = join3(projectRoot, "pages");
803
+ if (existsSync3(pagesDir)) {
804
+ return {
805
+ mode: "routes",
806
+ projectRoot,
807
+ pagesDir,
808
+ pagesDirOrigin: "schema-dir",
809
+ routes: scanRoutes(pagesDir),
810
+ appConfig: readAppConfig(absoluteSchemaPath, warnings),
811
+ appConfigPath: absoluteSchemaPath,
812
+ warnings
813
+ };
814
+ }
815
+ } else if (argumentStats.isDirectory()) {
816
+ const fromDirectory = resolveDirectoryArgument(absoluteSchemaPath, warnings);
817
+ if (fromDirectory) return fromDirectory;
818
+ }
768
819
  }
769
- body {
770
- @apply bg-background text-foreground font-sans antialiased min-h-screen;
820
+ const localPagesDir = join3(cwd, "pages");
821
+ if (existsSync3(localPagesDir)) {
822
+ const localAppConfigPath = join3(cwd, "app.json");
823
+ const hasLocalAppConfig = existsSync3(localAppConfigPath);
824
+ return {
825
+ mode: "routes",
826
+ projectRoot: cwd,
827
+ pagesDir: localPagesDir,
828
+ pagesDirOrigin: "cwd",
829
+ routes: scanRoutes(localPagesDir),
830
+ appConfig: hasLocalAppConfig ? readAppConfig(localAppConfigPath, warnings) : null,
831
+ appConfigPath: hasLocalAppConfig ? localAppConfigPath : null,
832
+ warnings
833
+ };
771
834
  }
772
- }`;
773
- writeFileSync(join(srcDir, "index.css"), indexCss);
774
- const cwd = process.cwd();
775
- const isMonorepo = existsSync(join(cwd, "pnpm-workspace.yaml"));
776
- const contentPaths = ["'./index.html'", "'./src/**/*.{js,ts,jsx,tsx,json}'"];
777
- if (isMonorepo) {
778
- const componentsPath = join(cwd, "packages/components/src/**/*.{ts,tsx}");
779
- const pluginsPath = join(cwd, "packages/plugin-*/src/**/*.{ts,tsx}");
780
- contentPaths.push(`'${componentsPath}'`);
781
- contentPaths.push(`'${pluginsPath}'`);
835
+ if (!existsSync3(absoluteSchemaPath)) {
836
+ throw new Error(
837
+ `Schema file not found: ${schemaArg}
838
+ Run 'objectui init' to create a sample schema.`
839
+ );
782
840
  }
783
- const tailwindConfig = `/** @type {import('tailwindcss').Config} */
784
- export default {
785
- darkMode: ['class'],
786
- content: [${contentPaths.join(", ")}],
787
- theme: {
788
- extend: {
789
- borderRadius: {
790
- lg: 'var(--radius)',
791
- md: 'calc(var(--radius) - 2px)',
792
- sm: 'calc(var(--radius) - 4px)',
793
- },
794
- colors: {
795
- background: 'hsl(var(--background))',
796
- foreground: 'hsl(var(--foreground))',
797
- card: {
798
- DEFAULT: 'hsl(var(--card))',
799
- foreground: 'hsl(var(--card-foreground))',
800
- },
801
- popover: {
802
- DEFAULT: 'hsl(var(--popover))',
803
- foreground: 'hsl(var(--popover-foreground))',
804
- },
805
- primary: {
806
- DEFAULT: 'hsl(var(--primary))',
807
- foreground: 'hsl(var(--primary-foreground))',
808
- },
809
- secondary: {
810
- DEFAULT: 'hsl(var(--secondary))',
811
- foreground: 'hsl(var(--secondary-foreground))',
812
- },
813
- muted: {
814
- DEFAULT: 'hsl(var(--muted))',
815
- foreground: 'hsl(var(--muted-foreground))',
816
- },
817
- accent: {
818
- DEFAULT: 'hsl(var(--accent))',
819
- foreground: 'hsl(var(--accent-foreground))',
820
- },
821
- destructive: {
822
- DEFAULT: 'hsl(var(--destructive))',
823
- foreground: 'hsl(var(--destructive-foreground))',
824
- },
825
- border: 'hsl(var(--border))',
826
- input: 'hsl(var(--input))',
827
- ring: 'hsl(var(--ring))',
828
- chart: {
829
- 1: 'hsl(var(--chart-1))',
830
- 2: 'hsl(var(--chart-2))',
831
- 3: 'hsl(var(--chart-3))',
832
- 4: 'hsl(var(--chart-4))',
833
- 5: 'hsl(var(--chart-5))',
834
- },
835
- },
836
- },
837
- },
838
- plugins: [],
839
- };`;
840
- writeFileSync(join(tmpDir, "tailwind.config.js"), tailwindConfig);
841
- const postcssConfig = `export default {
842
- plugins: {
843
- tailwindcss: {},
844
- autoprefixer: {},
845
- },
846
- };`;
847
- writeFileSync(join(tmpDir, "postcss.config.js"), postcssConfig);
848
- const packageJson = {
849
- name: "objectui-temp-app",
850
- private: true,
851
- type: "module",
852
- dependencies: {
853
- react: "^18.3.1",
854
- "react-dom": "^18.3.1",
855
- "react-router-dom": "^7.12.0",
856
- "@object-ui/react": "^0.1.0",
857
- "@object-ui/components": "^0.1.0"
858
- },
859
- devDependencies: {
860
- "@types/react": "^18.3.12",
861
- "@types/react-dom": "^18.3.1",
862
- "@vitejs/plugin-react": "^4.2.1",
863
- autoprefixer: "^10.4.23",
864
- postcss: "^8.5.6",
865
- tailwindcss: "^3.4.19",
866
- typescript: "~5.7.3",
867
- vite: "^5.0.0"
868
- }
841
+ if (statSync2(absoluteSchemaPath).isDirectory()) {
842
+ throw new Error(
843
+ `Not a project directory: ${schemaArg}
844
+ A directory argument must be a 'pages' directory, or contain one, for file-system routing \u2014 ${absoluteSchemaPath} is neither.
845
+ Pass a JSON/YAML schema file instead, or run 'objectui init' to create a project.`
846
+ );
847
+ }
848
+ let schema;
849
+ try {
850
+ schema = parseSchemaFile(absoluteSchemaPath);
851
+ } catch (error) {
852
+ throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
853
+ }
854
+ return {
855
+ mode: "schema",
856
+ projectRoot: dirname3(absoluteSchemaPath),
857
+ schemaFile: absoluteSchemaPath,
858
+ schema,
859
+ warnings
869
860
  };
870
- writeFileSync(join(tmpDir, "package.json"), JSON.stringify(packageJson, null, 2));
871
- const tsconfig = {
872
- compilerOptions: {
873
- target: "ES2020",
874
- useDefineForClassFields: true,
875
- lib: ["ES2020", "DOM", "DOM.Iterable"],
876
- module: "ESNext",
877
- skipLibCheck: true,
878
- moduleResolution: "bundler",
879
- allowImportingTsExtensions: true,
880
- resolveJsonModule: true,
881
- isolatedModules: true,
882
- noEmit: true,
883
- jsx: "react-jsx",
884
- strict: true,
885
- noUnusedLocals: true,
886
- noUnusedParameters: true,
887
- noFallthroughCasesInSwitch: true
888
- },
889
- include: ["src"]
861
+ }
862
+ function resolveDirectoryArgument(argumentDir, warnings) {
863
+ const isPagesDir = basename(argumentDir) === "pages";
864
+ const pagesDir = isPagesDir ? argumentDir : join3(argumentDir, "pages");
865
+ if (!isPagesDir && !(existsSync3(pagesDir) && statSync2(pagesDir).isDirectory())) {
866
+ return null;
867
+ }
868
+ const projectRoot = isPagesDir ? dirname3(argumentDir) : argumentDir;
869
+ const appConfigPath = join3(projectRoot, "app.json");
870
+ const hasAppConfig = existsSync3(appConfigPath) && statSync2(appConfigPath).isFile();
871
+ return {
872
+ mode: "routes",
873
+ projectRoot,
874
+ pagesDir,
875
+ pagesDirOrigin: "argument-dir",
876
+ routes: scanRoutes(pagesDir),
877
+ appConfig: hasAppConfig ? readAppConfig(appConfigPath, warnings) : null,
878
+ appConfigPath: hasAppConfig ? appConfigPath : null,
879
+ warnings
880
+ };
881
+ }
882
+ function scanRoutes(pagesDir) {
883
+ const routes = scanPagesDirectory(pagesDir);
884
+ if (routes.length === 0) {
885
+ throw new Error(`No schema files found in ${pagesDir}`);
886
+ }
887
+ return routes;
888
+ }
889
+ function reportProjectSource(source, cwd) {
890
+ for (const warning of source.warnings) {
891
+ console.warn(chalk2.yellow(`\u26A0 ${warning}`));
892
+ }
893
+ if (source.mode === "schema") {
894
+ console.log(chalk2.blue("\u{1F4CB} Loading schema:"), chalk2.cyan(relative(cwd, source.schemaFile) || source.schemaFile));
895
+ return;
896
+ }
897
+ console.log(chalk2.blue(`\u{1F4C2} Detected project structure at ${source.projectRoot}`));
898
+ if (source.appConfigPath && source.appConfig) {
899
+ console.log(chalk2.blue(`\u2699\uFE0F Loaded App Config from ${relative(cwd, source.appConfigPath) || source.appConfigPath}`));
900
+ }
901
+ console.log(chalk2.blue(`\u{1F4C1} Using file-system routing from ${source.pagesDir}`));
902
+ console.log(chalk2.green(`\u2713 Found ${source.routes.length} route(s)`));
903
+ for (const route of source.routes) {
904
+ console.log(chalk2.dim(` ${route.path} \u2192 ${relative(cwd, route.filePath)}`));
905
+ }
906
+ }
907
+
908
+ // src/utils/workspace-vite.ts
909
+ import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync3, unlinkSync } from "fs";
910
+ import { createRequire } from "module";
911
+ import { dirname as dirname4, join as join4 } from "path";
912
+ import { globSync } from "glob";
913
+ import { load as loadYaml } from "js-yaml";
914
+ var PLATFORM_SCOPE = "@object-ui/";
915
+ var BARREL_EXTENSIONS = [".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx"];
916
+ function isWorkspaceRoot(cwd) {
917
+ return existsSync4(join4(cwd, "pnpm-workspace.yaml"));
918
+ }
919
+ function workspacePatterns(cwd) {
920
+ const manifestPath = join4(cwd, "pnpm-workspace.yaml");
921
+ if (!existsSync4(manifestPath)) return [];
922
+ const parsed = loadYaml(readFileSync3(manifestPath, "utf-8"));
923
+ const patterns = parsed?.packages;
924
+ if (!Array.isArray(patterns)) return [];
925
+ return patterns.filter((p) => typeof p === "string");
926
+ }
927
+ function workspacePackageDirs(cwd) {
928
+ const patterns = workspacePatterns(cwd);
929
+ const include = patterns.filter((p) => !p.startsWith("!"));
930
+ const exclude = patterns.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
931
+ if (include.length === 0) return [];
932
+ const matches = globSync(include, {
933
+ cwd,
934
+ absolute: true,
935
+ ignore: ["**/node_modules/**", ...exclude]
936
+ });
937
+ return matches.filter((dir) => existsSync4(join4(dir, "package.json")) && statSync3(dir).isDirectory()).sort();
938
+ }
939
+ function findSourceBarrel(srcDir) {
940
+ for (const ext of BARREL_EXTENSIONS) {
941
+ const candidate = join4(srcDir, `index${ext}`);
942
+ if (existsSync4(candidate)) return candidate;
943
+ }
944
+ return void 0;
945
+ }
946
+ function workspaceSourceAliases(cwd) {
947
+ const aliases = {};
948
+ for (const dir of workspacePackageDirs(cwd)) {
949
+ let name;
950
+ try {
951
+ name = JSON.parse(readFileSync3(join4(dir, "package.json"), "utf-8")).name;
952
+ } catch {
953
+ continue;
954
+ }
955
+ if (typeof name !== "string" || !name.startsWith(PLATFORM_SCOPE)) continue;
956
+ const srcDir = join4(dir, "src");
957
+ if (!existsSync4(srcDir) || !findSourceBarrel(srcDir)) continue;
958
+ aliases[name] = srcDir;
959
+ }
960
+ return aliases;
961
+ }
962
+ function resolveLucideAlias(cwd) {
963
+ const require2 = createRequire(join4(cwd, "package.json"));
964
+ try {
965
+ return dirname4(require2.resolve("lucide-react/package.json", { paths: [join4(cwd, "packages/components")] }));
966
+ } catch {
967
+ return void 0;
968
+ }
969
+ }
970
+ async function loadTempAppPostcssPlugins() {
971
+ try {
972
+ const [tailwindPostcss, autoprefixer] = await Promise.all([
973
+ import("@tailwindcss/postcss"),
974
+ import("autoprefixer")
975
+ ]);
976
+ return [tailwindPostcss.default(), autoprefixer.default()];
977
+ } catch (error) {
978
+ throw new Error(
979
+ `Failed to load the Tailwind CSS PostCSS pipeline: ${error instanceof Error ? error.message : error}
980
+ Both '@tailwindcss/postcss' and 'autoprefixer' are dependencies of @object-ui/cli \u2014 a
981
+ broken install of the CLI is the likeliest cause; reinstall it and try again.
982
+ (Refusing to start unstyled: that failure is silent in the browser.)`
983
+ );
984
+ }
985
+ }
986
+ async function prepareWorkspaceTempApp(cwd, tmpDir) {
987
+ const postcssPath = join4(tmpDir, "postcss.config.js");
988
+ if (existsSync4(postcssPath)) {
989
+ unlinkSync(postcssPath);
990
+ }
991
+ const alias = { ...workspaceSourceAliases(cwd) };
992
+ const lucide = resolveLucideAlias(cwd);
993
+ if (lucide) {
994
+ alias["lucide-react"] = lucide;
995
+ }
996
+ return {
997
+ resolve: { alias },
998
+ css: { postcss: { plugins: await loadTempAppPostcssPlugins() } }
890
999
  };
891
- writeFileSync(join(tmpDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
892
1000
  }
893
1001
 
894
1002
  // src/commands/serve.ts
1003
+ function resolveServeOpenOption(open) {
1004
+ return open !== false;
1005
+ }
1006
+ var BROWSER_OPEN_SPAWN_ENOENT = /^Error: spawn (\S+) ENOENT/;
1007
+ function createServeLogger(base) {
1008
+ return {
1009
+ info: (msg, opts) => base.info(msg, opts),
1010
+ warn: (msg, opts) => base.warn(msg, opts),
1011
+ warnOnce: (msg, opts) => base.warnOnce(msg, opts),
1012
+ error(msg, opts) {
1013
+ const match = BROWSER_OPEN_SPAWN_ENOENT.exec(msg);
1014
+ if (match) {
1015
+ const [, command] = match;
1016
+ console.log(
1017
+ chalk3.dim(
1018
+ ` (could not open the browser automatically \u2014 '${command}' is not available in this environment; open the URL above manually)`
1019
+ )
1020
+ );
1021
+ return;
1022
+ }
1023
+ base.error(msg, opts);
1024
+ },
1025
+ clearScreen: (type) => base.clearScreen(type),
1026
+ hasErrorLogged: (error) => base.hasErrorLogged(error),
1027
+ get hasWarned() {
1028
+ return base.hasWarned;
1029
+ }
1030
+ };
1031
+ }
895
1032
  async function serve(schemaPath, options) {
896
1033
  const cwd = process.cwd();
897
- const pagesDir = join2(cwd, "pages");
898
- const hasPagesDir = existsSync2(pagesDir);
899
- let routes = [];
900
- let schema = null;
901
- let useFileSystemRouting = false;
902
- if (hasPagesDir) {
903
- console.log(chalk2.blue("\u{1F4C1} Detected pages/ directory - using file-system routing"));
904
- routes = scanPagesDirectory(pagesDir);
905
- useFileSystemRouting = true;
906
- if (routes.length === 0) {
907
- throw new Error("No schema files found in pages/ directory");
908
- }
909
- console.log(chalk2.green(`\u2713 Found ${routes.length} route(s)`));
910
- routes.forEach((route) => {
911
- console.log(chalk2.dim(` ${route.path} \u2192 ${relative(cwd, route.filePath)}`));
912
- });
1034
+ const source = resolveProjectSource(cwd, schemaPath);
1035
+ reportProjectSource(source, cwd);
1036
+ const tmpDir = join5(cwd, ".objectui-tmp");
1037
+ mkdirSync2(tmpDir, { recursive: true });
1038
+ if (source.mode === "routes") {
1039
+ createTempAppWithRouting(tmpDir, source.routes, source.appConfig);
913
1040
  } else {
914
- const fullSchemaPath = resolve(cwd, schemaPath);
915
- if (!existsSync2(fullSchemaPath)) {
916
- throw new Error(`Schema file not found: ${schemaPath}
917
- Run 'objectui init' to create a sample schema.`);
918
- }
919
- console.log(chalk2.blue("\u{1F4CB} Loading schema:"), chalk2.cyan(schemaPath));
920
- try {
921
- schema = parseSchemaFile(fullSchemaPath);
922
- } catch (error) {
923
- throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
924
- }
1041
+ createTempApp(tmpDir, source.schema);
925
1042
  }
926
- const tmpDir = join2(cwd, ".objectui-tmp");
927
- mkdirSync2(tmpDir, { recursive: true });
928
- if (useFileSystemRouting) {
929
- createTempAppWithRouting(tmpDir, routes);
1043
+ const isMonorepo = isWorkspaceRoot(cwd);
1044
+ if (isMonorepo) {
1045
+ console.log(chalk3.blue("\u{1F4E6} Detected monorepo - using root node_modules"));
930
1046
  } else {
931
- createTempApp(tmpDir, schema);
1047
+ console.log(chalk3.blue("\u{1F4E6} Installing dependencies..."));
1048
+ console.log(chalk3.dim(" This may take a moment on first run..."));
1049
+ try {
1050
+ execSync("npm install --silent --prefer-offline", {
1051
+ cwd: tmpDir,
1052
+ stdio: "inherit"
1053
+ });
1054
+ console.log(chalk3.green("\u2713 Dependencies installed"));
1055
+ } catch {
1056
+ throw new Error("Failed to install dependencies. Please check your internet connection and try again.");
1057
+ }
932
1058
  }
933
- console.log(chalk2.blue("\u{1F4E6} Installing dependencies..."));
934
- console.log(chalk2.dim(" This may take a moment on first run..."));
935
- try {
936
- execSync("npm install --silent --prefer-offline", {
937
- cwd: tmpDir,
938
- stdio: "inherit"
939
- });
940
- console.log(chalk2.green("\u2713 Dependencies installed"));
941
- } catch {
942
- throw new Error("Failed to install dependencies. Please check your internet connection and try again.");
1059
+ console.log(chalk3.green("\u2713 Schema loaded successfully"));
1060
+ console.log(chalk3.blue("\u{1F680} Starting development server...\n"));
1061
+ if (isMonorepo) {
1062
+ console.log(chalk3.blue("\u{1F4E6} Detected monorepo - configuring workspace aliases"));
943
1063
  }
944
- console.log(chalk2.green("\u2713 Schema loaded successfully"));
945
- console.log(chalk2.blue("\u{1F680} Starting development server...\n"));
1064
+ const workspaceConfig = isMonorepo ? await prepareWorkspaceTempApp(cwd, tmpDir) : {};
946
1065
  const viteConfig = {
947
1066
  root: tmpDir,
948
1067
  server: {
949
1068
  port: parseInt(options.port),
950
1069
  host: options.host,
951
- open: true
1070
+ open: resolveServeOpenOption(options.open),
1071
+ fs: {
1072
+ // Allow serving the workspace sources the aliases point at
1073
+ allow: [cwd]
1074
+ }
952
1075
  },
953
- plugins: [react()]
1076
+ plugins: [react()],
1077
+ customLogger: createServeLogger(createLogger()),
1078
+ ...workspaceConfig
954
1079
  };
955
1080
  const server = await createServer(viteConfig);
956
1081
  await server.listen();
@@ -958,18 +1083,18 @@ Run 'objectui init' to create a sample schema.`);
958
1083
  const protocol = server.config.server.https ? "https" : "http";
959
1084
  const displayHost = host === "0.0.0.0" ? "localhost" : host;
960
1085
  console.log();
961
- console.log(chalk2.green("\u2713 Server started successfully!"));
1086
+ console.log(chalk3.green("\u2713 Server started successfully!"));
962
1087
  console.log();
963
- console.log(chalk2.bold(" Local: ") + chalk2.cyan(`${protocol}://${displayHost}:${port}`));
1088
+ console.log(chalk3.bold(" Local: ") + chalk3.cyan(`${protocol}://${displayHost}:${port}`));
964
1089
  console.log();
965
- console.log(chalk2.dim(" Press Ctrl+C to stop the server"));
1090
+ console.log(chalk3.dim(" Press Ctrl+C to stop the server"));
966
1091
  console.log();
967
1092
  }
968
1093
 
969
1094
  // src/commands/init.ts
970
- import { existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
971
- import { join as join3 } from "path";
972
- import chalk3 from "chalk";
1095
+ import { existsSync as existsSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
1096
+ import { dirname as dirname5, join as join6 } from "path";
1097
+ import chalk4 from "chalk";
973
1098
  var templates = {
974
1099
  simple: {
975
1100
  type: "div",
@@ -1312,29 +1437,35 @@ var templates = {
1312
1437
  ]
1313
1438
  }
1314
1439
  };
1315
- async function init(name, options) {
1316
- const cwd = process.cwd();
1317
- const projectDir = join3(cwd, name);
1318
- if (existsSync3(projectDir) && name !== ".") {
1319
- throw new Error(`Directory "${name}" already exists. Please choose a different name.`);
1320
- }
1321
- const targetDir = name === "." ? cwd : projectDir;
1322
- if (name !== ".") {
1323
- mkdirSync3(projectDir, { recursive: true });
1324
- }
1325
- console.log(chalk3.blue("\u{1F3A8} Creating Object UI application..."));
1326
- console.log(chalk3.dim(` Template: ${options.template}`));
1327
- console.log();
1328
- const template = templates[options.template];
1440
+ function buildInitPackageJson(name) {
1441
+ const platformRange = platformPackageRange();
1442
+ return {
1443
+ name,
1444
+ version: "0.1.0",
1445
+ private: true,
1446
+ type: "module",
1447
+ scripts: {
1448
+ dev: "vite",
1449
+ build: "tsc && vite build",
1450
+ preview: "vite preview"
1451
+ },
1452
+ dependencies: {
1453
+ "@object-ui/components": platformRange,
1454
+ "@object-ui/react": platformRange,
1455
+ react: REACT_RANGE,
1456
+ "react-dom": REACT_RANGE
1457
+ },
1458
+ devDependencies: { ...SCAFFOLD_DEV_DEPENDENCIES }
1459
+ };
1460
+ }
1461
+ function buildInitFiles(name, templateName) {
1462
+ const template = templates[templateName];
1329
1463
  if (!template) {
1330
1464
  throw new Error(
1331
- `Unknown template: ${options.template}
1465
+ `Unknown template: ${templateName}
1332
1466
  Available templates: ${Object.keys(templates).join(", ")}`
1333
1467
  );
1334
1468
  }
1335
- const schemaPath = join3(targetDir, "app.json");
1336
- writeFileSync2(schemaPath, JSON.stringify(template, null, 2));
1337
- console.log(chalk3.green("\u2713 Created app.json"));
1338
1469
  const readme = `# ${name}
1339
1470
 
1340
1471
  An Object UI application built from JSON schemas.
@@ -1377,46 +1508,12 @@ Edit \`app.json\` to customize your application. The dev server will automatical
1377
1508
 
1378
1509
  Built with \u2764\uFE0F using [Object UI](https://www.objectui.org)
1379
1510
  `;
1380
- writeFileSync2(join3(targetDir, "README.md"), readme);
1381
- console.log(chalk3.green("\u2713 Created README.md"));
1382
1511
  const gitignore = `.objectui-tmp
1383
1512
  node_modules
1384
1513
  dist
1385
1514
  .DS_Store
1386
1515
  *.log
1387
1516
  `;
1388
- writeFileSync2(join3(targetDir, ".gitignore"), gitignore);
1389
- console.log(chalk3.green("\u2713 Created .gitignore"));
1390
- const packageJson = {
1391
- name,
1392
- version: "0.1.0",
1393
- private: true,
1394
- type: "module",
1395
- scripts: {
1396
- dev: "vite",
1397
- build: "tsc && vite build",
1398
- preview: "vite preview"
1399
- },
1400
- dependencies: {
1401
- "@object-ui/components": "^2.0.0",
1402
- "@object-ui/react": "^2.0.0",
1403
- react: "^19.2.0",
1404
- "react-dom": "^19.2.0"
1405
- },
1406
- devDependencies: {
1407
- "@tailwindcss/postcss": "^4.1.18",
1408
- "@types/react": "^19.2.13",
1409
- "@types/react-dom": "^19.2.6",
1410
- "@vitejs/plugin-react": "^5.1.3",
1411
- autoprefixer: "^10.4.23",
1412
- postcss: "^8.5.6",
1413
- tailwindcss: "^4.1.18",
1414
- typescript: "^5.9.3",
1415
- vite: "^7.3.1"
1416
- }
1417
- };
1418
- writeFileSync2(join3(targetDir, "package.json"), JSON.stringify(packageJson, null, 2));
1419
- console.log(chalk3.green("\u2713 Created package.json"));
1420
1517
  const viteConfig = `import { defineConfig } from 'vite';
1421
1518
  import react from '@vitejs/plugin-react';
1422
1519
 
@@ -1427,15 +1524,6 @@ export default defineConfig({
1427
1524
  },
1428
1525
  });
1429
1526
  `;
1430
- writeFileSync2(join3(targetDir, "vite.config.ts"), viteConfig);
1431
- console.log(chalk3.green("\u2713 Created vite.config.ts"));
1432
- const tailwindConfig = `/** @type {import('tailwindcss').Config} */
1433
- export default {
1434
- content: ['./index.html', './src/**/*.{ts,tsx}'],
1435
- };
1436
- `;
1437
- writeFileSync2(join3(targetDir, "tailwind.config.js"), tailwindConfig);
1438
- console.log(chalk3.green("\u2713 Created tailwind.config.js"));
1439
1527
  const postcssConfig = `export default {
1440
1528
  plugins: {
1441
1529
  '@tailwindcss/postcss': {},
@@ -1443,8 +1531,6 @@ export default {
1443
1531
  },
1444
1532
  };
1445
1533
  `;
1446
- writeFileSync2(join3(targetDir, "postcss.config.js"), postcssConfig);
1447
- console.log(chalk3.green("\u2713 Created postcss.config.js"));
1448
1534
  const indexHtml = `<!doctype html>
1449
1535
  <html lang="en">
1450
1536
  <head>
@@ -1458,14 +1544,9 @@ export default {
1458
1544
  </body>
1459
1545
  </html>
1460
1546
  `;
1461
- writeFileSync2(join3(targetDir, "index.html"), indexHtml);
1462
- console.log(chalk3.green("\u2713 Created index.html"));
1463
- const srcDir = join3(targetDir, "src");
1464
- mkdirSync3(srcDir, { recursive: true });
1465
1547
  const indexCss = `@import 'tailwindcss';
1548
+ @import '@object-ui/components/style.css';
1466
1549
  `;
1467
- writeFileSync2(join3(srcDir, "index.css"), indexCss);
1468
- console.log(chalk3.green("\u2713 Created src/index.css"));
1469
1550
  const mainTsx = `import { StrictMode } from 'react';
1470
1551
  import { createRoot } from 'react-dom/client';
1471
1552
  import App from './App';
@@ -1477,17 +1558,18 @@ createRoot(document.getElementById('root')!).render(
1477
1558
  </StrictMode>,
1478
1559
  );
1479
1560
  `;
1480
- writeFileSync2(join3(srcDir, "main.tsx"), mainTsx);
1481
- console.log(chalk3.green("\u2713 Created src/main.tsx"));
1482
1561
  const appTsx = `import { SchemaRenderer } from '@object-ui/react';
1562
+ // Registers the component renderers SchemaRenderer looks up. Side-effect
1563
+ // import \u2014 removing it makes every node render "Unknown component type".
1564
+ import '@object-ui/components';
1483
1565
  import schema from '../app.json';
1484
1566
 
1485
1567
  export default function App() {
1486
1568
  return <SchemaRenderer schema={schema} />;
1487
1569
  }
1488
1570
  `;
1489
- writeFileSync2(join3(srcDir, "App.tsx"), appTsx);
1490
- console.log(chalk3.green("\u2713 Created src/App.tsx"));
1571
+ const viteEnvDts = `/// <reference types="vite/client" />
1572
+ `;
1491
1573
  const tsconfig = {
1492
1574
  compilerOptions: {
1493
1575
  target: "ES2020",
@@ -1509,29 +1591,65 @@ export default function App() {
1509
1591
  },
1510
1592
  include: ["src"]
1511
1593
  };
1512
- writeFileSync2(join3(targetDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
1513
- console.log(chalk3.green("\u2713 Created tsconfig.json"));
1594
+ return {
1595
+ "app.json": JSON.stringify(template, null, 2),
1596
+ "README.md": readme,
1597
+ ".gitignore": gitignore,
1598
+ "package.json": JSON.stringify(buildInitPackageJson(name), null, 2),
1599
+ "vite.config.ts": viteConfig,
1600
+ "postcss.config.js": postcssConfig,
1601
+ "index.html": indexHtml,
1602
+ "src/index.css": indexCss,
1603
+ "src/main.tsx": mainTsx,
1604
+ "src/App.tsx": appTsx,
1605
+ "src/vite-env.d.ts": viteEnvDts,
1606
+ "tsconfig.json": JSON.stringify(tsconfig, null, 2)
1607
+ };
1608
+ }
1609
+ async function init(name, options) {
1610
+ const cwd = process.cwd();
1611
+ const projectDir = join6(cwd, name);
1612
+ if (existsSync5(projectDir) && name !== ".") {
1613
+ throw new Error(`Directory "${name}" already exists. Please choose a different name.`);
1614
+ }
1615
+ const targetDir = name === "." ? cwd : projectDir;
1616
+ if (name !== ".") {
1617
+ mkdirSync3(projectDir, { recursive: true });
1618
+ }
1619
+ console.log(chalk4.blue("\u{1F3A8} Creating Object UI application..."));
1620
+ console.log(chalk4.dim(` Template: ${options.template}`));
1621
+ console.log();
1622
+ for (const [relativePath, contents] of Object.entries(
1623
+ buildInitFiles(name, options.template)
1624
+ )) {
1625
+ const target = join6(targetDir, relativePath);
1626
+ mkdirSync3(dirname5(target), { recursive: true });
1627
+ writeFileSync2(target, contents);
1628
+ console.log(chalk4.green(`\u2713 Created ${relativePath}`));
1629
+ }
1514
1630
  console.log();
1515
- console.log(chalk3.green("\u2728 Application created successfully!"));
1631
+ console.log(chalk4.green("\u2728 Application created successfully!"));
1516
1632
  console.log();
1517
- console.log(chalk3.bold("Next steps:"));
1633
+ console.log(chalk4.bold("Next steps:"));
1518
1634
  console.log();
1519
1635
  if (name !== ".") {
1520
- console.log(chalk3.cyan(` cd ${name}`));
1636
+ console.log(chalk4.cyan(` cd ${name}`));
1521
1637
  }
1522
- console.log(chalk3.cyan(" npm install"));
1523
- console.log(chalk3.cyan(" npm run dev"));
1638
+ console.log(chalk4.cyan(" npm install"));
1639
+ console.log(chalk4.cyan(" npm run dev"));
1524
1640
  console.log();
1525
- console.log(chalk3.dim(" The development server will start on http://localhost:3000"));
1641
+ console.log(chalk4.dim(" The development server will start on http://localhost:3000"));
1526
1642
  console.log();
1527
1643
  }
1528
1644
 
1529
1645
  export {
1530
- parseSchemaFile,
1531
- scanPagesDirectory,
1532
1646
  createTempApp,
1533
1647
  createTempAppWithRouting,
1648
+ resolveProjectSource,
1649
+ reportProjectSource,
1650
+ isWorkspaceRoot,
1651
+ prepareWorkspaceTempApp,
1534
1652
  serve,
1535
1653
  init
1536
1654
  };
1537
- //# sourceMappingURL=chunk-EV2DCI7W.js.map
1655
+ //# sourceMappingURL=chunk-AP2INOE7.js.map