@object-ui/cli 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/chunk-WOV6EOMH.js +1272 -0
- package/dist/chunk-WOV6EOMH.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +708 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,1272 @@
|
|
|
1
|
+
// src/commands/serve.ts
|
|
2
|
+
import { createServer } from "vite";
|
|
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";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
|
|
9
|
+
// src/utils/app-generator.ts
|
|
10
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, existsSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import * as yaml from "js-yaml";
|
|
14
|
+
function isSupportedSchemaFile(filename) {
|
|
15
|
+
return filename.endsWith(".json") || filename.endsWith(".yml") || filename.endsWith(".yaml");
|
|
16
|
+
}
|
|
17
|
+
function getBaseFileName(filename) {
|
|
18
|
+
return filename.replace(/\.(json|yml|yaml)$/, "");
|
|
19
|
+
}
|
|
20
|
+
function parseSchemaFile(filePath) {
|
|
21
|
+
const content = readFileSync(filePath, "utf-8");
|
|
22
|
+
if (filePath.endsWith(".json")) {
|
|
23
|
+
return JSON.parse(content);
|
|
24
|
+
} else if (filePath.endsWith(".yml") || filePath.endsWith(".yaml")) {
|
|
25
|
+
return yaml.load(content);
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Unsupported file format: ${filePath}`);
|
|
28
|
+
}
|
|
29
|
+
function scanPagesDirectory(pagesDir) {
|
|
30
|
+
const routes = [];
|
|
31
|
+
const scanDir = (dir, routePrefix = "") => {
|
|
32
|
+
const entries = readdirSync(dir);
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const fullPath = join(dir, entry);
|
|
35
|
+
const stat = statSync(fullPath);
|
|
36
|
+
if (stat.isDirectory()) {
|
|
37
|
+
const newPrefix = routePrefix + "/" + entry;
|
|
38
|
+
scanDir(fullPath, newPrefix);
|
|
39
|
+
} else if (isSupportedSchemaFile(entry)) {
|
|
40
|
+
const fileName = getBaseFileName(entry);
|
|
41
|
+
let routePath;
|
|
42
|
+
let isDynamic = false;
|
|
43
|
+
let paramName;
|
|
44
|
+
if (fileName === "index") {
|
|
45
|
+
routePath = routePrefix || "/";
|
|
46
|
+
} else if (fileName.startsWith("[") && fileName.endsWith("]")) {
|
|
47
|
+
paramName = fileName.slice(1, -1);
|
|
48
|
+
routePath = routePrefix + "/:" + paramName;
|
|
49
|
+
isDynamic = true;
|
|
50
|
+
} else {
|
|
51
|
+
routePath = routePrefix + "/" + fileName;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const schema = parseSchemaFile(fullPath);
|
|
55
|
+
routes.push({
|
|
56
|
+
path: routePath,
|
|
57
|
+
filePath: fullPath,
|
|
58
|
+
schema,
|
|
59
|
+
isDynamic,
|
|
60
|
+
paramName
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn(chalk.yellow(`\u26A0 Warning: Failed to parse ${fullPath}: ${error instanceof Error ? error.message : error}`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
scanDir(pagesDir);
|
|
69
|
+
routes.sort((a, b) => {
|
|
70
|
+
if (a.isDynamic && !b.isDynamic) return 1;
|
|
71
|
+
if (!a.isDynamic && b.isDynamic) return -1;
|
|
72
|
+
return a.path.localeCompare(b.path);
|
|
73
|
+
});
|
|
74
|
+
return routes;
|
|
75
|
+
}
|
|
76
|
+
function createTempApp(tmpDir, schema) {
|
|
77
|
+
const html = `<!DOCTYPE html>
|
|
78
|
+
<html lang="en">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="UTF-8" />
|
|
81
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
82
|
+
<title>Object UI App</title>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<div id="root"></div>
|
|
86
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
87
|
+
</body>
|
|
88
|
+
</html>`;
|
|
89
|
+
writeFileSync(join(tmpDir, "index.html"), html);
|
|
90
|
+
const srcDir = join(tmpDir, "src");
|
|
91
|
+
mkdirSync(srcDir, { recursive: true });
|
|
92
|
+
const mainTsx = `import React from 'react';
|
|
93
|
+
import ReactDOM from 'react-dom/client';
|
|
94
|
+
import App from './App';
|
|
95
|
+
import './index.css';
|
|
96
|
+
|
|
97
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
98
|
+
<React.StrictMode>
|
|
99
|
+
<App />
|
|
100
|
+
</React.StrictMode>
|
|
101
|
+
);`;
|
|
102
|
+
writeFileSync(join(srcDir, "main.tsx"), mainTsx);
|
|
103
|
+
const appTsx = `import { SchemaRenderer } from '@object-ui/react';
|
|
104
|
+
import '@object-ui/components';
|
|
105
|
+
|
|
106
|
+
const schema = ${JSON.stringify(schema, null, 2)};
|
|
107
|
+
|
|
108
|
+
function App() {
|
|
109
|
+
return <SchemaRenderer schema={schema} />;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default App;`;
|
|
113
|
+
writeFileSync(join(srcDir, "App.tsx"), appTsx);
|
|
114
|
+
const indexCss = `@tailwind base;
|
|
115
|
+
@tailwind components;
|
|
116
|
+
@tailwind utilities;
|
|
117
|
+
|
|
118
|
+
@layer base {
|
|
119
|
+
:root {
|
|
120
|
+
--background: 0 0% 100%;
|
|
121
|
+
--foreground: 222.2 84% 4.9%;
|
|
122
|
+
--card: 0 0% 100%;
|
|
123
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
124
|
+
--popover: 0 0% 100%;
|
|
125
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
126
|
+
--primary: 222.2 47.4% 11.2%;
|
|
127
|
+
--primary-foreground: 210 40% 98%;
|
|
128
|
+
--secondary: 210 40% 96.1%;
|
|
129
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
130
|
+
--muted: 210 40% 96.1%;
|
|
131
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
132
|
+
--accent: 210 40% 96.1%;
|
|
133
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
134
|
+
--destructive: 0 84.2% 60.2%;
|
|
135
|
+
--destructive-foreground: 210 40% 98%;
|
|
136
|
+
--border: 214.3 31.8% 91.4%;
|
|
137
|
+
--input: 214.3 31.8% 91.4%;
|
|
138
|
+
--ring: 222.2 84% 4.9%;
|
|
139
|
+
--radius: 0.5rem;
|
|
140
|
+
--chart-1: 12 76% 61%;
|
|
141
|
+
--chart-2: 173 58% 39%;
|
|
142
|
+
--chart-3: 197 37% 24%;
|
|
143
|
+
--chart-4: 43 74% 66%;
|
|
144
|
+
--chart-5: 27 87% 67%;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.dark {
|
|
148
|
+
--background: 222.2 84% 4.9%;
|
|
149
|
+
--foreground: 210 40% 98%;
|
|
150
|
+
--card: 222.2 84% 4.9%;
|
|
151
|
+
--card-foreground: 210 40% 98%;
|
|
152
|
+
--popover: 222.2 84% 4.9%;
|
|
153
|
+
--popover-foreground: 210 40% 98%;
|
|
154
|
+
--primary: 210 40% 98%;
|
|
155
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
156
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
157
|
+
--secondary-foreground: 210 40% 98%;
|
|
158
|
+
--muted: 217.2 32.6% 17.5%;
|
|
159
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
160
|
+
--accent: 217.2 32.6% 17.5%;
|
|
161
|
+
--accent-foreground: 210 40% 98%;
|
|
162
|
+
--destructive: 0 62.8% 30.6%;
|
|
163
|
+
--destructive-foreground: 210 40% 98%;
|
|
164
|
+
--border: 217.2 32.6% 17.5%;
|
|
165
|
+
--input: 217.2 32.6% 17.5%;
|
|
166
|
+
--ring: 212.7 26.8% 83.9%;
|
|
167
|
+
--chart-1: 220 70% 50%;
|
|
168
|
+
--chart-2: 160 60% 45%;
|
|
169
|
+
--chart-3: 30 80% 55%;
|
|
170
|
+
--chart-4: 280 65% 60%;
|
|
171
|
+
--chart-5: 340 75% 55%;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@layer base {
|
|
176
|
+
* {
|
|
177
|
+
@apply border-border;
|
|
178
|
+
}
|
|
179
|
+
body {
|
|
180
|
+
@apply bg-background text-foreground;
|
|
181
|
+
}
|
|
182
|
+
}`;
|
|
183
|
+
writeFileSync(join(srcDir, "index.css"), indexCss);
|
|
184
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
185
|
+
export default {
|
|
186
|
+
darkMode: ['class'],
|
|
187
|
+
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
|
188
|
+
theme: {
|
|
189
|
+
extend: {
|
|
190
|
+
borderRadius: {
|
|
191
|
+
lg: 'var(--radius)',
|
|
192
|
+
md: 'calc(var(--radius) - 2px)',
|
|
193
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
194
|
+
},
|
|
195
|
+
colors: {
|
|
196
|
+
background: 'hsl(var(--background))',
|
|
197
|
+
foreground: 'hsl(var(--foreground))',
|
|
198
|
+
card: {
|
|
199
|
+
DEFAULT: 'hsl(var(--card))',
|
|
200
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
201
|
+
},
|
|
202
|
+
popover: {
|
|
203
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
204
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
205
|
+
},
|
|
206
|
+
primary: {
|
|
207
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
208
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
209
|
+
},
|
|
210
|
+
secondary: {
|
|
211
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
212
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
213
|
+
},
|
|
214
|
+
muted: {
|
|
215
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
216
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
217
|
+
},
|
|
218
|
+
accent: {
|
|
219
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
220
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
221
|
+
},
|
|
222
|
+
destructive: {
|
|
223
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
224
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
225
|
+
},
|
|
226
|
+
border: 'hsl(var(--border))',
|
|
227
|
+
input: 'hsl(var(--input))',
|
|
228
|
+
ring: 'hsl(var(--ring))',
|
|
229
|
+
chart: {
|
|
230
|
+
1: 'hsl(var(--chart-1))',
|
|
231
|
+
2: 'hsl(var(--chart-2))',
|
|
232
|
+
3: 'hsl(var(--chart-3))',
|
|
233
|
+
4: 'hsl(var(--chart-4))',
|
|
234
|
+
5: 'hsl(var(--chart-5))',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
plugins: [],
|
|
240
|
+
};`;
|
|
241
|
+
const cwd = process.cwd();
|
|
242
|
+
const isMonorepo = existsSync(join(cwd, "pnpm-workspace.yaml"));
|
|
243
|
+
const contentPaths = ["'./index.html'", "'./src/**/*.{js,ts,jsx,tsx,json}'"];
|
|
244
|
+
if (isMonorepo) {
|
|
245
|
+
const componentsPath = join(cwd, "packages/components/src/**/*.{ts,tsx}");
|
|
246
|
+
const pluginsPath = join(cwd, "packages/plugin-*/src/**/*.{ts,tsx}");
|
|
247
|
+
contentPaths.push(`'${componentsPath}'`);
|
|
248
|
+
contentPaths.push(`'${pluginsPath}'`);
|
|
249
|
+
}
|
|
250
|
+
const finalTailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
251
|
+
export default {
|
|
252
|
+
darkMode: ['class'],
|
|
253
|
+
content: [${contentPaths.join(", ")}],
|
|
254
|
+
theme: {
|
|
255
|
+
extend: {
|
|
256
|
+
borderRadius: {
|
|
257
|
+
lg: 'var(--radius)',
|
|
258
|
+
md: 'calc(var(--radius) - 2px)',
|
|
259
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
260
|
+
},
|
|
261
|
+
colors: {
|
|
262
|
+
background: 'hsl(var(--background))',
|
|
263
|
+
foreground: 'hsl(var(--foreground))',
|
|
264
|
+
card: {
|
|
265
|
+
DEFAULT: 'hsl(var(--card))',
|
|
266
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
267
|
+
},
|
|
268
|
+
popover: {
|
|
269
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
270
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
271
|
+
},
|
|
272
|
+
primary: {
|
|
273
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
274
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
275
|
+
},
|
|
276
|
+
secondary: {
|
|
277
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
278
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
279
|
+
},
|
|
280
|
+
muted: {
|
|
281
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
282
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
283
|
+
},
|
|
284
|
+
accent: {
|
|
285
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
286
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
287
|
+
},
|
|
288
|
+
destructive: {
|
|
289
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
290
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
291
|
+
},
|
|
292
|
+
border: 'hsl(var(--border))',
|
|
293
|
+
input: 'hsl(var(--input))',
|
|
294
|
+
ring: 'hsl(var(--ring))',
|
|
295
|
+
chart: {
|
|
296
|
+
1: 'hsl(var(--chart-1))',
|
|
297
|
+
2: 'hsl(var(--chart-2))',
|
|
298
|
+
3: 'hsl(var(--chart-3))',
|
|
299
|
+
4: 'hsl(var(--chart-4))',
|
|
300
|
+
5: 'hsl(var(--chart-5))',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
plugins: [],
|
|
306
|
+
};`;
|
|
307
|
+
writeFileSync(join(tmpDir, "tailwind.config.js"), finalTailwindConfig);
|
|
308
|
+
const finalPostcssConfig = `export default {
|
|
309
|
+
plugins: {
|
|
310
|
+
tailwindcss: {},
|
|
311
|
+
autoprefixer: {},
|
|
312
|
+
},
|
|
313
|
+
};`;
|
|
314
|
+
writeFileSync(join(tmpDir, "postcss.config.js"), finalPostcssConfig);
|
|
315
|
+
const baseDependencies = {
|
|
316
|
+
react: "^18.3.1",
|
|
317
|
+
"react-dom": "^18.3.1",
|
|
318
|
+
"@object-ui/react": "^0.1.0",
|
|
319
|
+
"@object-ui/components": "^0.1.0"
|
|
320
|
+
};
|
|
321
|
+
const baseDevDependencies = {
|
|
322
|
+
"@types/react": "^18.3.12",
|
|
323
|
+
"@types/react-dom": "^18.3.1",
|
|
324
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
325
|
+
autoprefixer: "^10.4.23",
|
|
326
|
+
postcss: "^8.5.6",
|
|
327
|
+
tailwindcss: "^3.4.19",
|
|
328
|
+
typescript: "~5.7.3",
|
|
329
|
+
vite: "^5.0.0"
|
|
330
|
+
};
|
|
331
|
+
const packageJson = {
|
|
332
|
+
name: "objectui-temp-app",
|
|
333
|
+
private: true,
|
|
334
|
+
type: "module",
|
|
335
|
+
// In monorepo, we use root node_modules, so we don't need dependencies here
|
|
336
|
+
dependencies: isMonorepo ? {} : baseDependencies,
|
|
337
|
+
devDependencies: isMonorepo ? {} : baseDevDependencies
|
|
338
|
+
};
|
|
339
|
+
writeFileSync(join(tmpDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
340
|
+
const tsconfig = {
|
|
341
|
+
compilerOptions: {
|
|
342
|
+
target: "ES2020",
|
|
343
|
+
useDefineForClassFields: true,
|
|
344
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
345
|
+
module: "ESNext",
|
|
346
|
+
skipLibCheck: true,
|
|
347
|
+
moduleResolution: "bundler",
|
|
348
|
+
allowImportingTsExtensions: true,
|
|
349
|
+
resolveJsonModule: true,
|
|
350
|
+
isolatedModules: true,
|
|
351
|
+
noEmit: true,
|
|
352
|
+
jsx: "react-jsx",
|
|
353
|
+
strict: true,
|
|
354
|
+
noUnusedLocals: true,
|
|
355
|
+
noUnusedParameters: true,
|
|
356
|
+
noFallthroughCasesInSwitch: true
|
|
357
|
+
},
|
|
358
|
+
include: ["src"]
|
|
359
|
+
};
|
|
360
|
+
writeFileSync(join(tmpDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
|
|
361
|
+
}
|
|
362
|
+
function createTempAppWithRouting(tmpDir, routes, appConfig) {
|
|
363
|
+
const html = `<!DOCTYPE html>
|
|
364
|
+
<html lang="en">
|
|
365
|
+
<head>
|
|
366
|
+
<meta charset="UTF-8" />
|
|
367
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
368
|
+
<title>${appConfig?.title || "Object UI App"}</title>
|
|
369
|
+
</head>
|
|
370
|
+
<body>
|
|
371
|
+
<div id="root"></div>
|
|
372
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
373
|
+
</body>
|
|
374
|
+
</html>`;
|
|
375
|
+
writeFileSync(join(tmpDir, "index.html"), html);
|
|
376
|
+
const srcDir = join(tmpDir, "src");
|
|
377
|
+
mkdirSync(srcDir, { recursive: true });
|
|
378
|
+
const schemasDir = join(srcDir, "schemas");
|
|
379
|
+
mkdirSync(schemasDir, { recursive: true });
|
|
380
|
+
const schemaImports = [];
|
|
381
|
+
const routeComponents = [];
|
|
382
|
+
routes.forEach((route, index) => {
|
|
383
|
+
const schemaVarName = `schema${index}`;
|
|
384
|
+
const schemaFileName = `page${index}.json`;
|
|
385
|
+
writeFileSync(
|
|
386
|
+
join(schemasDir, schemaFileName),
|
|
387
|
+
JSON.stringify(route.schema, null, 2)
|
|
388
|
+
);
|
|
389
|
+
schemaImports.push(`import ${schemaVarName} from './schemas/${schemaFileName}';`);
|
|
390
|
+
routeComponents.push(` <Route path="${route.path}" element={<SchemaRenderer schema={${schemaVarName}} />} />`);
|
|
391
|
+
});
|
|
392
|
+
const mainTsx = `import React from 'react';
|
|
393
|
+
import ReactDOM from 'react-dom/client';
|
|
394
|
+
import App from './App';
|
|
395
|
+
import './index.css';
|
|
396
|
+
|
|
397
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
398
|
+
<React.StrictMode>
|
|
399
|
+
<App />
|
|
400
|
+
</React.StrictMode>
|
|
401
|
+
);`;
|
|
402
|
+
writeFileSync(join(srcDir, "main.tsx"), mainTsx);
|
|
403
|
+
let layoutImport = "";
|
|
404
|
+
let layoutWrapperStart = "";
|
|
405
|
+
let layoutWrapperEnd = "";
|
|
406
|
+
if (appConfig) {
|
|
407
|
+
const layoutCode = `
|
|
408
|
+
import { Link, useLocation } from 'react-router-dom';
|
|
409
|
+
import * as LucideIcons from 'lucide-react';
|
|
410
|
+
import {
|
|
411
|
+
cn,
|
|
412
|
+
SidebarProvider,
|
|
413
|
+
Sidebar,
|
|
414
|
+
SidebarContent,
|
|
415
|
+
SidebarFooter,
|
|
416
|
+
SidebarHeader,
|
|
417
|
+
SidebarRail,
|
|
418
|
+
SidebarGroup,
|
|
419
|
+
SidebarGroupContent,
|
|
420
|
+
SidebarGroupLabel,
|
|
421
|
+
SidebarMenu,
|
|
422
|
+
SidebarMenuItem,
|
|
423
|
+
SidebarMenuButton,
|
|
424
|
+
SidebarMenuSub,
|
|
425
|
+
SidebarMenuSubItem,
|
|
426
|
+
SidebarMenuSubButton,
|
|
427
|
+
SidebarInset,
|
|
428
|
+
SidebarTrigger,
|
|
429
|
+
Separator,
|
|
430
|
+
Collapsible,
|
|
431
|
+
CollapsibleTrigger,
|
|
432
|
+
CollapsibleContent
|
|
433
|
+
} from '@object-ui/components';
|
|
434
|
+
|
|
435
|
+
const DynamicIcon = ({ name, className }) => {
|
|
436
|
+
// @ts-ignore
|
|
437
|
+
const Icon = LucideIcons[name];
|
|
438
|
+
if (!Icon) return null;
|
|
439
|
+
return <Icon className={className} />;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const AppLayout = ({ app, children }) => {
|
|
443
|
+
const location = useLocation();
|
|
444
|
+
const menu = app.menu || [];
|
|
445
|
+
|
|
446
|
+
return (
|
|
447
|
+
<SidebarProvider>
|
|
448
|
+
<Sidebar collapsible="icon">
|
|
449
|
+
<SidebarHeader>
|
|
450
|
+
<div className="flex items-center gap-2 px-2 py-2">
|
|
451
|
+
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
|
452
|
+
{app.logo && <DynamicIcon name={app.logo} className="size-4" />}
|
|
453
|
+
{!app.logo && <span className="text-xl font-bold">O</span>}
|
|
454
|
+
</div>
|
|
455
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
456
|
+
<span className="truncate font-semibold">{app.title || "Object UI"}</span>
|
|
457
|
+
<span className="truncate text-xs">Showcase</span>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
</SidebarHeader>
|
|
461
|
+
<SidebarContent>
|
|
462
|
+
<SidebarGroup>
|
|
463
|
+
<SidebarMenu>
|
|
464
|
+
{menu.map((item, idx) => {
|
|
465
|
+
// Collapsible Item (Children present)
|
|
466
|
+
if (item.children && item.children.length > 0) {
|
|
467
|
+
const isActive = item.children.some(child => child.path === location.pathname);
|
|
468
|
+
return (
|
|
469
|
+
<Collapsible key={idx} asChild defaultOpen={isActive} className="group/collapsible">
|
|
470
|
+
<SidebarMenuItem>
|
|
471
|
+
<CollapsibleTrigger asChild>
|
|
472
|
+
<SidebarMenuButton tooltip={item.label}>
|
|
473
|
+
{item.icon && <DynamicIcon name={item.icon} />}
|
|
474
|
+
<span>{item.label}</span>
|
|
475
|
+
<DynamicIcon name="ChevronRight" className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
|
476
|
+
</SidebarMenuButton>
|
|
477
|
+
</CollapsibleTrigger>
|
|
478
|
+
<CollapsibleContent>
|
|
479
|
+
<SidebarMenuSub>
|
|
480
|
+
{item.children.map((child, cIdx) => (
|
|
481
|
+
<SidebarMenuSubItem key={cIdx}>
|
|
482
|
+
<SidebarMenuSubButton asChild isActive={location.pathname === child.path}>
|
|
483
|
+
<Link to={child.path || '#'}>
|
|
484
|
+
<span>{child.label}</span>
|
|
485
|
+
</Link>
|
|
486
|
+
</SidebarMenuSubButton>
|
|
487
|
+
</SidebarMenuSubItem>
|
|
488
|
+
))}
|
|
489
|
+
</SidebarMenuSub>
|
|
490
|
+
</CollapsibleContent>
|
|
491
|
+
</SidebarMenuItem>
|
|
492
|
+
</Collapsible>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Single Item
|
|
497
|
+
if (item.path) {
|
|
498
|
+
return (
|
|
499
|
+
<SidebarMenuItem key={idx}>
|
|
500
|
+
<SidebarMenuButton asChild isActive={location.pathname === item.path} tooltip={item.label}>
|
|
501
|
+
<Link to={item.path}>
|
|
502
|
+
{item.icon && <DynamicIcon name={item.icon} />}
|
|
503
|
+
<span>{item.label}</span>
|
|
504
|
+
</Link>
|
|
505
|
+
</SidebarMenuButton>
|
|
506
|
+
</SidebarMenuItem>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return null;
|
|
510
|
+
})}
|
|
511
|
+
</SidebarMenu>
|
|
512
|
+
</SidebarGroup>
|
|
513
|
+
</SidebarContent>
|
|
514
|
+
<SidebarFooter>
|
|
515
|
+
</SidebarFooter>
|
|
516
|
+
<SidebarRail />
|
|
517
|
+
</Sidebar>
|
|
518
|
+
|
|
519
|
+
<SidebarInset>
|
|
520
|
+
<header className="flex h-16 shrink-0 items-center gap-2 border-b bg-background px-4">
|
|
521
|
+
<SidebarTrigger className="-ml-1" />
|
|
522
|
+
<Separator orientation="vertical" className="mr-2 h-4" />
|
|
523
|
+
<div className="flex flex-1 items-center justify-between">
|
|
524
|
+
<span className="font-medium">{app.title}</span>
|
|
525
|
+
</div>
|
|
526
|
+
</header>
|
|
527
|
+
<div className="flex-1 flex flex-col min-h-0 bg-muted/20 p-4">
|
|
528
|
+
<div className="mx-auto max-w-full w-full">
|
|
529
|
+
{children}
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
</SidebarInset>
|
|
533
|
+
</SidebarProvider>
|
|
534
|
+
);
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
export default AppLayout;
|
|
538
|
+
`;
|
|
539
|
+
writeFileSync(join(srcDir, "Layout.tsx"), layoutCode);
|
|
540
|
+
layoutImport = `import AppLayout from './Layout';
|
|
541
|
+
const appConfig = ${JSON.stringify(appConfig)};`;
|
|
542
|
+
layoutWrapperStart = `<AppLayout app={appConfig}>`;
|
|
543
|
+
layoutWrapperEnd = `</AppLayout>`;
|
|
544
|
+
}
|
|
545
|
+
const appTsx = `import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
|
|
546
|
+
import { SchemaRenderer } from '@object-ui/react';
|
|
547
|
+
import '@object-ui/components';
|
|
548
|
+
${schemaImports.join("\n")}
|
|
549
|
+
${layoutImport}
|
|
550
|
+
|
|
551
|
+
function App() {
|
|
552
|
+
return (
|
|
553
|
+
<BrowserRouter>
|
|
554
|
+
${layoutWrapperStart}
|
|
555
|
+
<Routes>
|
|
556
|
+
${routeComponents.join("\n")}
|
|
557
|
+
</Routes>
|
|
558
|
+
${layoutWrapperEnd}
|
|
559
|
+
</BrowserRouter>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export default App;`;
|
|
564
|
+
writeFileSync(join(srcDir, "App.tsx"), appTsx);
|
|
565
|
+
const indexCss = `@tailwind base;
|
|
566
|
+
@tailwind components;
|
|
567
|
+
@tailwind utilities;
|
|
568
|
+
|
|
569
|
+
@layer base {
|
|
570
|
+
:root {
|
|
571
|
+
--background: 0 0% 100%;
|
|
572
|
+
--foreground: 222.2 84% 4.9%;
|
|
573
|
+
--card: 0 0% 100%;
|
|
574
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
575
|
+
--popover: 0 0% 100%;
|
|
576
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
577
|
+
--primary: 222.2 47.4% 11.2%;
|
|
578
|
+
--primary-foreground: 210 40% 98%;
|
|
579
|
+
--secondary: 210 40% 96.1%;
|
|
580
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
581
|
+
--muted: 210 40% 96.1%;
|
|
582
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
583
|
+
--accent: 210 40% 96.1%;
|
|
584
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
585
|
+
--destructive: 0 84.2% 60.2%;
|
|
586
|
+
--destructive-foreground: 210 40% 98%;
|
|
587
|
+
--border: 214.3 31.8% 91.4%;
|
|
588
|
+
--input: 214.3 31.8% 91.4%;
|
|
589
|
+
--ring: 222.2 84% 4.9%;
|
|
590
|
+
--radius: 0.5rem;
|
|
591
|
+
--chart-1: 12 76% 61%;
|
|
592
|
+
--chart-2: 173 58% 39%;
|
|
593
|
+
--chart-3: 197 37% 24%;
|
|
594
|
+
--chart-4: 43 74% 66%;
|
|
595
|
+
--chart-5: 27 87% 67%;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.dark {
|
|
599
|
+
--background: 222.2 84% 4.9%;
|
|
600
|
+
--foreground: 210 40% 98%;
|
|
601
|
+
--card: 222.2 84% 4.9%;
|
|
602
|
+
--card-foreground: 210 40% 98%;
|
|
603
|
+
--popover: 222.2 84% 4.9%;
|
|
604
|
+
--popover-foreground: 210 40% 98%;
|
|
605
|
+
--primary: 210 40% 98%;
|
|
606
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
607
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
608
|
+
--secondary-foreground: 210 40% 98%;
|
|
609
|
+
--muted: 217.2 32.6% 17.5%;
|
|
610
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
611
|
+
--accent: 217.2 32.6% 17.5%;
|
|
612
|
+
--accent-foreground: 210 40% 98%;
|
|
613
|
+
--destructive: 0 62.8% 30.6%;
|
|
614
|
+
--destructive-foreground: 210 40% 98%;
|
|
615
|
+
--border: 217.2 32.6% 17.5%;
|
|
616
|
+
--input: 217.2 32.6% 17.5%;
|
|
617
|
+
--ring: 212.7 26.8% 83.9%;
|
|
618
|
+
--chart-1: 220 70% 50%;
|
|
619
|
+
--chart-2: 160 60% 45%;
|
|
620
|
+
--chart-3: 30 80% 55%;
|
|
621
|
+
--chart-4: 280 65% 60%;
|
|
622
|
+
--chart-5: 340 75% 55%;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
@layer base {
|
|
627
|
+
* {
|
|
628
|
+
@apply border-border;
|
|
629
|
+
}
|
|
630
|
+
body {
|
|
631
|
+
@apply bg-background text-foreground font-sans antialiased min-h-screen;
|
|
632
|
+
}
|
|
633
|
+
}`;
|
|
634
|
+
writeFileSync(join(srcDir, "index.css"), indexCss);
|
|
635
|
+
const cwd = process.cwd();
|
|
636
|
+
const isMonorepo = existsSync(join(cwd, "pnpm-workspace.yaml"));
|
|
637
|
+
const contentPaths = ["'./index.html'", "'./src/**/*.{js,ts,jsx,tsx,json}'"];
|
|
638
|
+
if (isMonorepo) {
|
|
639
|
+
const componentsPath = join(cwd, "packages/components/src/**/*.{ts,tsx}");
|
|
640
|
+
const pluginsPath = join(cwd, "packages/plugin-*/src/**/*.{ts,tsx}");
|
|
641
|
+
contentPaths.push(`'${componentsPath}'`);
|
|
642
|
+
contentPaths.push(`'${pluginsPath}'`);
|
|
643
|
+
}
|
|
644
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
645
|
+
export default {
|
|
646
|
+
darkMode: ['class'],
|
|
647
|
+
content: [${contentPaths.join(", ")}],
|
|
648
|
+
theme: {
|
|
649
|
+
extend: {
|
|
650
|
+
borderRadius: {
|
|
651
|
+
lg: 'var(--radius)',
|
|
652
|
+
md: 'calc(var(--radius) - 2px)',
|
|
653
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
654
|
+
},
|
|
655
|
+
colors: {
|
|
656
|
+
background: 'hsl(var(--background))',
|
|
657
|
+
foreground: 'hsl(var(--foreground))',
|
|
658
|
+
card: {
|
|
659
|
+
DEFAULT: 'hsl(var(--card))',
|
|
660
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
661
|
+
},
|
|
662
|
+
popover: {
|
|
663
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
664
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
665
|
+
},
|
|
666
|
+
primary: {
|
|
667
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
668
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
669
|
+
},
|
|
670
|
+
secondary: {
|
|
671
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
672
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
673
|
+
},
|
|
674
|
+
muted: {
|
|
675
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
676
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
677
|
+
},
|
|
678
|
+
accent: {
|
|
679
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
680
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
681
|
+
},
|
|
682
|
+
destructive: {
|
|
683
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
684
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
685
|
+
},
|
|
686
|
+
border: 'hsl(var(--border))',
|
|
687
|
+
input: 'hsl(var(--input))',
|
|
688
|
+
ring: 'hsl(var(--ring))',
|
|
689
|
+
chart: {
|
|
690
|
+
1: 'hsl(var(--chart-1))',
|
|
691
|
+
2: 'hsl(var(--chart-2))',
|
|
692
|
+
3: 'hsl(var(--chart-3))',
|
|
693
|
+
4: 'hsl(var(--chart-4))',
|
|
694
|
+
5: 'hsl(var(--chart-5))',
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
plugins: [],
|
|
700
|
+
};`;
|
|
701
|
+
writeFileSync(join(tmpDir, "tailwind.config.js"), tailwindConfig);
|
|
702
|
+
const postcssConfig = `export default {
|
|
703
|
+
plugins: {
|
|
704
|
+
tailwindcss: {},
|
|
705
|
+
autoprefixer: {},
|
|
706
|
+
},
|
|
707
|
+
};`;
|
|
708
|
+
writeFileSync(join(tmpDir, "postcss.config.js"), postcssConfig);
|
|
709
|
+
const packageJson = {
|
|
710
|
+
name: "objectui-temp-app",
|
|
711
|
+
private: true,
|
|
712
|
+
type: "module",
|
|
713
|
+
dependencies: {
|
|
714
|
+
react: "^18.3.1",
|
|
715
|
+
"react-dom": "^18.3.1",
|
|
716
|
+
"react-router-dom": "^7.12.0",
|
|
717
|
+
"@object-ui/react": "^0.1.0",
|
|
718
|
+
"@object-ui/components": "^0.1.0"
|
|
719
|
+
},
|
|
720
|
+
devDependencies: {
|
|
721
|
+
"@types/react": "^18.3.12",
|
|
722
|
+
"@types/react-dom": "^18.3.1",
|
|
723
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
724
|
+
autoprefixer: "^10.4.23",
|
|
725
|
+
postcss: "^8.5.6",
|
|
726
|
+
tailwindcss: "^3.4.19",
|
|
727
|
+
typescript: "~5.7.3",
|
|
728
|
+
vite: "^5.0.0"
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
writeFileSync(join(tmpDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
732
|
+
const tsconfig = {
|
|
733
|
+
compilerOptions: {
|
|
734
|
+
target: "ES2020",
|
|
735
|
+
useDefineForClassFields: true,
|
|
736
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
737
|
+
module: "ESNext",
|
|
738
|
+
skipLibCheck: true,
|
|
739
|
+
moduleResolution: "bundler",
|
|
740
|
+
allowImportingTsExtensions: true,
|
|
741
|
+
resolveJsonModule: true,
|
|
742
|
+
isolatedModules: true,
|
|
743
|
+
noEmit: true,
|
|
744
|
+
jsx: "react-jsx",
|
|
745
|
+
strict: true,
|
|
746
|
+
noUnusedLocals: true,
|
|
747
|
+
noUnusedParameters: true,
|
|
748
|
+
noFallthroughCasesInSwitch: true
|
|
749
|
+
},
|
|
750
|
+
include: ["src"]
|
|
751
|
+
};
|
|
752
|
+
writeFileSync(join(tmpDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/commands/serve.ts
|
|
756
|
+
async function serve(schemaPath, options) {
|
|
757
|
+
const cwd = process.cwd();
|
|
758
|
+
const pagesDir = join2(cwd, "pages");
|
|
759
|
+
const hasPagesDir = existsSync2(pagesDir);
|
|
760
|
+
let routes = [];
|
|
761
|
+
let schema = null;
|
|
762
|
+
let useFileSystemRouting = false;
|
|
763
|
+
if (hasPagesDir) {
|
|
764
|
+
console.log(chalk2.blue("\u{1F4C1} Detected pages/ directory - using file-system routing"));
|
|
765
|
+
routes = scanPagesDirectory(pagesDir);
|
|
766
|
+
useFileSystemRouting = true;
|
|
767
|
+
if (routes.length === 0) {
|
|
768
|
+
throw new Error("No schema files found in pages/ directory");
|
|
769
|
+
}
|
|
770
|
+
console.log(chalk2.green(`\u2713 Found ${routes.length} route(s)`));
|
|
771
|
+
routes.forEach((route) => {
|
|
772
|
+
console.log(chalk2.dim(` ${route.path} \u2192 ${relative(cwd, route.filePath)}`));
|
|
773
|
+
});
|
|
774
|
+
} else {
|
|
775
|
+
const fullSchemaPath = resolve(cwd, schemaPath);
|
|
776
|
+
if (!existsSync2(fullSchemaPath)) {
|
|
777
|
+
throw new Error(`Schema file not found: ${schemaPath}
|
|
778
|
+
Run 'objectui init' to create a sample schema.`);
|
|
779
|
+
}
|
|
780
|
+
console.log(chalk2.blue("\u{1F4CB} Loading schema:"), chalk2.cyan(schemaPath));
|
|
781
|
+
try {
|
|
782
|
+
schema = parseSchemaFile(fullSchemaPath);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
const tmpDir = join2(cwd, ".objectui-tmp");
|
|
788
|
+
mkdirSync2(tmpDir, { recursive: true });
|
|
789
|
+
if (useFileSystemRouting) {
|
|
790
|
+
createTempAppWithRouting(tmpDir, routes);
|
|
791
|
+
} else {
|
|
792
|
+
createTempApp(tmpDir, schema);
|
|
793
|
+
}
|
|
794
|
+
console.log(chalk2.blue("\u{1F4E6} Installing dependencies..."));
|
|
795
|
+
console.log(chalk2.dim(" This may take a moment on first run..."));
|
|
796
|
+
try {
|
|
797
|
+
execSync("npm install --silent --prefer-offline", {
|
|
798
|
+
cwd: tmpDir,
|
|
799
|
+
stdio: "inherit"
|
|
800
|
+
});
|
|
801
|
+
console.log(chalk2.green("\u2713 Dependencies installed"));
|
|
802
|
+
} catch {
|
|
803
|
+
throw new Error("Failed to install dependencies. Please check your internet connection and try again.");
|
|
804
|
+
}
|
|
805
|
+
console.log(chalk2.green("\u2713 Schema loaded successfully"));
|
|
806
|
+
console.log(chalk2.blue("\u{1F680} Starting development server...\n"));
|
|
807
|
+
const viteConfig = {
|
|
808
|
+
root: tmpDir,
|
|
809
|
+
server: {
|
|
810
|
+
port: parseInt(options.port),
|
|
811
|
+
host: options.host,
|
|
812
|
+
open: true
|
|
813
|
+
},
|
|
814
|
+
plugins: [react()]
|
|
815
|
+
};
|
|
816
|
+
const server = await createServer(viteConfig);
|
|
817
|
+
await server.listen();
|
|
818
|
+
const { port, host } = server.config.server;
|
|
819
|
+
const protocol = server.config.server.https ? "https" : "http";
|
|
820
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
821
|
+
console.log();
|
|
822
|
+
console.log(chalk2.green("\u2713 Server started successfully!"));
|
|
823
|
+
console.log();
|
|
824
|
+
console.log(chalk2.bold(" Local: ") + chalk2.cyan(`${protocol}://${displayHost}:${port}`));
|
|
825
|
+
console.log();
|
|
826
|
+
console.log(chalk2.dim(" Press Ctrl+C to stop the server"));
|
|
827
|
+
console.log();
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/commands/init.ts
|
|
831
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
832
|
+
import { join as join3 } from "path";
|
|
833
|
+
import chalk3 from "chalk";
|
|
834
|
+
var templates = {
|
|
835
|
+
simple: {
|
|
836
|
+
type: "div",
|
|
837
|
+
className: "min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100",
|
|
838
|
+
body: {
|
|
839
|
+
type: "card",
|
|
840
|
+
className: "w-full max-w-md shadow-lg",
|
|
841
|
+
title: "Welcome to Object UI",
|
|
842
|
+
description: "Start building your application with JSON schemas",
|
|
843
|
+
body: {
|
|
844
|
+
type: "div",
|
|
845
|
+
className: "p-6 space-y-4",
|
|
846
|
+
body: [
|
|
847
|
+
{
|
|
848
|
+
type: "text",
|
|
849
|
+
content: "This is a simple example. Edit app.json to customize your application.",
|
|
850
|
+
className: "text-sm text-muted-foreground"
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
type: "button",
|
|
854
|
+
label: "Get Started",
|
|
855
|
+
className: "w-full"
|
|
856
|
+
}
|
|
857
|
+
]
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
form: {
|
|
862
|
+
type: "div",
|
|
863
|
+
className: "min-h-screen flex items-center justify-center bg-gradient-to-br from-purple-50 to-pink-100 p-4",
|
|
864
|
+
body: {
|
|
865
|
+
type: "card",
|
|
866
|
+
className: "w-full max-w-2xl shadow-xl",
|
|
867
|
+
title: "Contact Form",
|
|
868
|
+
description: "Fill out the form below to get in touch",
|
|
869
|
+
body: {
|
|
870
|
+
type: "div",
|
|
871
|
+
className: "p-6 space-y-6",
|
|
872
|
+
body: [
|
|
873
|
+
{
|
|
874
|
+
type: "div",
|
|
875
|
+
className: "grid grid-cols-2 gap-4",
|
|
876
|
+
body: [
|
|
877
|
+
{
|
|
878
|
+
type: "input",
|
|
879
|
+
label: "First Name",
|
|
880
|
+
placeholder: "John",
|
|
881
|
+
required: true
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
type: "input",
|
|
885
|
+
label: "Last Name",
|
|
886
|
+
placeholder: "Doe",
|
|
887
|
+
required: true
|
|
888
|
+
}
|
|
889
|
+
]
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
type: "input",
|
|
893
|
+
label: "Email Address",
|
|
894
|
+
inputType: "email",
|
|
895
|
+
placeholder: "john.doe@example.com",
|
|
896
|
+
required: true
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
type: "input",
|
|
900
|
+
label: "Phone Number",
|
|
901
|
+
inputType: "tel",
|
|
902
|
+
placeholder: "+1 (555) 000-0000"
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
type: "textarea",
|
|
906
|
+
label: "Message",
|
|
907
|
+
placeholder: "Tell us what you need...",
|
|
908
|
+
rows: 4
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
type: "div",
|
|
912
|
+
className: "flex gap-3",
|
|
913
|
+
body: [
|
|
914
|
+
{
|
|
915
|
+
type: "button",
|
|
916
|
+
label: "Submit",
|
|
917
|
+
className: "flex-1"
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
type: "button",
|
|
921
|
+
label: "Reset",
|
|
922
|
+
variant: "outline",
|
|
923
|
+
className: "flex-1"
|
|
924
|
+
}
|
|
925
|
+
]
|
|
926
|
+
}
|
|
927
|
+
]
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
dashboard: {
|
|
932
|
+
type: "div",
|
|
933
|
+
className: "min-h-screen bg-muted/10",
|
|
934
|
+
body: [
|
|
935
|
+
{
|
|
936
|
+
type: "div",
|
|
937
|
+
className: "border-b bg-background",
|
|
938
|
+
body: {
|
|
939
|
+
type: "div",
|
|
940
|
+
className: "container mx-auto px-6 py-4",
|
|
941
|
+
body: {
|
|
942
|
+
type: "div",
|
|
943
|
+
className: "flex items-center justify-between",
|
|
944
|
+
body: [
|
|
945
|
+
{
|
|
946
|
+
type: "div",
|
|
947
|
+
className: "text-2xl font-bold",
|
|
948
|
+
body: { type: "text", content: "Dashboard" }
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
type: "button",
|
|
952
|
+
label: "New Item",
|
|
953
|
+
size: "sm"
|
|
954
|
+
}
|
|
955
|
+
]
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
type: "div",
|
|
961
|
+
className: "container mx-auto p-6 space-y-6",
|
|
962
|
+
body: [
|
|
963
|
+
{
|
|
964
|
+
type: "div",
|
|
965
|
+
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-4",
|
|
966
|
+
body: [
|
|
967
|
+
{
|
|
968
|
+
type: "card",
|
|
969
|
+
className: "shadow-sm",
|
|
970
|
+
body: [
|
|
971
|
+
{
|
|
972
|
+
type: "div",
|
|
973
|
+
className: "p-6 pb-2",
|
|
974
|
+
body: {
|
|
975
|
+
type: "div",
|
|
976
|
+
className: "text-sm font-medium text-muted-foreground",
|
|
977
|
+
body: { type: "text", content: "Total Revenue" }
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
type: "div",
|
|
982
|
+
className: "p-6 pt-0",
|
|
983
|
+
body: [
|
|
984
|
+
{
|
|
985
|
+
type: "div",
|
|
986
|
+
className: "text-2xl font-bold",
|
|
987
|
+
body: { type: "text", content: "$45,231.89" }
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
type: "div",
|
|
991
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
992
|
+
body: { type: "text", content: "+20.1% from last month" }
|
|
993
|
+
}
|
|
994
|
+
]
|
|
995
|
+
}
|
|
996
|
+
]
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
type: "card",
|
|
1000
|
+
className: "shadow-sm",
|
|
1001
|
+
body: [
|
|
1002
|
+
{
|
|
1003
|
+
type: "div",
|
|
1004
|
+
className: "p-6 pb-2",
|
|
1005
|
+
body: {
|
|
1006
|
+
type: "div",
|
|
1007
|
+
className: "text-sm font-medium text-muted-foreground",
|
|
1008
|
+
body: { type: "text", content: "Active Users" }
|
|
1009
|
+
}
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
type: "div",
|
|
1013
|
+
className: "p-6 pt-0",
|
|
1014
|
+
body: [
|
|
1015
|
+
{
|
|
1016
|
+
type: "div",
|
|
1017
|
+
className: "text-2xl font-bold",
|
|
1018
|
+
body: { type: "text", content: "+2,350" }
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
type: "div",
|
|
1022
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
1023
|
+
body: { type: "text", content: "+180.1% from last month" }
|
|
1024
|
+
}
|
|
1025
|
+
]
|
|
1026
|
+
}
|
|
1027
|
+
]
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
type: "card",
|
|
1031
|
+
className: "shadow-sm",
|
|
1032
|
+
body: [
|
|
1033
|
+
{
|
|
1034
|
+
type: "div",
|
|
1035
|
+
className: "p-6 pb-2",
|
|
1036
|
+
body: {
|
|
1037
|
+
type: "div",
|
|
1038
|
+
className: "text-sm font-medium text-muted-foreground",
|
|
1039
|
+
body: { type: "text", content: "Sales" }
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
type: "div",
|
|
1044
|
+
className: "p-6 pt-0",
|
|
1045
|
+
body: [
|
|
1046
|
+
{
|
|
1047
|
+
type: "div",
|
|
1048
|
+
className: "text-2xl font-bold",
|
|
1049
|
+
body: { type: "text", content: "+12,234" }
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
type: "div",
|
|
1053
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
1054
|
+
body: { type: "text", content: "+19% from last month" }
|
|
1055
|
+
}
|
|
1056
|
+
]
|
|
1057
|
+
}
|
|
1058
|
+
]
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
type: "card",
|
|
1062
|
+
className: "shadow-sm",
|
|
1063
|
+
body: [
|
|
1064
|
+
{
|
|
1065
|
+
type: "div",
|
|
1066
|
+
className: "p-6 pb-2",
|
|
1067
|
+
body: {
|
|
1068
|
+
type: "div",
|
|
1069
|
+
className: "text-sm font-medium text-muted-foreground",
|
|
1070
|
+
body: { type: "text", content: "Active Now" }
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
type: "div",
|
|
1075
|
+
className: "p-6 pt-0",
|
|
1076
|
+
body: [
|
|
1077
|
+
{
|
|
1078
|
+
type: "div",
|
|
1079
|
+
className: "text-2xl font-bold",
|
|
1080
|
+
body: { type: "text", content: "+573" }
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
type: "div",
|
|
1084
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
1085
|
+
body: { type: "text", content: "+201 since last hour" }
|
|
1086
|
+
}
|
|
1087
|
+
]
|
|
1088
|
+
}
|
|
1089
|
+
]
|
|
1090
|
+
}
|
|
1091
|
+
]
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
type: "card",
|
|
1095
|
+
className: "shadow-sm",
|
|
1096
|
+
title: "Recent Activity",
|
|
1097
|
+
description: "Your latest updates and notifications",
|
|
1098
|
+
body: {
|
|
1099
|
+
type: "div",
|
|
1100
|
+
className: "p-6 pt-0 space-y-4",
|
|
1101
|
+
body: [
|
|
1102
|
+
{
|
|
1103
|
+
type: "div",
|
|
1104
|
+
className: "flex items-center gap-4 border-b pb-4",
|
|
1105
|
+
body: [
|
|
1106
|
+
{
|
|
1107
|
+
type: "div",
|
|
1108
|
+
className: "flex-1",
|
|
1109
|
+
body: [
|
|
1110
|
+
{
|
|
1111
|
+
type: "div",
|
|
1112
|
+
className: "font-medium",
|
|
1113
|
+
body: { type: "text", content: "New user registration" }
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
type: "div",
|
|
1117
|
+
className: "text-sm text-muted-foreground",
|
|
1118
|
+
body: { type: "text", content: "2 minutes ago" }
|
|
1119
|
+
}
|
|
1120
|
+
]
|
|
1121
|
+
}
|
|
1122
|
+
]
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
type: "div",
|
|
1126
|
+
className: "flex items-center gap-4 border-b pb-4",
|
|
1127
|
+
body: [
|
|
1128
|
+
{
|
|
1129
|
+
type: "div",
|
|
1130
|
+
className: "flex-1",
|
|
1131
|
+
body: [
|
|
1132
|
+
{
|
|
1133
|
+
type: "div",
|
|
1134
|
+
className: "font-medium",
|
|
1135
|
+
body: { type: "text", content: "Payment received" }
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
type: "div",
|
|
1139
|
+
className: "text-sm text-muted-foreground",
|
|
1140
|
+
body: { type: "text", content: "15 minutes ago" }
|
|
1141
|
+
}
|
|
1142
|
+
]
|
|
1143
|
+
}
|
|
1144
|
+
]
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
type: "div",
|
|
1148
|
+
className: "flex items-center gap-4",
|
|
1149
|
+
body: [
|
|
1150
|
+
{
|
|
1151
|
+
type: "div",
|
|
1152
|
+
className: "flex-1",
|
|
1153
|
+
body: [
|
|
1154
|
+
{
|
|
1155
|
+
type: "div",
|
|
1156
|
+
className: "font-medium",
|
|
1157
|
+
body: { type: "text", content: "New order placed" }
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
type: "div",
|
|
1161
|
+
className: "text-sm text-muted-foreground",
|
|
1162
|
+
body: { type: "text", content: "1 hour ago" }
|
|
1163
|
+
}
|
|
1164
|
+
]
|
|
1165
|
+
}
|
|
1166
|
+
]
|
|
1167
|
+
}
|
|
1168
|
+
]
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
]
|
|
1172
|
+
}
|
|
1173
|
+
]
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
async function init(name, options) {
|
|
1177
|
+
const cwd = process.cwd();
|
|
1178
|
+
const projectDir = join3(cwd, name);
|
|
1179
|
+
if (existsSync3(projectDir) && name !== ".") {
|
|
1180
|
+
throw new Error(`Directory "${name}" already exists. Please choose a different name.`);
|
|
1181
|
+
}
|
|
1182
|
+
const targetDir = name === "." ? cwd : projectDir;
|
|
1183
|
+
if (name !== ".") {
|
|
1184
|
+
mkdirSync3(projectDir, { recursive: true });
|
|
1185
|
+
}
|
|
1186
|
+
console.log(chalk3.blue("\u{1F3A8} Creating Object UI application..."));
|
|
1187
|
+
console.log(chalk3.dim(` Template: ${options.template}`));
|
|
1188
|
+
console.log();
|
|
1189
|
+
const template = templates[options.template];
|
|
1190
|
+
if (!template) {
|
|
1191
|
+
throw new Error(
|
|
1192
|
+
`Unknown template: ${options.template}
|
|
1193
|
+
Available templates: ${Object.keys(templates).join(", ")}`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
const schemaPath = join3(targetDir, "app.json");
|
|
1197
|
+
writeFileSync2(schemaPath, JSON.stringify(template, null, 2));
|
|
1198
|
+
console.log(chalk3.green("\u2713 Created app.json"));
|
|
1199
|
+
const readme = `# ${name}
|
|
1200
|
+
|
|
1201
|
+
A Object UI application built from JSON schemas.
|
|
1202
|
+
|
|
1203
|
+
## Getting Started
|
|
1204
|
+
|
|
1205
|
+
1. Install Object UI CLI globally (if you haven't already):
|
|
1206
|
+
\`\`\`bash
|
|
1207
|
+
npm install -g @object-ui/cli
|
|
1208
|
+
\`\`\`
|
|
1209
|
+
|
|
1210
|
+
2. Start the development server:
|
|
1211
|
+
\`\`\`bash
|
|
1212
|
+
objectui serve app.json
|
|
1213
|
+
\`\`\`
|
|
1214
|
+
|
|
1215
|
+
3. Open your browser and visit http://localhost:3000
|
|
1216
|
+
|
|
1217
|
+
## Customize Your App
|
|
1218
|
+
|
|
1219
|
+
Edit \`app.json\` to customize your application. The dev server will automatically reload when you save changes.
|
|
1220
|
+
|
|
1221
|
+
## Available Templates
|
|
1222
|
+
|
|
1223
|
+
- **simple**: A minimal getting started template
|
|
1224
|
+
- **form**: A contact form example
|
|
1225
|
+
- **dashboard**: A full dashboard with metrics and activity feed
|
|
1226
|
+
|
|
1227
|
+
## Learn More
|
|
1228
|
+
|
|
1229
|
+
- [Object UI Documentation](https://www.objectui.org)
|
|
1230
|
+
- [Schema Reference](https://www.objectui.org/docs/protocol/overview)
|
|
1231
|
+
- [Component Library](https://www.objectui.org/docs/api/components)
|
|
1232
|
+
|
|
1233
|
+
## Commands
|
|
1234
|
+
|
|
1235
|
+
- \`objectui serve [schema]\` - Start development server
|
|
1236
|
+
- \`objectui init [name]\` - Create a new application
|
|
1237
|
+
|
|
1238
|
+
Built with \u2764\uFE0F using [Object UI](https://www.objectui.org)
|
|
1239
|
+
`;
|
|
1240
|
+
writeFileSync2(join3(targetDir, "README.md"), readme);
|
|
1241
|
+
console.log(chalk3.green("\u2713 Created README.md"));
|
|
1242
|
+
const gitignore = `.objectui-tmp
|
|
1243
|
+
node_modules
|
|
1244
|
+
dist
|
|
1245
|
+
.DS_Store
|
|
1246
|
+
*.log
|
|
1247
|
+
`;
|
|
1248
|
+
writeFileSync2(join3(targetDir, ".gitignore"), gitignore);
|
|
1249
|
+
console.log(chalk3.green("\u2713 Created .gitignore"));
|
|
1250
|
+
console.log();
|
|
1251
|
+
console.log(chalk3.green("\u2728 Application created successfully!"));
|
|
1252
|
+
console.log();
|
|
1253
|
+
console.log(chalk3.bold("Next steps:"));
|
|
1254
|
+
console.log();
|
|
1255
|
+
if (name !== ".") {
|
|
1256
|
+
console.log(chalk3.cyan(` cd ${name}`));
|
|
1257
|
+
}
|
|
1258
|
+
console.log(chalk3.cyan(" objectui serve app.json"));
|
|
1259
|
+
console.log();
|
|
1260
|
+
console.log(chalk3.dim(" The development server will start on http://localhost:3000"));
|
|
1261
|
+
console.log();
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
export {
|
|
1265
|
+
parseSchemaFile,
|
|
1266
|
+
scanPagesDirectory,
|
|
1267
|
+
createTempApp,
|
|
1268
|
+
createTempAppWithRouting,
|
|
1269
|
+
serve,
|
|
1270
|
+
init
|
|
1271
|
+
};
|
|
1272
|
+
//# sourceMappingURL=chunk-WOV6EOMH.js.map
|