@object-ui/cli 17.5.0 → 17.7.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/CHANGELOG.md +2376 -0
- package/README.md +15 -24
- package/dist/{chunk-2MZA766D.js → chunk-VMMIGYU3.js} +390 -151
- package/dist/chunk-VMMIGYU3.js.map +1 -0
- package/dist/cli.js +1161 -582
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/package.json +9 -7
- package/dist/chunk-2MZA766D.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -3,115 +3,36 @@ import {
|
|
|
3
3
|
createTempApp,
|
|
4
4
|
createTempAppWithRouting,
|
|
5
5
|
init,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
isWorkspaceRoot,
|
|
7
|
+
prepareWorkspaceTempApp,
|
|
8
|
+
reportProjectSource,
|
|
9
|
+
resolveProjectSource,
|
|
8
10
|
serve
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-VMMIGYU3.js";
|
|
10
12
|
|
|
11
13
|
// src/cli.ts
|
|
12
14
|
import { Command } from "commander";
|
|
13
|
-
import
|
|
15
|
+
import chalk9 from "chalk";
|
|
14
16
|
|
|
15
17
|
// src/commands/dev.ts
|
|
16
18
|
import { createServer } from "vite";
|
|
17
19
|
import react from "@vitejs/plugin-react";
|
|
18
|
-
import {
|
|
19
|
-
import { join
|
|
20
|
+
import { mkdirSync } from "fs";
|
|
21
|
+
import { join } from "path";
|
|
20
22
|
import chalk from "chalk";
|
|
21
23
|
import { execSync } from "child_process";
|
|
22
|
-
import { createRequire } from "module";
|
|
23
|
-
async function loadTempAppPostcssPlugins() {
|
|
24
|
-
try {
|
|
25
|
-
const [tailwindPostcss, autoprefixer] = await Promise.all([
|
|
26
|
-
import("@tailwindcss/postcss"),
|
|
27
|
-
import("autoprefixer")
|
|
28
|
-
]);
|
|
29
|
-
return [tailwindPostcss.default(), autoprefixer.default()];
|
|
30
|
-
} catch (error) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Failed to load the Tailwind CSS PostCSS pipeline: ${error instanceof Error ? error.message : error}
|
|
33
|
-
Both '@tailwindcss/postcss' and 'autoprefixer' are dependencies of @object-ui/cli \u2014 a
|
|
34
|
-
broken install of the CLI is the likeliest cause; reinstall it and try again.
|
|
35
|
-
(Refusing to start unstyled: that failure is silent in the browser.)`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
24
|
async function dev(schemaPath, options) {
|
|
40
25
|
const cwd = process.cwd();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let hasPagesDir = false;
|
|
44
|
-
let pagesDir = "";
|
|
45
|
-
let appConfig = null;
|
|
46
|
-
const absoluteSchemaPath = resolve(cwd, schemaPath);
|
|
47
|
-
if (existsSync(absoluteSchemaPath) && statSync(absoluteSchemaPath).isFile()) {
|
|
48
|
-
const fileDir = dirname(absoluteSchemaPath);
|
|
49
|
-
const potentialPagesDir = join(fileDir, "pages");
|
|
50
|
-
if (existsSync(potentialPagesDir)) {
|
|
51
|
-
console.log(chalk.blue(`\u{1F4C2} Detected project structure at ${fileDir}`));
|
|
52
|
-
_projectRoot = fileDir;
|
|
53
|
-
hasPagesDir = true;
|
|
54
|
-
pagesDir = potentialPagesDir;
|
|
55
|
-
try {
|
|
56
|
-
appConfig = parseSchemaFile(absoluteSchemaPath);
|
|
57
|
-
console.log(chalk.blue("\u2699\uFE0F Loaded App Config from app.json"));
|
|
58
|
-
} catch (_e) {
|
|
59
|
-
console.warn("Failed to parse app config");
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!hasPagesDir) {
|
|
64
|
-
const localPagesDir = join(cwd, "pages");
|
|
65
|
-
if (existsSync(localPagesDir)) {
|
|
66
|
-
hasPagesDir = true;
|
|
67
|
-
pagesDir = localPagesDir;
|
|
68
|
-
const appJsonPath = join(cwd, "app.json");
|
|
69
|
-
if (existsSync(appJsonPath)) {
|
|
70
|
-
try {
|
|
71
|
-
appConfig = parseSchemaFile(appJsonPath);
|
|
72
|
-
console.log(chalk.blue("\u2699\uFE0F Loaded App Config from app.json"));
|
|
73
|
-
} catch (_e) {
|
|
74
|
-
console.warn("Failed to parse app.json config");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
const require2 = createRequire(join(cwd, "package.json"));
|
|
80
|
-
let routes = [];
|
|
81
|
-
let schema = null;
|
|
82
|
-
let useFileSystemRouting = false;
|
|
83
|
-
if (hasPagesDir) {
|
|
84
|
-
console.log(chalk.blue(`\u{1F4C1} Using file-system routing from ${pagesDir}`));
|
|
85
|
-
routes = scanPagesDirectory(pagesDir);
|
|
86
|
-
useFileSystemRouting = true;
|
|
87
|
-
if (routes.length === 0) {
|
|
88
|
-
throw new Error(`No schema files found in ${pagesDir}`);
|
|
89
|
-
}
|
|
90
|
-
console.log(chalk.green(`\u2713 Found ${routes.length} route(s)`));
|
|
91
|
-
routes.forEach((route) => {
|
|
92
|
-
console.log(chalk.dim(` ${route.path} \u2192 ${route.filePath.replace(cwd, ".")}`));
|
|
93
|
-
});
|
|
94
|
-
} else {
|
|
95
|
-
const fullSchemaPath = resolve(cwd, schemaPath);
|
|
96
|
-
if (!existsSync(fullSchemaPath)) {
|
|
97
|
-
throw new Error(`Schema file not found: ${schemaPath}
|
|
98
|
-
Run 'objectui init' to create a sample schema.`);
|
|
99
|
-
}
|
|
100
|
-
console.log(chalk.blue("\u{1F4CB} Loading schema:"), chalk.cyan(schemaPath));
|
|
101
|
-
try {
|
|
102
|
-
schema = parseSchemaFile(fullSchemaPath);
|
|
103
|
-
} catch (error) {
|
|
104
|
-
throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
26
|
+
const source = resolveProjectSource(cwd, schemaPath);
|
|
27
|
+
reportProjectSource(source, cwd);
|
|
107
28
|
const tmpDir = join(cwd, ".objectui-tmp");
|
|
108
29
|
mkdirSync(tmpDir, { recursive: true });
|
|
109
|
-
if (
|
|
110
|
-
createTempAppWithRouting(tmpDir, routes, appConfig);
|
|
30
|
+
if (source.mode === "routes") {
|
|
31
|
+
createTempAppWithRouting(tmpDir, source.routes, source.appConfig);
|
|
111
32
|
} else {
|
|
112
|
-
createTempApp(tmpDir, schema);
|
|
33
|
+
createTempApp(tmpDir, source.schema);
|
|
113
34
|
}
|
|
114
|
-
const isMonorepo =
|
|
35
|
+
const isMonorepo = isWorkspaceRoot(cwd);
|
|
115
36
|
if (isMonorepo) {
|
|
116
37
|
console.log(chalk.blue("\u{1F4E6} Detected monorepo - using root node_modules"));
|
|
117
38
|
} else {
|
|
@@ -129,6 +50,10 @@ Run 'objectui init' to create a sample schema.`);
|
|
|
129
50
|
}
|
|
130
51
|
console.log(chalk.green("\u2713 Schema loaded successfully"));
|
|
131
52
|
console.log(chalk.blue("\u{1F680} Starting development server...\n"));
|
|
53
|
+
if (isMonorepo) {
|
|
54
|
+
console.log(chalk.blue("\u{1F4E6} Detected monorepo - configuring workspace aliases"));
|
|
55
|
+
}
|
|
56
|
+
const workspaceConfig = isMonorepo ? await prepareWorkspaceTempApp(cwd, tmpDir) : {};
|
|
132
57
|
const viteConfig = {
|
|
133
58
|
root: tmpDir,
|
|
134
59
|
server: {
|
|
@@ -140,38 +65,9 @@ Run 'objectui init' to create a sample schema.`);
|
|
|
140
65
|
allow: [cwd]
|
|
141
66
|
}
|
|
142
67
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
146
|
-
plugins: [react()]
|
|
68
|
+
plugins: [react()],
|
|
69
|
+
...workspaceConfig
|
|
147
70
|
};
|
|
148
|
-
if (isMonorepo) {
|
|
149
|
-
console.log(chalk.blue("\u{1F4E6} Detected monorepo - configuring workspace aliases"));
|
|
150
|
-
const postcssPath = join(tmpDir, "postcss.config.js");
|
|
151
|
-
if (existsSync(postcssPath)) {
|
|
152
|
-
unlinkSync(postcssPath);
|
|
153
|
-
}
|
|
154
|
-
viteConfig.resolve.alias = {
|
|
155
|
-
"@object-ui/react": join(cwd, "packages/react/src/index.ts"),
|
|
156
|
-
"@object-ui/components": join(cwd, "packages/components/src/index.ts"),
|
|
157
|
-
"@object-ui/core": join(cwd, "packages/core/src/index.ts"),
|
|
158
|
-
"@object-ui/types": join(cwd, "packages/types/src/index.ts"),
|
|
159
|
-
"@object-ui/plugin-charts": join(cwd, "packages/plugin-charts/src/index.tsx"),
|
|
160
|
-
"@object-ui/plugin-editor": join(cwd, "packages/plugin-editor/src/index.tsx"),
|
|
161
|
-
"@object-ui/plugin-kanban": join(cwd, "packages/plugin-kanban/src/index.tsx"),
|
|
162
|
-
"@object-ui/plugin-markdown": join(cwd, "packages/plugin-markdown/src/index.tsx"),
|
|
163
|
-
"@object-ui/plugin-form": join(cwd, "packages/plugin-form/src/index.tsx"),
|
|
164
|
-
"@object-ui/plugin-grid": join(cwd, "packages/plugin-grid/src/index.tsx"),
|
|
165
|
-
"@object-ui/plugin-view": join(cwd, "packages/plugin-view/src/index.tsx")
|
|
166
|
-
};
|
|
167
|
-
try {
|
|
168
|
-
const lucidePath = require2.resolve("lucide-react", { paths: [join(cwd, "packages/components")] });
|
|
169
|
-
viteConfig.resolve.alias["lucide-react"] = lucidePath;
|
|
170
|
-
} catch (e) {
|
|
171
|
-
console.warn("\u26A0\uFE0F Could not resolve lucide-react automatically:", e);
|
|
172
|
-
}
|
|
173
|
-
viteConfig.css = { postcss: { plugins: await loadTempAppPostcssPlugins() } };
|
|
174
|
-
}
|
|
175
71
|
const server = await createServer(viteConfig);
|
|
176
72
|
await server.listen();
|
|
177
73
|
const { port, host } = server.config.server;
|
|
@@ -189,62 +85,47 @@ Run 'objectui init' to create a sample schema.`);
|
|
|
189
85
|
// src/commands/build.ts
|
|
190
86
|
import { build as viteBuild } from "vite";
|
|
191
87
|
import react2 from "@vitejs/plugin-react";
|
|
192
|
-
import { existsSync
|
|
193
|
-
import { join as join2, resolve
|
|
88
|
+
import { existsSync, mkdirSync as mkdirSync2, cpSync, rmSync } from "fs";
|
|
89
|
+
import { join as join2, resolve } from "path";
|
|
194
90
|
import chalk2 from "chalk";
|
|
195
91
|
import { execSync as execSync2 } from "child_process";
|
|
196
92
|
async function buildApp(schemaPath, options) {
|
|
197
93
|
const cwd = process.cwd();
|
|
198
94
|
const outDir = options.outDir || "dist";
|
|
199
|
-
const outputPath =
|
|
95
|
+
const outputPath = resolve(cwd, outDir);
|
|
200
96
|
console.log(chalk2.blue("\u{1F528} Building application for production..."));
|
|
201
97
|
console.log();
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
let routes = [];
|
|
205
|
-
let schema = null;
|
|
206
|
-
let useFileSystemRouting = false;
|
|
207
|
-
if (hasPagesDir) {
|
|
208
|
-
console.log(chalk2.blue("\u{1F4C1} Using file-system routing"));
|
|
209
|
-
routes = scanPagesDirectory(pagesDir);
|
|
210
|
-
useFileSystemRouting = true;
|
|
211
|
-
if (routes.length === 0) {
|
|
212
|
-
throw new Error("No schema files found in pages/ directory");
|
|
213
|
-
}
|
|
214
|
-
console.log(chalk2.green(`\u2713 Found ${routes.length} route(s)`));
|
|
215
|
-
} else {
|
|
216
|
-
const fullSchemaPath = resolve2(cwd, schemaPath);
|
|
217
|
-
if (!existsSync2(fullSchemaPath)) {
|
|
218
|
-
throw new Error(`Schema file not found: ${schemaPath}
|
|
219
|
-
Run 'objectui init' to create a sample schema.`);
|
|
220
|
-
}
|
|
221
|
-
console.log(chalk2.blue("\u{1F4CB} Loading schema:"), chalk2.cyan(schemaPath));
|
|
222
|
-
try {
|
|
223
|
-
schema = parseSchemaFile(fullSchemaPath);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
98
|
+
const source = resolveProjectSource(cwd, schemaPath);
|
|
99
|
+
reportProjectSource(source, cwd);
|
|
228
100
|
const tmpDir = join2(cwd, ".objectui-tmp");
|
|
229
101
|
mkdirSync2(tmpDir, { recursive: true });
|
|
230
|
-
if (
|
|
231
|
-
createTempAppWithRouting(tmpDir, routes);
|
|
102
|
+
if (source.mode === "routes") {
|
|
103
|
+
createTempAppWithRouting(tmpDir, source.routes, source.appConfig);
|
|
232
104
|
} else {
|
|
233
|
-
createTempApp(tmpDir, schema);
|
|
105
|
+
createTempApp(tmpDir, source.schema);
|
|
234
106
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
107
|
+
const isMonorepo = isWorkspaceRoot(cwd);
|
|
108
|
+
if (isMonorepo) {
|
|
109
|
+
console.log(chalk2.blue("\u{1F4E6} Detected monorepo - using root node_modules"));
|
|
110
|
+
} else {
|
|
111
|
+
console.log(chalk2.blue("\u{1F4E6} Installing dependencies..."));
|
|
112
|
+
try {
|
|
113
|
+
execSync2("npm install --silent --prefer-offline", {
|
|
114
|
+
cwd: tmpDir,
|
|
115
|
+
stdio: "pipe"
|
|
116
|
+
});
|
|
117
|
+
console.log(chalk2.green("\u2713 Dependencies installed"));
|
|
118
|
+
} catch {
|
|
119
|
+
throw new Error("Failed to install dependencies. Please check your internet connection and try again.");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (isMonorepo) {
|
|
123
|
+
console.log(chalk2.blue("\u{1F4E6} Detected monorepo - configuring workspace aliases"));
|
|
244
124
|
}
|
|
125
|
+
const workspaceConfig = isMonorepo ? await prepareWorkspaceTempApp(cwd, tmpDir) : {};
|
|
245
126
|
console.log(chalk2.blue("\u2699\uFE0F Building with Vite..."));
|
|
246
127
|
console.log();
|
|
247
|
-
if (options.clean &&
|
|
128
|
+
if (options.clean && existsSync(outputPath)) {
|
|
248
129
|
console.log(chalk2.dim(` Cleaning ${outDir}/ directory...`));
|
|
249
130
|
rmSync(outputPath, { recursive: true, force: true });
|
|
250
131
|
}
|
|
@@ -257,7 +138,8 @@ Run 'objectui init' to create a sample schema.`);
|
|
|
257
138
|
reportCompressedSize: true
|
|
258
139
|
},
|
|
259
140
|
plugins: [react2()],
|
|
260
|
-
logLevel: "info"
|
|
141
|
+
logLevel: "info",
|
|
142
|
+
...workspaceConfig
|
|
261
143
|
});
|
|
262
144
|
mkdirSync2(outputPath, { recursive: true });
|
|
263
145
|
cpSync(join2(tmpDir, "dist"), outputPath, { recursive: true });
|
|
@@ -277,21 +159,21 @@ Run 'objectui init' to create a sample schema.`);
|
|
|
277
159
|
// src/commands/start.ts
|
|
278
160
|
import express from "express";
|
|
279
161
|
import rateLimit from "express-rate-limit";
|
|
280
|
-
import { existsSync as
|
|
281
|
-
import { join as join3, resolve as
|
|
162
|
+
import { existsSync as existsSync2 } from "fs";
|
|
163
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
282
164
|
import chalk3 from "chalk";
|
|
283
165
|
async function start(options) {
|
|
284
166
|
const cwd = process.cwd();
|
|
285
167
|
const distDir = options.dir || "dist";
|
|
286
|
-
const distPath =
|
|
287
|
-
if (!
|
|
168
|
+
const distPath = resolve2(cwd, distDir);
|
|
169
|
+
if (!existsSync2(distPath)) {
|
|
288
170
|
throw new Error(
|
|
289
171
|
`Build directory not found: ${distDir}
|
|
290
172
|
Run 'objectui build' first to create a production build.`
|
|
291
173
|
);
|
|
292
174
|
}
|
|
293
175
|
const indexPath = join3(distPath, "index.html");
|
|
294
|
-
if (!
|
|
176
|
+
if (!existsSync2(indexPath)) {
|
|
295
177
|
throw new Error(
|
|
296
178
|
`index.html not found in ${distDir}/
|
|
297
179
|
Make sure you have a valid production build.`
|
|
@@ -330,108 +212,10 @@ Make sure you have a valid production build.`
|
|
|
330
212
|
});
|
|
331
213
|
}
|
|
332
214
|
|
|
333
|
-
// src/commands/lint.ts
|
|
334
|
-
import { execSync as execSync3 } from "child_process";
|
|
335
|
-
import { existsSync as existsSync4 } from "fs";
|
|
336
|
-
import { join as join4 } from "path";
|
|
337
|
-
import chalk4 from "chalk";
|
|
338
|
-
async function lint(options) {
|
|
339
|
-
const cwd = process.cwd();
|
|
340
|
-
console.log(chalk4.blue("\u{1F50D} Running linter...\n"));
|
|
341
|
-
const tmpDir = join4(cwd, ".objectui-tmp");
|
|
342
|
-
const hasTempApp = existsSync4(tmpDir);
|
|
343
|
-
if (!hasTempApp) {
|
|
344
|
-
throw new Error(
|
|
345
|
-
"No Object UI application found. Run 'objectui dev' first to generate the application."
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
const packageJsonPath = join4(tmpDir, "package.json");
|
|
349
|
-
const nodeModulesPath = join4(tmpDir, "node_modules");
|
|
350
|
-
if (!existsSync4(packageJsonPath) || !existsSync4(nodeModulesPath)) {
|
|
351
|
-
throw new Error(
|
|
352
|
-
"Dependencies not installed. Run 'objectui dev' first to set up the application."
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
try {
|
|
356
|
-
const fixFlag = options.fix ? "--fix" : "";
|
|
357
|
-
const command = `npx eslint "src/**/*.{js,jsx,ts,tsx}" ${fixFlag}`.trim();
|
|
358
|
-
console.log(chalk4.dim(` Running: ${command}
|
|
359
|
-
`));
|
|
360
|
-
execSync3(command, {
|
|
361
|
-
cwd: tmpDir,
|
|
362
|
-
stdio: "inherit"
|
|
363
|
-
});
|
|
364
|
-
console.log();
|
|
365
|
-
console.log(chalk4.green("\u2713 Linting completed successfully!"));
|
|
366
|
-
console.log();
|
|
367
|
-
} catch {
|
|
368
|
-
console.log();
|
|
369
|
-
console.log(chalk4.yellow("\u26A0 Linting found issues."));
|
|
370
|
-
if (!options.fix) {
|
|
371
|
-
console.log(chalk4.dim(" Run 'objectui lint --fix' to automatically fix some issues."));
|
|
372
|
-
}
|
|
373
|
-
console.log();
|
|
374
|
-
process.exit(1);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// src/commands/test.ts
|
|
379
|
-
import { execSync as execSync4 } from "child_process";
|
|
380
|
-
import { existsSync as existsSync5 } from "fs";
|
|
381
|
-
import { join as join5 } from "path";
|
|
382
|
-
import chalk5 from "chalk";
|
|
383
|
-
async function test(options) {
|
|
384
|
-
const cwd = process.cwd();
|
|
385
|
-
console.log(chalk5.blue("\u{1F9EA} Running tests...\n"));
|
|
386
|
-
const tmpDir = join5(cwd, ".objectui-tmp");
|
|
387
|
-
const hasTempApp = existsSync5(tmpDir);
|
|
388
|
-
if (!hasTempApp) {
|
|
389
|
-
throw new Error(
|
|
390
|
-
"No Object UI application found. Run 'objectui dev' first to generate the application."
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
const packageJsonPath = join5(tmpDir, "package.json");
|
|
394
|
-
const nodeModulesPath = join5(tmpDir, "node_modules");
|
|
395
|
-
if (!existsSync5(packageJsonPath) || !existsSync5(nodeModulesPath)) {
|
|
396
|
-
throw new Error(
|
|
397
|
-
"Dependencies not installed. Run 'objectui dev' first to set up the application."
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
try {
|
|
401
|
-
let command = "npx vitest";
|
|
402
|
-
if (options.watch) {
|
|
403
|
-
command += " --watch";
|
|
404
|
-
} else if (options.ui) {
|
|
405
|
-
command += " --ui";
|
|
406
|
-
} else {
|
|
407
|
-
command += " run";
|
|
408
|
-
}
|
|
409
|
-
if (options.coverage) {
|
|
410
|
-
command += " --coverage";
|
|
411
|
-
}
|
|
412
|
-
console.log(chalk5.dim(` Running: ${command}
|
|
413
|
-
`));
|
|
414
|
-
execSync4(command, {
|
|
415
|
-
cwd: tmpDir,
|
|
416
|
-
stdio: "inherit"
|
|
417
|
-
});
|
|
418
|
-
if (!options.watch && !options.ui) {
|
|
419
|
-
console.log();
|
|
420
|
-
console.log(chalk5.green("\u2713 Tests completed successfully!"));
|
|
421
|
-
console.log();
|
|
422
|
-
}
|
|
423
|
-
} catch {
|
|
424
|
-
console.log();
|
|
425
|
-
console.log(chalk5.red("\u2717 Some tests failed."));
|
|
426
|
-
console.log();
|
|
427
|
-
process.exit(1);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
215
|
// src/commands/generate.ts
|
|
432
|
-
import
|
|
433
|
-
import { existsSync as
|
|
434
|
-
import { join as
|
|
216
|
+
import chalk4 from "chalk";
|
|
217
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync } from "fs";
|
|
218
|
+
import { join as join4 } from "path";
|
|
435
219
|
async function generate(type, name) {
|
|
436
220
|
const cwd = process.cwd();
|
|
437
221
|
switch (type) {
|
|
@@ -446,18 +230,18 @@ async function generate(type, name) {
|
|
|
446
230
|
generatePlugin(cwd, name);
|
|
447
231
|
break;
|
|
448
232
|
default:
|
|
449
|
-
console.log(
|
|
233
|
+
console.log(chalk4.red(`Unknown type: ${type}`));
|
|
450
234
|
console.log(`Available types: resource, page, plugin`);
|
|
451
235
|
process.exit(1);
|
|
452
236
|
}
|
|
453
237
|
}
|
|
454
238
|
function generateResource(cwd, name) {
|
|
455
|
-
const dir =
|
|
456
|
-
if (!
|
|
239
|
+
const dir = join4(cwd, "objects");
|
|
240
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
457
241
|
const fileName = `${name.toLowerCase()}.json`;
|
|
458
|
-
const filePath =
|
|
459
|
-
if (
|
|
460
|
-
console.log(
|
|
242
|
+
const filePath = join4(dir, fileName);
|
|
243
|
+
if (existsSync3(filePath)) {
|
|
244
|
+
console.log(chalk4.yellow(`Object ${name} already exists at ${filePath}`));
|
|
461
245
|
return;
|
|
462
246
|
}
|
|
463
247
|
const content = {
|
|
@@ -467,34 +251,35 @@ function generateResource(cwd, name) {
|
|
|
467
251
|
}
|
|
468
252
|
};
|
|
469
253
|
writeFileSync(filePath, JSON.stringify(content, null, 2));
|
|
470
|
-
console.log(
|
|
254
|
+
console.log(chalk4.green(`\u2713 Generated resource schema: ${filePath}`));
|
|
471
255
|
}
|
|
472
256
|
function generatePage(cwd, name) {
|
|
473
|
-
const dir =
|
|
474
|
-
if (!
|
|
257
|
+
const dir = join4(cwd, "pages");
|
|
258
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
475
259
|
const fileName = `${name.toLowerCase()}.json`;
|
|
476
|
-
const filePath =
|
|
477
|
-
if (
|
|
478
|
-
console.log(
|
|
260
|
+
const filePath = join4(dir, fileName);
|
|
261
|
+
if (existsSync3(filePath)) {
|
|
262
|
+
console.log(chalk4.yellow(`Page ${name} already exists at ${filePath}`));
|
|
479
263
|
return;
|
|
480
264
|
}
|
|
481
265
|
const content = {
|
|
482
266
|
type: "page",
|
|
267
|
+
pageType: "app",
|
|
483
268
|
title: name,
|
|
484
|
-
|
|
269
|
+
children: [
|
|
485
270
|
{
|
|
486
271
|
type: "markdown",
|
|
487
|
-
content:
|
|
272
|
+
content: `Welcome to ${name}`
|
|
488
273
|
}
|
|
489
274
|
]
|
|
490
275
|
};
|
|
491
276
|
writeFileSync(filePath, JSON.stringify(content, null, 2));
|
|
492
|
-
console.log(
|
|
277
|
+
console.log(chalk4.green(`\u2713 Generated page schema: ${filePath}`));
|
|
493
278
|
}
|
|
494
279
|
function generatePlugin(cwd, name) {
|
|
495
|
-
const dir =
|
|
496
|
-
if (
|
|
497
|
-
console.log(
|
|
280
|
+
const dir = join4(cwd, "plugins", name);
|
|
281
|
+
if (existsSync3(dir)) {
|
|
282
|
+
console.log(chalk4.yellow(`Plugin ${name} already exists`));
|
|
498
283
|
return;
|
|
499
284
|
}
|
|
500
285
|
mkdirSync3(dir, { recursive: true });
|
|
@@ -506,14 +291,14 @@ export const ${name}Plugin = {
|
|
|
506
291
|
}
|
|
507
292
|
};
|
|
508
293
|
`;
|
|
509
|
-
writeFileSync(
|
|
510
|
-
console.log(
|
|
294
|
+
writeFileSync(join4(dir, "index.ts"), content.trim());
|
|
295
|
+
console.log(chalk4.green(`\u2713 Generated plugin: ${dir}`));
|
|
511
296
|
}
|
|
512
297
|
|
|
513
298
|
// src/commands/doctor.ts
|
|
514
|
-
import
|
|
515
|
-
import { existsSync as
|
|
516
|
-
import { dirname
|
|
299
|
+
import chalk5 from "chalk";
|
|
300
|
+
import { existsSync as existsSync4, readFileSync } from "fs";
|
|
301
|
+
import { dirname, join as join5, resolve as resolvePath } from "path";
|
|
517
302
|
var CSS_ENTRY_CANDIDATES = [
|
|
518
303
|
"src/index.css",
|
|
519
304
|
"src/main.css",
|
|
@@ -548,15 +333,15 @@ var V4_POSTCSS_PLUGIN = /@tailwindcss\/postcss/;
|
|
|
548
333
|
var V3_POSTCSS_PLUGIN = /["']tailwindcss["']|(?:^|[{,\s])tailwindcss\s*:/m;
|
|
549
334
|
function readIfExists(path) {
|
|
550
335
|
try {
|
|
551
|
-
return
|
|
336
|
+
return existsSync4(path) ? readFileSync(path, "utf-8") : null;
|
|
552
337
|
} catch {
|
|
553
338
|
return null;
|
|
554
339
|
}
|
|
555
340
|
}
|
|
556
341
|
function findFile(cwd, candidates) {
|
|
557
342
|
for (const rel of candidates) {
|
|
558
|
-
const abs =
|
|
559
|
-
if (
|
|
343
|
+
const abs = join5(cwd, rel);
|
|
344
|
+
if (existsSync4(abs)) return abs;
|
|
560
345
|
}
|
|
561
346
|
return null;
|
|
562
347
|
}
|
|
@@ -569,8 +354,8 @@ function majorOf(range) {
|
|
|
569
354
|
function isInstalledFrom(cwd, specifier) {
|
|
570
355
|
let dir = resolvePath(cwd);
|
|
571
356
|
for (; ; ) {
|
|
572
|
-
if (
|
|
573
|
-
const parent =
|
|
357
|
+
if (existsSync4(join5(dir, "node_modules", ...specifier.split("/")))) return true;
|
|
358
|
+
const parent = dirname(dir);
|
|
574
359
|
if (parent === dir) return false;
|
|
575
360
|
dir = parent;
|
|
576
361
|
}
|
|
@@ -586,7 +371,7 @@ function runDiagnostics(cwd) {
|
|
|
586
371
|
const error = (id, message) => {
|
|
587
372
|
results.push({ id, level: "error", message });
|
|
588
373
|
};
|
|
589
|
-
const pkgPath =
|
|
374
|
+
const pkgPath = join5(cwd, "package.json");
|
|
590
375
|
const pkgRaw = readIfExists(pkgPath);
|
|
591
376
|
let pkg = null;
|
|
592
377
|
if (pkgRaw === null) {
|
|
@@ -654,7 +439,7 @@ function runDiagnostics(cwd) {
|
|
|
654
439
|
}
|
|
655
440
|
const cssEntries = CSS_ENTRY_CANDIDATES.map((rel) => ({
|
|
656
441
|
rel,
|
|
657
|
-
content: readIfExists(
|
|
442
|
+
content: readIfExists(join5(cwd, rel))
|
|
658
443
|
})).filter((entry) => entry.content !== null);
|
|
659
444
|
const importsTailwind = cssEntries.some((entry) => V4_CSS_IMPORT.test(entry.content));
|
|
660
445
|
const usesV3Directives = cssEntries.some((entry) => V3_CSS_DIRECTIVE.test(entry.content));
|
|
@@ -688,7 +473,7 @@ function runDiagnostics(cwd) {
|
|
|
688
473
|
ok("tailwind-config-active", "JS Tailwind config is loaded via `@config`");
|
|
689
474
|
}
|
|
690
475
|
}
|
|
691
|
-
if (
|
|
476
|
+
if (existsSync4(join5(cwd, "tsconfig.json"))) {
|
|
692
477
|
ok("tsconfig-found", "tsconfig.json found");
|
|
693
478
|
const tsRange = deps.typescript;
|
|
694
479
|
if (tsRange) {
|
|
@@ -716,142 +501,879 @@ function countIssues(results) {
|
|
|
716
501
|
return results.filter((result) => result.level !== "ok").length;
|
|
717
502
|
}
|
|
718
503
|
async function doctor() {
|
|
719
|
-
console.log(
|
|
504
|
+
console.log(chalk5.bold("Object UI Doctor"));
|
|
720
505
|
console.log("Diagnosis in progress...\n");
|
|
721
506
|
const results = runDiagnostics(process.cwd());
|
|
722
507
|
for (const result of results) {
|
|
723
508
|
if (result.level === "ok") {
|
|
724
|
-
console.log(
|
|
509
|
+
console.log(chalk5.green(`\u2713 ${result.message}`));
|
|
725
510
|
} else if (result.level === "warn") {
|
|
726
|
-
console.log(
|
|
511
|
+
console.log(chalk5.yellow(`\u26A0\uFE0F ${result.message}`));
|
|
727
512
|
} else {
|
|
728
|
-
console.log(
|
|
513
|
+
console.log(chalk5.red(`x ${result.message}`));
|
|
729
514
|
}
|
|
730
515
|
}
|
|
731
516
|
const issues = countIssues(results);
|
|
732
517
|
console.log("\nResult:");
|
|
733
518
|
if (issues === 0) {
|
|
734
|
-
console.log(
|
|
519
|
+
console.log(chalk5.green("Everything looks good! \u2728"));
|
|
735
520
|
} else {
|
|
736
|
-
console.log(
|
|
521
|
+
console.log(chalk5.yellow(`Found ${issues} issue(s).`));
|
|
737
522
|
}
|
|
738
523
|
}
|
|
739
524
|
|
|
740
|
-
// src/commands/
|
|
741
|
-
import
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
525
|
+
// src/commands/check.ts
|
|
526
|
+
import chalk6 from "chalk";
|
|
527
|
+
import { globSync } from "glob";
|
|
528
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
529
|
+
import { join as join6 } from "path";
|
|
530
|
+
import { parse as parseJsonc, printParseErrorCode } from "jsonc-parser";
|
|
531
|
+
import { safeValidateSchema } from "@object-ui/types/zod";
|
|
532
|
+
|
|
533
|
+
// src/utils/issue-path.ts
|
|
534
|
+
function formatIssuePath(path) {
|
|
535
|
+
const segments = path ?? [];
|
|
536
|
+
return segments.length > 0 ? segments.join(" \u2192 ") : "(root)";
|
|
746
537
|
}
|
|
747
538
|
|
|
748
|
-
// src/
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
539
|
+
// src/utils/known-schema-types.ts
|
|
540
|
+
var KNOWN_SCHEMA_TYPES = [
|
|
541
|
+
"a",
|
|
542
|
+
"abbr",
|
|
543
|
+
"accordion",
|
|
544
|
+
"action:bar",
|
|
545
|
+
"action:button",
|
|
546
|
+
"action:group",
|
|
547
|
+
"action:icon",
|
|
548
|
+
"action:menu",
|
|
549
|
+
"address",
|
|
550
|
+
"ai-form-assist",
|
|
551
|
+
"ai-recommendations",
|
|
552
|
+
"ai:feedback",
|
|
553
|
+
"ai:input",
|
|
554
|
+
"ai:suggestion",
|
|
555
|
+
"alert",
|
|
556
|
+
"alert-dialog",
|
|
557
|
+
"app",
|
|
558
|
+
"app-schema-renderer",
|
|
559
|
+
"app-shell:cloud-connection:panel",
|
|
560
|
+
"app-shell:cloud:ai-model-status",
|
|
561
|
+
"app-shell:cloud:onboarding-next",
|
|
562
|
+
"app-shell:marketplace:installed-list",
|
|
563
|
+
"app-shell:mcp:connect-agent",
|
|
564
|
+
"app:launcher",
|
|
565
|
+
"article",
|
|
566
|
+
"aside",
|
|
567
|
+
"aspect-ratio",
|
|
568
|
+
"avatar",
|
|
569
|
+
"b",
|
|
570
|
+
"badge",
|
|
571
|
+
"bar-chart",
|
|
572
|
+
"blockquote",
|
|
573
|
+
"box",
|
|
574
|
+
"br",
|
|
575
|
+
"breadcrumb",
|
|
576
|
+
"button",
|
|
577
|
+
"button-group",
|
|
578
|
+
"calendar",
|
|
579
|
+
"calendar-view",
|
|
580
|
+
"card",
|
|
581
|
+
"carousel",
|
|
582
|
+
"chart",
|
|
583
|
+
"chart:bar",
|
|
584
|
+
"chatbot",
|
|
585
|
+
"chatbot-enhanced",
|
|
586
|
+
"chatbot-floating",
|
|
587
|
+
"checkbox",
|
|
588
|
+
"cite",
|
|
589
|
+
"cloud-connection:panel",
|
|
590
|
+
"cloud:ai-model-status",
|
|
591
|
+
"cloud:onboarding-next",
|
|
592
|
+
"cloud:plan-status",
|
|
593
|
+
"code",
|
|
594
|
+
"code-editor",
|
|
595
|
+
"collapsible",
|
|
596
|
+
"combobox",
|
|
597
|
+
"command",
|
|
598
|
+
"container",
|
|
599
|
+
"context-menu",
|
|
600
|
+
"dashboard",
|
|
601
|
+
"data-model-designer",
|
|
602
|
+
"data-table",
|
|
603
|
+
"date-picker",
|
|
604
|
+
"dd",
|
|
605
|
+
"del",
|
|
606
|
+
"detail",
|
|
607
|
+
"detail-section",
|
|
608
|
+
"detail-view",
|
|
609
|
+
"dialog",
|
|
610
|
+
"div",
|
|
611
|
+
"dl",
|
|
612
|
+
"drawer",
|
|
613
|
+
"dropdown-menu",
|
|
614
|
+
"dt",
|
|
615
|
+
"element:button",
|
|
616
|
+
"element:definition-list",
|
|
617
|
+
"element:divider",
|
|
618
|
+
"element:image",
|
|
619
|
+
"element:metadata_viewer",
|
|
620
|
+
"element:number",
|
|
621
|
+
"element:record_picker",
|
|
622
|
+
"element:repeater",
|
|
623
|
+
"element:text",
|
|
624
|
+
"element:text_input",
|
|
625
|
+
"em",
|
|
626
|
+
"email",
|
|
627
|
+
"embeddable-form",
|
|
628
|
+
"empty",
|
|
629
|
+
"field-designer",
|
|
630
|
+
"field:address",
|
|
631
|
+
"field:audio",
|
|
632
|
+
"field:auto_number",
|
|
633
|
+
"field:avatar",
|
|
634
|
+
"field:boolean",
|
|
635
|
+
"field:checkboxes",
|
|
636
|
+
"field:code",
|
|
637
|
+
"field:color",
|
|
638
|
+
"field:currency",
|
|
639
|
+
"field:date",
|
|
640
|
+
"field:datetime",
|
|
641
|
+
"field:duration",
|
|
642
|
+
"field:email",
|
|
643
|
+
"field:file",
|
|
644
|
+
"field:filter-condition",
|
|
645
|
+
"field:formula",
|
|
646
|
+
"field:geolocation",
|
|
647
|
+
"field:grid",
|
|
648
|
+
"field:html",
|
|
649
|
+
"field:image",
|
|
650
|
+
"field:json",
|
|
651
|
+
"field:location",
|
|
652
|
+
"field:lookup",
|
|
653
|
+
"field:markdown",
|
|
654
|
+
"field:master_detail",
|
|
655
|
+
"field:multiselect",
|
|
656
|
+
"field:number",
|
|
657
|
+
"field:object",
|
|
658
|
+
"field:object-ref",
|
|
659
|
+
"field:password",
|
|
660
|
+
"field:percent",
|
|
661
|
+
"field:permission-facet-link",
|
|
662
|
+
"field:phone",
|
|
663
|
+
"field:progress",
|
|
664
|
+
"field:qrcode",
|
|
665
|
+
"field:radio",
|
|
666
|
+
"field:rating",
|
|
667
|
+
"field:recipient-picker",
|
|
668
|
+
"field:richtext",
|
|
669
|
+
"field:select",
|
|
670
|
+
"field:signature",
|
|
671
|
+
"field:slider",
|
|
672
|
+
"field:summary",
|
|
673
|
+
"field:tags",
|
|
674
|
+
"field:text",
|
|
675
|
+
"field:textarea",
|
|
676
|
+
"field:time",
|
|
677
|
+
"field:tree",
|
|
678
|
+
"field:url",
|
|
679
|
+
"field:user",
|
|
680
|
+
"field:vector",
|
|
681
|
+
"field:video",
|
|
682
|
+
"figcaption",
|
|
683
|
+
"figure",
|
|
684
|
+
"file-upload",
|
|
685
|
+
"filter-builder",
|
|
686
|
+
"filter-ui",
|
|
687
|
+
"flex",
|
|
688
|
+
"footer",
|
|
689
|
+
"form",
|
|
690
|
+
"global:notifications",
|
|
691
|
+
"global:search",
|
|
692
|
+
"grid",
|
|
693
|
+
"h1",
|
|
694
|
+
"h2",
|
|
695
|
+
"h3",
|
|
696
|
+
"h4",
|
|
697
|
+
"h5",
|
|
698
|
+
"h6",
|
|
699
|
+
"header",
|
|
700
|
+
"header-bar",
|
|
701
|
+
"home",
|
|
702
|
+
"hover-card",
|
|
703
|
+
"hr",
|
|
704
|
+
"html",
|
|
705
|
+
"i",
|
|
706
|
+
"icon",
|
|
707
|
+
"image",
|
|
708
|
+
"img",
|
|
709
|
+
"input",
|
|
710
|
+
"input-otp",
|
|
711
|
+
"ins",
|
|
712
|
+
"kbd",
|
|
713
|
+
"label",
|
|
714
|
+
"layout:app-schema-renderer",
|
|
715
|
+
"layout:page:card",
|
|
716
|
+
"li",
|
|
717
|
+
"list",
|
|
718
|
+
"list-view",
|
|
719
|
+
"loading",
|
|
720
|
+
"main",
|
|
721
|
+
"mark",
|
|
722
|
+
"markdown",
|
|
723
|
+
"marketplace:installed-list",
|
|
724
|
+
"mcp:connect-agent",
|
|
725
|
+
"menubar",
|
|
726
|
+
"nav",
|
|
727
|
+
"nav:breadcrumb",
|
|
728
|
+
"nav:menu",
|
|
729
|
+
"navigation-menu",
|
|
730
|
+
"nl-query",
|
|
731
|
+
"object-calendar",
|
|
732
|
+
"object-chart",
|
|
733
|
+
"object-data-table",
|
|
734
|
+
"object-form",
|
|
735
|
+
"object-gallery",
|
|
736
|
+
"object-gantt",
|
|
737
|
+
"object-grid",
|
|
738
|
+
"object-kanban",
|
|
739
|
+
"object-manager",
|
|
740
|
+
"object-map",
|
|
741
|
+
"object-master-detail-form",
|
|
742
|
+
"object-metric",
|
|
743
|
+
"object-pivot",
|
|
744
|
+
"object-timeline",
|
|
745
|
+
"object-tree",
|
|
746
|
+
"object-view",
|
|
747
|
+
"ol",
|
|
748
|
+
"p",
|
|
749
|
+
"page",
|
|
750
|
+
"page-designer",
|
|
751
|
+
"page:accordion",
|
|
752
|
+
"page:card",
|
|
753
|
+
"page:footer",
|
|
754
|
+
"page:header",
|
|
755
|
+
"page:section",
|
|
756
|
+
"page:sidebar",
|
|
757
|
+
"page:tabs",
|
|
758
|
+
"pagination",
|
|
759
|
+
"password",
|
|
760
|
+
"pivot",
|
|
761
|
+
"plugin-calendar:calendar-view",
|
|
762
|
+
"plugin-calendar:object-calendar",
|
|
763
|
+
"plugin-charts:bar-chart",
|
|
764
|
+
"plugin-charts:chart",
|
|
765
|
+
"plugin-charts:chart:bar",
|
|
766
|
+
"plugin-charts:object-chart",
|
|
767
|
+
"plugin-chatbot:chatbot",
|
|
768
|
+
"plugin-chatbot:chatbot-enhanced",
|
|
769
|
+
"plugin-chatbot:chatbot-floating",
|
|
770
|
+
"plugin-dashboard:dashboard",
|
|
771
|
+
"plugin-dashboard:metric",
|
|
772
|
+
"plugin-dashboard:metric-card",
|
|
773
|
+
"plugin-dashboard:object-data-table",
|
|
774
|
+
"plugin-dashboard:object-metric",
|
|
775
|
+
"plugin-dashboard:object-pivot",
|
|
776
|
+
"plugin-dashboard:pivot",
|
|
777
|
+
"plugin-designer:data-model-designer",
|
|
778
|
+
"plugin-designer:field-designer",
|
|
779
|
+
"plugin-designer:object-manager",
|
|
780
|
+
"plugin-designer:page-designer",
|
|
781
|
+
"plugin-designer:process-designer",
|
|
782
|
+
"plugin-designer:report-designer",
|
|
783
|
+
"plugin-detail:detail-section",
|
|
784
|
+
"plugin-detail:detail-view",
|
|
785
|
+
"plugin-editor:code-editor",
|
|
786
|
+
"plugin-form:embeddable-form",
|
|
787
|
+
"plugin-form:object-form",
|
|
788
|
+
"plugin-form:object-master-detail-form",
|
|
789
|
+
"plugin-gantt:object-gantt",
|
|
790
|
+
"plugin-grid:object-grid",
|
|
791
|
+
"plugin-kanban:object-kanban",
|
|
792
|
+
"plugin-list:list-view",
|
|
793
|
+
"plugin-list:object-gallery",
|
|
794
|
+
"plugin-map:object-map",
|
|
795
|
+
"plugin-markdown:markdown",
|
|
796
|
+
"plugin-report:report",
|
|
797
|
+
"plugin-report:report-viewer",
|
|
798
|
+
"plugin-timeline:object-timeline",
|
|
799
|
+
"plugin-timeline:timeline",
|
|
800
|
+
"plugin-tree:object-tree",
|
|
801
|
+
"plugin-view:object-view",
|
|
802
|
+
"plugin-view:view:simple",
|
|
803
|
+
"popover",
|
|
804
|
+
"pre",
|
|
805
|
+
"process-designer",
|
|
806
|
+
"progress",
|
|
807
|
+
"protocol-placeholder:ai:feedback",
|
|
808
|
+
"protocol-placeholder:ai:input",
|
|
809
|
+
"protocol-placeholder:ai:suggestion",
|
|
810
|
+
"protocol-placeholder:app:launcher",
|
|
811
|
+
"protocol-placeholder:field:address",
|
|
812
|
+
"protocol-placeholder:field:audio",
|
|
813
|
+
"protocol-placeholder:field:avatar",
|
|
814
|
+
"protocol-placeholder:field:boolean",
|
|
815
|
+
"protocol-placeholder:field:checkboxes",
|
|
816
|
+
"protocol-placeholder:field:code",
|
|
817
|
+
"protocol-placeholder:field:color",
|
|
818
|
+
"protocol-placeholder:field:currency",
|
|
819
|
+
"protocol-placeholder:field:date",
|
|
820
|
+
"protocol-placeholder:field:datetime",
|
|
821
|
+
"protocol-placeholder:field:duration",
|
|
822
|
+
"protocol-placeholder:field:email",
|
|
823
|
+
"protocol-placeholder:field:file",
|
|
824
|
+
"protocol-placeholder:field:html",
|
|
825
|
+
"protocol-placeholder:field:image",
|
|
826
|
+
"protocol-placeholder:field:json",
|
|
827
|
+
"protocol-placeholder:field:location",
|
|
828
|
+
"protocol-placeholder:field:lookup",
|
|
829
|
+
"protocol-placeholder:field:markdown",
|
|
830
|
+
"protocol-placeholder:field:master_detail",
|
|
831
|
+
"protocol-placeholder:field:multiselect",
|
|
832
|
+
"protocol-placeholder:field:number",
|
|
833
|
+
"protocol-placeholder:field:password",
|
|
834
|
+
"protocol-placeholder:field:percent",
|
|
835
|
+
"protocol-placeholder:field:phone",
|
|
836
|
+
"protocol-placeholder:field:progress",
|
|
837
|
+
"protocol-placeholder:field:qrcode",
|
|
838
|
+
"protocol-placeholder:field:radio",
|
|
839
|
+
"protocol-placeholder:field:rating",
|
|
840
|
+
"protocol-placeholder:field:select",
|
|
841
|
+
"protocol-placeholder:field:signature",
|
|
842
|
+
"protocol-placeholder:field:slider",
|
|
843
|
+
"protocol-placeholder:field:text",
|
|
844
|
+
"protocol-placeholder:field:textarea",
|
|
845
|
+
"protocol-placeholder:field:time",
|
|
846
|
+
"protocol-placeholder:field:tree",
|
|
847
|
+
"protocol-placeholder:field:url",
|
|
848
|
+
"protocol-placeholder:field:video",
|
|
849
|
+
"protocol-placeholder:global:notifications",
|
|
850
|
+
"protocol-placeholder:global:search",
|
|
851
|
+
"protocol-placeholder:nav:breadcrumb",
|
|
852
|
+
"protocol-placeholder:nav:menu",
|
|
853
|
+
"protocol-placeholder:page:accordion",
|
|
854
|
+
"protocol-placeholder:page:card",
|
|
855
|
+
"protocol-placeholder:page:footer",
|
|
856
|
+
"protocol-placeholder:page:header",
|
|
857
|
+
"protocol-placeholder:page:sidebar",
|
|
858
|
+
"protocol-placeholder:page:tabs",
|
|
859
|
+
"protocol-placeholder:record:activity",
|
|
860
|
+
"protocol-placeholder:record:chatter",
|
|
861
|
+
"protocol-placeholder:record:details",
|
|
862
|
+
"protocol-placeholder:record:highlights",
|
|
863
|
+
"protocol-placeholder:record:path",
|
|
864
|
+
"protocol-placeholder:record:related_list",
|
|
865
|
+
"protocol-placeholder:view:calendar",
|
|
866
|
+
"protocol-placeholder:view:drawer",
|
|
867
|
+
"protocol-placeholder:view:gallery",
|
|
868
|
+
"protocol-placeholder:view:gantt",
|
|
869
|
+
"protocol-placeholder:view:grid",
|
|
870
|
+
"protocol-placeholder:view:kanban",
|
|
871
|
+
"protocol-placeholder:view:map",
|
|
872
|
+
"protocol-placeholder:view:modal",
|
|
873
|
+
"protocol-placeholder:view:simple",
|
|
874
|
+
"protocol-placeholder:view:split",
|
|
875
|
+
"protocol-placeholder:view:tabbed",
|
|
876
|
+
"protocol-placeholder:view:timeline",
|
|
877
|
+
"protocol-placeholder:view:wizard",
|
|
878
|
+
"protocol-placeholder:widget:bar",
|
|
879
|
+
"protocol-placeholder:widget:funnel",
|
|
880
|
+
"protocol-placeholder:widget:heatmap",
|
|
881
|
+
"protocol-placeholder:widget:image",
|
|
882
|
+
"protocol-placeholder:widget:line",
|
|
883
|
+
"protocol-placeholder:widget:metric",
|
|
884
|
+
"protocol-placeholder:widget:pie",
|
|
885
|
+
"protocol-placeholder:widget:pivot",
|
|
886
|
+
"protocol-placeholder:widget:radar",
|
|
887
|
+
"protocol-placeholder:widget:scatter",
|
|
888
|
+
"protocol-placeholder:widget:table",
|
|
889
|
+
"protocol-placeholder:widget:text",
|
|
890
|
+
"q",
|
|
891
|
+
"radio-group",
|
|
892
|
+
"record",
|
|
893
|
+
"record:activity",
|
|
894
|
+
"record:alert",
|
|
895
|
+
"record:approvals",
|
|
896
|
+
"record:attachments",
|
|
897
|
+
"record:chatter",
|
|
898
|
+
"record:details",
|
|
899
|
+
"record:discussion",
|
|
900
|
+
"record:highlights",
|
|
901
|
+
"record:history",
|
|
902
|
+
"record:line_items",
|
|
903
|
+
"record:path",
|
|
904
|
+
"record:quick_actions",
|
|
905
|
+
"record:reference_rail",
|
|
906
|
+
"record:related_list",
|
|
907
|
+
"report",
|
|
908
|
+
"report-designer",
|
|
909
|
+
"report-viewer",
|
|
910
|
+
"resizable",
|
|
911
|
+
"scroll-area",
|
|
912
|
+
"section",
|
|
913
|
+
"select",
|
|
914
|
+
"separator",
|
|
915
|
+
"sheet",
|
|
916
|
+
"sidebar",
|
|
917
|
+
"skeleton",
|
|
918
|
+
"slider",
|
|
919
|
+
"small",
|
|
920
|
+
"sonner",
|
|
921
|
+
"sort-ui",
|
|
922
|
+
"span",
|
|
923
|
+
"spinner",
|
|
924
|
+
"stack",
|
|
925
|
+
"statistic",
|
|
926
|
+
"strong",
|
|
927
|
+
"sub",
|
|
928
|
+
"sup",
|
|
929
|
+
"switch",
|
|
930
|
+
"table",
|
|
931
|
+
"tabs",
|
|
932
|
+
"text",
|
|
933
|
+
"textarea",
|
|
934
|
+
"time",
|
|
935
|
+
"timeline",
|
|
936
|
+
"toast",
|
|
937
|
+
"toaster",
|
|
938
|
+
"toggle",
|
|
939
|
+
"toggle-group",
|
|
940
|
+
"tooltip",
|
|
941
|
+
"tree-view",
|
|
942
|
+
"u",
|
|
943
|
+
"ui:a",
|
|
944
|
+
"ui:abbr",
|
|
945
|
+
"ui:accordion",
|
|
946
|
+
"ui:address",
|
|
947
|
+
"ui:alert",
|
|
948
|
+
"ui:alert-dialog",
|
|
949
|
+
"ui:app",
|
|
950
|
+
"ui:article",
|
|
951
|
+
"ui:aside",
|
|
952
|
+
"ui:aspect-ratio",
|
|
953
|
+
"ui:avatar",
|
|
954
|
+
"ui:b",
|
|
955
|
+
"ui:badge",
|
|
956
|
+
"ui:blockquote",
|
|
957
|
+
"ui:box",
|
|
958
|
+
"ui:br",
|
|
959
|
+
"ui:breadcrumb",
|
|
960
|
+
"ui:button",
|
|
961
|
+
"ui:button-group",
|
|
962
|
+
"ui:calendar",
|
|
963
|
+
"ui:card",
|
|
964
|
+
"ui:carousel",
|
|
965
|
+
"ui:checkbox",
|
|
966
|
+
"ui:cite",
|
|
967
|
+
"ui:code",
|
|
968
|
+
"ui:collapsible",
|
|
969
|
+
"ui:combobox",
|
|
970
|
+
"ui:command",
|
|
971
|
+
"ui:container",
|
|
972
|
+
"ui:context-menu",
|
|
973
|
+
"ui:data-table",
|
|
974
|
+
"ui:date-picker",
|
|
975
|
+
"ui:dd",
|
|
976
|
+
"ui:del",
|
|
977
|
+
"ui:dialog",
|
|
978
|
+
"ui:div",
|
|
979
|
+
"ui:dl",
|
|
980
|
+
"ui:drawer",
|
|
981
|
+
"ui:dropdown-menu",
|
|
982
|
+
"ui:dt",
|
|
983
|
+
"ui:em",
|
|
984
|
+
"ui:email",
|
|
985
|
+
"ui:empty",
|
|
986
|
+
"ui:figcaption",
|
|
987
|
+
"ui:figure",
|
|
988
|
+
"ui:file-upload",
|
|
989
|
+
"ui:filter-builder",
|
|
990
|
+
"ui:flex",
|
|
991
|
+
"ui:footer",
|
|
992
|
+
"ui:grid",
|
|
993
|
+
"ui:h1",
|
|
994
|
+
"ui:h2",
|
|
995
|
+
"ui:h3",
|
|
996
|
+
"ui:h4",
|
|
997
|
+
"ui:h5",
|
|
998
|
+
"ui:h6",
|
|
999
|
+
"ui:header",
|
|
1000
|
+
"ui:header-bar",
|
|
1001
|
+
"ui:home",
|
|
1002
|
+
"ui:hover-card",
|
|
1003
|
+
"ui:hr",
|
|
1004
|
+
"ui:html",
|
|
1005
|
+
"ui:i",
|
|
1006
|
+
"ui:icon",
|
|
1007
|
+
"ui:image",
|
|
1008
|
+
"ui:img",
|
|
1009
|
+
"ui:input",
|
|
1010
|
+
"ui:input-otp",
|
|
1011
|
+
"ui:ins",
|
|
1012
|
+
"ui:kbd",
|
|
1013
|
+
"ui:label",
|
|
1014
|
+
"ui:li",
|
|
1015
|
+
"ui:list",
|
|
1016
|
+
"ui:loading",
|
|
1017
|
+
"ui:main",
|
|
1018
|
+
"ui:mark",
|
|
1019
|
+
"ui:menubar",
|
|
1020
|
+
"ui:nav",
|
|
1021
|
+
"ui:navigation-menu",
|
|
1022
|
+
"ui:ol",
|
|
1023
|
+
"ui:p",
|
|
1024
|
+
"ui:page",
|
|
1025
|
+
"ui:pagination",
|
|
1026
|
+
"ui:password",
|
|
1027
|
+
"ui:popover",
|
|
1028
|
+
"ui:pre",
|
|
1029
|
+
"ui:progress",
|
|
1030
|
+
"ui:q",
|
|
1031
|
+
"ui:radio-group",
|
|
1032
|
+
"ui:record",
|
|
1033
|
+
"ui:resizable",
|
|
1034
|
+
"ui:scroll-area",
|
|
1035
|
+
"ui:section",
|
|
1036
|
+
"ui:select",
|
|
1037
|
+
"ui:separator",
|
|
1038
|
+
"ui:sheet",
|
|
1039
|
+
"ui:sidebar",
|
|
1040
|
+
"ui:skeleton",
|
|
1041
|
+
"ui:slider",
|
|
1042
|
+
"ui:small",
|
|
1043
|
+
"ui:sonner",
|
|
1044
|
+
"ui:span",
|
|
1045
|
+
"ui:spinner",
|
|
1046
|
+
"ui:stack",
|
|
1047
|
+
"ui:statistic",
|
|
1048
|
+
"ui:strong",
|
|
1049
|
+
"ui:sub",
|
|
1050
|
+
"ui:sup",
|
|
1051
|
+
"ui:switch",
|
|
1052
|
+
"ui:table",
|
|
1053
|
+
"ui:tabs",
|
|
1054
|
+
"ui:text",
|
|
1055
|
+
"ui:textarea",
|
|
1056
|
+
"ui:time",
|
|
1057
|
+
"ui:toast",
|
|
1058
|
+
"ui:toaster",
|
|
1059
|
+
"ui:toggle",
|
|
1060
|
+
"ui:toggle-group",
|
|
1061
|
+
"ui:tooltip",
|
|
1062
|
+
"ui:tree-view",
|
|
1063
|
+
"ui:u",
|
|
1064
|
+
"ui:ul",
|
|
1065
|
+
"ui:utility",
|
|
1066
|
+
"ul",
|
|
1067
|
+
"utility",
|
|
1068
|
+
"view-switcher",
|
|
1069
|
+
"view:calendar",
|
|
1070
|
+
"view:chart",
|
|
1071
|
+
"view:detail",
|
|
1072
|
+
"view:drawer",
|
|
1073
|
+
"view:filter-ui",
|
|
1074
|
+
"view:form",
|
|
1075
|
+
"view:gallery",
|
|
1076
|
+
"view:gantt",
|
|
1077
|
+
"view:grid",
|
|
1078
|
+
"view:kanban",
|
|
1079
|
+
"view:list",
|
|
1080
|
+
"view:map",
|
|
1081
|
+
"view:modal",
|
|
1082
|
+
"view:simple",
|
|
1083
|
+
"view:sort-ui",
|
|
1084
|
+
"view:split",
|
|
1085
|
+
"view:tabbed",
|
|
1086
|
+
"view:timeline",
|
|
1087
|
+
"view:view-switcher",
|
|
1088
|
+
"view:wizard",
|
|
1089
|
+
"widget:bar",
|
|
1090
|
+
"widget:funnel",
|
|
1091
|
+
"widget:heatmap",
|
|
1092
|
+
"widget:image",
|
|
1093
|
+
"widget:line",
|
|
1094
|
+
"widget:metric",
|
|
1095
|
+
"widget:pie",
|
|
1096
|
+
"widget:pivot",
|
|
1097
|
+
"widget:radar",
|
|
1098
|
+
"widget:scatter",
|
|
1099
|
+
"widget:table",
|
|
1100
|
+
"widget:text"
|
|
1101
|
+
];
|
|
1102
|
+
var KNOWN_SCHEMA_TYPE_SET = new Set(KNOWN_SCHEMA_TYPES);
|
|
1103
|
+
function isKnownSchemaType(type) {
|
|
1104
|
+
return KNOWN_SCHEMA_TYPE_SET.has(type);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// src/utils/known-type-case-suggestion.ts
|
|
1108
|
+
function suggestKnownTypeByCase(type) {
|
|
1109
|
+
if (typeof type !== "string" || type === "") return void 0;
|
|
1110
|
+
const wanted = type.toLowerCase();
|
|
1111
|
+
for (const candidate of KNOWN_SCHEMA_TYPES) {
|
|
1112
|
+
if (candidate !== type && candidate.toLowerCase() === wanted) return candidate;
|
|
1113
|
+
}
|
|
1114
|
+
return void 0;
|
|
1115
|
+
}
|
|
1116
|
+
function didYouMeanClause(type) {
|
|
1117
|
+
const match = suggestKnownTypeByCase(type);
|
|
1118
|
+
return match === void 0 ? "" : ` \u2014 did you mean "${match}"?`;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/utils/unbindable-text-expressions.ts
|
|
1122
|
+
import {
|
|
1123
|
+
EXPRESSION_BINDABLE_TEXT_KEYS,
|
|
1124
|
+
expressionBindableTextKeysFor
|
|
1125
|
+
} from "@objectstack/spec/ui";
|
|
1126
|
+
var EXPRESSION_PATTERN = /\$\{[^}]+\}/;
|
|
1127
|
+
var COMPOSITION_KEY = "children";
|
|
1128
|
+
var PAGE_DOCUMENT_ROOT_TYPE = "page";
|
|
1129
|
+
function isComponentNode(value) {
|
|
1130
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string";
|
|
1131
|
+
}
|
|
1132
|
+
function judge(node, path, into) {
|
|
1133
|
+
const evaluatedKeys = expressionBindableTextKeysFor(node.type);
|
|
1134
|
+
for (const key of EXPRESSION_BINDABLE_TEXT_KEYS) {
|
|
1135
|
+
const value = node[key];
|
|
1136
|
+
if (typeof value !== "string" || !EXPRESSION_PATTERN.test(value)) continue;
|
|
1137
|
+
if (evaluatedKeys.includes(key)) continue;
|
|
1138
|
+
into.push({
|
|
1139
|
+
severity: isKnownSchemaType(node.type) ? "refusal" : "warning",
|
|
1140
|
+
path: [...path, key],
|
|
1141
|
+
type: node.type,
|
|
1142
|
+
key,
|
|
1143
|
+
evaluatedKeys
|
|
1144
|
+
});
|
|
770
1145
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1146
|
+
}
|
|
1147
|
+
function visit(node, path, isDocumentRoot, into) {
|
|
1148
|
+
if (!(isDocumentRoot && node.type === PAGE_DOCUMENT_ROOT_TYPE)) {
|
|
1149
|
+
judge(node, path, into);
|
|
1150
|
+
}
|
|
1151
|
+
const children = node[COMPOSITION_KEY];
|
|
1152
|
+
if (Array.isArray(children)) {
|
|
1153
|
+
children.forEach((child, index) => {
|
|
1154
|
+
if (isComponentNode(child)) visit(child, [...path, COMPOSITION_KEY, index], false, into);
|
|
1155
|
+
});
|
|
1156
|
+
} else if (isComponentNode(children)) {
|
|
1157
|
+
visit(children, [...path, COMPOSITION_KEY], false, into);
|
|
776
1158
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
})
|
|
790
|
-
|
|
1159
|
+
}
|
|
1160
|
+
function findUnbindableTextExpressions(document) {
|
|
1161
|
+
const found = [];
|
|
1162
|
+
if (isComponentNode(document)) visit(document, [], true, found);
|
|
1163
|
+
return found;
|
|
1164
|
+
}
|
|
1165
|
+
var inCode = (key) => "`" + key + "`";
|
|
1166
|
+
function describeUnbindableTextExpression(finding) {
|
|
1167
|
+
const at = formatIssuePath(finding.path);
|
|
1168
|
+
if (finding.severity === "warning") {
|
|
1169
|
+
return `at ${at}: "${finding.type}" is not a registered component, so the expression on ${inCode(finding.key)} is not refused; a custom renderer may evaluate it itself.`;
|
|
1170
|
+
}
|
|
1171
|
+
const evaluates = finding.evaluatedKeys.length > 0 ? `it evaluates only ${finding.evaluatedKeys.map(inCode).join(", ")}` : `it evaluates none of ${EXPRESSION_BINDABLE_TEXT_KEYS.map(inCode).join(", ")}`;
|
|
1172
|
+
return `at ${at}: "${finding.type}" never evaluates ${inCode(finding.key)} (${evaluates}), so the expression is never resolved: the user sees its literal text, or nothing.`;
|
|
1173
|
+
}
|
|
1174
|
+
function workingChannels(finding) {
|
|
1175
|
+
const channels = [
|
|
1176
|
+
...finding.evaluatedKeys.map(inCode),
|
|
1177
|
+
inCode("content"),
|
|
1178
|
+
`${inCode(`properties.${finding.key}`)} (evaluated, then hoisted onto the node)`
|
|
1179
|
+
];
|
|
1180
|
+
return `Channels SchemaRenderer evaluates: ${channels.join(", ")}; or resolve the value in the host before the schema reaches SchemaRenderer.`;
|
|
791
1181
|
}
|
|
792
1182
|
|
|
793
1183
|
// src/commands/check.ts
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1184
|
+
var OBJECTUI_STRUCTURAL_KEYS = [
|
|
1185
|
+
// Composition — the `children`/`body`-class keys the ruling names.
|
|
1186
|
+
// `body` is the RETIRED spelling, kept so old files are still identified and
|
|
1187
|
+
// then refused — see the docblock above (objectui#6771).
|
|
1188
|
+
"body",
|
|
1189
|
+
"children",
|
|
1190
|
+
// Presentation.
|
|
1191
|
+
"className",
|
|
1192
|
+
"style",
|
|
1193
|
+
"placeholder",
|
|
1194
|
+
// Conditional rendering and interaction state.
|
|
1195
|
+
"visible",
|
|
1196
|
+
"visibleWhen",
|
|
1197
|
+
"visibleOn",
|
|
1198
|
+
"hidden",
|
|
1199
|
+
"hiddenOn",
|
|
1200
|
+
"disabled",
|
|
1201
|
+
"disabledOn",
|
|
1202
|
+
// Test and accessibility handles.
|
|
1203
|
+
"testId",
|
|
1204
|
+
"ariaLabel"
|
|
1205
|
+
];
|
|
1206
|
+
function recogniseObjectUiSchemaFile(content) {
|
|
1207
|
+
if (OBJECTUI_STRUCTURAL_KEYS.some((key) => key in content)) {
|
|
1208
|
+
return { recognised: true, validated: false };
|
|
1209
|
+
}
|
|
1210
|
+
const result = safeValidateSchema(content);
|
|
1211
|
+
return result.success ? { recognised: true, validated: true } : { recognised: false, issues: result.error.issues };
|
|
1212
|
+
}
|
|
1213
|
+
function describeFirstIssue(issues) {
|
|
1214
|
+
const [first] = issues;
|
|
1215
|
+
if (first === void 0) return void 0;
|
|
1216
|
+
const lead = issues.length === 1 ? "Issue" : `First of ${issues.length} issues`;
|
|
1217
|
+
return `${lead} at ${formatIssuePath(first.path)}: ${first.message}`;
|
|
1218
|
+
}
|
|
1219
|
+
function closingLine(tally) {
|
|
1220
|
+
return `ObjectUI schema files: ${tally.validated} validated, ${tally.notValidated} recognised but not validated, ${tally.didNotValidate} did not validate. This sweep is advisory; run \`objectui validate <file>\` for a verdict.`;
|
|
1221
|
+
}
|
|
1222
|
+
function isUnvalidatedCandidate(type) {
|
|
1223
|
+
return isKnownSchemaType(type);
|
|
1224
|
+
}
|
|
1225
|
+
function describeParseError(text, error) {
|
|
1226
|
+
const upTo = text.slice(0, error.offset);
|
|
1227
|
+
const line = upTo.split("\n").length;
|
|
1228
|
+
const column = error.offset - (upTo.lastIndexOf("\n") + 1) + 1;
|
|
1229
|
+
return `${printParseErrorCode(error.error)} at line ${line} column ${column}`;
|
|
1230
|
+
}
|
|
1231
|
+
async function check(cwd = process.cwd()) {
|
|
1232
|
+
console.log(chalk6.bold("Object UI Schema Check"));
|
|
801
1233
|
const files = globSync("**/*.{json,yaml,yml}", {
|
|
802
1234
|
cwd,
|
|
803
|
-
ignore
|
|
1235
|
+
// `glob` matches `ignore` patterns against the path relative to `cwd`, so
|
|
1236
|
+
// an unanchored `dist/**`/`node_modules/**` excludes only a directory of
|
|
1237
|
+
// that name at the scan root — every nested `packages/*/dist/`,
|
|
1238
|
+
// `examples/*/dist/`, `apps/*/dist/` (and their `node_modules/`) is still
|
|
1239
|
+
// scanned. `**/` anchors the match at any depth (objectui#6320): measured
|
|
1240
|
+
// on this repository, a built tree without it reports roughly double the
|
|
1241
|
+
// files of a clean checkout, almost all of it re-reading the author's own
|
|
1242
|
+
// schemas from build output.
|
|
1243
|
+
ignore: ["**/node_modules/**", "**/dist/**", ".git/**"]
|
|
804
1244
|
});
|
|
805
1245
|
console.log(`Analyzing ${files.length} files...`);
|
|
806
1246
|
let errors = 0;
|
|
1247
|
+
let skipped = 0;
|
|
1248
|
+
const unvalidatedCandidates = [];
|
|
1249
|
+
let validated = 0;
|
|
1250
|
+
let notValidated = 0;
|
|
807
1251
|
for (const file of files) {
|
|
808
1252
|
try {
|
|
809
1253
|
if (file.endsWith(".json")) {
|
|
810
|
-
const
|
|
1254
|
+
const text = readFileSync2(join6(cwd, file), "utf-8");
|
|
1255
|
+
const parseErrors = [];
|
|
1256
|
+
const content = parseJsonc(text, parseErrors, { allowTrailingComma: true });
|
|
1257
|
+
if (parseErrors.length > 0) {
|
|
1258
|
+
const [first] = parseErrors;
|
|
1259
|
+
console.log(
|
|
1260
|
+
chalk6.red(`x Invalid JSON in ${file}: ${describeParseError(text, first)}`)
|
|
1261
|
+
);
|
|
1262
|
+
errors++;
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
811
1265
|
if (content && typeof content === "object" && content.type) {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1266
|
+
if (typeof content.type === "string") {
|
|
1267
|
+
const recognition = recogniseObjectUiSchemaFile(
|
|
1268
|
+
content
|
|
1269
|
+
);
|
|
1270
|
+
if (!recognition.recognised) {
|
|
1271
|
+
if (isUnvalidatedCandidate(content.type)) {
|
|
1272
|
+
unvalidatedCandidates.push({
|
|
1273
|
+
file,
|
|
1274
|
+
type: content.type,
|
|
1275
|
+
issues: recognition.issues
|
|
1276
|
+
});
|
|
1277
|
+
} else {
|
|
1278
|
+
skipped++;
|
|
1279
|
+
}
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (recognition.validated) {
|
|
1283
|
+
validated++;
|
|
1284
|
+
} else {
|
|
1285
|
+
notValidated++;
|
|
1286
|
+
}
|
|
1287
|
+
if (!isKnownSchemaType(content.type)) {
|
|
1288
|
+
console.log(
|
|
1289
|
+
chalk6.yellow(
|
|
1290
|
+
`\u26A0\uFE0F Unknown schema type "${content.type}" in ${file}` + didYouMeanClause(content.type)
|
|
1291
|
+
)
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
for (const finding of findUnbindableTextExpressions(content)) {
|
|
1295
|
+
const line = `${file} ${describeUnbindableTextExpression(finding)}`;
|
|
1296
|
+
if (finding.severity === "refusal") {
|
|
1297
|
+
console.log(chalk6.red(`x Unevaluated expression in ${line}`));
|
|
1298
|
+
console.log(chalk6.dim(` ${workingChannels(finding)}`));
|
|
1299
|
+
errors++;
|
|
1300
|
+
} else {
|
|
1301
|
+
console.log(chalk6.yellow(`\u26A0\uFE0F Expression not judged in ${line}`));
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
833
1304
|
}
|
|
834
1305
|
}
|
|
835
1306
|
}
|
|
836
1307
|
} catch (e) {
|
|
837
|
-
console.log(
|
|
1308
|
+
console.log(chalk6.red(`x Invalid JSON in ${file}: ${e.message}`));
|
|
838
1309
|
errors++;
|
|
839
1310
|
}
|
|
840
1311
|
}
|
|
1312
|
+
if (unvalidatedCandidates.length > 0) {
|
|
1313
|
+
const n = unvalidatedCandidates.length;
|
|
1314
|
+
console.log(
|
|
1315
|
+
chalk6.yellow(
|
|
1316
|
+
`\u26A0\uFE0F ${n} file${n === 1 ? "" : "s"} carr${n === 1 ? "ies" : "y"} a registered ObjectUI component type but did not validate as an ObjectUI schema:`
|
|
1317
|
+
)
|
|
1318
|
+
);
|
|
1319
|
+
for (const { file, type, issues } of unvalidatedCandidates) {
|
|
1320
|
+
console.log(chalk6.yellow(` ${file} (type "${type}")`));
|
|
1321
|
+
const firstIssue = describeFirstIssue(issues);
|
|
1322
|
+
if (firstIssue !== void 0) {
|
|
1323
|
+
console.log(chalk6.yellow(` ${firstIssue}`));
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
console.log(
|
|
1327
|
+
chalk6.dim(" Run `objectui validate <file>` on any of them for the full diagnosis.")
|
|
1328
|
+
);
|
|
1329
|
+
console.log(
|
|
1330
|
+
chalk6.dim(
|
|
1331
|
+
" Either the document is off-spec, or its component type is not modelled by @object-ui/types."
|
|
1332
|
+
)
|
|
1333
|
+
);
|
|
1334
|
+
console.log(
|
|
1335
|
+
chalk6.dim(
|
|
1336
|
+
" These are NOT counted as skipped: a schema that fails validation is a broken schema, not a foreign file."
|
|
1337
|
+
)
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
if (skipped > 0) {
|
|
1341
|
+
console.log(
|
|
1342
|
+
chalk6.dim(
|
|
1343
|
+
`Skipped ${skipped} file${skipped === 1 ? "" : "s"} with a root "type" that no ObjectUI recogniser admitted.`
|
|
1344
|
+
)
|
|
1345
|
+
);
|
|
1346
|
+
console.log(
|
|
1347
|
+
chalk6.dim(
|
|
1348
|
+
` A file is checked when its root carries an ObjectUI structural key: ${OBJECTUI_STRUCTURAL_KEYS.join(", ")}.`
|
|
1349
|
+
)
|
|
1350
|
+
);
|
|
1351
|
+
console.log(
|
|
1352
|
+
chalk6.dim(
|
|
1353
|
+
" It is also checked when the whole document validates as an ObjectUI component schema."
|
|
1354
|
+
)
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
841
1357
|
if (errors === 0) {
|
|
842
|
-
console.log(
|
|
1358
|
+
console.log(
|
|
1359
|
+
closingLine({
|
|
1360
|
+
validated,
|
|
1361
|
+
notValidated,
|
|
1362
|
+
didNotValidate: unvalidatedCandidates.length
|
|
1363
|
+
})
|
|
1364
|
+
);
|
|
843
1365
|
} else {
|
|
844
|
-
console.log(
|
|
1366
|
+
console.log(chalk6.red(`Found ${errors} errors`));
|
|
845
1367
|
process.exit(1);
|
|
846
1368
|
}
|
|
847
1369
|
}
|
|
848
1370
|
|
|
849
1371
|
// src/commands/validate.ts
|
|
850
|
-
import
|
|
851
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
852
|
-
import { resolve as
|
|
1372
|
+
import chalk7 from "chalk";
|
|
1373
|
+
import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
|
|
1374
|
+
import { resolve as resolve3 } from "path";
|
|
853
1375
|
import { load as loadYaml } from "js-yaml";
|
|
854
|
-
import { safeValidateSchema } from "@object-ui/types/zod";
|
|
1376
|
+
import { safeValidateSchema as safeValidateSchema2 } from "@object-ui/types/zod";
|
|
855
1377
|
|
|
856
1378
|
// src/utils/spec-vocabulary-hint.ts
|
|
857
1379
|
function findSpecVocabularyFormFields(node, path = "") {
|
|
@@ -882,53 +1404,185 @@ function findSpecVocabularyFormFields(node, path = "") {
|
|
|
882
1404
|
return findings;
|
|
883
1405
|
}
|
|
884
1406
|
|
|
1407
|
+
// src/utils/union-arm-diagnostics.ts
|
|
1408
|
+
var MAX_UNION_ARMS_REPORTED = 5;
|
|
1409
|
+
function editDistance(a, b) {
|
|
1410
|
+
if (a === b) return 0;
|
|
1411
|
+
if (a.length === 0) return b.length;
|
|
1412
|
+
if (b.length === 0) return a.length;
|
|
1413
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1414
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
1415
|
+
const current = [i];
|
|
1416
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
1417
|
+
current[j] = Math.min(
|
|
1418
|
+
previous[j] + 1,
|
|
1419
|
+
current[j - 1] + 1,
|
|
1420
|
+
previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
previous = current;
|
|
1424
|
+
}
|
|
1425
|
+
return previous[b.length];
|
|
1426
|
+
}
|
|
1427
|
+
function nearestArmNames(authoredType, armNames) {
|
|
1428
|
+
const unique = [...new Set(armNames)].sort();
|
|
1429
|
+
if (typeof authoredType !== "string" || authoredType === "") {
|
|
1430
|
+
return { candidates: [], total: unique.length };
|
|
1431
|
+
}
|
|
1432
|
+
const ordered = [...unique].sort((x, y) => {
|
|
1433
|
+
const d = editDistance(authoredType, x) - editDistance(authoredType, y);
|
|
1434
|
+
return d !== 0 ? d : x.localeCompare(y);
|
|
1435
|
+
});
|
|
1436
|
+
return { candidates: ordered.slice(0, MAX_UNION_ARMS_REPORTED), total: unique.length };
|
|
1437
|
+
}
|
|
1438
|
+
function authoredTypeAt(document, path) {
|
|
1439
|
+
let node = document;
|
|
1440
|
+
for (const key of path) {
|
|
1441
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
1442
|
+
node = node[key];
|
|
1443
|
+
}
|
|
1444
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
1445
|
+
const type = node.type;
|
|
1446
|
+
return typeof type === "string" ? type : void 0;
|
|
1447
|
+
}
|
|
1448
|
+
function isUnion(issue) {
|
|
1449
|
+
return issue.code === "invalid_union" && Array.isArray(issue.errors);
|
|
1450
|
+
}
|
|
1451
|
+
var NO_MATCHING_DISCRIMINATOR = "No matching discriminator";
|
|
1452
|
+
function noArmAccepts(node, literals, document) {
|
|
1453
|
+
const authoredType = authoredTypeAt(document, node);
|
|
1454
|
+
const { candidates, total } = nearestArmNames(authoredType, literals);
|
|
1455
|
+
return {
|
|
1456
|
+
kind: "note",
|
|
1457
|
+
path: [...node],
|
|
1458
|
+
...authoredType === void 0 ? {} : { authoredType },
|
|
1459
|
+
candidates,
|
|
1460
|
+
totalArmNames: total
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
function noMatchingDiscriminator(issue, document) {
|
|
1464
|
+
if (issue.code !== "invalid_union") return void 0;
|
|
1465
|
+
if (issue.note !== NO_MATCHING_DISCRIMINATOR) return void 0;
|
|
1466
|
+
if (!Array.isArray(issue.options)) return void 0;
|
|
1467
|
+
if ((issue.errors ?? []).length > 0) return void 0;
|
|
1468
|
+
const path = issue.path ?? [];
|
|
1469
|
+
if (path[path.length - 1] !== "type") return void 0;
|
|
1470
|
+
if (issue.discriminator !== void 0 && issue.discriminator !== "type") return void 0;
|
|
1471
|
+
const literals = issue.options.filter((v) => typeof v === "string");
|
|
1472
|
+
if (literals.length === 0) return void 0;
|
|
1473
|
+
return noArmAccepts(path.slice(0, -1), literals, document);
|
|
1474
|
+
}
|
|
1475
|
+
function isAtTypeKey(issue) {
|
|
1476
|
+
const path = issue.path ?? [];
|
|
1477
|
+
return path.length === 1 && path[0] === "type";
|
|
1478
|
+
}
|
|
1479
|
+
function declaredTypeLiterals(armIssues) {
|
|
1480
|
+
const literals = [];
|
|
1481
|
+
let declares = false;
|
|
1482
|
+
for (const issue of armIssues) {
|
|
1483
|
+
if (isAtTypeKey(issue) && issue.code === "invalid_value" && Array.isArray(issue.values)) {
|
|
1484
|
+
declares = true;
|
|
1485
|
+
literals.push(...issue.values.filter((v) => typeof v === "string"));
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
if (isAtTypeKey(issue) && isUnion(issue) && Array.isArray(issue.options)) {
|
|
1489
|
+
declares = true;
|
|
1490
|
+
literals.push(...issue.options.filter((v) => typeof v === "string"));
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
if ((issue.path ?? []).length === 0 && isUnion(issue)) {
|
|
1494
|
+
const nested = (issue.errors ?? []).map((sub) => declaredTypeLiterals(sub));
|
|
1495
|
+
if (nested.length > 0 && nested.every((n) => n !== void 0)) {
|
|
1496
|
+
declares = true;
|
|
1497
|
+
for (const n of nested) literals.push(...n);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return declares ? literals : void 0;
|
|
1502
|
+
}
|
|
1503
|
+
function explain(unionIssue, prefix, document) {
|
|
1504
|
+
const arms = unionIssue.errors ?? [];
|
|
1505
|
+
const declared = arms.map((arm) => declaredTypeLiterals(arm));
|
|
1506
|
+
const declaringArms = declared.filter((d) => d !== void 0);
|
|
1507
|
+
const acceptingIndexes = declared.map((d, i) => d === void 0 ? i : -1).filter((i) => i >= 0);
|
|
1508
|
+
const expand = (armIssues, arm) => armIssues.flatMap((issue) => {
|
|
1509
|
+
const absolute = [...prefix, ...issue.path ?? []];
|
|
1510
|
+
const discriminated = noMatchingDiscriminator({ ...issue, path: absolute }, document);
|
|
1511
|
+
if (discriminated) return [discriminated];
|
|
1512
|
+
if (isUnion(issue)) return explain(issue, absolute, document);
|
|
1513
|
+
return [
|
|
1514
|
+
{
|
|
1515
|
+
kind: "issue",
|
|
1516
|
+
path: absolute,
|
|
1517
|
+
message: issue.message ?? "",
|
|
1518
|
+
code: issue.code,
|
|
1519
|
+
...arm === void 0 ? {} : { arm }
|
|
1520
|
+
}
|
|
1521
|
+
];
|
|
1522
|
+
});
|
|
1523
|
+
if (declaringArms.length > 0 && acceptingIndexes.length === 1) {
|
|
1524
|
+
return expand(arms[acceptingIndexes[0]]);
|
|
1525
|
+
}
|
|
1526
|
+
if (declaringArms.length > 0 && acceptingIndexes.length === 0) {
|
|
1527
|
+
return [noArmAccepts(prefix, declaringArms.flat(), document)];
|
|
1528
|
+
}
|
|
1529
|
+
return arms.slice(0, MAX_UNION_ARMS_REPORTED).flatMap((arm, i) => expand(arm, `${i + 1}/${arms.length}`));
|
|
1530
|
+
}
|
|
1531
|
+
function explainUnionIssue(issue, document) {
|
|
1532
|
+
const discriminated = noMatchingDiscriminator(issue, document);
|
|
1533
|
+
if (discriminated) return [discriminated];
|
|
1534
|
+
if (!isUnion(issue)) return [];
|
|
1535
|
+
return explain(issue, issue.path ?? [], document);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
885
1538
|
// src/commands/validate.ts
|
|
886
1539
|
async function validate(schemaPath) {
|
|
887
|
-
console.log(
|
|
888
|
-
const resolvedPath =
|
|
889
|
-
if (!
|
|
890
|
-
console.error(
|
|
1540
|
+
console.log(chalk7.blue("\u{1F50D} ObjectUI Schema Validator\n"));
|
|
1541
|
+
const resolvedPath = resolve3(process.cwd(), schemaPath);
|
|
1542
|
+
if (!existsSync5(resolvedPath)) {
|
|
1543
|
+
console.error(chalk7.red(`\u2717 Error: Schema file not found: ${schemaPath}`));
|
|
891
1544
|
process.exit(1);
|
|
892
1545
|
}
|
|
893
1546
|
try {
|
|
894
1547
|
const fileContent = readFileSync3(resolvedPath, "utf-8");
|
|
895
1548
|
let schema;
|
|
896
1549
|
if (schemaPath.endsWith(".yaml") || schemaPath.endsWith(".yml")) {
|
|
897
|
-
console.log(
|
|
1550
|
+
console.log(chalk7.gray(`Reading YAML schema from: ${schemaPath}`));
|
|
898
1551
|
schema = loadYaml(fileContent);
|
|
899
1552
|
} else if (schemaPath.endsWith(".json")) {
|
|
900
|
-
console.log(
|
|
1553
|
+
console.log(chalk7.gray(`Reading JSON schema from: ${schemaPath}`));
|
|
901
1554
|
schema = JSON.parse(fileContent);
|
|
902
1555
|
} else {
|
|
903
1556
|
try {
|
|
904
1557
|
schema = JSON.parse(fileContent);
|
|
905
|
-
console.log(
|
|
1558
|
+
console.log(chalk7.gray(`Reading schema from: ${schemaPath} (detected as JSON)`));
|
|
906
1559
|
} catch {
|
|
907
1560
|
schema = loadYaml(fileContent);
|
|
908
|
-
console.log(
|
|
1561
|
+
console.log(chalk7.gray(`Reading schema from: ${schemaPath} (detected as YAML)`));
|
|
909
1562
|
}
|
|
910
1563
|
}
|
|
911
|
-
console.log(
|
|
912
|
-
const result =
|
|
1564
|
+
console.log(chalk7.gray("Validating schema...\n"));
|
|
1565
|
+
const result = safeValidateSchema2(schema);
|
|
913
1566
|
if (result.success) {
|
|
914
|
-
console.log(
|
|
1567
|
+
console.log(chalk7.green("\u2713 Schema is valid!\n"));
|
|
915
1568
|
const data = result.data;
|
|
916
|
-
console.log(
|
|
917
|
-
console.log(
|
|
1569
|
+
console.log(chalk7.bold("Schema Information:"));
|
|
1570
|
+
console.log(chalk7.gray(" Type:"), data.type || "unknown");
|
|
918
1571
|
if (data.id) {
|
|
919
|
-
console.log(
|
|
1572
|
+
console.log(chalk7.gray(" ID:"), data.id);
|
|
920
1573
|
}
|
|
921
|
-
|
|
922
|
-
|
|
1574
|
+
const title = "title" in data ? data.title : void 0;
|
|
1575
|
+
if (data.label || title) {
|
|
1576
|
+
console.log(chalk7.gray(" Label:"), data.label || title);
|
|
923
1577
|
}
|
|
924
1578
|
if (data.children && Array.isArray(data.children)) {
|
|
925
|
-
console.log(
|
|
1579
|
+
console.log(chalk7.gray(" Children:"), data.children.length);
|
|
926
1580
|
}
|
|
927
1581
|
const mixed = findSpecVocabularyFormFields(schema).filter((f) => f.mixedName);
|
|
928
1582
|
if (mixed.length > 0) {
|
|
929
|
-
console.log(
|
|
1583
|
+
console.log(chalk7.yellow("\nWarnings:"));
|
|
930
1584
|
for (const m of mixed) {
|
|
931
|
-
console.log(
|
|
1585
|
+
console.log(chalk7.yellow(
|
|
932
1586
|
` ${m.path}: '${m.mixedName}' also carries { field: '${m.field}' } \u2014 mixed form-field vocabularies. The renderer ignores \`field\` here; drop one of the two.`
|
|
933
1587
|
));
|
|
934
1588
|
}
|
|
@@ -936,27 +1590,56 @@ async function validate(schemaPath) {
|
|
|
936
1590
|
console.log("");
|
|
937
1591
|
process.exit(0);
|
|
938
1592
|
} else {
|
|
939
|
-
console.error(
|
|
940
|
-
console.error(
|
|
1593
|
+
console.error(chalk7.red("\u2717 Schema validation failed!\n"));
|
|
1594
|
+
console.error(chalk7.bold("Validation Errors:"));
|
|
941
1595
|
result.error.issues.forEach((issue, index) => {
|
|
942
|
-
console.error(
|
|
1596
|
+
console.error(chalk7.red(`
|
|
943
1597
|
${index + 1}. ${issue.message}`));
|
|
944
|
-
|
|
945
|
-
console.error(chalk11.gray(` Path: ${issue.path.join(" \u2192 ")}`));
|
|
946
|
-
}
|
|
1598
|
+
console.error(chalk7.gray(` Path: ${formatIssuePath(issue.path)}`));
|
|
947
1599
|
if (issue.code) {
|
|
948
|
-
console.error(
|
|
1600
|
+
console.error(chalk7.gray(` Code: ${issue.code}`));
|
|
949
1601
|
}
|
|
1602
|
+
explainUnionIssue(issue, schema).forEach((line, sub) => {
|
|
1603
|
+
const where = formatIssuePath(line.path);
|
|
1604
|
+
if (line.kind === "note") {
|
|
1605
|
+
const at = line.path.length > 0 ? ` at ${where}` : "";
|
|
1606
|
+
if (line.authoredType === void 0) {
|
|
1607
|
+
console.error(
|
|
1608
|
+
chalk7.yellow(
|
|
1609
|
+
` No \`type\` is declared${at}, so none of the ${line.totalArmNames} accepted component types can be selected.`
|
|
1610
|
+
)
|
|
1611
|
+
);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
console.error(
|
|
1615
|
+
chalk7.yellow(` No arm accepts type "${line.authoredType}"${at}.`)
|
|
1616
|
+
);
|
|
1617
|
+
if (line.candidates.length > 0) {
|
|
1618
|
+
console.error(
|
|
1619
|
+
chalk7.gray(
|
|
1620
|
+
` Nearest of the ${line.totalArmNames} accepted types: ` + line.candidates.join(", ")
|
|
1621
|
+
)
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
const arm = line.arm === void 0 ? "" : `[arm ${line.arm}] `;
|
|
1627
|
+
console.error(chalk7.red(` ${index + 1}.${sub + 1} ${arm}${line.message}`));
|
|
1628
|
+
console.error(chalk7.gray(` Path: ${where}`));
|
|
1629
|
+
if (line.code) {
|
|
1630
|
+
console.error(chalk7.gray(` Code: ${line.code}`));
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
950
1633
|
});
|
|
951
1634
|
const specShaped = findSpecVocabularyFormFields(schema).filter((f) => !f.mixedName);
|
|
952
1635
|
if (specShaped.length > 0) {
|
|
953
|
-
console.error(
|
|
1636
|
+
console.error(chalk7.bold("\nLikely cause \u2014 spec form-view vocabulary in a standalone form:"));
|
|
954
1637
|
for (const s of specShaped) {
|
|
955
|
-
console.error(
|
|
1638
|
+
console.error(chalk7.yellow(
|
|
956
1639
|
` ${s.path}: { field: '${s.field}' } is the spec form-VIEW shape (an object-field reference).`
|
|
957
1640
|
));
|
|
958
1641
|
}
|
|
959
|
-
console.error(
|
|
1642
|
+
console.error(chalk7.yellow(
|
|
960
1643
|
` A standalone \`type: 'form'\` component uses { name: \u2026 } (the form data path).
|
|
961
1644
|
Fix: author the field with \`name\` and its own type/label \u2014 or, to reference object fields
|
|
962
1645
|
with the spec shape, use an object-bound form (\`type: 'object-form'\` with objectName +
|
|
@@ -967,62 +1650,29 @@ ${index + 1}. ${issue.message}`));
|
|
|
967
1650
|
process.exit(1);
|
|
968
1651
|
}
|
|
969
1652
|
} catch (error) {
|
|
970
|
-
console.error(
|
|
971
|
-
console.error(
|
|
1653
|
+
console.error(chalk7.red("\n\u2717 Error reading or parsing schema file:"));
|
|
1654
|
+
console.error(chalk7.red(error.message));
|
|
972
1655
|
console.error("");
|
|
973
1656
|
process.exit(1);
|
|
974
1657
|
}
|
|
975
1658
|
}
|
|
976
1659
|
|
|
977
|
-
// src/commands/create-plugin.ts
|
|
978
|
-
import chalk12 from "chalk";
|
|
979
|
-
import { spawn as spawn2 } from "child_process";
|
|
980
|
-
import { resolve as resolve6, dirname as dirname4 } from "path";
|
|
981
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
982
|
-
var __filename = fileURLToPath2(import.meta.url);
|
|
983
|
-
var __dirname = dirname4(__filename);
|
|
984
|
-
async function createPlugin(pluginName) {
|
|
985
|
-
console.log(chalk12.blue("\u{1F680} Creating ObjectUI plugin...\n"));
|
|
986
|
-
const createPluginScript = resolve6(
|
|
987
|
-
__dirname,
|
|
988
|
-
"../../../create-plugin/dist/index.js"
|
|
989
|
-
);
|
|
990
|
-
return new Promise((resolve8, reject) => {
|
|
991
|
-
const child = spawn2("node", [createPluginScript, pluginName], {
|
|
992
|
-
stdio: "inherit",
|
|
993
|
-
shell: true
|
|
994
|
-
});
|
|
995
|
-
child.on("error", (error) => {
|
|
996
|
-
console.error(chalk12.red("Failed to create plugin:"), error);
|
|
997
|
-
reject(error);
|
|
998
|
-
});
|
|
999
|
-
child.on("exit", (code) => {
|
|
1000
|
-
if (code === 0) {
|
|
1001
|
-
resolve8();
|
|
1002
|
-
} else {
|
|
1003
|
-
reject(new Error(`create-plugin exited with code ${code}`));
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
1660
|
// src/commands/analyze.ts
|
|
1010
|
-
import
|
|
1011
|
-
import { existsSync as
|
|
1012
|
-
import { resolve as
|
|
1661
|
+
import chalk8 from "chalk";
|
|
1662
|
+
import { existsSync as existsSync6, statSync, readdirSync } from "fs";
|
|
1663
|
+
import { resolve as resolve4, join as join7, extname } from "path";
|
|
1013
1664
|
async function analyzeBundleSize() {
|
|
1014
|
-
console.log(
|
|
1015
|
-
const distDir =
|
|
1016
|
-
if (!
|
|
1017
|
-
|
|
1018
|
-
return;
|
|
1665
|
+
console.log(chalk8.bold("\n\u{1F4E6} Bundle Size Analysis\n"));
|
|
1666
|
+
const distDir = resolve4(process.cwd(), "dist");
|
|
1667
|
+
if (!existsSync6(distDir)) {
|
|
1668
|
+
throw new Error("No dist/ directory to analyze. Run `objectui build` first.");
|
|
1019
1669
|
}
|
|
1020
1670
|
const files = [];
|
|
1021
1671
|
function scanDirectory(dir) {
|
|
1022
1672
|
const items = readdirSync(dir);
|
|
1023
1673
|
for (const item of items) {
|
|
1024
|
-
const fullPath =
|
|
1025
|
-
const stat =
|
|
1674
|
+
const fullPath = join7(dir, item);
|
|
1675
|
+
const stat = statSync(fullPath);
|
|
1026
1676
|
if (stat.isDirectory()) {
|
|
1027
1677
|
scanDirectory(fullPath);
|
|
1028
1678
|
} else {
|
|
@@ -1040,48 +1690,34 @@ async function analyzeBundleSize() {
|
|
|
1040
1690
|
const cssFiles = files.filter((f) => extname(f.path) === ".css");
|
|
1041
1691
|
const jsSize = jsFiles.reduce((sum, file) => sum + file.size, 0);
|
|
1042
1692
|
const cssSize = cssFiles.reduce((sum, file) => sum + file.size, 0);
|
|
1043
|
-
console.log(
|
|
1044
|
-
console.log(
|
|
1045
|
-
console.log(
|
|
1046
|
-
console.log(
|
|
1047
|
-
console.log(
|
|
1048
|
-
console.log(
|
|
1693
|
+
console.log(chalk8.bold("Summary:"));
|
|
1694
|
+
console.log(chalk8.gray(" Total Size:"), formatBytes(totalSize));
|
|
1695
|
+
console.log(chalk8.gray(" JavaScript:"), formatBytes(jsSize), chalk8.dim(`(${jsFiles.length} files)`));
|
|
1696
|
+
console.log(chalk8.gray(" CSS: "), formatBytes(cssSize), chalk8.dim(`(${cssFiles.length} files)`));
|
|
1697
|
+
console.log(chalk8.gray(" Other: "), formatBytes(totalSize - jsSize - cssSize));
|
|
1698
|
+
console.log(chalk8.bold("\nLargest Files:"));
|
|
1049
1699
|
files.slice(0, 10).forEach((file) => {
|
|
1050
1700
|
const sizeStr = formatBytes(file.size).padStart(10);
|
|
1051
|
-
console.log(
|
|
1701
|
+
console.log(chalk8.gray(` ${sizeStr}`), file.path);
|
|
1052
1702
|
});
|
|
1053
|
-
console.log(
|
|
1703
|
+
console.log(chalk8.bold("\n\u{1F4A1} Recommendations:"));
|
|
1054
1704
|
if (totalSize > 1024 * 1024) {
|
|
1055
|
-
console.log(
|
|
1056
|
-
console.log(
|
|
1705
|
+
console.log(chalk8.yellow(" \u26A0 Total bundle size is large (> 1MB)"));
|
|
1706
|
+
console.log(chalk8.gray(" Consider code splitting or lazy loading"));
|
|
1057
1707
|
}
|
|
1058
1708
|
if (jsSize > 500 * 1024) {
|
|
1059
|
-
console.log(
|
|
1060
|
-
console.log(
|
|
1061
|
-
console.log(
|
|
1062
|
-
console.log(
|
|
1063
|
-
console.log(
|
|
1709
|
+
console.log(chalk8.yellow(" \u26A0 JavaScript bundle is large (> 500KB)"));
|
|
1710
|
+
console.log(chalk8.gray(" Consider:"));
|
|
1711
|
+
console.log(chalk8.gray(" - Tree shaking unused code"));
|
|
1712
|
+
console.log(chalk8.gray(" - Lazy loading components"));
|
|
1713
|
+
console.log(chalk8.gray(" - Using dynamic imports"));
|
|
1064
1714
|
}
|
|
1065
1715
|
if (files.length > 100) {
|
|
1066
|
-
console.log(
|
|
1067
|
-
console.log(
|
|
1716
|
+
console.log(chalk8.yellow(` \u26A0 Large number of files (${files.length})`));
|
|
1717
|
+
console.log(chalk8.gray(" Consider bundling or combining files"));
|
|
1068
1718
|
}
|
|
1069
1719
|
console.log("");
|
|
1070
1720
|
}
|
|
1071
|
-
async function analyzeRenderPerformance() {
|
|
1072
|
-
console.log(chalk13.bold("\n\u26A1 Render Performance Analysis\n"));
|
|
1073
|
-
console.log(chalk13.gray("Performance analysis features:"));
|
|
1074
|
-
console.log(chalk13.gray(" \u2713 Expression caching enabled"));
|
|
1075
|
-
console.log(chalk13.gray(" \u2713 Component memoization available"));
|
|
1076
|
-
console.log(chalk13.gray(" \u2713 Virtual scrolling support for large lists"));
|
|
1077
|
-
console.log(chalk13.bold("\n\u{1F4A1} Performance Tips:"));
|
|
1078
|
-
console.log(chalk13.gray(" \u2022 Use virtual scrolling for lists > 100 items"));
|
|
1079
|
-
console.log(chalk13.gray(" \u2022 Cache frequently evaluated expressions"));
|
|
1080
|
-
console.log(chalk13.gray(" \u2022 Use React.memo for expensive components"));
|
|
1081
|
-
console.log(chalk13.gray(" \u2022 Implement pagination for large datasets"));
|
|
1082
|
-
console.log(chalk13.gray(" \u2022 Use code splitting for large apps"));
|
|
1083
|
-
console.log("");
|
|
1084
|
-
}
|
|
1085
1721
|
function formatBytes(bytes) {
|
|
1086
1722
|
if (bytes === 0) return "0 B";
|
|
1087
1723
|
const k = 1024;
|
|
@@ -1089,50 +1725,44 @@ function formatBytes(bytes) {
|
|
|
1089
1725
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1090
1726
|
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
|
1091
1727
|
}
|
|
1092
|
-
async function analyze(
|
|
1093
|
-
console.log(
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
await analyzeBundleSize();
|
|
1097
|
-
}
|
|
1098
|
-
if (options.renderPerformance || runAll) {
|
|
1099
|
-
await analyzeRenderPerformance();
|
|
1100
|
-
}
|
|
1101
|
-
console.log(chalk13.green("\u2713 Analysis complete!\n"));
|
|
1728
|
+
async function analyze() {
|
|
1729
|
+
console.log(chalk8.blue("\u{1F50D} ObjectUI Performance Analyzer\n"));
|
|
1730
|
+
await analyzeBundleSize();
|
|
1731
|
+
console.log(chalk8.green("\u2713 Analysis complete!\n"));
|
|
1102
1732
|
}
|
|
1103
1733
|
|
|
1104
1734
|
// src/cli.ts
|
|
1105
1735
|
import { readFileSync as readFileSync4 } from "fs";
|
|
1106
|
-
import { fileURLToPath
|
|
1107
|
-
import { dirname as
|
|
1108
|
-
var
|
|
1109
|
-
var
|
|
1736
|
+
import { fileURLToPath } from "url";
|
|
1737
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
1738
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
1739
|
+
var __dirname = dirname2(__filename);
|
|
1110
1740
|
var packageJson = JSON.parse(
|
|
1111
|
-
readFileSync4(
|
|
1741
|
+
readFileSync4(join8(__dirname, "../package.json"), "utf-8")
|
|
1112
1742
|
);
|
|
1113
1743
|
var program = new Command();
|
|
1114
1744
|
program.name("objectui").description("CLI tool for Object UI - Build applications from JSON schemas").version(packageJson.version);
|
|
1115
|
-
program.command("serve").description("Start a development server with your JSON/YAML schema").argument("[schema]", "Path to JSON/YAML schema file", "app.json").option("-p, --port <port>", "Port to run the server on", "3000").option("-h, --host <host>", "Host to bind the server to", "localhost").action(async (schema, options) => {
|
|
1745
|
+
program.command("serve").description("Start a development server with your JSON/YAML schema").argument("[schema]", "Path to JSON/YAML schema file, or a project / pages directory", "app.json").option("-p, --port <port>", "Port to run the server on", "3000").option("-h, --host <host>", "Host to bind the server to", "localhost").option("--no-open", "Do not open browser automatically").action(async (schema, options) => {
|
|
1116
1746
|
try {
|
|
1117
1747
|
await serve(schema, options);
|
|
1118
1748
|
} catch (error) {
|
|
1119
|
-
console.error(
|
|
1749
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1120
1750
|
process.exit(1);
|
|
1121
1751
|
}
|
|
1122
1752
|
});
|
|
1123
|
-
program.command("dev").description("Start development server (alias for serve)").argument("[schema]", "Path to JSON/YAML schema file", "app.json").option("-p, --port <port>", "Port to run the server on", "3000").option("-h, --host <host>", "Host to bind the server to", "localhost").option("--no-open", "Do not open browser automatically").action(async (schema, options) => {
|
|
1753
|
+
program.command("dev").description("Start development server (alias for serve)").argument("[schema]", "Path to JSON/YAML schema file, or a project / pages directory", "app.json").option("-p, --port <port>", "Port to run the server on", "3000").option("-h, --host <host>", "Host to bind the server to", "localhost").option("--no-open", "Do not open browser automatically").action(async (schema, options) => {
|
|
1124
1754
|
try {
|
|
1125
1755
|
await dev(schema, options);
|
|
1126
1756
|
} catch (error) {
|
|
1127
|
-
console.error(
|
|
1757
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1128
1758
|
process.exit(1);
|
|
1129
1759
|
}
|
|
1130
1760
|
});
|
|
1131
|
-
program.command("build").description("Build application for production").argument("[schema]", "Path to JSON/YAML schema file", "app.json").option("-o, --out-dir <dir>", "Output directory", "dist").option("--clean", "Clean output directory before build", false).action(async (schema, options) => {
|
|
1761
|
+
program.command("build").description("Build application for production").argument("[schema]", "Path to JSON/YAML schema file, or a project / pages directory", "app.json").option("-o, --out-dir <dir>", "Output directory", "dist").option("--clean", "Clean output directory before build", false).action(async (schema, options) => {
|
|
1132
1762
|
try {
|
|
1133
1763
|
await buildApp(schema, options);
|
|
1134
1764
|
} catch (error) {
|
|
1135
|
-
console.error(
|
|
1765
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1136
1766
|
process.exit(1);
|
|
1137
1767
|
}
|
|
1138
1768
|
});
|
|
@@ -1140,7 +1770,7 @@ program.command("start").description("Start production server").option("-p, --po
|
|
|
1140
1770
|
try {
|
|
1141
1771
|
await start(options);
|
|
1142
1772
|
} catch (error) {
|
|
1143
|
-
console.error(
|
|
1773
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1144
1774
|
process.exit(1);
|
|
1145
1775
|
}
|
|
1146
1776
|
});
|
|
@@ -1148,68 +1778,31 @@ program.command("init").description("Initialize a new Object UI application with
|
|
|
1148
1778
|
try {
|
|
1149
1779
|
await init(name, options);
|
|
1150
1780
|
} catch (error) {
|
|
1151
|
-
console.error(
|
|
1152
|
-
process.exit(1);
|
|
1153
|
-
}
|
|
1154
|
-
});
|
|
1155
|
-
program.command("lint").description("Lint the generated application code").option("--fix", "Automatically fix linting issues").action(async (options) => {
|
|
1156
|
-
try {
|
|
1157
|
-
await lint(options);
|
|
1158
|
-
} catch (error) {
|
|
1159
|
-
console.error(chalk14.red("Error:"), error instanceof Error ? error.message : error);
|
|
1781
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1160
1782
|
process.exit(1);
|
|
1161
1783
|
}
|
|
1162
1784
|
});
|
|
1163
|
-
program.command("
|
|
1785
|
+
program.command("generate").alias("g").description("Generate new resources (objects, pages, plugins)").argument("<type>", "Type of resource to generate (resource/object, page, plugin)").argument("<name>", "Name of the resource").action(async (type, name) => {
|
|
1164
1786
|
try {
|
|
1165
|
-
await test(options);
|
|
1166
|
-
} catch (error) {
|
|
1167
|
-
console.error(chalk14.red("Error:"), error instanceof Error ? error.message : error);
|
|
1168
|
-
process.exit(1);
|
|
1169
|
-
}
|
|
1170
|
-
});
|
|
1171
|
-
program.command("generate").alias("g").description("Generate new resources (objects, pages, plugins)").argument("<type>", "Type of resource to generate (resource/object, page, plugin)").argument("<name>", "Name of the resource").option("--from <source>", "Generate schema from external source (openapi.yaml, prisma.schema)").option("--output <dir>", "Output directory for generated schemas", "schemas/").action(async (type, name, options) => {
|
|
1172
|
-
try {
|
|
1173
|
-
if (options.from) {
|
|
1174
|
-
console.log(chalk14.yellow("\n\u26A0 Schema generation from external sources (OpenAPI/Prisma) is not yet implemented."));
|
|
1175
|
-
console.log(chalk14.gray("This feature will be available in a future release.\n"));
|
|
1176
|
-
process.exit(0);
|
|
1177
|
-
}
|
|
1178
1787
|
await generate(type, name);
|
|
1179
1788
|
} catch (error) {
|
|
1180
|
-
console.error(
|
|
1789
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1181
1790
|
process.exit(1);
|
|
1182
1791
|
}
|
|
1183
1792
|
});
|
|
1184
|
-
program.command("doctor").description("Diagnose
|
|
1793
|
+
program.command("doctor").description("Diagnose common project setup issues (reports only, changes nothing)").action(async () => {
|
|
1185
1794
|
try {
|
|
1186
1795
|
await doctor();
|
|
1187
1796
|
} catch (error) {
|
|
1188
|
-
console.error(
|
|
1797
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1189
1798
|
process.exit(1);
|
|
1190
1799
|
}
|
|
1191
1800
|
});
|
|
1192
|
-
program.command("
|
|
1193
|
-
try {
|
|
1194
|
-
await add(component);
|
|
1195
|
-
} catch (error) {
|
|
1196
|
-
console.error(chalk14.red("Error:"), error instanceof Error ? error.message : error);
|
|
1197
|
-
process.exit(1);
|
|
1198
|
-
}
|
|
1199
|
-
});
|
|
1200
|
-
program.command("studio").description("Start the visual designer").action(async () => {
|
|
1201
|
-
try {
|
|
1202
|
-
await studio();
|
|
1203
|
-
} catch (error) {
|
|
1204
|
-
console.error(chalk14.red("Error:"), error instanceof Error ? error.message : error);
|
|
1205
|
-
process.exit(1);
|
|
1206
|
-
}
|
|
1207
|
-
});
|
|
1208
|
-
program.command("check").description("Validate schema files").action(async () => {
|
|
1801
|
+
program.command("check").description("Advisory JSON file sweep; run objectui validate for a verdict").action(async () => {
|
|
1209
1802
|
try {
|
|
1210
1803
|
await check();
|
|
1211
1804
|
} catch (error) {
|
|
1212
|
-
console.error(
|
|
1805
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1213
1806
|
process.exit(1);
|
|
1214
1807
|
}
|
|
1215
1808
|
});
|
|
@@ -1217,29 +1810,15 @@ program.command("validate").description("Validate a schema file against ObjectUI
|
|
|
1217
1810
|
try {
|
|
1218
1811
|
await validate(schema);
|
|
1219
1812
|
} catch (error) {
|
|
1220
|
-
console.error(
|
|
1221
|
-
process.exit(1);
|
|
1222
|
-
}
|
|
1223
|
-
});
|
|
1224
|
-
program.command("create").description("Create new resources").argument("<type>", "Type of resource to create (plugin)").argument("<name>", "Name of the resource").action(async (type, name) => {
|
|
1225
|
-
try {
|
|
1226
|
-
if (type === "plugin") {
|
|
1227
|
-
await createPlugin(name);
|
|
1228
|
-
} else {
|
|
1229
|
-
console.error(chalk14.red(`Unknown resource type: ${type}`));
|
|
1230
|
-
console.log(chalk14.gray("Available types: plugin"));
|
|
1231
|
-
process.exit(1);
|
|
1232
|
-
}
|
|
1233
|
-
} catch (error) {
|
|
1234
|
-
console.error(chalk14.red("Error:"), error instanceof Error ? error.message : error);
|
|
1813
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1235
1814
|
process.exit(1);
|
|
1236
1815
|
}
|
|
1237
1816
|
});
|
|
1238
|
-
program.command("analyze").description("Analyze application performance").
|
|
1817
|
+
program.command("analyze").description("Analyze application performance").action(async () => {
|
|
1239
1818
|
try {
|
|
1240
|
-
await analyze(
|
|
1819
|
+
await analyze();
|
|
1241
1820
|
} catch (error) {
|
|
1242
|
-
console.error(
|
|
1821
|
+
console.error(chalk9.red("Error:"), error instanceof Error ? error.message : error);
|
|
1243
1822
|
process.exit(1);
|
|
1244
1823
|
}
|
|
1245
1824
|
});
|