@ggui-ai/project-config 0.1.0-rc.1
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 +66 -0
- package/dist/discovery.d.ts +74 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +167 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/mcp-mount-discovery.d.ts +82 -0
- package/dist/mcp-mount-discovery.d.ts.map +1 -0
- package/dist/mcp-mount-discovery.js +226 -0
- package/dist/node.d.ts +69 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +157 -0
- package/dist/primitive-discovery.d.ts +95 -0
- package/dist/primitive-discovery.d.ts.map +1 -0
- package/dist/primitive-discovery.js +281 -0
- package/dist/primitives-manifest.d.ts +106 -0
- package/dist/primitives-manifest.d.ts.map +1 -0
- package/dist/primitives-manifest.js +131 -0
- package/dist/schema.d.ts +520 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +556 -0
- package/dist/theme-loader.d.ts +104 -0
- package/dist/theme-loader.d.ts.map +1 -0
- package/dist/theme-loader.js +263 -0
- package/dist/theme.d.ts +311 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +366 -0
- package/dist/ui-manifest.d.ts +95 -0
- package/dist/ui-manifest.d.ts.map +1 -0
- package/dist/ui-manifest.js +114 -0
- package/package.json +67 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitive manifest discovery — turns a loaded `ggui.json` into the
|
|
3
|
+
* set of primitive catalogs the project declares via
|
|
4
|
+
* `primitives.packages` (npm specifiers) and `primitives.local`
|
|
5
|
+
* (globs).
|
|
6
|
+
*
|
|
7
|
+
* The flow:
|
|
8
|
+
*
|
|
9
|
+
* ggui.json.primitives.packages (npm specifiers)
|
|
10
|
+
* → resolve each spec through Node's module resolution
|
|
11
|
+
* → walk up to the enclosing `package.json`
|
|
12
|
+
* → read `ggui.primitives.json` alongside it
|
|
13
|
+
* → parse via {@link parsePrimitivesManifest}
|
|
14
|
+
*
|
|
15
|
+
* ggui.json.primitives.local (globs relative to project root)
|
|
16
|
+
* → filesystem match (tinyglobby, same options as blueprint
|
|
17
|
+
* discovery)
|
|
18
|
+
* → filter to basename === `ggui.primitives.json`
|
|
19
|
+
* → parse via {@link parsePrimitivesManifest}
|
|
20
|
+
*
|
|
21
|
+
* For npm specifiers, the resolver walks UP from the resolved subpath
|
|
22
|
+
* file to find the enclosing `package.json` — this avoids requiring
|
|
23
|
+
* packages to add `ggui.primitives.json` to their `exports` block and
|
|
24
|
+
* matches the intuition that "the manifest lives at the package root".
|
|
25
|
+
*
|
|
26
|
+
* ## Error model
|
|
27
|
+
*
|
|
28
|
+
* Discovery never throws on a per-source failure. One malformed
|
|
29
|
+
* manifest should not black-hole the rest of the project — the
|
|
30
|
+
* `ggui dev` hub renders the valid catalogs + flags the bad ones in
|
|
31
|
+
* the UI. Each issue carries a path + message so callers can render
|
|
32
|
+
* a useful list.
|
|
33
|
+
*
|
|
34
|
+
* Callers that need fail-fast semantics (e.g. `ggui serve` boot,
|
|
35
|
+
* where continuing with a partial set is a silent deploy-time
|
|
36
|
+
* regression) inspect `result.issues` themselves and escalate to a
|
|
37
|
+
* thrown error when non-empty. This helper stays policy-neutral —
|
|
38
|
+
* mirrors the blueprint-discovery split.
|
|
39
|
+
*
|
|
40
|
+
* ## Why `@ggui-ai/project-config/node`, not a sibling package
|
|
41
|
+
*
|
|
42
|
+
* Discovery is filesystem + parse + identity check. It is NOT HTTP,
|
|
43
|
+
* agent runtime, or generator state. Consumers today:
|
|
44
|
+
*
|
|
45
|
+
* - `@ggui-ai/mcp-server` at `ggui serve` boot time — builds the
|
|
46
|
+
* primitive index so future generator consumers can enumerate
|
|
47
|
+
* every declared source.
|
|
48
|
+
* - `@ggui-ai/dev-stack` may consume it later for a unified
|
|
49
|
+
* `ggui dev` hub view (symmetric with blueprint discovery).
|
|
50
|
+
*
|
|
51
|
+
* Housing it here keeps one source of truth and avoids an inbound
|
|
52
|
+
* dep on either consumer.
|
|
53
|
+
*/
|
|
54
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
55
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
56
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
57
|
+
import { glob } from 'tinyglobby';
|
|
58
|
+
import { GGUI_PRIMITIVES_JSON_FILENAME, parsePrimitivesManifest, } from './primitives-manifest.js';
|
|
59
|
+
/**
|
|
60
|
+
* Walk both `primitives.packages` and `primitives.local`, build the
|
|
61
|
+
* catalog, accumulate issues.
|
|
62
|
+
*
|
|
63
|
+
* Non-throwing per source. Duplicate-`import`-specifier across
|
|
64
|
+
* sources is an issue (first-seen wins, later ones accumulate as
|
|
65
|
+
* issues). A glob pattern that matches nothing is allowed — the
|
|
66
|
+
* manifest may exist ahead of primitives being authored; operators
|
|
67
|
+
* can still run `ggui dev` without the catalog being complete.
|
|
68
|
+
*/
|
|
69
|
+
export async function discoverPrimitives(options) {
|
|
70
|
+
const { projectRoot, manifest } = options;
|
|
71
|
+
if (!isAbsolute(projectRoot)) {
|
|
72
|
+
throw new Error(`discoverPrimitives: projectRoot must be absolute, got ${projectRoot}`);
|
|
73
|
+
}
|
|
74
|
+
const catalogs = [];
|
|
75
|
+
const issues = [];
|
|
76
|
+
const seenImports = new Map(); // import → manifestPath
|
|
77
|
+
const resolveModule = options.resolveModule ?? makeDefaultModuleResolver(projectRoot);
|
|
78
|
+
// ── packages: npm specifiers ────────────────────────────────────
|
|
79
|
+
for (const spec of manifest.primitives.packages) {
|
|
80
|
+
let entryPath;
|
|
81
|
+
try {
|
|
82
|
+
entryPath = resolveModule(spec, projectRoot);
|
|
83
|
+
}
|
|
84
|
+
catch (cause) {
|
|
85
|
+
issues.push({
|
|
86
|
+
path: spec,
|
|
87
|
+
message: `Could not resolve primitive package "${spec}" — is it installed in the project? ` +
|
|
88
|
+
`(${errorMessage(cause)})`,
|
|
89
|
+
cause,
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const pkgRoot = findEnclosingPackageRoot(entryPath);
|
|
94
|
+
if (!pkgRoot) {
|
|
95
|
+
issues.push({
|
|
96
|
+
path: spec,
|
|
97
|
+
message: `Primitive package "${spec}" resolved to ${entryPath}, but no enclosing package.json was found.`,
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const manifestPath = join(pkgRoot, GGUI_PRIMITIVES_JSON_FILENAME);
|
|
102
|
+
if (!existsSync(manifestPath)) {
|
|
103
|
+
issues.push({
|
|
104
|
+
path: spec,
|
|
105
|
+
message: `Primitive package "${spec}" is missing ${GGUI_PRIMITIVES_JSON_FILENAME} at its root (${pkgRoot}). ` +
|
|
106
|
+
`Expected convention: one ggui.primitives.json alongside package.json.`,
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const parsed = loadManifestAt(manifestPath, spec);
|
|
111
|
+
if (!parsed.ok) {
|
|
112
|
+
issues.push(parsed.issue);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// The declared `ggui.json` spec MUST match the manifest's own
|
|
116
|
+
// `import` field. A mismatch means the operator declared the
|
|
117
|
+
// wrong package name OR the package is advertising a different
|
|
118
|
+
// specifier — either way, the generator's import-line would be
|
|
119
|
+
// wrong. Surface it here rather than hand the agent a broken
|
|
120
|
+
// catalog.
|
|
121
|
+
if (parsed.manifest.import !== spec) {
|
|
122
|
+
issues.push({
|
|
123
|
+
path: spec,
|
|
124
|
+
message: `Primitive package "${spec}" declares import="${parsed.manifest.import}" in its ` +
|
|
125
|
+
`ggui.primitives.json, but ggui.json lists it under primitives.packages as "${spec}". ` +
|
|
126
|
+
`These must match — either update ggui.json's entry or the package's manifest.`,
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
recordCatalog(catalogs, issues, seenImports, {
|
|
131
|
+
source: 'package',
|
|
132
|
+
import: parsed.manifest.import,
|
|
133
|
+
manifestPath,
|
|
134
|
+
manifest: parsed.manifest,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// ── local: globs ────────────────────────────────────────────────
|
|
138
|
+
const localPatterns = manifest.primitives.local;
|
|
139
|
+
if (localPatterns.length > 0) {
|
|
140
|
+
const matches = await glob(localPatterns, {
|
|
141
|
+
cwd: projectRoot,
|
|
142
|
+
absolute: true,
|
|
143
|
+
onlyFiles: true,
|
|
144
|
+
followSymbolicLinks: false,
|
|
145
|
+
dot: false,
|
|
146
|
+
});
|
|
147
|
+
for (const absolutePath of matches) {
|
|
148
|
+
const rel = relative(projectRoot, absolutePath);
|
|
149
|
+
const displayPath = rel === '' ? absolutePath : rel;
|
|
150
|
+
// Mirror blueprint discovery's wide-`**` safety — skip matches
|
|
151
|
+
// whose basename isn't the canonical filename. Users sometimes
|
|
152
|
+
// write `ui/**/*.json` patterns and pick up arbitrary JSON.
|
|
153
|
+
if (!absolutePath.endsWith(sep + GGUI_PRIMITIVES_JSON_FILENAME)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const parsed = loadManifestAt(absolutePath, displayPath);
|
|
157
|
+
if (!parsed.ok) {
|
|
158
|
+
issues.push(parsed.issue);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
recordCatalog(catalogs, issues, seenImports, {
|
|
162
|
+
source: 'local',
|
|
163
|
+
import: parsed.manifest.import,
|
|
164
|
+
manifestPath: absolutePath,
|
|
165
|
+
manifest: parsed.manifest,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { catalogs, issues };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Convenience wrapper for the common "I have a `ggui.json` path,
|
|
173
|
+
* give me the primitives" flow. Resolves the project root from the
|
|
174
|
+
* manifest path.
|
|
175
|
+
*/
|
|
176
|
+
export async function discoverPrimitivesFromGguiJsonPath(manifestPath, manifest, extra = {}) {
|
|
177
|
+
const projectRoot = dirname(resolve(manifestPath));
|
|
178
|
+
return discoverPrimitives({ projectRoot, manifest, ...extra });
|
|
179
|
+
}
|
|
180
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
181
|
+
// Internals
|
|
182
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
183
|
+
function makeDefaultModuleResolver(projectRoot) {
|
|
184
|
+
// Anchor at `<projectRoot>/package.json` — the file doesn't have
|
|
185
|
+
// to exist for resolution to produce a working answer; Node uses
|
|
186
|
+
// the path only to establish the ancestor `node_modules/` chain.
|
|
187
|
+
//
|
|
188
|
+
// We use `import.meta.resolve` rather than `createRequire().resolve`
|
|
189
|
+
// because primitive packages (`@ggui-ai/design`, sibling catalogs
|
|
190
|
+
// operators may author) are ESM-only — their `package.json#exports`
|
|
191
|
+
// declare only the `"import"` condition per the open-source
|
|
192
|
+
// subtree's `"type": "module"` convention. CommonJS `require.resolve`
|
|
193
|
+
// cannot satisfy a subpath whose `exports` entry has no `"require"`
|
|
194
|
+
// condition, even though the subpath IS defined for ESM consumers —
|
|
195
|
+
// the resolver throws `ERR_PACKAGE_PATH_NOT_EXPORTED` with a
|
|
196
|
+
// misleading "not defined by exports" message.
|
|
197
|
+
//
|
|
198
|
+
// Crucially, discovery never actually `require()`s the resolved
|
|
199
|
+
// entry; it only walks UP from the entry path to find the enclosing
|
|
200
|
+
// `package.json` (see `findEnclosingPackageRoot` above). So using
|
|
201
|
+
// an ESM-native resolver is both honest (matches the package's
|
|
202
|
+
// actual condition set) and safe (no downstream CJS load).
|
|
203
|
+
//
|
|
204
|
+
// `import.meta.resolve(spec, parent)` is stable + synchronous in
|
|
205
|
+
// Node 20+. The returned URL is converted back to a filesystem path
|
|
206
|
+
// with `fileURLToPath` so the rest of the pipeline stays
|
|
207
|
+
// path-based.
|
|
208
|
+
const originUrl = pathToFileURL(join(projectRoot, 'package.json')).href;
|
|
209
|
+
return (specifier) => fileURLToPath(import.meta.resolve(specifier, originUrl));
|
|
210
|
+
}
|
|
211
|
+
function findEnclosingPackageRoot(startFile) {
|
|
212
|
+
let dir = dirname(resolve(startFile));
|
|
213
|
+
for (;;) {
|
|
214
|
+
if (existsSync(join(dir, 'package.json')))
|
|
215
|
+
return dir;
|
|
216
|
+
const parent = dirname(dir);
|
|
217
|
+
if (parent === dir)
|
|
218
|
+
return null;
|
|
219
|
+
dir = parent;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function loadManifestAt(absolutePath, displayPath) {
|
|
223
|
+
let raw;
|
|
224
|
+
try {
|
|
225
|
+
raw = readFileSync(absolutePath, 'utf-8');
|
|
226
|
+
}
|
|
227
|
+
catch (cause) {
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
issue: {
|
|
231
|
+
path: displayPath,
|
|
232
|
+
message: `Could not read ${GGUI_PRIMITIVES_JSON_FILENAME}: ${errorMessage(cause)}`,
|
|
233
|
+
cause,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
let decoded;
|
|
238
|
+
try {
|
|
239
|
+
decoded = JSON.parse(raw);
|
|
240
|
+
}
|
|
241
|
+
catch (cause) {
|
|
242
|
+
return {
|
|
243
|
+
ok: false,
|
|
244
|
+
issue: {
|
|
245
|
+
path: displayPath,
|
|
246
|
+
message: `${GGUI_PRIMITIVES_JSON_FILENAME} is not valid JSON: ${errorMessage(cause)}`,
|
|
247
|
+
cause,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const manifest = parsePrimitivesManifest(decoded);
|
|
253
|
+
return { ok: true, manifest };
|
|
254
|
+
}
|
|
255
|
+
catch (cause) {
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
issue: {
|
|
259
|
+
path: displayPath,
|
|
260
|
+
message: `${GGUI_PRIMITIVES_JSON_FILENAME} failed schema validation: ${errorMessage(cause)}`,
|
|
261
|
+
cause,
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function recordCatalog(catalogs, issues, seenImports, candidate) {
|
|
267
|
+
const prior = seenImports.get(candidate.import);
|
|
268
|
+
if (prior !== undefined) {
|
|
269
|
+
issues.push({
|
|
270
|
+
path: candidate.manifestPath,
|
|
271
|
+
message: `Duplicate primitive import specifier "${candidate.import}" — already declared by ${prior}. ` +
|
|
272
|
+
`Two sources cannot claim the same specifier (the generator would emit duplicate import lines).`,
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
seenImports.set(candidate.import, candidate.manifestPath);
|
|
277
|
+
catalogs.push(candidate);
|
|
278
|
+
}
|
|
279
|
+
function errorMessage(cause) {
|
|
280
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
281
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ggui.primitives.json` v1 — primitive package manifest.
|
|
3
|
+
*
|
|
4
|
+
* Fourth open file-format surface in the ggui manifest model, after
|
|
5
|
+
* `ggui.json` (root) and `ggui.ui.json` (per-UI). One file at the
|
|
6
|
+
* root of each primitive source:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* packages/design/
|
|
10
|
+
* ├── ggui.primitives.json ← this file
|
|
11
|
+
* ├── package.json
|
|
12
|
+
* └── src/primitives/
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The root `ggui.json#primitives.packages` (npm specifiers) +
|
|
16
|
+
* `primitives.local` (globs) declare *which* primitive sources the
|
|
17
|
+
* app uses; this file declares *what each source exports* + the
|
|
18
|
+
* `import` specifier consumers use + an optional pre-generated LLM
|
|
19
|
+
* docs pointer.
|
|
20
|
+
*
|
|
21
|
+
* Both `primitives.packages` (npm specifiers) and `primitives.local`
|
|
22
|
+
* (globs) follow the same discovery convention: the resolver walks up
|
|
23
|
+
* from the resolved source to the enclosing `package.json` and reads
|
|
24
|
+
* `ggui.primitives.json` next to it.
|
|
25
|
+
*
|
|
26
|
+
* **Ownership boundary:**
|
|
27
|
+
*
|
|
28
|
+
* - *Which* packages / local paths declare primitives → `ggui.json`.
|
|
29
|
+
* - *Which* primitives a source exports + import specifier + docs
|
|
30
|
+
* pointer → this file.
|
|
31
|
+
* - *What* each primitive renders / its prop interface / its
|
|
32
|
+
* JSDoc description → TSX source in the declaring package. NOT a
|
|
33
|
+
* ggui concern.
|
|
34
|
+
*
|
|
35
|
+
* **Extending rules:**
|
|
36
|
+
*
|
|
37
|
+
* 1. Additive only within `schema: '1'`. Optional fields default to
|
|
38
|
+
* no-op behaviour so older tooling ignores them.
|
|
39
|
+
* 2. Framework-neutral. Nothing Claude-SDK / Vercel-AI-SDK /
|
|
40
|
+
* LangGraph / framework-specific belongs here.
|
|
41
|
+
* 3. Root and per-primitive objects are strict — unknown keys fail
|
|
42
|
+
* parse. Same discipline as `ggui.json` + `ggui.ui.json`.
|
|
43
|
+
*/
|
|
44
|
+
import { z } from 'zod';
|
|
45
|
+
/**
|
|
46
|
+
* Root manifest. `import` MUST be a non-empty string — either an npm
|
|
47
|
+
* specifier (for `primitives.packages` sources) or a relative path
|
|
48
|
+
* (for `primitives.local` sources). The discovery layer validates
|
|
49
|
+
* that `import` matches the declared `packages` entry when the
|
|
50
|
+
* source was resolved through an npm specifier.
|
|
51
|
+
*/
|
|
52
|
+
export declare const PrimitivesManifestV1: z.ZodObject<{
|
|
53
|
+
/** File-format version — always `"1"` for v1. */
|
|
54
|
+
schema: z.ZodLiteral<"1">;
|
|
55
|
+
/**
|
|
56
|
+
* The specifier consumers import from. For npm-distributed
|
|
57
|
+
* primitive packages this is the same string the app lists in
|
|
58
|
+
* `ggui.json#primitives.packages` (e.g.
|
|
59
|
+
* `@ggui-ai/design/primitives`). For in-project local primitives
|
|
60
|
+
* it is the path consumers import from (e.g.
|
|
61
|
+
* `./src/ui/primitives/index.js`).
|
|
62
|
+
*/
|
|
63
|
+
import: z.ZodString;
|
|
64
|
+
/**
|
|
65
|
+
* List of primitive components this source exports. At least one
|
|
66
|
+
* entry — a manifest that declares zero primitives is always a
|
|
67
|
+
* mistake.
|
|
68
|
+
*/
|
|
69
|
+
primitives: z.ZodArray<z.ZodObject<{
|
|
70
|
+
name: z.ZodString;
|
|
71
|
+
}, z.core.$strict>>;
|
|
72
|
+
/**
|
|
73
|
+
* Optional path to a pre-generated LLM docs blob the generator
|
|
74
|
+
* can inject into the system prompt. Resolved relative to this
|
|
75
|
+
* manifest's directory at load time (not parse time).
|
|
76
|
+
*
|
|
77
|
+
* Absent → the server falls back to a names-only doc block and
|
|
78
|
+
* emits a boot warning (not fatal). The package's own build
|
|
79
|
+
* pipeline is responsible for emitting the file; ggui tooling
|
|
80
|
+
* never writes it.
|
|
81
|
+
*/
|
|
82
|
+
docs: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, z.core.$strict>;
|
|
84
|
+
/** Static TypeScript type derived from the v1 schema. */
|
|
85
|
+
export type PrimitivesManifestV1 = z.infer<typeof PrimitivesManifestV1>;
|
|
86
|
+
/**
|
|
87
|
+
* Canonical type alias — `PrimitivesManifest` is the term used
|
|
88
|
+
* everywhere else (discovery, docs, mcp-server wiring).
|
|
89
|
+
* `PrimitivesManifestV1` is the version-pinned name.
|
|
90
|
+
*/
|
|
91
|
+
export type PrimitivesManifest = PrimitivesManifestV1;
|
|
92
|
+
/**
|
|
93
|
+
* Canonical filename — always `ggui.primitives.json`, always at the
|
|
94
|
+
* root of the declaring package / local primitive directory.
|
|
95
|
+
*/
|
|
96
|
+
export declare const GGUI_PRIMITIVES_JSON_FILENAME = "ggui.primitives.json";
|
|
97
|
+
/**
|
|
98
|
+
* Parse a raw JSON value into a validated {@link PrimitivesManifest}.
|
|
99
|
+
* Throws a `ZodError` with human-readable issues on invalid input.
|
|
100
|
+
*/
|
|
101
|
+
export declare function parsePrimitivesManifest(raw: unknown): PrimitivesManifest;
|
|
102
|
+
/**
|
|
103
|
+
* Safe-parse variant — returns a discriminated `z.safeParse` result.
|
|
104
|
+
*/
|
|
105
|
+
export declare function safeParsePrimitivesManifest(raw: unknown): ReturnType<typeof PrimitivesManifestV1.safeParse>;
|
|
106
|
+
//# sourceMappingURL=primitives-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives-manifest.d.ts","sourceRoot":"","sources":["../src/primitives-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiCxB;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;IAE7B,iDAAiD;;IAGjD;;;;;;;OAOG;;IAGH;;;;OAIG;;;;IASH;;;;;;;;;OASG;;kBAEH,CAAC;AAEL,yDAAyD;AACzD,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,yBAAyB,CAAC;AAEpE;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,kBAAkB,CAExE;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,OAAO,GACX,UAAU,CAAC,OAAO,oBAAoB,CAAC,SAAS,CAAC,CAEnD"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ggui.primitives.json` v1 — primitive package manifest.
|
|
3
|
+
*
|
|
4
|
+
* Fourth open file-format surface in the ggui manifest model, after
|
|
5
|
+
* `ggui.json` (root) and `ggui.ui.json` (per-UI). One file at the
|
|
6
|
+
* root of each primitive source:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* packages/design/
|
|
10
|
+
* ├── ggui.primitives.json ← this file
|
|
11
|
+
* ├── package.json
|
|
12
|
+
* └── src/primitives/
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The root `ggui.json#primitives.packages` (npm specifiers) +
|
|
16
|
+
* `primitives.local` (globs) declare *which* primitive sources the
|
|
17
|
+
* app uses; this file declares *what each source exports* + the
|
|
18
|
+
* `import` specifier consumers use + an optional pre-generated LLM
|
|
19
|
+
* docs pointer.
|
|
20
|
+
*
|
|
21
|
+
* Both `primitives.packages` (npm specifiers) and `primitives.local`
|
|
22
|
+
* (globs) follow the same discovery convention: the resolver walks up
|
|
23
|
+
* from the resolved source to the enclosing `package.json` and reads
|
|
24
|
+
* `ggui.primitives.json` next to it.
|
|
25
|
+
*
|
|
26
|
+
* **Ownership boundary:**
|
|
27
|
+
*
|
|
28
|
+
* - *Which* packages / local paths declare primitives → `ggui.json`.
|
|
29
|
+
* - *Which* primitives a source exports + import specifier + docs
|
|
30
|
+
* pointer → this file.
|
|
31
|
+
* - *What* each primitive renders / its prop interface / its
|
|
32
|
+
* JSDoc description → TSX source in the declaring package. NOT a
|
|
33
|
+
* ggui concern.
|
|
34
|
+
*
|
|
35
|
+
* **Extending rules:**
|
|
36
|
+
*
|
|
37
|
+
* 1. Additive only within `schema: '1'`. Optional fields default to
|
|
38
|
+
* no-op behaviour so older tooling ignores them.
|
|
39
|
+
* 2. Framework-neutral. Nothing Claude-SDK / Vercel-AI-SDK /
|
|
40
|
+
* LangGraph / framework-specific belongs here.
|
|
41
|
+
* 3. Root and per-primitive objects are strict — unknown keys fail
|
|
42
|
+
* parse. Same discipline as `ggui.json` + `ggui.ui.json`.
|
|
43
|
+
*/
|
|
44
|
+
import { z } from 'zod';
|
|
45
|
+
/**
|
|
46
|
+
* Primitive component name — must be a valid JavaScript identifier.
|
|
47
|
+
* Stricter than a freeform string because the generator emits literal
|
|
48
|
+
* `import { <name> } from '<import>'` lines, and anything that isn't
|
|
49
|
+
* a valid identifier would break the TSX output.
|
|
50
|
+
*
|
|
51
|
+
* Also rejects reserved words implicitly via the "leading letter"
|
|
52
|
+
* rule — JS identifiers can't start with a digit. A downstream
|
|
53
|
+
* allowlist of reserved words is avoided here on purpose: the list
|
|
54
|
+
* is long, version-dependent, and would add maintenance burden
|
|
55
|
+
* without meaningfully changing what authors actually write.
|
|
56
|
+
*/
|
|
57
|
+
const PrimitiveNameSchema = z
|
|
58
|
+
.string()
|
|
59
|
+
.min(1)
|
|
60
|
+
.max(64)
|
|
61
|
+
.regex(/^[A-Za-z_$][A-Za-z0-9_$]*$/, {
|
|
62
|
+
message: 'primitive name must be a valid JavaScript identifier (letters, digits, _, $; not starting with a digit).',
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Per-primitive entry. v1 only requires `name`; the TSX source +
|
|
66
|
+
* JSDoc is the authoritative prop-surface. Additive fields
|
|
67
|
+
* (description override, deprecation marker, variant hint) land
|
|
68
|
+
* later under `schema: "1"`.
|
|
69
|
+
*/
|
|
70
|
+
const PrimitiveEntrySchema = z.strictObject({
|
|
71
|
+
name: PrimitiveNameSchema,
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* Root manifest. `import` MUST be a non-empty string — either an npm
|
|
75
|
+
* specifier (for `primitives.packages` sources) or a relative path
|
|
76
|
+
* (for `primitives.local` sources). The discovery layer validates
|
|
77
|
+
* that `import` matches the declared `packages` entry when the
|
|
78
|
+
* source was resolved through an npm specifier.
|
|
79
|
+
*/
|
|
80
|
+
export const PrimitivesManifestV1 = z
|
|
81
|
+
.strictObject({
|
|
82
|
+
/** File-format version — always `"1"` for v1. */
|
|
83
|
+
schema: z.literal('1'),
|
|
84
|
+
/**
|
|
85
|
+
* The specifier consumers import from. For npm-distributed
|
|
86
|
+
* primitive packages this is the same string the app lists in
|
|
87
|
+
* `ggui.json#primitives.packages` (e.g.
|
|
88
|
+
* `@ggui-ai/design/primitives`). For in-project local primitives
|
|
89
|
+
* it is the path consumers import from (e.g.
|
|
90
|
+
* `./src/ui/primitives/index.js`).
|
|
91
|
+
*/
|
|
92
|
+
import: z.string().min(1, 'import must not be empty'),
|
|
93
|
+
/**
|
|
94
|
+
* List of primitive components this source exports. At least one
|
|
95
|
+
* entry — a manifest that declares zero primitives is always a
|
|
96
|
+
* mistake.
|
|
97
|
+
*/
|
|
98
|
+
primitives: z
|
|
99
|
+
.array(PrimitiveEntrySchema)
|
|
100
|
+
.min(1, 'primitives must declare at least one entry')
|
|
101
|
+
.refine((arr) => new Set(arr.map((p) => p.name)).size === arr.length, { message: 'primitive names must be unique within a manifest' }),
|
|
102
|
+
/**
|
|
103
|
+
* Optional path to a pre-generated LLM docs blob the generator
|
|
104
|
+
* can inject into the system prompt. Resolved relative to this
|
|
105
|
+
* manifest's directory at load time (not parse time).
|
|
106
|
+
*
|
|
107
|
+
* Absent → the server falls back to a names-only doc block and
|
|
108
|
+
* emits a boot warning (not fatal). The package's own build
|
|
109
|
+
* pipeline is responsible for emitting the file; ggui tooling
|
|
110
|
+
* never writes it.
|
|
111
|
+
*/
|
|
112
|
+
docs: z.string().min(1).optional(),
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* Canonical filename — always `ggui.primitives.json`, always at the
|
|
116
|
+
* root of the declaring package / local primitive directory.
|
|
117
|
+
*/
|
|
118
|
+
export const GGUI_PRIMITIVES_JSON_FILENAME = 'ggui.primitives.json';
|
|
119
|
+
/**
|
|
120
|
+
* Parse a raw JSON value into a validated {@link PrimitivesManifest}.
|
|
121
|
+
* Throws a `ZodError` with human-readable issues on invalid input.
|
|
122
|
+
*/
|
|
123
|
+
export function parsePrimitivesManifest(raw) {
|
|
124
|
+
return PrimitivesManifestV1.parse(raw);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Safe-parse variant — returns a discriminated `z.safeParse` result.
|
|
128
|
+
*/
|
|
129
|
+
export function safeParsePrimitivesManifest(raw) {
|
|
130
|
+
return PrimitivesManifestV1.safeParse(raw);
|
|
131
|
+
}
|