@estiva-app/ui 0.21.0 → 0.22.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/dist/Avatar.d.ts +3 -0
- package/dist/Avatar.d.ts.map +1 -1
- package/dist/ChipInput.d.ts +3 -0
- package/dist/ChipInput.d.ts.map +1 -1
- package/dist/CommandPalette.d.ts +6 -0
- package/dist/CommandPalette.d.ts.map +1 -1
- package/dist/IdentityMenu.d.ts +3 -0
- package/dist/IdentityMenu.d.ts.map +1 -1
- package/dist/Menu.d.ts +7 -0
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Skeleton.d.ts +2 -0
- package/dist/Skeleton.d.ts.map +1 -1
- package/dist/Toast.d.ts +5 -0
- package/dist/Toast.d.ts.map +1 -1
- package/dist/Toolbar.d.ts +4 -0
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +4 -0
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/gates/app-checks.d.ts.map +1 -1
- package/dist/gates/{chunk-AWO7SZSA.js → chunk-AUXD4GCY.js} +10 -9
- package/dist/gates/chunk-AUXD4GCY.js.map +7 -0
- package/dist/gates/cli.js +2 -2
- package/dist/gates/cli.js.map +2 -2
- package/dist/gates/index.js +4 -2
- package/dist/gates/index.js.map +2 -2
- package/dist/gates/status.d.ts +5 -1
- package/dist/gates/status.d.ts.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/registry/build-GOVLABI6.js +13 -0
- package/dist/registry/build-GOVLABI6.js.map +7 -0
- package/dist/registry/build.d.ts +29 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/chunk-IJNCYVH4.js +163 -0
- package/dist/registry/chunk-IJNCYVH4.js.map +7 -0
- package/dist/registry/chunk-MRSBS5OP.js +99 -0
- package/dist/registry/chunk-MRSBS5OP.js.map +7 -0
- package/dist/registry/chunk-QDYGB3QN.js +352 -0
- package/dist/registry/chunk-QDYGB3QN.js.map +7 -0
- package/dist/registry/cli.d.ts +2 -0
- package/dist/registry/cli.d.ts.map +1 -0
- package/dist/registry/cli.js +121 -0
- package/dist/registry/cli.js.map +7 -0
- package/dist/registry/find.d.ts +43 -0
- package/dist/registry/find.d.ts.map +1 -0
- package/dist/registry/index.d.ts +14 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +26 -0
- package/dist/registry/index.js.map +7 -0
- package/dist/registry/schema.d.ts +147 -0
- package/dist/registry/schema.d.ts.map +1 -0
- package/package.json +15 -2
- package/registry.json +4470 -0
- package/src/Avatar.tsx +3 -0
- package/src/ChipInput.tsx +3 -0
- package/src/CommandPalette.tsx +6 -0
- package/src/IdentityMenu.tsx +3 -0
- package/src/Menu.tsx +7 -0
- package/src/Skeleton.tsx +2 -0
- package/src/Toast.tsx +5 -0
- package/src/Toolbar.tsx +4 -0
- package/src/Tooltip.tsx +4 -0
- package/src/gates/app-checks.ts +3 -1
- package/src/gates/cli.ts +8 -2
- package/src/gates/gates.test.ts +11 -0
- package/src/gates/status.ts +24 -11
- package/src/registry/build.ts +590 -0
- package/src/registry/cli.ts +141 -0
- package/src/registry/find.ts +195 -0
- package/src/registry/index.ts +24 -0
- package/src/registry/registry.test.ts +448 -0
- package/src/registry/schema.ts +268 -0
- package/dist/gates/chunk-AWO7SZSA.js.map +0 -7
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildRegistry,
|
|
4
|
+
readIndexExports,
|
|
5
|
+
serializeRegistry
|
|
6
|
+
} from "./chunk-QDYGB3QN.js";
|
|
7
|
+
import "./chunk-MRSBS5OP.js";
|
|
8
|
+
export {
|
|
9
|
+
buildRegistry,
|
|
10
|
+
readIndexExports,
|
|
11
|
+
serializeRegistry
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=build-GOVLABI6.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Registry } from './schema';
|
|
2
|
+
export interface BuildOptions {
|
|
3
|
+
/** The repository's top folder. */
|
|
4
|
+
root?: string;
|
|
5
|
+
/** The name this registry's entries are recorded under. One repo per registry until UIG-13. */
|
|
6
|
+
repo?: string;
|
|
7
|
+
}
|
|
8
|
+
/** One name as `index.ts` exports it. */
|
|
9
|
+
interface RawExport {
|
|
10
|
+
name: string;
|
|
11
|
+
/** The sibling it comes from, without `./`. */
|
|
12
|
+
module: string;
|
|
13
|
+
isType: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Read `src/index.ts`. Its `export { … } from './Sibling'` statements are the target set. */
|
|
16
|
+
export declare function readIndexExports(source: string): RawExport[];
|
|
17
|
+
/**
|
|
18
|
+
* Read the repository and return the catalogue.
|
|
19
|
+
*
|
|
20
|
+
* Throws on anything it cannot explain — a missing sibling, a name with no
|
|
21
|
+
* purpose anywhere. A registry that quietly drops what it could not read is
|
|
22
|
+
* worse than no registry, because a reader cannot tell the difference between
|
|
23
|
+
* "we have nothing for that" and "the builder gave up".
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildRegistry({ root, repo }?: BuildOptions): Registry;
|
|
26
|
+
/** The committed file's text, so a check and a write produce the same bytes. */
|
|
27
|
+
export declare function serializeRegistry(registry: Registry): string;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/registry/build.ts"],"names":[],"mappings":"AAqBA,OAAO,EAA0F,KAAK,QAAQ,EAAsB,MAAM,UAAU,CAAA;AAEpJ,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,yCAAyC;AACzC,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;AAuDD,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CAa5D;AAsXD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAAE,IAAoB,EAAE,IAAkB,EAAE,GAAE,YAAiB,GAAG,QAAQ,CAiGvG;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAE5D"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/registry/find.ts
|
|
4
|
+
var STOP = /* @__PURE__ */ new Set([
|
|
5
|
+
"a",
|
|
6
|
+
"an",
|
|
7
|
+
"and",
|
|
8
|
+
"are",
|
|
9
|
+
"as",
|
|
10
|
+
"at",
|
|
11
|
+
"be",
|
|
12
|
+
"but",
|
|
13
|
+
"by",
|
|
14
|
+
"can",
|
|
15
|
+
"do",
|
|
16
|
+
"for",
|
|
17
|
+
"from",
|
|
18
|
+
"how",
|
|
19
|
+
"i",
|
|
20
|
+
"if",
|
|
21
|
+
"in",
|
|
22
|
+
"is",
|
|
23
|
+
"it",
|
|
24
|
+
"its",
|
|
25
|
+
"me",
|
|
26
|
+
"my",
|
|
27
|
+
"no",
|
|
28
|
+
"not",
|
|
29
|
+
"of",
|
|
30
|
+
"on",
|
|
31
|
+
"or",
|
|
32
|
+
"our",
|
|
33
|
+
"so",
|
|
34
|
+
"that",
|
|
35
|
+
"the",
|
|
36
|
+
"their",
|
|
37
|
+
"them",
|
|
38
|
+
"then",
|
|
39
|
+
"there",
|
|
40
|
+
"they",
|
|
41
|
+
"this",
|
|
42
|
+
"to",
|
|
43
|
+
"up",
|
|
44
|
+
"was",
|
|
45
|
+
"we",
|
|
46
|
+
"what",
|
|
47
|
+
"when",
|
|
48
|
+
"which",
|
|
49
|
+
"with",
|
|
50
|
+
"you",
|
|
51
|
+
"your"
|
|
52
|
+
]);
|
|
53
|
+
function words(text) {
|
|
54
|
+
const found = [];
|
|
55
|
+
for (const token of text.match(/[A-Za-z0-9]+/g) ?? []) {
|
|
56
|
+
found.push(token.toLowerCase());
|
|
57
|
+
const parts = token.split(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/);
|
|
58
|
+
if (parts.length > 1) found.push(...parts.map((part) => part.toLowerCase()));
|
|
59
|
+
}
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
function alike(query, text) {
|
|
63
|
+
if (query === text) return true;
|
|
64
|
+
if (Math.min(query.length, text.length) < 3) return false;
|
|
65
|
+
if (text.startsWith(query) || query.startsWith(text)) return true;
|
|
66
|
+
return query.slice(0, 5) === text.slice(0, 5);
|
|
67
|
+
}
|
|
68
|
+
var hit = (query, text) => words(text).some((word) => alike(query, word));
|
|
69
|
+
function findInRegistry(registry, query, { limit = 5 } = {}) {
|
|
70
|
+
const all = words(query);
|
|
71
|
+
const carrying = all.filter((word) => word.length >= 3 && !STOP.has(word));
|
|
72
|
+
const asked = carrying.length ? carrying : all;
|
|
73
|
+
if (asked.length === 0) return [];
|
|
74
|
+
const findings = [];
|
|
75
|
+
for (const entry of registry.entries) {
|
|
76
|
+
let score = 0;
|
|
77
|
+
let matched = 0;
|
|
78
|
+
const where = /* @__PURE__ */ new Set();
|
|
79
|
+
const byName = /* @__PURE__ */ new Set();
|
|
80
|
+
const byNote = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const word of asked) {
|
|
82
|
+
let best = 0;
|
|
83
|
+
if (entry.name.toLowerCase() === word) {
|
|
84
|
+
best = 100;
|
|
85
|
+
where.add("name");
|
|
86
|
+
} else if (hit(word, entry.name)) {
|
|
87
|
+
best = 40;
|
|
88
|
+
where.add("name");
|
|
89
|
+
}
|
|
90
|
+
if (hit(word, entry.purpose)) {
|
|
91
|
+
best = Math.max(best, 20);
|
|
92
|
+
where.add("purpose");
|
|
93
|
+
}
|
|
94
|
+
for (const owned of entry.ownsBehaviours) {
|
|
95
|
+
if (hit(word, owned.behaviour) || hit(word, owned.id)) {
|
|
96
|
+
best = Math.max(best, 15);
|
|
97
|
+
where.add("behaviour");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const variant of entry.variants) {
|
|
101
|
+
if (variant.values.some((value) => hit(word, value)) || hit(word, variant.prop)) {
|
|
102
|
+
best = Math.max(best, 8);
|
|
103
|
+
where.add("variant");
|
|
104
|
+
byName.add(variant.prop);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const prop of entry.props) {
|
|
108
|
+
if (hit(word, prop.name)) {
|
|
109
|
+
best = Math.max(best, 14);
|
|
110
|
+
where.add("prop");
|
|
111
|
+
byName.add(prop.name);
|
|
112
|
+
} else if (prop.note !== null && hit(word, prop.note)) {
|
|
113
|
+
best = Math.max(best, 10);
|
|
114
|
+
where.add("prop");
|
|
115
|
+
byNote.add(prop.name);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (best > 0) matched += 1;
|
|
119
|
+
score += best;
|
|
120
|
+
}
|
|
121
|
+
if (matched > 0) findings.push({ entry, matched, score, where: [...where], props: [...byName] });
|
|
122
|
+
}
|
|
123
|
+
findings.sort((a, b) => b.matched - a.matched || b.score - a.score || a.entry.name.localeCompare(b.entry.name));
|
|
124
|
+
return findings.slice(0, limit);
|
|
125
|
+
}
|
|
126
|
+
function docsLink(registry, entry) {
|
|
127
|
+
if (!entry.docsId) return null;
|
|
128
|
+
return registry.storybook.devUrl + registry.storybook.docsPath.replace("{docsId}", entry.docsId);
|
|
129
|
+
}
|
|
130
|
+
function formatFindings(registry, findings, query) {
|
|
131
|
+
if (findings.length === 0) {
|
|
132
|
+
return [
|
|
133
|
+
`Nothing in ${registry.builtFrom.repo} matches "${query}".`,
|
|
134
|
+
"If nothing here does what you need, say so and ask \u2014 do not invent a component."
|
|
135
|
+
].join("\n");
|
|
136
|
+
}
|
|
137
|
+
const blocks = findings.map((finding) => {
|
|
138
|
+
const { entry } = finding;
|
|
139
|
+
const lines = [
|
|
140
|
+
`${entry.name} \xB7 ${entry.kind} \xB7 ${entry.repo} (matched ${finding.where.join(", ")})`,
|
|
141
|
+
` ${entry.purpose}`,
|
|
142
|
+
` import { ${entry.name} } from '${entry.importPath}'`
|
|
143
|
+
];
|
|
144
|
+
if (entry.ownsBehaviours.length) lines.push(` owns: ${entry.ownsBehaviours.map((owned) => owned.behaviour).join(" \xB7 ")}`);
|
|
145
|
+
if (entry.variants.length) lines.push(` ${entry.variants.map((variant) => `${variant.prop}: ${variant.values.join(" | ")}`).join(" ")}`);
|
|
146
|
+
const shown = finding.props.filter((name) => !entry.variants.some((variant) => variant.prop === name)).slice(0, 3).map((name) => entry.props.find((prop) => prop.name === name));
|
|
147
|
+
for (const prop of shown) {
|
|
148
|
+
if (prop) lines.push(` ${prop.name}: ${prop.takes}${prop.note ? ` \u2014 ${prop.note}` : ""}`);
|
|
149
|
+
}
|
|
150
|
+
if (entry.props.length) lines.push(` ${entry.props.length} props in all; see the page for the rest`);
|
|
151
|
+
const link = docsLink(registry, entry);
|
|
152
|
+
if (link) lines.push(` ${link}`);
|
|
153
|
+
return lines.join("\n");
|
|
154
|
+
});
|
|
155
|
+
return blocks.join("\n\n");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
findInRegistry,
|
|
160
|
+
docsLink,
|
|
161
|
+
formatFindings
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=chunk-IJNCYVH4.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/registry/find.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The catalogue's reader (UIG-12): `ui:find <words>`.\n *\n * It exists to prove the format. A schema nobody consumes is a schema nobody\n * has tested, and the questions it has to answer are regression tests, not the\n * scope: \"floating panel\" must find `Popover`, \"scrolling\" `ScrollArea`,\n * \"empty\" `EmptyState`.\n *\n * It is also the answer to \"what do we have for this?\", which is the question\n * the wall cannot answer: a lint rule refuses the wrong thing, and this is\n * where the right one is named (UIG-20 makes a session ask it first).\n */\nimport type { Registry, RegistryEntry } from './schema'\n\nexport interface Finding {\n entry: RegistryEntry\n /** How many of the query's words matched anywhere. Ranked on this first. */\n matched: number\n score: number\n /** Which fields matched, for the reader to say why. */\n where: string[]\n /**\n * The props the query named **by name**, so the answer shows those rather\n * than all of them. A prop whose note merely carries one of the words counts\n * towards the score and is not shown: on \"floating panel\", six of `Popover`'s\n * notes say \"panel\", and printing all six buries the answer.\n */\n props: string[]\n}\n\n/**\n * Words that are in nearly every sentence, and so tell a search nothing.\n *\n * Measured: `ui:find \"truncate a long link\"` printed `Link`'s `external` prop,\n * because its note says \"in **a** new tab\" and the one-letter word matched\n * itself exactly. The minimum length in `alike` only guards a *prefix* match;\n * an exact match short-circuits it, which is right for `tab` and useless for `a`.\n */\nconst STOP = new Set([\n 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'can', 'do', 'for', 'from', 'how', 'i', 'if', 'in', 'is', 'it', 'its', 'me', 'my', 'no', 'not', 'of',\n 'on', 'or', 'our', 'so', 'that', 'the', 'their', 'them', 'then', 'there', 'they', 'this', 'to', 'up', 'was', 'we', 'what', 'when', 'which', 'with', 'you', 'your',\n])\n\n/**\n * The words of a piece of text, with names taken apart.\n *\n * `CommandPalette` is kept whole *and* split, so \"palette\" reaches it and\n * \"commandpalette\" still does. Prose is already words; this costs it nothing.\n */\nfunction words(text: string): string[] {\n const found: string[] = []\n for (const token of text.match(/[A-Za-z0-9]+/g) ?? []) {\n found.push(token.toLowerCase())\n const parts = token.split(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/)\n if (parts.length > 1) found.push(...parts.map((part) => part.toLowerCase()))\n }\n return found\n}\n\n/**\n * Whether a word of the query means a word of the text.\n *\n * Either being a prefix of the other covers \"scroll\" against \"scrolling\"; the\n * five-letter key covers \"scrolls\" against \"scrolling\", where neither is. Crude\n * on purpose \u2014 a stemmer is a dependency and a surprise, and the failure here\n * is an extra row in a list of five, not a wrong answer.\n *\n * **Three letters at least**, or a prefix match is no match at all: `person's`\n * puts the word \"s\" in the text, and every query on earth starts with some\n * letter. Measured \u2014 before this, \"scrolling\" found `Avatar`.\n */\nfunction alike(query: string, text: string): boolean {\n if (query === text) return true\n if (Math.min(query.length, text.length) < 3) return false\n if (text.startsWith(query) || query.startsWith(text)) return true\n return query.slice(0, 5) === text.slice(0, 5)\n}\n\nconst hit = (query: string, text: string) => words(text).some((word) => alike(query, word))\n\n/**\n * Search name, purpose, behaviours and variants.\n *\n * A row that answers more of the question comes first, whatever it scored:\n * \"floating panel\" has to reach `Popover`, whose purpose carries both words,\n * over `MenuPanel`, whose name carries one of them loudly.\n */\nexport function findInRegistry(registry: Registry, query: string, { limit = 5 }: { limit?: number } = {}): Finding[] {\n const all = words(query)\n // Drop the words that mean nothing. If the question was only those, keep them\n // rather than answer nothing at all.\n const carrying = all.filter((word) => word.length >= 3 && !STOP.has(word))\n const asked = carrying.length ? carrying : all\n if (asked.length === 0) return []\n\n const findings: Finding[] = []\n for (const entry of registry.entries) {\n let score = 0\n let matched = 0\n const where = new Set<string>()\n const byName = new Set<string>()\n const byNote = new Set<string>()\n for (const word of asked) {\n let best = 0\n if (entry.name.toLowerCase() === word) {\n best = 100\n where.add('name')\n } else if (hit(word, entry.name)) {\n best = 40\n where.add('name')\n }\n if (hit(word, entry.purpose)) {\n best = Math.max(best, 20)\n where.add('purpose')\n }\n for (const owned of entry.ownsBehaviours) {\n if (hit(word, owned.behaviour) || hit(word, owned.id)) {\n best = Math.max(best, 15)\n where.add('behaviour')\n }\n }\n for (const variant of entry.variants) {\n if (variant.values.some((value) => hit(word, value)) || hit(word, variant.prop)) {\n best = Math.max(best, 8)\n where.add('variant')\n byName.add(variant.prop)\n }\n }\n // What it can do. \"does Link already truncate?\" is the question this\n // answers, and answering it is what stops the thing being built twice.\n for (const prop of entry.props) {\n // A prop the question named by name is the answer; one whose note\n // happens to carry the word is a hint. Named ones go first, so a short\n // answer shows the right lines.\n if (hit(word, prop.name)) {\n best = Math.max(best, 14)\n where.add('prop')\n byName.add(prop.name)\n } else if (prop.note !== null && hit(word, prop.note)) {\n best = Math.max(best, 10)\n where.add('prop')\n byNote.add(prop.name)\n }\n }\n if (best > 0) matched += 1\n score += best\n }\n if (matched > 0) findings.push({ entry, matched, score, where: [...where], props: [...byName] })\n }\n\n findings.sort((a, b) => b.matched - a.matched || b.score - a.score || a.entry.name.localeCompare(b.entry.name))\n return findings.slice(0, limit)\n}\n\n/** `http://localhost:6008/?path=/docs/overlays-popover--docs`, or `null` where the entry has no page. */\nexport function docsLink(registry: Registry, entry: RegistryEntry): string | null {\n if (!entry.docsId) return null\n return registry.storybook.devUrl + registry.storybook.docsPath.replace('{docsId}', entry.docsId)\n}\n\n/** What the command prints: the import line first, because that is what the reader came for. */\nexport function formatFindings(registry: Registry, findings: Finding[], query: string): string {\n if (findings.length === 0) {\n return [\n `Nothing in ${registry.builtFrom.repo} matches \"${query}\".`,\n 'If nothing here does what you need, say so and ask \u2014 do not invent a component.',\n ].join('\\n')\n }\n\n const blocks = findings.map((finding) => {\n const { entry } = finding\n const lines = [\n `${entry.name} \u00B7 ${entry.kind} \u00B7 ${entry.repo} (matched ${finding.where.join(', ')})`,\n ` ${entry.purpose}`,\n ` import { ${entry.name} } from '${entry.importPath}'`,\n ]\n if (entry.ownsBehaviours.length) lines.push(` owns: ${entry.ownsBehaviours.map((owned) => owned.behaviour).join(' \u00B7 ')}`)\n if (entry.variants.length) lines.push(` ${entry.variants.map((variant) => `${variant.prop}: ${variant.values.join(' | ')}`).join(' ')}`)\n // The props the question named, with their own line \u2014 not all of them. A\n // component with twenty props would bury its own answer.\n const shown = finding.props\n .filter((name) => !entry.variants.some((variant) => variant.prop === name))\n .slice(0, 3)\n .map((name) => entry.props.find((prop) => prop.name === name))\n for (const prop of shown) {\n if (prop) lines.push(` ${prop.name}: ${prop.takes}${prop.note ? ` \u2014 ${prop.note}` : ''}`)\n }\n if (entry.props.length) lines.push(` ${entry.props.length} props in all; see the page for the rest`)\n const link = docsLink(registry, entry)\n if (link) lines.push(` ${link}`)\n return lines.join('\\n')\n })\n\n return blocks.join('\\n\\n')\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/registry/schema.ts
|
|
4
|
+
var SCHEMA_VERSION = 1;
|
|
5
|
+
var KINDS = ["component", "hook", "helper"];
|
|
6
|
+
var STATUSES = ["stable", "migrating", "deprecated"];
|
|
7
|
+
var SOURCES = ["page", "comment"];
|
|
8
|
+
var isString = (v) => typeof v === "string";
|
|
9
|
+
var isFilledString = (v) => isString(v) && v.trim().length > 0;
|
|
10
|
+
function validateRegistry(value) {
|
|
11
|
+
const problems = [];
|
|
12
|
+
const fail = (message) => problems.push(message);
|
|
13
|
+
if (typeof value !== "object" || value === null) return ["the registry is not an object"];
|
|
14
|
+
const registry = value;
|
|
15
|
+
if (registry.schemaVersion !== SCHEMA_VERSION) fail(`schemaVersion is ${String(registry.schemaVersion)}, expected ${SCHEMA_VERSION}`);
|
|
16
|
+
const built = registry.builtFrom;
|
|
17
|
+
if (typeof built !== "object" || built === null) fail("builtFrom is missing");
|
|
18
|
+
else {
|
|
19
|
+
if (!isFilledString(built.repo)) fail("builtFrom.repo is missing");
|
|
20
|
+
if (!isFilledString(built.package)) fail("builtFrom.package is missing");
|
|
21
|
+
if (!isFilledString(built.packageVersion)) fail("builtFrom.packageVersion is missing");
|
|
22
|
+
if (typeof built.exports !== "number") fail("builtFrom.exports is not a number");
|
|
23
|
+
if (typeof built.typeExports !== "number") fail("builtFrom.typeExports is not a number");
|
|
24
|
+
}
|
|
25
|
+
const book = registry.storybook;
|
|
26
|
+
if (typeof book !== "object" || book === null) fail("storybook is missing");
|
|
27
|
+
else {
|
|
28
|
+
for (const key of ["docsPath", "storyPath", "devUrl"]) {
|
|
29
|
+
if (!isFilledString(book[key])) fail(`storybook.${key} is missing`);
|
|
30
|
+
}
|
|
31
|
+
if (isString(book.docsPath) && !book.docsPath.includes("{docsId}")) fail("storybook.docsPath does not carry {docsId}");
|
|
32
|
+
if (isString(book.storyPath) && !book.storyPath.includes("{storyId}")) fail("storybook.storyPath does not carry {storyId}");
|
|
33
|
+
}
|
|
34
|
+
const entries = registry.entries;
|
|
35
|
+
const excluded = registry.excluded;
|
|
36
|
+
if (!Array.isArray(entries)) return [...problems, "entries is not an array"];
|
|
37
|
+
if (!Array.isArray(excluded)) return [...problems, "excluded is not an array"];
|
|
38
|
+
if (entries.length === 0) fail("entries is empty");
|
|
39
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const [i, raw] of entries.entries()) {
|
|
41
|
+
const at = (field) => `entries[${i}] (${isString(raw?.name) ? raw.name : "?"}).${field}`;
|
|
42
|
+
if (typeof raw !== "object" || raw === null) {
|
|
43
|
+
fail(`entries[${i}] is not an object`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const entry = raw;
|
|
47
|
+
if (!isFilledString(entry.name)) fail(at("name") + " is missing");
|
|
48
|
+
else if (seen.has(entry.name)) fail(`${entry.name} appears twice`);
|
|
49
|
+
else seen.add(entry.name);
|
|
50
|
+
if (!isFilledString(entry.repo)) fail(at("repo") + " is missing");
|
|
51
|
+
if (!isFilledString(entry.importPath)) fail(at("importPath") + " is missing");
|
|
52
|
+
if (!isFilledString(entry.sourceFile)) fail(at("sourceFile") + " is missing");
|
|
53
|
+
if (!isFilledString(entry.purpose)) fail(at("purpose") + " is empty");
|
|
54
|
+
if (!KINDS.includes(entry.kind)) fail(at("kind") + ` is ${String(entry.kind)}`);
|
|
55
|
+
if (!SOURCES.includes(entry.purposeFrom)) fail(at("purposeFrom") + ` is ${String(entry.purposeFrom)}`);
|
|
56
|
+
if (!STATUSES.includes(entry.status)) fail(at("status") + ` is ${String(entry.status)}`);
|
|
57
|
+
if (!(entry.migrationStage === null || typeof entry.migrationStage === "number")) fail(at("migrationStage") + " is neither a number nor null");
|
|
58
|
+
if (!Array.isArray(entry.props)) fail(at("props") + " is not an array");
|
|
59
|
+
else
|
|
60
|
+
for (const prop of entry.props) {
|
|
61
|
+
if (!isFilledString(prop?.name) || !isFilledString(prop?.takes) || typeof prop?.required !== "boolean") fail(at("props") + " has a malformed entry");
|
|
62
|
+
else if (!(prop.note === null || isFilledString(prop.note))) fail(at("props") + `.${prop.name}.note is neither a string nor null`);
|
|
63
|
+
}
|
|
64
|
+
if (!Array.isArray(entry.variants)) fail(at("variants") + " is not an array");
|
|
65
|
+
else
|
|
66
|
+
for (const variant of entry.variants) {
|
|
67
|
+
if (!isFilledString(variant?.prop) || !Array.isArray(variant?.values) || variant.values.length === 0) fail(at("variants") + " has a malformed entry");
|
|
68
|
+
else if (Array.isArray(entry.props) && !entry.props.some((prop) => prop?.name === variant.prop)) fail(at("variants") + `.${variant.prop} is not one of its props`);
|
|
69
|
+
}
|
|
70
|
+
if (!Array.isArray(entry.ownsBehaviours)) fail(at("ownsBehaviours") + " is not an array");
|
|
71
|
+
else for (const behaviour of entry.ownsBehaviours) {
|
|
72
|
+
if (!isFilledString(behaviour?.id) || !isFilledString(behaviour?.behaviour)) fail(at("ownsBehaviours") + " has a malformed entry");
|
|
73
|
+
}
|
|
74
|
+
for (const key of ["docsId", "storyId", "docPage"]) {
|
|
75
|
+
const held = entry[key];
|
|
76
|
+
if (!(held === null || isFilledString(held))) fail(at(key) + " is neither a string nor null");
|
|
77
|
+
}
|
|
78
|
+
if (entry.kind === "component" && (entry.docsId === null || entry.storyId === null)) {
|
|
79
|
+
fail(at("docsId") + " is null, and only a helper or a hook may have no page or story");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const [i, raw] of excluded.entries()) {
|
|
83
|
+
const gap = raw;
|
|
84
|
+
if (!isFilledString(gap?.name)) fail(`excluded[${i}].name is missing`);
|
|
85
|
+
if (!isFilledString(gap?.reason)) fail(`excluded[${i}].reason is missing`);
|
|
86
|
+
if (isString(gap?.name) && seen.has(gap.name)) fail(`${gap.name} is both an entry and excluded`);
|
|
87
|
+
}
|
|
88
|
+
const counted = entries.length + excluded.length;
|
|
89
|
+
if (typeof built === "object" && built !== null && typeof built.exports === "number" && counted !== built.exports) {
|
|
90
|
+
fail(`${entries.length} entries + ${excluded.length} excluded = ${counted}, but index.ts exports ${built.exports} values`);
|
|
91
|
+
}
|
|
92
|
+
return problems;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
SCHEMA_VERSION,
|
|
97
|
+
validateRegistry
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=chunk-MRSBS5OP.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/registry/schema.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The catalogue's shape (UIG-12, seam S2 in docs/GATES.md \u00A716).\n *\n * A **data source read by machines first** and rendered second \u2014 not a\n * documentation generator. The skill (UIG-20), the copy detector (UIG-25) and\n * the merged search (UIG-19) all read this and nothing else, so the field\n * names are the contract and `schemaVersion` is what makes a change to them\n * detectable.\n *\n * Names are plain and ours. Open-sourcing is far off; when it comes, a\n * renaming pass is one version bump, and guessing at a public vocabulary now\n * would cost every reader between here and there.\n */\n\n/**\n * Raise this whenever a field is added, removed or given a new meaning. A\n * reader that knows version N and is handed N+1 must be able to say so rather\n * than silently read a field that moved.\n */\nexport const SCHEMA_VERSION = 1\n\n/** What a name is, which decides how a reader offers it. */\nexport type EntryKind =\n /** Draws something. Used in JSX. */\n | 'component'\n /** A React hook: `use\u2026`, callable only inside a component. */\n | 'hook'\n /** A function or a constant. Neither of the above. */\n | 'helper'\n\n/** Where an entry's one-line purpose was read from. */\nexport type PurposeSource =\n /** The `.mdx` page's opening paragraph \u2014 the name has a page of its own. */\n | 'page'\n /** The doc comment above the export \u2014 the name is documented on a sibling's page. */\n | 'comment'\n\nexport type EntryStatus = 'stable' | 'migrating' | 'deprecated'\n\n/** A prop that takes one of a known set of words, and the set. */\nexport interface EntryVariant {\n prop: string\n values: string[]\n}\n\n/**\n * One prop a component declares itself.\n *\n * **Itself**, not what it inherits: a component that extends\n * `ComponentPropsWithRef<'div'>` would otherwise drag in every DOM attribute,\n * which is noise in a catalogue whose whole point is a short answer.\n */\nexport interface EntryProp {\n name: string\n /**\n * What it takes, in a word a person reads: `true/false`, `number`, `text`,\n * `anything`, `a handler`, the words themselves for a set (`left | right`),\n * or the type as written where none of those fit.\n */\n takes: string\n required: boolean\n /** The first sentence of the prop's own comment, where it has one. */\n note: string | null\n}\n\n/** A behaviour this component owns, from UIG-8's enumeration. */\nexport interface EntryBehaviour {\n /** `OwnedBehaviour.id` \u2014 `portal`, `walking`, `scroll`\u2026 */\n id: string\n /** What a person notices it doing, in words. */\n behaviour: string\n}\n\nexport interface RegistryEntry {\n /** The exported name, exactly as `index.ts` exports it. */\n name: string\n /** Which library it came from. One repo per registry until UIG-13 merges them. */\n repo: string\n kind: EntryKind\n /** What a caller actually types. */\n importPath: string\n /** The file the name is declared in, relative to the repo. Several names share one file. */\n sourceFile: string\n /** One line: what it is for. Never empty \u2014 the build fails instead. */\n purpose: string\n purposeFrom: PurposeSource\n /**\n * Every prop it declares itself \u2014 what it can do. This is the field a reader\n * asks \"does it already do X?\" of, before writing X by hand.\n */\n props: EntryProp[]\n /**\n * The subset of `props` that takes one of a known set of words, structured.\n * Kept beside `props` because it is what a search ranks well on, and what a\n * caller most often needs named back at them.\n */\n variants: EntryVariant[]\n /** What it owns, so a caller never rebuilds it (UIG-8). Empty for most. */\n ownsBehaviours: EntryBehaviour[]\n status: EntryStatus\n /**\n * Which migration stage plans to change it.\n *\n * Always `null` here. The migration's plan lives in `K:/Estiva/migration\n * docs`, which is not in this repo and is not in CI \u2014 and a copy kept here by\n * hand is the drifting list this whole project exists to remove. The field\n * stays in the schema because a later ticket may have a source a build can\n * read; until then a reader gets an honest `null` rather than a stale number.\n */\n migrationStage: number | null\n /** The `.mdx` page's Storybook id, or `null` where the name has no page of its own. */\n docsId: string | null\n /** A story that shows it, by Storybook id. `null` where nothing renders it alone. */\n storyId: string | null\n /** The `.mdx` path, relative to the repo, or `null`. */\n docPage: string | null\n}\n\n/** A name that is exported and deliberately not an entry. Empty today; the count still has to reconcile. */\nexport interface RegistryExclusion {\n name: string\n reason: string\n}\n\nexport interface Registry {\n schemaVersion: number\n builtFrom: {\n repo: string\n /** The npm package the entries are imported from. */\n package: string\n packageVersion: string\n /** Value exports counted in `index.ts`. `entries + excluded` must equal it. */\n exports: number\n /** Type-only exports, which are not entries. Recorded so the count reconciles. */\n typeExports: number\n }\n /**\n * How to turn an id into a link. There is no Storybook on the internet yet\n * (UIG-19), so an absolute URL committed here would resolve nowhere: the\n * reader joins its own base to these.\n */\n storybook: {\n docsPath: string\n storyPath: string\n devUrl: string\n }\n entries: RegistryEntry[]\n excluded: RegistryExclusion[]\n}\n\nconst KINDS: EntryKind[] = ['component', 'hook', 'helper']\nconst STATUSES: EntryStatus[] = ['stable', 'migrating', 'deprecated']\nconst SOURCES: PurposeSource[] = ['page', 'comment']\n\nconst isString = (v: unknown): v is string => typeof v === 'string'\nconst isFilledString = (v: unknown): v is string => isString(v) && v.trim().length > 0\n\n/**\n * Check a registry against this schema. Returns every problem, not the first:\n * a build that reports one missing purpose per run costs one run per entry.\n *\n * An empty list is the only pass. CI runs this on the committed file.\n */\nexport function validateRegistry(value: unknown): string[] {\n const problems: string[] = []\n const fail = (message: string) => problems.push(message)\n\n if (typeof value !== 'object' || value === null) return ['the registry is not an object']\n const registry = value as Partial<Registry>\n\n if (registry.schemaVersion !== SCHEMA_VERSION) fail(`schemaVersion is ${String(registry.schemaVersion)}, expected ${SCHEMA_VERSION}`)\n\n const built = registry.builtFrom\n if (typeof built !== 'object' || built === null) fail('builtFrom is missing')\n else {\n if (!isFilledString(built.repo)) fail('builtFrom.repo is missing')\n if (!isFilledString(built.package)) fail('builtFrom.package is missing')\n if (!isFilledString(built.packageVersion)) fail('builtFrom.packageVersion is missing')\n if (typeof built.exports !== 'number') fail('builtFrom.exports is not a number')\n if (typeof built.typeExports !== 'number') fail('builtFrom.typeExports is not a number')\n }\n\n const book = registry.storybook\n if (typeof book !== 'object' || book === null) fail('storybook is missing')\n else {\n for (const key of ['docsPath', 'storyPath', 'devUrl'] as const) {\n if (!isFilledString(book[key])) fail(`storybook.${key} is missing`)\n }\n if (isString(book.docsPath) && !book.docsPath.includes('{docsId}')) fail('storybook.docsPath does not carry {docsId}')\n if (isString(book.storyPath) && !book.storyPath.includes('{storyId}')) fail('storybook.storyPath does not carry {storyId}')\n }\n\n const entries = registry.entries\n const excluded = registry.excluded\n if (!Array.isArray(entries)) return [...problems, 'entries is not an array']\n if (!Array.isArray(excluded)) return [...problems, 'excluded is not an array']\n if (entries.length === 0) fail('entries is empty')\n\n const seen = new Set<string>()\n for (const [i, raw] of entries.entries()) {\n const at = (field: string) => `entries[${i}] (${isString((raw as RegistryEntry)?.name) ? (raw as RegistryEntry).name : '?'}).${field}`\n if (typeof raw !== 'object' || raw === null) {\n fail(`entries[${i}] is not an object`)\n continue\n }\n const entry = raw as Partial<RegistryEntry>\n if (!isFilledString(entry.name)) fail(at('name') + ' is missing')\n else if (seen.has(entry.name)) fail(`${entry.name} appears twice`)\n else seen.add(entry.name)\n\n if (!isFilledString(entry.repo)) fail(at('repo') + ' is missing')\n if (!isFilledString(entry.importPath)) fail(at('importPath') + ' is missing')\n if (!isFilledString(entry.sourceFile)) fail(at('sourceFile') + ' is missing')\n // The reason this schema exists: an entry with no purpose is an entry the\n // search cannot find and the skill cannot quote.\n if (!isFilledString(entry.purpose)) fail(at('purpose') + ' is empty')\n if (!KINDS.includes(entry.kind as EntryKind)) fail(at('kind') + ` is ${String(entry.kind)}`)\n if (!SOURCES.includes(entry.purposeFrom as PurposeSource)) fail(at('purposeFrom') + ` is ${String(entry.purposeFrom)}`)\n if (!STATUSES.includes(entry.status as EntryStatus)) fail(at('status') + ` is ${String(entry.status)}`)\n if (!(entry.migrationStage === null || typeof entry.migrationStage === 'number')) fail(at('migrationStage') + ' is neither a number nor null')\n if (!Array.isArray(entry.props)) fail(at('props') + ' is not an array')\n else\n for (const prop of entry.props) {\n if (!isFilledString(prop?.name) || !isFilledString(prop?.takes) || typeof prop?.required !== 'boolean') fail(at('props') + ' has a malformed entry')\n else if (!(prop.note === null || isFilledString(prop.note))) fail(at('props') + `.${prop.name}.note is neither a string nor null`)\n }\n if (!Array.isArray(entry.variants)) fail(at('variants') + ' is not an array')\n else\n for (const variant of entry.variants) {\n if (!isFilledString(variant?.prop) || !Array.isArray(variant?.values) || variant.values.length === 0) fail(at('variants') + ' has a malformed entry')\n // variants is a view of props, never a second source that can disagree.\n else if (Array.isArray(entry.props) && !entry.props.some((prop) => prop?.name === variant.prop)) fail(at('variants') + `.${variant.prop} is not one of its props`)\n }\n if (!Array.isArray(entry.ownsBehaviours)) fail(at('ownsBehaviours') + ' is not an array')\n else for (const behaviour of entry.ownsBehaviours) {\n if (!isFilledString(behaviour?.id) || !isFilledString(behaviour?.behaviour)) fail(at('ownsBehaviours') + ' has a malformed entry')\n }\n for (const key of ['docsId', 'storyId', 'docPage'] as const) {\n const held = entry[key]\n if (!(held === null || isFilledString(held))) fail(at(key) + ' is neither a string nor null')\n }\n // \"Every storyUrl resolves, or is recorded as absent with a reason\" \u2014 the\n // reason is enforced rather than written down: only something that draws\n // nothing may have nowhere to be looked at. A component with no story is\n // already an error of UIG-5's component-has-a-story; this stops the\n // catalogue quietly carrying one.\n if (entry.kind === 'component' && (entry.docsId === null || entry.storyId === null)) {\n fail(at('docsId') + ' is null, and only a helper or a hook may have no page or story')\n }\n }\n\n for (const [i, raw] of excluded.entries()) {\n const gap = raw as Partial<RegistryExclusion>\n if (!isFilledString(gap?.name)) fail(`excluded[${i}].name is missing`)\n if (!isFilledString(gap?.reason)) fail(`excluded[${i}].reason is missing`)\n if (isString(gap?.name) && seen.has(gap.name)) fail(`${gap.name} is both an entry and excluded`)\n }\n\n // Completeness (the ticket's acceptance): every value export is either an\n // entry or an exclusion with a reason. Averaging the difference away is the\n // failure this check exists to make impossible.\n const counted = entries.length + excluded.length\n if (typeof built === 'object' && built !== null && typeof built.exports === 'number' && counted !== built.exports) {\n fail(`${entries.length} entries + ${excluded.length} excluded = ${counted}, but index.ts exports ${built.exports} values`)\n }\n\n return problems\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|