openuispec 0.1.28 → 0.1.29
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/README.md +31 -37
- package/cli/configure-target.ts +416 -0
- package/cli/index.ts +14 -3
- package/cli/init.ts +235 -49
- package/cli/target-presets.json +746 -0
- package/docs/implementation-notes.md +42 -9
- package/docs/release-notes-v0.1.26.md +1 -1
- package/drift/index.ts +10 -7
- package/examples/taskflow/AGENTS.md +4 -3
- package/examples/taskflow/CLAUDE.md +4 -3
- package/examples/taskflow/backend/.gitkeep +1 -0
- package/examples/taskflow/openuispec/README.md +7 -2
- package/examples/taskflow/openuispec/openuispec.yaml +2 -0
- package/examples/todo-orbit/AGENTS.md +4 -3
- package/examples/todo-orbit/CLAUDE.md +4 -3
- package/examples/todo-orbit/backend/.gitkeep +1 -0
- package/examples/todo-orbit/openuispec/README.md +7 -2
- package/examples/todo-orbit/openuispec/openuispec.yaml +2 -0
- package/package.json +1 -1
- package/prepare/index.ts +811 -25
- package/schema/openuispec.schema.json +10 -0
- package/schema/semantic-lint.ts +36 -12
|
@@ -111,6 +111,16 @@
|
|
|
111
111
|
"type": "string"
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
|
+
"code_roots": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"description": "Additional project code roots used as generation context.",
|
|
117
|
+
"properties": {
|
|
118
|
+
"backend": {
|
|
119
|
+
"type": "string"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"additionalProperties": false
|
|
123
|
+
},
|
|
114
124
|
"output_format": {
|
|
115
125
|
"type": "object",
|
|
116
126
|
"description": "Per-target generation config",
|
package/schema/semantic-lint.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
import YAML from "yaml";
|
|
4
|
+
import { listFiles } from "../drift/index.js";
|
|
4
5
|
|
|
5
6
|
type UnknownRecord = Record<string, unknown>;
|
|
6
7
|
|
|
@@ -19,6 +20,7 @@ export interface UsageLint {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
interface SemanticContext {
|
|
23
|
+
manifest: unknown;
|
|
22
24
|
localeFiles: Map<string, Set<string>>;
|
|
23
25
|
formatterNames: Set<string>;
|
|
24
26
|
mapperNames: Set<string>;
|
|
@@ -66,17 +68,6 @@ function loadData(filePath: string): unknown {
|
|
|
66
68
|
return filePath.endsWith(".json") ? loadJson(filePath) : loadYaml(filePath);
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
function listFiles(dir: string, ext: string): string[] {
|
|
70
|
-
try {
|
|
71
|
-
return readdirSync(dir)
|
|
72
|
-
.filter((file) => file.endsWith(ext))
|
|
73
|
-
.sort()
|
|
74
|
-
.map((file) => join(dir, file));
|
|
75
|
-
} catch {
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
71
|
function isRecord(value: unknown): value is UnknownRecord {
|
|
81
72
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
82
73
|
}
|
|
@@ -281,6 +272,7 @@ function buildContext(projectDir: string, includes: Includes): SemanticContext {
|
|
|
281
272
|
: localeFiles.keys().next().value ?? null;
|
|
282
273
|
|
|
283
274
|
return {
|
|
275
|
+
manifest,
|
|
284
276
|
localeFiles,
|
|
285
277
|
formatterNames,
|
|
286
278
|
mapperNames,
|
|
@@ -552,6 +544,37 @@ function lintLocaleCoverage(context: SemanticContext): UsageLint[] {
|
|
|
552
544
|
return errors;
|
|
553
545
|
}
|
|
554
546
|
|
|
547
|
+
function lintManifestGenerationContext(projectDir: string, manifest: unknown): UsageLint[] {
|
|
548
|
+
if (!isRecord(manifest)) return [];
|
|
549
|
+
|
|
550
|
+
const hasApiEndpoints =
|
|
551
|
+
isRecord(manifest.api) &&
|
|
552
|
+
isRecord(manifest.api.endpoints) &&
|
|
553
|
+
Object.keys(manifest.api.endpoints).length > 0;
|
|
554
|
+
if (!hasApiEndpoints) return [];
|
|
555
|
+
|
|
556
|
+
const generation = isRecord(manifest.generation) ? manifest.generation : {};
|
|
557
|
+
const codeRoots = isRecord(generation.code_roots) ? generation.code_roots : null;
|
|
558
|
+
const backendRoot = codeRoots && typeof codeRoots.backend === "string" ? codeRoots.backend.trim() : "";
|
|
559
|
+
|
|
560
|
+
if (!backendRoot) {
|
|
561
|
+
return [{
|
|
562
|
+
path: "openuispec.yaml",
|
|
563
|
+
message: 'api endpoints require generation.code_roots.backend to point at the backend folder',
|
|
564
|
+
}];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const resolvedBackendRoot = resolve(projectDir, backendRoot);
|
|
568
|
+
if (!existsSync(resolvedBackendRoot)) {
|
|
569
|
+
return [{
|
|
570
|
+
path: "openuispec.yaml",
|
|
571
|
+
message: `generation.code_roots.backend points to a missing folder: ${backendRoot}`,
|
|
572
|
+
}];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return [];
|
|
576
|
+
}
|
|
577
|
+
|
|
555
578
|
function printSemanticErrors(label: string, errors: UsageLint[]): number {
|
|
556
579
|
if (errors.length === 0) return 0;
|
|
557
580
|
const previewLimit = 10;
|
|
@@ -572,6 +595,7 @@ export function runSemanticLint(projectDir: string, includes: Includes): number
|
|
|
572
595
|
const contractsDir = resolve(projectDir, includes.contracts);
|
|
573
596
|
|
|
574
597
|
total += printSemanticErrors("locales", lintLocaleCoverage(context));
|
|
598
|
+
total += printSemanticErrors("openuispec.yaml", lintManifestGenerationContext(projectDir, context.manifest));
|
|
575
599
|
|
|
576
600
|
const files = [
|
|
577
601
|
join(projectDir, "openuispec.yaml"),
|