aub-workspace 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +23 -0
- package/bin/aub-workspace.mjs +246 -0
- package/package.json +32 -0
- package/vendor/aub/apps/editor/dist/assets/_commonjs-dynamic-modules-TDtrdbi3.js +1 -0
- package/vendor/aub/apps/editor/dist/assets/angular-importer.lib-dB_jK4mR.js +32 -0
- package/vendor/aub/apps/editor/dist/assets/canvas-tools-CuYC7cA2.js +364 -0
- package/vendor/aub/apps/editor/dist/assets/design-bridge.lib-DJvaK6AX.js +1 -0
- package/vendor/aub/apps/editor/dist/assets/export-agent-prompt.lib-BsP0KNqo.js +2 -0
- package/vendor/aub/apps/editor/dist/assets/export-md.lib-DdmdeWgO.js +3 -0
- package/vendor/aub/apps/editor/dist/assets/handoff-package.lib-DDYpcEma.js +20 -0
- package/vendor/aub/apps/editor/dist/assets/implementation-report.lib-CmsSB_8s.js +1 -0
- package/vendor/aub/apps/editor/dist/assets/index-BCH-ek3h.js +2 -0
- package/vendor/aub/apps/editor/dist/assets/index-lAnc928Q.css +1 -0
- package/vendor/aub/apps/editor/dist/assets/index-vt1nM1M4.js +507 -0
- package/vendor/aub/apps/editor/dist/assets/jszip.min-CRfXyL92.js +12 -0
- package/vendor/aub/apps/editor/dist/assets/react-vendor-ByX9Pqse.js +40 -0
- package/vendor/aub/apps/editor/dist/brand/android-chrome-192x192.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/android-chrome-512x512.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/app-icon-1024.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/app-icon-192.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/app-icon-512.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/apple-touch-icon.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/aub-logo-mark.svg +28 -0
- package/vendor/aub/apps/editor/dist/brand/favicon-16x16.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/favicon-32x32.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/favicon-48x48.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/favicon.ico +0 -0
- package/vendor/aub/apps/editor/dist/brand/favicon.svg +9 -0
- package/vendor/aub/apps/editor/dist/brand/maskable-icon-512.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/mstile-150x150.png +0 -0
- package/vendor/aub/apps/editor/dist/brand/safari-pinned-tab.svg +8 -0
- package/vendor/aub/apps/editor/dist/browserconfig.xml +9 -0
- package/vendor/aub/apps/editor/dist/index.html +22 -0
- package/vendor/aub/apps/editor/dist/manifest.webmanifest +28 -0
- package/vendor/aub/apps/editor/dist/template-previews/admin-table.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/booking.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/calendar.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/catalog.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/chat.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/checkout.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/crm.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/dashboard.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/feed.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/files.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/kanban.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/landing.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/mail.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/onboarding.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/pricing.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/product-detail.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/settings.png +0 -0
- package/vendor/aub/apps/editor/dist/template-previews/wiki.png +0 -0
- package/vendor/aub/apps/mcp-server/dist/aub.js +15 -0
- package/vendor/aub/apps/mcp-server/dist/context.js +1 -0
- package/vendor/aub/apps/mcp-server/dist/http.js +123 -0
- package/vendor/aub/apps/mcp-server/dist/index.js +23 -0
- package/vendor/aub/apps/mcp-server/dist/repo.js +17 -0
- package/vendor/aub/apps/mcp-server/dist/schema.js +42 -0
- package/vendor/aub/apps/mcp-server/dist/server.js +80 -0
- package/vendor/aub/apps/mcp-server/dist/tools/approve-component-candidate.js +27 -0
- package/vendor/aub/apps/mcp-server/dist/tools/diff-blueprints.js +27 -0
- package/vendor/aub/apps/mcp-server/dist/tools/export-handoff.js +87 -0
- package/vendor/aub/apps/mcp-server/dist/tools/export-prompt.js +35 -0
- package/vendor/aub/apps/mcp-server/dist/tools/export-template-authoring-prompt.js +13 -0
- package/vendor/aub/apps/mcp-server/dist/tools/generate-template-from-source.js +25 -0
- package/vendor/aub/apps/mcp-server/dist/tools/get-aub-session.js +13 -0
- package/vendor/aub/apps/mcp-server/dist/tools/get-blueprint.js +28 -0
- package/vendor/aub/apps/mcp-server/dist/tools/get-project.js +45 -0
- package/vendor/aub/apps/mcp-server/dist/tools/get-workspace-status.js +10 -0
- package/vendor/aub/apps/mcp-server/dist/tools/import-design-bridge.js +62 -0
- package/vendor/aub/apps/mcp-server/dist/tools/list-blueprints.js +11 -0
- package/vendor/aub/apps/mcp-server/dist/tools/list-projects.js +11 -0
- package/vendor/aub/apps/mcp-server/dist/tools/lock-blueprint.js +33 -0
- package/vendor/aub/apps/mcp-server/dist/tools/migrate-blueprint.js +38 -0
- package/vendor/aub/apps/mcp-server/dist/tools/resolve-component.js +51 -0
- package/vendor/aub/apps/mcp-server/dist/tools/scaffold-blueprint.js +53 -0
- package/vendor/aub/apps/mcp-server/dist/tools/scan-project-ui.js +18 -0
- package/vendor/aub/apps/mcp-server/dist/tools/submit-report.js +48 -0
- package/vendor/aub/apps/mcp-server/dist/tools/update-aub-session.js +14 -0
- package/vendor/aub/apps/mcp-server/dist/tools/validate-blueprint.js +67 -0
- package/vendor/aub/apps/mcp-server/dist/tools/validate-project.js +74 -0
- package/vendor/aub/apps/mcp-server/dist/tools/write-blueprint.js +72 -0
- package/vendor/aub/apps/mcp-server/dist/workspace.js +138 -0
- package/vendor/aub/docs/agent-handoff.md +85 -0
- package/vendor/aub/docs/agent-handoff.zh-Hant.md +85 -0
- package/vendor/aub/docs/template-authoring-agent.md +86 -0
- package/vendor/aub/schema/aub-ci.schema.json +34 -0
- package/vendor/aub/schema/aub.registry.schema.json +118 -0
- package/vendor/aub/schema/design-bridge.schema.json +44 -0
- package/vendor/aub/schema/implementation-report.schema.json +93 -0
- package/vendor/aub/schema/project-types.ts +72 -0
- package/vendor/aub/schema/registry/components.json +118 -0
- package/vendor/aub/schema/types.js +13 -0
- package/vendor/aub/schema/types.ts +348 -0
- package/vendor/aub/schema/ui-blueprint-lock.schema.json +61 -0
- package/vendor/aub/schema/ui-blueprint.schema.json +1339 -0
- package/vendor/aub/schema/ui-project.schema.json +139 -0
- package/vendor/aub/scripts/agent-implementation-benchmark.lib.mjs +125 -0
- package/vendor/aub/scripts/angular-importer.lib.mjs +982 -0
- package/vendor/aub/scripts/check-editor-bundle-budget.mjs +36 -0
- package/vendor/aub/scripts/ci-verify.lib.mjs +256 -0
- package/vendor/aub/scripts/ci-verify.mjs +45 -0
- package/vendor/aub/scripts/create-authoring-kit.mjs +84 -0
- package/vendor/aub/scripts/create-implementation-report.mjs +24 -0
- package/vendor/aub/scripts/design-bridge.lib.d.mts +32 -0
- package/vendor/aub/scripts/design-bridge.lib.mjs +69 -0
- package/vendor/aub/scripts/diff-blueprint.lib.d.mts +18 -0
- package/vendor/aub/scripts/diff-blueprint.lib.mjs +148 -0
- package/vendor/aub/scripts/diff-blueprint.mjs +25 -0
- package/vendor/aub/scripts/export-agent-prompt.lib.d.mts +10 -0
- package/vendor/aub/scripts/export-agent-prompt.lib.mjs +160 -0
- package/vendor/aub/scripts/export-agent-prompt.mjs +79 -0
- package/vendor/aub/scripts/export-md.lib.d.mts +3 -0
- package/vendor/aub/scripts/export-md.lib.mjs +302 -0
- package/vendor/aub/scripts/export-md.mjs +43 -0
- package/vendor/aub/scripts/generate-registry-artifacts.lib.mjs +118 -0
- package/vendor/aub/scripts/generate-registry-artifacts.mjs +65 -0
- package/vendor/aub/scripts/generate-site-locales.mjs +545 -0
- package/vendor/aub/scripts/handoff-package.lib.d.mts +20 -0
- package/vendor/aub/scripts/handoff-package.lib.mjs +111 -0
- package/vendor/aub/scripts/implementation-report.lib.d.mts +21 -0
- package/vendor/aub/scripts/implementation-report.lib.mjs +97 -0
- package/vendor/aub/scripts/import-angular-component.mjs +72 -0
- package/vendor/aub/scripts/import-design-bridge.mjs +59 -0
- package/vendor/aub/scripts/lock-blueprint.lib.d.mts +23 -0
- package/vendor/aub/scripts/lock-blueprint.lib.mjs +58 -0
- package/vendor/aub/scripts/lock-blueprint.mjs +36 -0
- package/vendor/aub/scripts/migrate-blueprint-cli.mjs +28 -0
- package/vendor/aub/scripts/migrate-blueprint.d.mts +5 -0
- package/vendor/aub/scripts/migrate-blueprint.mjs +95 -0
- package/vendor/aub/scripts/package-workspace-cli.mjs +34 -0
- package/vendor/aub/scripts/project.lib.d.mts +44 -0
- package/vendor/aub/scripts/project.lib.mjs +175 -0
- package/vendor/aub/scripts/project.mjs +332 -0
- package/vendor/aub/scripts/registry.lib.d.mts +52 -0
- package/vendor/aub/scripts/registry.lib.mjs +222 -0
- package/vendor/aub/scripts/run-agent-implementation.mjs +423 -0
- package/vendor/aub/scripts/run-agent-readability.mjs +145 -0
- package/vendor/aub/scripts/run-ollama-prompt.mjs +30 -0
- package/vendor/aub/scripts/scaffold-blueprint.lib.d.mts +38 -0
- package/vendor/aub/scripts/scaffold-blueprint.lib.mjs +316 -0
- package/vendor/aub/scripts/scaffold-blueprint.mjs +86 -0
- package/vendor/aub/scripts/score-agent-implementation.mjs +27 -0
- package/vendor/aub/scripts/score-agent-readability.mjs +54 -0
- package/vendor/aub/scripts/sync-brand-assets.mjs +33 -0
- package/vendor/aub/scripts/validate-blueprint.lib.d.mts +14 -0
- package/vendor/aub/scripts/validate-blueprint.lib.mjs +136 -0
- package/vendor/aub/scripts/validate.mjs +128 -0
- package/vendor/aub/scripts/verify-implementation-report.mjs +36 -0
- package/vendor/aub/scripts/workspace-loop.lib.d.mts +17 -0
- package/vendor/aub/scripts/workspace-loop.lib.mjs +674 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Project, ProjectScreenRef, NavigationTrigger } from '../schema/project-types';
|
|
2
|
+
import type { Blueprint } from '../schema/types';
|
|
3
|
+
|
|
4
|
+
export const NAVIGATION_TRIGGERS: NavigationTrigger[];
|
|
5
|
+
export const PROJECT_VERSION: '0.1.0';
|
|
6
|
+
|
|
7
|
+
export function parseProjectText(text: string, filePath?: string): Project;
|
|
8
|
+
|
|
9
|
+
export function validateProjectSemantics(
|
|
10
|
+
project: Project,
|
|
11
|
+
options?: { screensById?: Map<string, Blueprint> }
|
|
12
|
+
): string[];
|
|
13
|
+
|
|
14
|
+
export function resolveScreenPath(projectFilePath: string, screenPath: string): string;
|
|
15
|
+
|
|
16
|
+
export function readBlueprintFile(absPath: string): Promise<Blueprint>;
|
|
17
|
+
|
|
18
|
+
export interface LoadedScreen {
|
|
19
|
+
ref: ProjectScreenRef;
|
|
20
|
+
path: string;
|
|
21
|
+
blueprint: Blueprint | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface LoadedProject {
|
|
25
|
+
project: Project;
|
|
26
|
+
projectPath: string;
|
|
27
|
+
screens: LoadedScreen[];
|
|
28
|
+
screensById: Map<string, Blueprint>;
|
|
29
|
+
errors: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadProject(projectPathArg: string): Promise<LoadedProject>;
|
|
33
|
+
|
|
34
|
+
export function mergeDesignSystem(
|
|
35
|
+
project: Project,
|
|
36
|
+
blueprint: Blueprint
|
|
37
|
+
): Record<string, unknown>;
|
|
38
|
+
|
|
39
|
+
export function buildProject(input: {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
screens: Array<{ blueprint: Blueprint; path: string }>;
|
|
44
|
+
}): Project;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// Pure-ish library for reference-based multi-screen AUB projects.
|
|
2
|
+
//
|
|
3
|
+
// A project document (*.aub.project.json) lists member single-screen Blueprint
|
|
4
|
+
// files by relative path, declares a navigation graph between them, names an
|
|
5
|
+
// entry screen, and optionally carries project-level shared design tokens.
|
|
6
|
+
//
|
|
7
|
+
// This module keeps the *semantic* validation pure (no IO), and isolates file
|
|
8
|
+
// resolution into clearly-named async helpers so callers (CLI/MCP) can load a
|
|
9
|
+
// project and its member screens. It never mutates inputs.
|
|
10
|
+
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { extname, dirname, resolve, isAbsolute } from 'node:path';
|
|
13
|
+
import yaml from 'js-yaml';
|
|
14
|
+
|
|
15
|
+
export const NAVIGATION_TRIGGERS = ['click', 'submit', 'change', 'load', 'system', 'gesture'];
|
|
16
|
+
export const PROJECT_VERSION = '0.1.0';
|
|
17
|
+
|
|
18
|
+
/** Parse a project document from text by extension (.json or .yaml/.yml). */
|
|
19
|
+
export function parseProjectText(text, filePath = 'project') {
|
|
20
|
+
const ext = extname(filePath).toLowerCase();
|
|
21
|
+
if (ext === '.yaml' || ext === '.yml') {
|
|
22
|
+
return yaml.load(text);
|
|
23
|
+
}
|
|
24
|
+
return JSON.parse(text);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate project-level semantic rules that JSON Schema cannot express.
|
|
29
|
+
* Pure: takes the parsed project plus an optional map of loaded member
|
|
30
|
+
* blueprints (id -> blueprint) to cross-check screen.id consistency.
|
|
31
|
+
*
|
|
32
|
+
* Returns an array of human-readable error strings (empty = valid).
|
|
33
|
+
*/
|
|
34
|
+
export function validateProjectSemantics(project, { screensById } = {}) {
|
|
35
|
+
const errors = [];
|
|
36
|
+
const screens = Array.isArray(project?.screens) ? project.screens : [];
|
|
37
|
+
|
|
38
|
+
const ids = new Set();
|
|
39
|
+
const paths = new Set();
|
|
40
|
+
for (const screen of screens) {
|
|
41
|
+
if (!screen || typeof screen !== 'object') {
|
|
42
|
+
errors.push('screens[] contains a non-object entry');
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ids.has(screen.id)) {
|
|
46
|
+
errors.push(`duplicate screen id: ${screen.id}`);
|
|
47
|
+
}
|
|
48
|
+
ids.add(screen.id);
|
|
49
|
+
if (screen.path) {
|
|
50
|
+
const normalized = String(screen.path);
|
|
51
|
+
if (paths.has(normalized)) {
|
|
52
|
+
errors.push(`duplicate screen path: ${normalized}`);
|
|
53
|
+
}
|
|
54
|
+
paths.add(normalized);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (project?.entry_screen != null && !ids.has(project.entry_screen)) {
|
|
59
|
+
errors.push(`entry_screen "${project.entry_screen}" is not a declared screen`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const edges = Array.isArray(project?.navigation) ? project.navigation : [];
|
|
63
|
+
edges.forEach((edge, index) => {
|
|
64
|
+
if (!edge || typeof edge !== 'object') {
|
|
65
|
+
errors.push(`navigation[${index}] is not an object`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!ids.has(edge.from)) {
|
|
69
|
+
errors.push(`navigation[${index}].from "${edge.from}" is not a declared screen`);
|
|
70
|
+
}
|
|
71
|
+
if (!ids.has(edge.to)) {
|
|
72
|
+
errors.push(`navigation[${index}].to "${edge.to}" is not a declared screen`);
|
|
73
|
+
}
|
|
74
|
+
if (edge.trigger != null && !NAVIGATION_TRIGGERS.includes(edge.trigger)) {
|
|
75
|
+
errors.push(`navigation[${index}].trigger "${edge.trigger}" is not a known trigger`);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (screensById) {
|
|
80
|
+
for (const screen of screens) {
|
|
81
|
+
const blueprint = screensById.get(screen.id);
|
|
82
|
+
if (!blueprint) continue;
|
|
83
|
+
const realId = blueprint?.screen?.id;
|
|
84
|
+
if (realId && realId !== screen.id) {
|
|
85
|
+
errors.push(
|
|
86
|
+
`screen "${screen.id}" references a Blueprint whose screen.id is "${realId}" (id mismatch)`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return errors;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Resolve a member screen path relative to the project file directory. */
|
|
96
|
+
export function resolveScreenPath(projectFilePath, screenPath) {
|
|
97
|
+
if (isAbsolute(screenPath)) return screenPath;
|
|
98
|
+
return resolve(dirname(projectFilePath), screenPath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Read and parse a single Blueprint file (json or yaml). */
|
|
102
|
+
export async function readBlueprintFile(absPath) {
|
|
103
|
+
const raw = await readFile(absPath, 'utf8');
|
|
104
|
+
const ext = extname(absPath).toLowerCase();
|
|
105
|
+
if (ext === '.yaml' || ext === '.yml') {
|
|
106
|
+
return yaml.load(raw);
|
|
107
|
+
}
|
|
108
|
+
return JSON.parse(raw);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Load a project file and all of its member screens from disk.
|
|
113
|
+
* Returns { project, projectPath, screens: [{ ref, path, blueprint }], screensById }.
|
|
114
|
+
* Missing member files are reported via the `errors` array rather than thrown,
|
|
115
|
+
* so callers can surface every problem at once.
|
|
116
|
+
*/
|
|
117
|
+
export async function loadProject(projectPathArg) {
|
|
118
|
+
const projectPath = resolve(projectPathArg);
|
|
119
|
+
const raw = await readFile(projectPath, 'utf8');
|
|
120
|
+
const project = parseProjectText(raw, projectPath);
|
|
121
|
+
|
|
122
|
+
const screens = [];
|
|
123
|
+
const screensById = new Map();
|
|
124
|
+
const errors = [];
|
|
125
|
+
|
|
126
|
+
for (const ref of project?.screens ?? []) {
|
|
127
|
+
const memberPath = resolveScreenPath(projectPath, ref.path);
|
|
128
|
+
try {
|
|
129
|
+
const blueprint = await readBlueprintFile(memberPath);
|
|
130
|
+
screens.push({ ref, path: memberPath, blueprint });
|
|
131
|
+
screensById.set(ref.id, blueprint);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
errors.push(`screen "${ref.id}": cannot read ${ref.path} (${err.message})`);
|
|
134
|
+
screens.push({ ref, path: memberPath, blueprint: null });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { project, projectPath, screens, screensById, errors };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Merge project-level shared design_system with a member screen's own
|
|
143
|
+
* design_system (screen overrides win). Shallow merge at the top level; pure.
|
|
144
|
+
*/
|
|
145
|
+
export function mergeDesignSystem(project, blueprint) {
|
|
146
|
+
const shared = project?.design_system ?? {};
|
|
147
|
+
const own = blueprint?.design_system ?? {};
|
|
148
|
+
return { ...shared, ...own };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Build a project document that wraps a set of existing single-screen
|
|
153
|
+
* Blueprints. Pure: caller supplies the already-loaded screens with their
|
|
154
|
+
* relative paths. The first screen becomes the entry by default.
|
|
155
|
+
*/
|
|
156
|
+
export function buildProject({ id, name, description, screens }) {
|
|
157
|
+
if (!Array.isArray(screens) || screens.length === 0) {
|
|
158
|
+
throw new Error('buildProject requires at least one screen');
|
|
159
|
+
}
|
|
160
|
+
const screenRefs = screens.map(({ blueprint, path }) => ({
|
|
161
|
+
id: blueprint?.screen?.id,
|
|
162
|
+
name: blueprint?.screen?.name,
|
|
163
|
+
path,
|
|
164
|
+
}));
|
|
165
|
+
return {
|
|
166
|
+
$schema: '../schema/ui-project.schema.json',
|
|
167
|
+
version: PROJECT_VERSION,
|
|
168
|
+
id,
|
|
169
|
+
name,
|
|
170
|
+
...(description ? { description } : {}),
|
|
171
|
+
screens: screenRefs,
|
|
172
|
+
entry_screen: screenRefs[0].id,
|
|
173
|
+
navigation: [],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI for reference-based multi-screen AUB projects (*.aub.project.json).
|
|
3
|
+
// Usage:
|
|
4
|
+
// node scripts/project.mjs validate <project.aub.project.json>
|
|
5
|
+
// node scripts/project.mjs init <out.aub.project.json> <screen1.ui.json> [screen2.ui.json ...]
|
|
6
|
+
// node scripts/project.mjs export-md <project.aub.project.json> [outDir]
|
|
7
|
+
|
|
8
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
9
|
+
import { resolve, dirname, join, basename, relative } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
12
|
+
import addFormats from 'ajv-formats';
|
|
13
|
+
import {
|
|
14
|
+
loadProject,
|
|
15
|
+
validateProjectSemantics,
|
|
16
|
+
readBlueprintFile,
|
|
17
|
+
buildProject,
|
|
18
|
+
} from './project.lib.mjs';
|
|
19
|
+
import { validateBlueprintSemantics } from './validate-blueprint.lib.mjs';
|
|
20
|
+
import { buildKnownTypes } from './registry.lib.mjs';
|
|
21
|
+
import { exportMarkdown } from './export-md.lib.mjs';
|
|
22
|
+
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const ROOT = resolve(__dirname, '..');
|
|
25
|
+
|
|
26
|
+
function toForwardSlashes(p) {
|
|
27
|
+
return p.split('\\').join('/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ajvErrorLines(errors, label) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
for (const err of errors ?? []) {
|
|
33
|
+
const path = err.instancePath || '(root)';
|
|
34
|
+
let line = ` ${label}${path} ${err.message}`;
|
|
35
|
+
if (err.params && Object.keys(err.params).length > 0) {
|
|
36
|
+
line += ` (params: ${JSON.stringify(err.params)})`;
|
|
37
|
+
}
|
|
38
|
+
lines.push(line);
|
|
39
|
+
}
|
|
40
|
+
return lines;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Shared project-validation routine used by both this CLI's `validate`
|
|
45
|
+
* subcommand and by scripts/validate.mjs when it detects a project document.
|
|
46
|
+
* Validates the project against the project schema + project semantics, and
|
|
47
|
+
* each member screen against the blueprint schema + blueprint semantics.
|
|
48
|
+
* Returns { ok, errors } where errors is a flat array of human-readable strings.
|
|
49
|
+
*/
|
|
50
|
+
export async function validateProjectFile(projectPathArg) {
|
|
51
|
+
const errors = [];
|
|
52
|
+
const projectPath = resolve(projectPathArg);
|
|
53
|
+
|
|
54
|
+
let loaded;
|
|
55
|
+
try {
|
|
56
|
+
loaded = await loadProject(projectPath);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return { ok: false, errors: [`cannot load project: ${err.message}`] };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { project, screens, screensById, errors: loadErrors } = loaded;
|
|
62
|
+
|
|
63
|
+
// Member files that could not be read.
|
|
64
|
+
for (const loadError of loadErrors) {
|
|
65
|
+
errors.push(loadError);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Project schema validation.
|
|
69
|
+
const projectSchemaPath = join(ROOT, 'schema', 'ui-project.schema.json');
|
|
70
|
+
const projectSchema = JSON.parse(await readFile(projectSchemaPath, 'utf8'));
|
|
71
|
+
const ajvProject = new Ajv2020({ allErrors: true, strict: false });
|
|
72
|
+
addFormats(ajvProject);
|
|
73
|
+
const validateProject = ajvProject.compile(projectSchema);
|
|
74
|
+
if (!validateProject(project)) {
|
|
75
|
+
for (const line of ajvErrorLines(validateProject.errors, 'project schema: ')) {
|
|
76
|
+
errors.push(line.trim());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Project semantic validation.
|
|
81
|
+
for (const semanticError of validateProjectSemantics(project, { screensById })) {
|
|
82
|
+
errors.push(`project semantic: ${semanticError}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Member screen validation (schema + semantics).
|
|
86
|
+
const blueprintSchemaPath = join(ROOT, 'schema', 'ui-blueprint.schema.json');
|
|
87
|
+
const blueprintSchema = JSON.parse(await readFile(blueprintSchemaPath, 'utf8'));
|
|
88
|
+
const ajvBlueprint = new Ajv2020({ allErrors: true, strict: true });
|
|
89
|
+
addFormats(ajvBlueprint);
|
|
90
|
+
const validateBlueprint = ajvBlueprint.compile(blueprintSchema);
|
|
91
|
+
|
|
92
|
+
const perScreen = [];
|
|
93
|
+
for (const { ref, path: memberPath, blueprint } of screens) {
|
|
94
|
+
const screenErrors = [];
|
|
95
|
+
if (!blueprint) {
|
|
96
|
+
// Already reported via loadErrors; mark as failing.
|
|
97
|
+
perScreen.push({ id: ref.id, ok: false, errors: [] });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const schemaOk = validateBlueprint(blueprint);
|
|
101
|
+
if (!schemaOk) {
|
|
102
|
+
for (const line of ajvErrorLines(validateBlueprint.errors, 'schema: ')) {
|
|
103
|
+
screenErrors.push(line.trim());
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
let knownTypes;
|
|
107
|
+
try {
|
|
108
|
+
const resolved = await buildKnownTypes({ startDir: dirname(memberPath) });
|
|
109
|
+
knownTypes = resolved.knownTypes;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
screenErrors.push(`registry: ${err.message}`);
|
|
112
|
+
}
|
|
113
|
+
if (schemaOk && knownTypes) {
|
|
114
|
+
for (const semanticError of validateBlueprintSemantics(blueprint, { knownTypes })) {
|
|
115
|
+
screenErrors.push(`semantic: ${semanticError}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
perScreen.push({ id: ref.id, ok: screenErrors.length === 0, errors: screenErrors });
|
|
119
|
+
for (const screenError of screenErrors) {
|
|
120
|
+
errors.push(`screen "${ref.id}": ${screenError}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { ok: errors.length === 0, errors, perScreen };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function cmdValidate(arg) {
|
|
128
|
+
if (!arg) {
|
|
129
|
+
console.error('Usage: node scripts/project.mjs validate <project.aub.project.json>');
|
|
130
|
+
process.exit(2);
|
|
131
|
+
}
|
|
132
|
+
const projectPath = resolve(arg);
|
|
133
|
+
const result = await validateProjectFile(projectPath);
|
|
134
|
+
|
|
135
|
+
if (result.perScreen) {
|
|
136
|
+
for (const screen of result.perScreen) {
|
|
137
|
+
if (screen.ok) {
|
|
138
|
+
console.log(` ✓ screen ${screen.id}`);
|
|
139
|
+
} else {
|
|
140
|
+
console.error(` ✗ screen ${screen.id}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (result.ok) {
|
|
146
|
+
console.log(`✓ valid project: ${arg}`);
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.error(`✗ invalid project: ${arg}`);
|
|
151
|
+
for (const error of result.errors) {
|
|
152
|
+
console.error(` ${error}`);
|
|
153
|
+
}
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function kebabCase(value) {
|
|
158
|
+
const kebab = String(value)
|
|
159
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
160
|
+
.toLowerCase()
|
|
161
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
162
|
+
.replace(/^-+|-+$/g, '');
|
|
163
|
+
return kebab || 'project';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function humanize(value) {
|
|
167
|
+
const words = String(value)
|
|
168
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
169
|
+
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
|
170
|
+
.trim()
|
|
171
|
+
.split(/\s+/)
|
|
172
|
+
.filter(Boolean)
|
|
173
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1));
|
|
174
|
+
return words.join(' ') || 'Project';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function projectBaseName(outPath) {
|
|
178
|
+
let base = basename(outPath);
|
|
179
|
+
if (base.endsWith('.aub.project.json')) {
|
|
180
|
+
base = base.slice(0, -'.aub.project.json'.length);
|
|
181
|
+
} else if (base.endsWith('.json')) {
|
|
182
|
+
base = base.slice(0, -'.json'.length);
|
|
183
|
+
}
|
|
184
|
+
return base;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function cmdInit(outArg, screenArgs) {
|
|
188
|
+
if (!outArg || screenArgs.length === 0) {
|
|
189
|
+
console.error(
|
|
190
|
+
'Usage: node scripts/project.mjs init <out.aub.project.json> <screen1.ui.json> [screen2.ui.json ...]'
|
|
191
|
+
);
|
|
192
|
+
process.exit(2);
|
|
193
|
+
}
|
|
194
|
+
const outPath = resolve(outArg);
|
|
195
|
+
const outDir = dirname(outPath);
|
|
196
|
+
|
|
197
|
+
const screens = [];
|
|
198
|
+
for (const screenArg of screenArgs) {
|
|
199
|
+
const screenAbs = resolve(screenArg);
|
|
200
|
+
const blueprint = await readBlueprintFile(screenAbs);
|
|
201
|
+
const relPath = toForwardSlashes(relative(outDir, screenAbs));
|
|
202
|
+
screens.push({ blueprint, path: relPath });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const base = projectBaseName(outPath);
|
|
206
|
+
const project = buildProject({
|
|
207
|
+
id: kebabCase(base),
|
|
208
|
+
name: humanize(base),
|
|
209
|
+
screens,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await writeFile(outPath, `${JSON.stringify(project, null, 2)}\n`, 'utf8');
|
|
213
|
+
console.log(`✓ wrote ${outArg} with ${screens.length} screens`);
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function navEdgeLine(edge) {
|
|
218
|
+
const trigger = edge.trigger ? ` [${edge.trigger}]` : '';
|
|
219
|
+
const label = edge.label ? ` — ${edge.label}` : '';
|
|
220
|
+
return `- ${edge.from} → ${edge.to}${trigger}${label}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function buildOverviewMarkdown(project, screens) {
|
|
224
|
+
const lines = [];
|
|
225
|
+
lines.push(`# ${project.name}`);
|
|
226
|
+
lines.push('');
|
|
227
|
+
if (project.description) {
|
|
228
|
+
lines.push(project.description);
|
|
229
|
+
lines.push('');
|
|
230
|
+
}
|
|
231
|
+
lines.push(`- **Project id:** ${project.id}`);
|
|
232
|
+
lines.push(`- **Version:** ${project.version}`);
|
|
233
|
+
lines.push(`- **Entry screen:** ${project.entry_screen}`);
|
|
234
|
+
lines.push('');
|
|
235
|
+
lines.push('## Screens');
|
|
236
|
+
lines.push('');
|
|
237
|
+
for (const { ref } of screens) {
|
|
238
|
+
const name = ref.name ? ` — ${ref.name}` : '';
|
|
239
|
+
lines.push(`- \`${ref.id}\`${name} (\`${ref.path}\`)`);
|
|
240
|
+
}
|
|
241
|
+
lines.push('');
|
|
242
|
+
lines.push('## Navigation');
|
|
243
|
+
lines.push('');
|
|
244
|
+
const edges = Array.isArray(project.navigation) ? project.navigation : [];
|
|
245
|
+
if (edges.length === 0) {
|
|
246
|
+
lines.push('_No navigation edges declared._');
|
|
247
|
+
} else {
|
|
248
|
+
for (const edge of edges) {
|
|
249
|
+
lines.push(navEdgeLine(edge));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
lines.push('');
|
|
253
|
+
return lines.join('\n');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function cmdExportMd(arg, outDirArg) {
|
|
257
|
+
if (!arg) {
|
|
258
|
+
console.error('Usage: node scripts/project.mjs export-md <project.aub.project.json> [outDir]');
|
|
259
|
+
process.exit(2);
|
|
260
|
+
}
|
|
261
|
+
const projectPath = resolve(arg);
|
|
262
|
+
const loaded = await loadProject(projectPath);
|
|
263
|
+
const { project, screens, errors } = loaded;
|
|
264
|
+
|
|
265
|
+
if (errors.length > 0) {
|
|
266
|
+
console.error(`✗ cannot export markdown: ${arg}`);
|
|
267
|
+
for (const error of errors) {
|
|
268
|
+
console.error(` ${error}`);
|
|
269
|
+
}
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const outDir = outDirArg ? resolve(outDirArg) : dirname(projectPath);
|
|
274
|
+
await mkdir(outDir, { recursive: true });
|
|
275
|
+
|
|
276
|
+
const written = [];
|
|
277
|
+
|
|
278
|
+
const overviewPath = join(outDir, `${project.id}.project.md`);
|
|
279
|
+
await writeFile(overviewPath, `${buildOverviewMarkdown(project, screens)}`, 'utf8');
|
|
280
|
+
written.push(overviewPath);
|
|
281
|
+
|
|
282
|
+
for (const { ref, blueprint } of screens) {
|
|
283
|
+
const md = exportMarkdown(blueprint);
|
|
284
|
+
const screenPath = join(outDir, `${ref.id}.ui.md`);
|
|
285
|
+
await writeFile(screenPath, md.endsWith('\n') ? md : `${md}\n`, 'utf8');
|
|
286
|
+
written.push(screenPath);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log(`✓ wrote ${written.length} markdown files to ${outDir}`);
|
|
290
|
+
for (const path of written) {
|
|
291
|
+
console.log(` - ${path}`);
|
|
292
|
+
}
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function usage() {
|
|
297
|
+
console.error('Usage:');
|
|
298
|
+
console.error(' node scripts/project.mjs validate <project.aub.project.json>');
|
|
299
|
+
console.error(
|
|
300
|
+
' node scripts/project.mjs init <out.aub.project.json> <screen1.ui.json> [screen2.ui.json ...]'
|
|
301
|
+
);
|
|
302
|
+
console.error(' node scripts/project.mjs export-md <project.aub.project.json> [outDir]');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function main() {
|
|
306
|
+
const argv = process.argv.slice(2);
|
|
307
|
+
const subcommand = argv[0];
|
|
308
|
+
const rest = argv.slice(1);
|
|
309
|
+
|
|
310
|
+
switch (subcommand) {
|
|
311
|
+
case 'validate':
|
|
312
|
+
await cmdValidate(rest[0]);
|
|
313
|
+
break;
|
|
314
|
+
case 'init':
|
|
315
|
+
await cmdInit(rest[0], rest.slice(1));
|
|
316
|
+
break;
|
|
317
|
+
case 'export-md':
|
|
318
|
+
await cmdExportMd(rest[0], rest[1]);
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
usage();
|
|
322
|
+
process.exit(2);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Only run the CLI when invoked directly (not when imported by validate.mjs).
|
|
327
|
+
if (resolve(fileURLToPath(import.meta.url)) === resolve(process.argv[1] ?? '')) {
|
|
328
|
+
main().catch((err) => {
|
|
329
|
+
console.error(err);
|
|
330
|
+
process.exit(2);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface KnownTypeMeta {
|
|
2
|
+
isContainer: boolean;
|
|
3
|
+
source?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
implementations?: ComponentImplementation[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type KnownTypes = Map<string, KnownTypeMeta>;
|
|
9
|
+
|
|
10
|
+
export interface ExtensionComponent {
|
|
11
|
+
name: string;
|
|
12
|
+
isContainer: boolean;
|
|
13
|
+
description: string;
|
|
14
|
+
implementations: ComponentImplementation[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ComponentImplementation {
|
|
18
|
+
id: string;
|
|
19
|
+
framework: 'react' | 'vue' | 'angular' | 'svelte' | 'web-component' | 'html' | 'other';
|
|
20
|
+
module: string;
|
|
21
|
+
export?: string;
|
|
22
|
+
importStyle: 'named' | 'default' | 'namespace' | 'side-effect' | 'custom-element';
|
|
23
|
+
sourcePath?: string;
|
|
24
|
+
storybookUrl?: string;
|
|
25
|
+
docsUrl?: string;
|
|
26
|
+
props?: Record<string, { from: string; required?: boolean; description?: string }>;
|
|
27
|
+
notes?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BuildKnownTypesResult {
|
|
31
|
+
knownTypes: KnownTypes;
|
|
32
|
+
extensionPath: string | null;
|
|
33
|
+
extensions: ExtensionComponent[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const EXTENSION_REGISTRY_FILENAME: string;
|
|
37
|
+
export const EXTENSION_NAME_PATTERN: RegExp;
|
|
38
|
+
export const REPO_ROOT: string;
|
|
39
|
+
|
|
40
|
+
export function buildCoreKnownTypes(): Promise<KnownTypes>;
|
|
41
|
+
export function discoverExtensionRegistry(startDir?: string): string | null;
|
|
42
|
+
export function parseExtensionRegistry(
|
|
43
|
+
doc: unknown,
|
|
44
|
+
coreTypes: Set<string>,
|
|
45
|
+
sourceLabel?: string
|
|
46
|
+
): { components: ExtensionComponent[] };
|
|
47
|
+
export function buildKnownTypes(options?: {
|
|
48
|
+
extensionPath?: string | null;
|
|
49
|
+
startDir?: string;
|
|
50
|
+
discover?: boolean;
|
|
51
|
+
}): Promise<BuildKnownTypesResult>;
|
|
52
|
+
export function coreTypeLists(): Promise<{ all: string[]; containers: string[]; leaves: string[] }>;
|