@estiva-app/ui 0.21.1 → 0.23.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/README.md +21 -0
- package/dist/Avatar.d.ts +3 -0
- package/dist/Avatar.d.ts.map +1 -1
- package/dist/Banner.d.ts +20 -2
- package/dist/Banner.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/ContainerHeader.d.ts +25 -0
- package/dist/ContainerHeader.d.ts.map +1 -0
- package/dist/EmptyState.d.ts +10 -1
- package/dist/EmptyState.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-ZGJ2J5NU.js → chunk-EA33NP5B.js} +275 -11
- package/dist/gates/chunk-EA33NP5B.js.map +7 -0
- package/dist/gates/{chunk-AUXD4GCY.js → chunk-GTQZEHPC.js} +15 -1
- package/dist/gates/chunk-GTQZEHPC.js.map +7 -0
- package/dist/gates/cli.js +1 -1
- package/dist/gates/create-app.d.ts +2 -0
- package/dist/gates/create-app.d.ts.map +1 -1
- package/dist/gates/create-app.js +1 -1
- package/dist/gates/index.js +25 -3
- package/dist/gates/index.js.map +2 -2
- package/dist/gates/status.d.ts +2 -0
- package/dist/gates/status.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +69 -48
- package/dist/index.js.map +4 -4
- package/dist/registry/app-DQI6WLHR.js +10 -0
- package/dist/registry/app-DQI6WLHR.js.map +7 -0
- package/dist/registry/app.d.ts +21 -0
- package/dist/registry/app.d.ts.map +1 -0
- package/dist/registry/build-LGFCCOLR.js +27 -0
- package/dist/registry/build-LGFCCOLR.js.map +7 -0
- package/dist/registry/build.d.ts +95 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/chunk-E4JNV7PC.js +155 -0
- package/dist/registry/chunk-E4JNV7PC.js.map +7 -0
- package/dist/registry/chunk-NJN4MQAM.js +397 -0
- package/dist/registry/chunk-NJN4MQAM.js.map +7 -0
- package/dist/registry/chunk-W2B2G7OE.js +188 -0
- package/dist/registry/chunk-W2B2G7OE.js.map +7 -0
- package/dist/registry/chunk-WMFF3MPP.js +648 -0
- package/dist/registry/chunk-WMFF3MPP.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 +176 -0
- package/dist/registry/cli.js.map +7 -0
- package/dist/registry/find.d.ts +54 -0
- package/dist/registry/find.d.ts.map +1 -0
- package/dist/registry/index.d.ts +17 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +34 -0
- package/dist/registry/index.js.map +7 -0
- package/dist/registry/schema.d.ts +224 -0
- package/dist/registry/schema.d.ts.map +1 -0
- package/package.json +15 -2
- package/registry.json +4615 -0
- package/src/Avatar.tsx +3 -0
- package/src/Banner.mdx +17 -4
- package/src/Banner.stories.tsx +22 -0
- package/src/Banner.test.tsx +35 -0
- package/src/Banner.tsx +34 -9
- package/src/ChipInput.tsx +3 -0
- package/src/CommandPalette.tsx +6 -0
- package/src/ContainerHeader.mdx +56 -0
- package/src/ContainerHeader.stories.tsx +62 -0
- package/src/ContainerHeader.test.tsx +47 -0
- package/src/ContainerHeader.tsx +45 -0
- package/src/EmptyState.mdx +12 -0
- package/src/EmptyState.stories.tsx +6 -1
- package/src/EmptyState.test.tsx +18 -1
- package/src/EmptyState.tsx +14 -1
- 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 +23 -1
- package/src/gates/create-app.test.ts +77 -6
- package/src/gates/create-app.ts +288 -15
- package/src/gates/status.ts +16 -0
- package/src/index.ts +1 -0
- package/src/registry/app.test.ts +562 -0
- package/src/registry/app.ts +854 -0
- package/src/registry/build.ts +679 -0
- package/src/registry/cli.ts +204 -0
- package/src/registry/find.ts +232 -0
- package/src/registry/index.ts +31 -0
- package/src/registry/registry.test.ts +448 -0
- package/src/registry/schema.ts +419 -0
- package/dist/gates/chunk-AUXD4GCY.js.map +0 -7
- package/dist/gates/chunk-ZGJ2J5NU.js.map +0 -7
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The catalogue's shape (UIG-12, seam S2 in docs/GATES.md §16).
|
|
3
|
+
*
|
|
4
|
+
* A **data source read by machines first** and rendered second — not a
|
|
5
|
+
* documentation generator. The skill (UIG-20), the copy detector (UIG-25) and
|
|
6
|
+
* the merged search (UIG-19) all read this and nothing else, so the field
|
|
7
|
+
* names are the contract and `schemaVersion` is what makes a change to them
|
|
8
|
+
* detectable.
|
|
9
|
+
*
|
|
10
|
+
* Names are plain and ours. Open-sourcing is far off; when it comes, a
|
|
11
|
+
* renaming pass is one version bump, and guessing at a public vocabulary now
|
|
12
|
+
* would cost every reader between here and there.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Raise this whenever a field is added, removed or given a new meaning. A
|
|
17
|
+
* reader that knows version N and is handed N+1 must be able to say so rather
|
|
18
|
+
* than silently read a field that moved.
|
|
19
|
+
*/
|
|
20
|
+
export const SCHEMA_VERSION = 2
|
|
21
|
+
|
|
22
|
+
/** What a name is, which decides how a reader offers it. */
|
|
23
|
+
export type EntryKind =
|
|
24
|
+
/** Draws something. Used in JSX. */
|
|
25
|
+
| 'component'
|
|
26
|
+
/** A React hook: `use…`, callable only inside a component. */
|
|
27
|
+
| 'hook'
|
|
28
|
+
/** A function or a constant. Neither of the above. */
|
|
29
|
+
| 'helper'
|
|
30
|
+
|
|
31
|
+
/** Where an entry's one-line purpose was read from. */
|
|
32
|
+
export type PurposeSource =
|
|
33
|
+
/** The `.mdx` page's opening paragraph — the name has a page of its own. */
|
|
34
|
+
| 'page'
|
|
35
|
+
/** The doc comment above the export — the name is documented on a sibling's page. */
|
|
36
|
+
| 'comment'
|
|
37
|
+
/** An app's file that holds this one part: the comment at the top of the file (UIG-13). */
|
|
38
|
+
| 'file'
|
|
39
|
+
/** An app's pass-on: the part is the package's, and says so (UIG-13). */
|
|
40
|
+
| 'package'
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Which of four kinds an app's part is (UIG-13), or that nothing uses it.
|
|
44
|
+
*
|
|
45
|
+
* Worked out from the code, never from the folder a file sits in: Peek's
|
|
46
|
+
* `components/ui` holds one-line pass-ons beside 187-line components.
|
|
47
|
+
*/
|
|
48
|
+
export type EntryClass =
|
|
49
|
+
/** Its file only hands a package part on, so the app keeps its own import path. */
|
|
50
|
+
| 're-export'
|
|
51
|
+
/** Used in two or more places in the app. Gets a usage page (UIG-17, UIG-18). */
|
|
52
|
+
| 'reusable'
|
|
53
|
+
/** Used in one place. Needs only its purpose line. */
|
|
54
|
+
| 'one-off'
|
|
55
|
+
/** Reusable, and everything it uses is already in the package: it could move there. Katerina rules. */
|
|
56
|
+
| 'promote-candidate'
|
|
57
|
+
/** No file of the app uses it: only its stories or tests, or nothing. Listed, never deleted by the catalogue. */
|
|
58
|
+
| 'unused'
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* What the catalogue knows about a part that lives in an app (UIG-13). `null`
|
|
62
|
+
* on the package's own entries.
|
|
63
|
+
*/
|
|
64
|
+
export interface AppFacts {
|
|
65
|
+
class: EntryClass
|
|
66
|
+
/** Why it has that class, in words: the evidence, or the written reason. */
|
|
67
|
+
reason: string
|
|
68
|
+
/**
|
|
69
|
+
* `true` when the class is written beside the part (`@registry <class>: <reason>`
|
|
70
|
+
* in its comment) rather than worked out — "general, though one screen uses it yet".
|
|
71
|
+
*/
|
|
72
|
+
written: boolean
|
|
73
|
+
/** A pass-on's part, as `Name` for the package's and `Name from <package>` for another's. `null` otherwise. */
|
|
74
|
+
handsOn: string | null
|
|
75
|
+
/**
|
|
76
|
+
* The app's files that use it, relative to the app: every file that imports it,
|
|
77
|
+
* and its own file when something else there draws it. Stories and tests are not
|
|
78
|
+
* uses — a part only they reach is unused.
|
|
79
|
+
*/
|
|
80
|
+
usedIn: string[]
|
|
81
|
+
/**
|
|
82
|
+
* What ties it to this app: each import that is neither in the package's own
|
|
83
|
+
* dependencies nor an app file that is itself untied. Empty means it could
|
|
84
|
+
* live in the package as it is.
|
|
85
|
+
*/
|
|
86
|
+
tiedTo: string[]
|
|
87
|
+
/** The package part with the same name, when there is one: the reader should see both. */
|
|
88
|
+
packageNamesake: string | null
|
|
89
|
+
/** Imported as `import X from …` rather than `import { X } from …`. */
|
|
90
|
+
defaultExport: boolean
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A file in an app's target set that holds no part, and why, in words. */
|
|
94
|
+
export interface FileWithoutPart {
|
|
95
|
+
file: string
|
|
96
|
+
reason: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type EntryStatus = 'stable' | 'migrating' | 'deprecated'
|
|
100
|
+
|
|
101
|
+
/** A prop that takes one of a known set of words, and the set. */
|
|
102
|
+
export interface EntryVariant {
|
|
103
|
+
prop: string
|
|
104
|
+
values: string[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* One prop a component declares itself.
|
|
109
|
+
*
|
|
110
|
+
* **Itself**, not what it inherits: a component that extends
|
|
111
|
+
* `ComponentPropsWithRef<'div'>` would otherwise drag in every DOM attribute,
|
|
112
|
+
* which is noise in a catalogue whose whole point is a short answer.
|
|
113
|
+
*/
|
|
114
|
+
export interface EntryProp {
|
|
115
|
+
name: string
|
|
116
|
+
/**
|
|
117
|
+
* What it takes, in a word a person reads: `true/false`, `number`, `text`,
|
|
118
|
+
* `anything`, `a handler`, the words themselves for a set (`left | right`),
|
|
119
|
+
* or the type as written where none of those fit.
|
|
120
|
+
*/
|
|
121
|
+
takes: string
|
|
122
|
+
required: boolean
|
|
123
|
+
/** The first sentence of the prop's own comment, where it has one. */
|
|
124
|
+
note: string | null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A behaviour this component owns, from UIG-8's enumeration. */
|
|
128
|
+
export interface EntryBehaviour {
|
|
129
|
+
/** `OwnedBehaviour.id` — `portal`, `walking`, `scroll`… */
|
|
130
|
+
id: string
|
|
131
|
+
/** What a person notices it doing, in words. */
|
|
132
|
+
behaviour: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface RegistryEntry {
|
|
136
|
+
/** The exported name, exactly as `index.ts` exports it. */
|
|
137
|
+
name: string
|
|
138
|
+
/** Which library it came from: `estiva-ui`, or the app that owns it. */
|
|
139
|
+
repo: string
|
|
140
|
+
kind: EntryKind
|
|
141
|
+
/** What a caller actually types. */
|
|
142
|
+
importPath: string
|
|
143
|
+
/** The file the name is declared in, relative to the repo. Several names share one file. */
|
|
144
|
+
sourceFile: string
|
|
145
|
+
/** One line: what it is for. Never empty — the build fails instead. */
|
|
146
|
+
purpose: string
|
|
147
|
+
purposeFrom: PurposeSource
|
|
148
|
+
/**
|
|
149
|
+
* Every prop it declares itself — what it can do. This is the field a reader
|
|
150
|
+
* asks "does it already do X?" of, before writing X by hand.
|
|
151
|
+
*/
|
|
152
|
+
props: EntryProp[]
|
|
153
|
+
/**
|
|
154
|
+
* The subset of `props` that takes one of a known set of words, structured.
|
|
155
|
+
* Kept beside `props` because it is what a search ranks well on, and what a
|
|
156
|
+
* caller most often needs named back at them.
|
|
157
|
+
*/
|
|
158
|
+
variants: EntryVariant[]
|
|
159
|
+
/** What it owns, so a caller never rebuilds it (UIG-8). Empty for most. */
|
|
160
|
+
ownsBehaviours: EntryBehaviour[]
|
|
161
|
+
status: EntryStatus
|
|
162
|
+
/**
|
|
163
|
+
* Which migration stage plans to change it.
|
|
164
|
+
*
|
|
165
|
+
* Always `null` here. The migration's plan lives in `K:/Estiva/migration
|
|
166
|
+
* docs`, which is not in this repo and is not in CI — and a copy kept here by
|
|
167
|
+
* hand is the drifting list this whole project exists to remove. The field
|
|
168
|
+
* stays in the schema because a later ticket may have a source a build can
|
|
169
|
+
* read; until then a reader gets an honest `null` rather than a stale number.
|
|
170
|
+
*/
|
|
171
|
+
migrationStage: number | null
|
|
172
|
+
/** The `.mdx` page's Storybook id, or `null` where the name has no page of its own. */
|
|
173
|
+
docsId: string | null
|
|
174
|
+
/** A story that shows it, by Storybook id. `null` where nothing renders it alone. */
|
|
175
|
+
storyId: string | null
|
|
176
|
+
/** The `.mdx` path, relative to the repo, or `null`. */
|
|
177
|
+
docPage: string | null
|
|
178
|
+
/** An app's part: its class and the evidence for it (UIG-13). `null` on the package's own entries. */
|
|
179
|
+
app: AppFacts | null
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** A name that is exported and deliberately not an entry. Empty today; the count still has to reconcile. */
|
|
183
|
+
export interface RegistryExclusion {
|
|
184
|
+
name: string
|
|
185
|
+
reason: string
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface Registry {
|
|
189
|
+
schemaVersion: number
|
|
190
|
+
builtFrom: {
|
|
191
|
+
/**
|
|
192
|
+
* `package`: the package's catalogue, committed and shipped. `app`: one app's,
|
|
193
|
+
* built fresh from its code each time and never committed — Peek and Ship are
|
|
194
|
+
* private, the package is public (Katerina, 18 September 2026).
|
|
195
|
+
*/
|
|
196
|
+
kind: 'package' | 'app'
|
|
197
|
+
repo: string
|
|
198
|
+
/** The package the entries are imported from, or the app's own name. */
|
|
199
|
+
package: string
|
|
200
|
+
packageVersion: string
|
|
201
|
+
/**
|
|
202
|
+
* The package: value exports counted in `index.ts`. An app: parts found in its
|
|
203
|
+
* files. `entries + excluded` must equal it.
|
|
204
|
+
*/
|
|
205
|
+
exports: number
|
|
206
|
+
/** Type-only exports, which are not entries. Recorded so the count reconciles. `0` for an app. */
|
|
207
|
+
typeExports: number
|
|
208
|
+
/**
|
|
209
|
+
* An app only: its `.tsx` files outside stories and tests. Each one either holds
|
|
210
|
+
* an entry or is in `filesWithoutParts`. `null` for the package.
|
|
211
|
+
*/
|
|
212
|
+
files: number | null
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* How to turn an id into a link. There is no Storybook on the internet yet
|
|
216
|
+
* (UIG-19), so an absolute URL committed here would resolve nowhere: the
|
|
217
|
+
* reader joins its own base to these.
|
|
218
|
+
*/
|
|
219
|
+
storybook: {
|
|
220
|
+
docsPath: string
|
|
221
|
+
storyPath: string
|
|
222
|
+
devUrl: string
|
|
223
|
+
}
|
|
224
|
+
entries: RegistryEntry[]
|
|
225
|
+
excluded: RegistryExclusion[]
|
|
226
|
+
/** An app's `.tsx` files that hold no part, each with its reason. Empty for the package. */
|
|
227
|
+
filesWithoutParts: FileWithoutPart[]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const KINDS: EntryKind[] = ['component', 'hook', 'helper']
|
|
231
|
+
const STATUSES: EntryStatus[] = ['stable', 'migrating', 'deprecated']
|
|
232
|
+
const SOURCES: PurposeSource[] = ['page', 'comment', 'file', 'package']
|
|
233
|
+
export const CLASSES: EntryClass[] = ['re-export', 'reusable', 'one-off', 'promote-candidate', 'unused']
|
|
234
|
+
|
|
235
|
+
const isString = (v: unknown): v is string => typeof v === 'string'
|
|
236
|
+
const isFilledString = (v: unknown): v is string => isString(v) && v.trim().length > 0
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check a registry against this schema. Returns every problem, not the first:
|
|
240
|
+
* a build that reports one missing purpose per run costs one run per entry.
|
|
241
|
+
*
|
|
242
|
+
* An empty list is the only pass. CI runs this on the committed file.
|
|
243
|
+
*/
|
|
244
|
+
export function validateRegistry(value: unknown): string[] {
|
|
245
|
+
const problems: string[] = []
|
|
246
|
+
const fail = (message: string) => problems.push(message)
|
|
247
|
+
|
|
248
|
+
if (typeof value !== 'object' || value === null) return ['the registry is not an object']
|
|
249
|
+
const registry = value as Partial<Registry>
|
|
250
|
+
|
|
251
|
+
if (registry.schemaVersion !== SCHEMA_VERSION) fail(`schemaVersion is ${String(registry.schemaVersion)}, expected ${SCHEMA_VERSION}`)
|
|
252
|
+
|
|
253
|
+
const built = registry.builtFrom
|
|
254
|
+
let isApp = false
|
|
255
|
+
if (typeof built !== 'object' || built === null) fail('builtFrom is missing')
|
|
256
|
+
else {
|
|
257
|
+
if (built.kind !== 'package' && built.kind !== 'app') fail(`builtFrom.kind is ${String(built.kind)}, expected package or app`)
|
|
258
|
+
isApp = built.kind === 'app'
|
|
259
|
+
if (!isFilledString(built.repo)) fail('builtFrom.repo is missing')
|
|
260
|
+
if (!isFilledString(built.package)) fail('builtFrom.package is missing')
|
|
261
|
+
if (!isFilledString(built.packageVersion)) fail('builtFrom.packageVersion is missing')
|
|
262
|
+
if (typeof built.exports !== 'number') fail('builtFrom.exports is not a number')
|
|
263
|
+
if (typeof built.typeExports !== 'number') fail('builtFrom.typeExports is not a number')
|
|
264
|
+
if (isApp ? typeof built.files !== 'number' : built.files !== null) fail(`builtFrom.files is ${String(built.files)}: a number for an app, null for the package`)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const book = registry.storybook
|
|
268
|
+
if (typeof book !== 'object' || book === null) fail('storybook is missing')
|
|
269
|
+
else {
|
|
270
|
+
for (const key of ['docsPath', 'storyPath', 'devUrl'] as const) {
|
|
271
|
+
if (!isFilledString(book[key])) fail(`storybook.${key} is missing`)
|
|
272
|
+
}
|
|
273
|
+
if (isString(book.docsPath) && !book.docsPath.includes('{docsId}')) fail('storybook.docsPath does not carry {docsId}')
|
|
274
|
+
if (isString(book.storyPath) && !book.storyPath.includes('{storyId}')) fail('storybook.storyPath does not carry {storyId}')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const entries = registry.entries
|
|
278
|
+
const excluded = registry.excluded
|
|
279
|
+
const withoutParts = registry.filesWithoutParts
|
|
280
|
+
if (!Array.isArray(entries)) return [...problems, 'entries is not an array']
|
|
281
|
+
if (!Array.isArray(excluded)) return [...problems, 'excluded is not an array']
|
|
282
|
+
if (!Array.isArray(withoutParts)) return [...problems, 'filesWithoutParts is not an array']
|
|
283
|
+
// An app with no part of its own yet is still an app: only the package must have entries.
|
|
284
|
+
if (entries.length === 0 && !isApp) fail('entries is empty')
|
|
285
|
+
|
|
286
|
+
// The package's names are unique; an app may have two parts of one name in two
|
|
287
|
+
// files, and the catalogue says which is which rather than refusing the app.
|
|
288
|
+
const seen = new Set<string>()
|
|
289
|
+
const seenNames = new Set<string>()
|
|
290
|
+
for (const [i, raw] of entries.entries()) {
|
|
291
|
+
const at = (field: string) => `entries[${i}] (${isString((raw as RegistryEntry)?.name) ? (raw as RegistryEntry).name : '?'}).${field}`
|
|
292
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
293
|
+
fail(`entries[${i}] is not an object`)
|
|
294
|
+
continue
|
|
295
|
+
}
|
|
296
|
+
const entry = raw as Partial<RegistryEntry>
|
|
297
|
+
const key = isApp ? `${String(entry.sourceFile)}#${String(entry.name)}` : String(entry.name)
|
|
298
|
+
if (!isFilledString(entry.name)) fail(at('name') + ' is missing')
|
|
299
|
+
else if (seen.has(key)) fail(`${entry.name} appears twice${isApp ? ` in ${String(entry.sourceFile)}` : ''}`)
|
|
300
|
+
else {
|
|
301
|
+
seen.add(key)
|
|
302
|
+
seenNames.add(entry.name)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (!isFilledString(entry.repo)) fail(at('repo') + ' is missing')
|
|
306
|
+
if (!isFilledString(entry.importPath)) fail(at('importPath') + ' is missing')
|
|
307
|
+
if (!isFilledString(entry.sourceFile)) fail(at('sourceFile') + ' is missing')
|
|
308
|
+
// The reason this schema exists: an entry with no purpose is an entry the
|
|
309
|
+
// search cannot find and the skill cannot quote.
|
|
310
|
+
if (!isFilledString(entry.purpose)) fail(at('purpose') + ' is empty')
|
|
311
|
+
if (!KINDS.includes(entry.kind as EntryKind)) fail(at('kind') + ` is ${String(entry.kind)}`)
|
|
312
|
+
if (!SOURCES.includes(entry.purposeFrom as PurposeSource)) fail(at('purposeFrom') + ` is ${String(entry.purposeFrom)}`)
|
|
313
|
+
if (!STATUSES.includes(entry.status as EntryStatus)) fail(at('status') + ` is ${String(entry.status)}`)
|
|
314
|
+
if (!(entry.migrationStage === null || typeof entry.migrationStage === 'number')) fail(at('migrationStage') + ' is neither a number nor null')
|
|
315
|
+
if (!Array.isArray(entry.props)) fail(at('props') + ' is not an array')
|
|
316
|
+
else
|
|
317
|
+
for (const prop of entry.props) {
|
|
318
|
+
if (!isFilledString(prop?.name) || !isFilledString(prop?.takes) || typeof prop?.required !== 'boolean') fail(at('props') + ' has a malformed entry')
|
|
319
|
+
else if (!(prop.note === null || isFilledString(prop.note))) fail(at('props') + `.${prop.name}.note is neither a string nor null`)
|
|
320
|
+
}
|
|
321
|
+
if (!Array.isArray(entry.variants)) fail(at('variants') + ' is not an array')
|
|
322
|
+
else
|
|
323
|
+
for (const variant of entry.variants) {
|
|
324
|
+
if (!isFilledString(variant?.prop) || !Array.isArray(variant?.values) || variant.values.length === 0) fail(at('variants') + ' has a malformed entry')
|
|
325
|
+
// variants is a view of props, never a second source that can disagree.
|
|
326
|
+
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`)
|
|
327
|
+
}
|
|
328
|
+
if (!Array.isArray(entry.ownsBehaviours)) fail(at('ownsBehaviours') + ' is not an array')
|
|
329
|
+
else for (const behaviour of entry.ownsBehaviours) {
|
|
330
|
+
if (!isFilledString(behaviour?.id) || !isFilledString(behaviour?.behaviour)) fail(at('ownsBehaviours') + ' has a malformed entry')
|
|
331
|
+
}
|
|
332
|
+
for (const key of ['docsId', 'storyId', 'docPage'] as const) {
|
|
333
|
+
const held = entry[key]
|
|
334
|
+
if (!(held === null || isFilledString(held))) fail(at(key) + ' is neither a string nor null')
|
|
335
|
+
}
|
|
336
|
+
// "Every storyUrl resolves, or is recorded as absent with a reason" — the
|
|
337
|
+
// reason is enforced rather than written down: only something that draws
|
|
338
|
+
// nothing may have nowhere to be looked at. A component with no story is
|
|
339
|
+
// already an error of UIG-5's component-has-a-story; this stops the
|
|
340
|
+
// catalogue quietly carrying one.
|
|
341
|
+
//
|
|
342
|
+
// The package only. An app's parts are described before they are drawn:
|
|
343
|
+
// absence of a story says nothing about whether a part is reused, and UIG-19
|
|
344
|
+
// is where a reusable one must have one.
|
|
345
|
+
if (!isApp && entry.kind === 'component' && (entry.docsId === null || entry.storyId === null)) {
|
|
346
|
+
fail(at('docsId') + ' is null, and only a helper or a hook may have no page or story')
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!isApp) {
|
|
350
|
+
if (entry.app !== null) fail(at('app') + ' is set on a package entry')
|
|
351
|
+
} else {
|
|
352
|
+
problems.push(...validateAppFacts(entry, at))
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
for (const [i, raw] of excluded.entries()) {
|
|
357
|
+
const gap = raw as Partial<RegistryExclusion>
|
|
358
|
+
if (!isFilledString(gap?.name)) fail(`excluded[${i}].name is missing`)
|
|
359
|
+
if (!isFilledString(gap?.reason)) fail(`excluded[${i}].reason is missing`)
|
|
360
|
+
if (!isApp && isString(gap?.name) && seenNames.has(gap.name)) fail(`${gap.name} is both an entry and excluded`)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Completeness (the ticket's acceptance): every value export is either an
|
|
364
|
+
// entry or an exclusion with a reason. Averaging the difference away is the
|
|
365
|
+
// failure this check exists to make impossible.
|
|
366
|
+
const counted = entries.length + excluded.length
|
|
367
|
+
if (typeof built === 'object' && built !== null && typeof built.exports === 'number' && counted !== built.exports) {
|
|
368
|
+
fail(`${entries.length} entries + ${excluded.length} excluded = ${counted}, but ${isApp ? 'the app has' : 'index.ts exports'} ${built.exports} ${isApp ? 'parts' : 'values'}`)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// And an app's files: every one either holds a part or says why it does not.
|
|
372
|
+
const fileProblems: string[] = []
|
|
373
|
+
const withParts = new Set(entries.map((entry) => (entry as RegistryEntry).sourceFile))
|
|
374
|
+
for (const [i, raw] of withoutParts.entries()) {
|
|
375
|
+
const gap = raw as Partial<FileWithoutPart>
|
|
376
|
+
if (!isFilledString(gap?.file)) fileProblems.push(`filesWithoutParts[${i}].file is missing`)
|
|
377
|
+
else if (withParts.has(gap.file)) fileProblems.push(`${gap.file} holds a part and is listed as holding none`)
|
|
378
|
+
if (!isFilledString(gap?.reason)) fileProblems.push(`filesWithoutParts[${i}].reason is missing`)
|
|
379
|
+
}
|
|
380
|
+
if (!isApp && withoutParts.length) fileProblems.push('filesWithoutParts is for an app; the package lists its exports instead')
|
|
381
|
+
if (isApp && typeof built === 'object' && built !== null && typeof built.files === 'number' && withParts.size + withoutParts.length !== built.files) {
|
|
382
|
+
fileProblems.push(`${withParts.size} files hold a part + ${withoutParts.length} hold none = ${withParts.size + withoutParts.length}, but the app has ${built.files} files`)
|
|
383
|
+
}
|
|
384
|
+
problems.push(...fileProblems)
|
|
385
|
+
|
|
386
|
+
return problems
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* An app entry's class must be what its own evidence says, unless it is written
|
|
391
|
+
* beside the part with a reason. A pass-on is a fact about the file, so it can
|
|
392
|
+
* never be written, and only a pass-on hands a part on.
|
|
393
|
+
*/
|
|
394
|
+
function validateAppFacts(entry: Partial<RegistryEntry>, at: (field: string) => string): string[] {
|
|
395
|
+
const problems: string[] = []
|
|
396
|
+
const fail = (message: string) => problems.push(message)
|
|
397
|
+
const facts = entry.app
|
|
398
|
+
if (typeof facts !== 'object' || facts === null) return [at('app') + ' is missing on an app entry']
|
|
399
|
+
if (!CLASSES.includes(facts.class)) fail(at('app.class') + ` is ${String(facts.class)}`)
|
|
400
|
+
if (!isFilledString(facts.reason)) fail(at('app.reason') + ' is empty')
|
|
401
|
+
if (typeof facts.written !== 'boolean') fail(at('app.written') + ' is not true/false')
|
|
402
|
+
if (typeof facts.defaultExport !== 'boolean') fail(at('app.defaultExport') + ' is not true/false')
|
|
403
|
+
if (!Array.isArray(facts.usedIn) || !facts.usedIn.every(isFilledString)) fail(at('app.usedIn') + ' is not a list of files')
|
|
404
|
+
if (!Array.isArray(facts.tiedTo) || !facts.tiedTo.every(isFilledString)) fail(at('app.tiedTo') + ' is not a list')
|
|
405
|
+
if (!(facts.handsOn === null || isFilledString(facts.handsOn))) fail(at('app.handsOn') + ' is neither a name nor null')
|
|
406
|
+
if (!(facts.packageNamesake === null || isFilledString(facts.packageNamesake))) fail(at('app.packageNamesake') + ' is neither a name nor null')
|
|
407
|
+
if (problems.length) return problems
|
|
408
|
+
|
|
409
|
+
const passOn = facts.class === 're-export'
|
|
410
|
+
if (passOn !== (facts.handsOn !== null)) fail(at('app.handsOn') + (passOn ? ' is missing on a pass-on' : ' is set on a part that is not a pass-on'))
|
|
411
|
+
if (passOn !== (entry.purposeFrom === 'package')) fail(at('purposeFrom') + (passOn ? ' must be package on a pass-on' : ' is package on a part that is not a pass-on'))
|
|
412
|
+
if (passOn && facts.written) fail(at('app.written') + ': a pass-on is read from the file, never written')
|
|
413
|
+
if (!facts.written && !passOn) {
|
|
414
|
+
const uses = facts.usedIn.length
|
|
415
|
+
const expected: EntryClass = uses === 0 ? 'unused' : uses === 1 ? 'one-off' : facts.tiedTo.length ? 'reusable' : 'promote-candidate'
|
|
416
|
+
if (facts.class !== expected) fail(at('app.class') + ` is ${facts.class}, but its evidence (${uses} uses, ${facts.tiedTo.length} ties) says ${expected}`)
|
|
417
|
+
}
|
|
418
|
+
return problems
|
|
419
|
+
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/gates/count.ts", "../../src/gates/hook.ts", "../../src/gates/status.ts"],
|
|
4
|
-
"sourcesContent": ["/// <reference types=\"node\" />\n/**\n * Write `.gates-count.json`: what the gate finds, per rule \u2014 errors, warnings\n * and escapes (seam S3 in docs/GATES.md \u00A716). Written once (\u00A723): until UIG-10\n * Peek, Ship and this package each carried the same script, differing only in\n * the repo's name and which rules it seeded.\n *\n * Runs after the gate lint (npm's `postlint:rules`), so CI runs it too. The\n * file is committed: nothing compares it yet, and the later ratchet needs the\n * history to start at the first commit. It lints with the gate's own config,\n * with escapes reported, so an escape is counted rather than silent. The file\n * is rewritten only when a count changes, so running the lint does not leave a\n * changed file behind every time.\n *\n * It fails when an `eslint-disable` switched a gate rule off: that is not an\n * escape, and the escape count cannot see it.\n */\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { join, relative } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { ESLint as ESLintClass } from 'eslint'\nimport { APP_RULE_IDS, countGates, PACKAGE_RULE_IDS, type GateCount } from '../eslint/index'\n\nexport interface CountOptions {\n /** The folder the gate lints from, where `.gates-count.json` is written. */\n root?: string\n /** The name written into the file: `peek`, `ship`, `estiva-ui`, or the app's. */\n repo: string\n /** `app` seeds the app rules; `package` this package's inward set (UIG-5). */\n audience?: 'app' | 'package'\n /** The gate's config file, relative to `root`. */\n config?: string\n}\n\nexport interface CountResult extends GateCount {\n changed: boolean\n /** What to print, line by line. */\n lines: string[]\n /** What to print to stderr when a rule was switched off; empty when none was. */\n failures: string[]\n}\n\n/** ESLint from where the gate lints, never from beside this file: an app's own install. */\nasync function eslintFrom(root: string): Promise<typeof ESLintClass> {\n const resolved = createRequire(join(root, 'package.json')).resolve('eslint')\n return ((await import(pathToFileURL(resolved).href)) as { ESLint: typeof ESLintClass }).ESLint\n}\n\nexport async function writeGateCount({ root = process.cwd(), repo, audience = 'app', config = 'eslint.gates.config.js' }: CountOptions): Promise<CountResult> {\n const out = join(root, '.gates-count.json')\n const ESLint = await eslintFrom(root)\n const eslint = new ESLint({\n cwd: root,\n overrideConfigFile: join(root, config),\n overrideConfig: { settings: { estiva: { reportEscapes: true } } },\n })\n // The set the gate runs, so a rule with nothing to report still has its row.\n const { rules, disabled } = countGates(await eslint.lintFiles(['.']), audience === 'package' ? PACKAGE_RULE_IDS : APP_RULE_IDS)\n\n const previous = existsSync(out) ? (JSON.parse(readFileSync(out, 'utf8')) as { rules?: unknown }) : null\n const changed = JSON.stringify(previous?.rules) !== JSON.stringify(rules)\n if (changed) {\n const count = { schemaVersion: 1, repo, generatedAt: new Date().toISOString(), rules }\n writeFileSync(out, `${JSON.stringify(count, null, 2)}\\n`)\n }\n const lines = [changed ? '.gates-count.json: written' : '.gates-count.json: unchanged']\n for (const [id, c] of Object.entries(rules)) lines.push(` ${id}: ${c.errors} errors, ${c.warnings} warnings, ${c.escapes} escapes`)\n\n const failures: string[] = []\n if (disabled.length > 0) {\n failures.push('', 'An eslint-disable switched a gate rule off. Write the reason above the element instead: // @estiva-escape: <reason>')\n for (const d of disabled) failures.push(` ${relative(root, d.filePath)}:${d.line} ${d.ruleId}`)\n }\n return { rules, disabled, changed, lines, failures }\n}\n", "/// <reference types=\"node\" />\n/**\n * The editor gate: before Claude writes a `.ts` or `.tsx` under `src/`, lint the\n * text it is about to write with the gate, and refuse the write on an error.\n * Written once (docs/GATES.md \u00A723): until UIG-10 Peek, Ship and this package\n * each carried their own copy of this script.\n *\n * A `PreToolUse` hook in the repository's committed `.claude/settings.json`\n * runs it. Claude Code documents exit 2 as blocking the tool call, with stderr\n * handed back to Claude; exit 0 raises no objection. It reads\n * `$CLAUDE_PROJECT_DIR`, the project root where the session started \u2014 a session\n * started in a folder above or below the repository gets no hook (UIG-3,\n * UIG-4 findings).\n *\n * It runs the gate's own config and reimplements nothing: a hook and a lint\n * that could disagree would be worse than neither. Tests are not checked\n * (Katerina, 13 September); stories are. This package's inward gate reads `.tsx`\n * only, as its lint does.\n *\n * Input, from Claude Code on stdin: `tool_name` and `tool_input` \u2014 `file_path`\n * and `content` for Write; `file_path`, `old_string`, `new_string` and\n * `replace_all` for Edit. An Edit carries no whole file, so the edit is applied\n * to the file on disk here. Anything it cannot read passes, and the tool itself\n * reports its own failure.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { ESLint as ESLintClass } from 'eslint'\nimport { countGates } from '../eslint/index'\n\nexport interface HookOptions {\n /** The repository's top folder. Defaults to `$CLAUDE_PROJECT_DIR`, then the tool call's `cwd`. */\n root?: string\n /** The folder that holds the app, relative to `root`: `web` in Ship, `.` elsewhere. */\n app?: string\n /** `package` gates `.tsx` only, as this package's inward lint does. */\n audience?: 'app' | 'package'\n /** The hook's input, when not read from stdin (tests). */\n input?: unknown\n}\n\nexport interface HookResult {\n /** 0 lets the write through; 2 refuses it. */\n code: 0 | 2\n /** What goes to stderr when refused. */\n message: string\n}\n\ninterface ToolCall {\n tool_name?: string\n cwd?: string\n tool_input?: { file_path?: unknown; content?: unknown; old_string?: unknown; new_string?: unknown; replace_all?: unknown }\n}\n\nconst PASS: HookResult = { code: 0, message: '' }\n\nexport async function runHook({ root, app = '.', audience = 'app', input }: HookOptions = {}): Promise<HookResult> {\n let call: ToolCall\n try {\n call = (input ?? JSON.parse(readFileSync(0, 'utf8'))) as ToolCall\n } catch {\n return PASS\n }\n const args = call.tool_input ?? {}\n if (typeof args.file_path !== 'string') return PASS\n\n const top = root ?? process.env.CLAUDE_PROJECT_DIR ?? call.cwd ?? process.cwd()\n const appDir = resolve(top, app)\n const file = isAbsolute(args.file_path) ? args.file_path : resolve(call.cwd ?? top, args.file_path)\n const rel = relative(appDir, file).split(sep).join('/')\n const source = audience === 'package' ? /^src\\/.+\\.tsx$/ : /^src\\/.+\\.tsx?$/\n if (rel.startsWith('../') || isAbsolute(rel) || !source.test(rel) || /\\.test\\.tsx?$/.test(rel) || /\\.d\\.ts$/.test(rel)) return PASS\n\n const text = proposed(call.tool_name, args, file)\n if (text === null) return PASS\n\n // ESLint from the app's own install: in Ship the top folder has none.\n const resolved = createRequire(join(appDir, 'package.json')).resolve('eslint')\n const { ESLint } = (await import(pathToFileURL(resolved).href)) as { ESLint: typeof ESLintClass }\n const config = join(appDir, 'eslint.gates.config.js')\n const eslint = new ESLint({ cwd: appDir, overrideConfigFile: config })\n const results = await eslint.lintText(text, { filePath: file })\n // A rule's errors only. Code that does not parse yet (one edit of several) is\n // the typecheck's to judge; the gate looks again at the edit that completes it.\n const errors = results.flatMap((r) => r.messages.filter((m) => m.severity === 2 && m.ruleId))\n const { disabled } = countGates(results)\n if (errors.length === 0 && disabled.length === 0) return PASS\n\n const shown = (p: string) => relative(top, p).split(sep).join('/')\n const lines = [`${shown(file)} was not written: the UI Guardrails refuse it (${shown(config)}).`]\n for (const m of errors) lines.push(` ${m.line}:${m.column} ${m.message}${m.ruleId ? ` (${m.ruleId})` : ''}`)\n const keep = audience === 'package' ? 'Keep it only' : 'Keep the element only'\n for (const d of disabled) lines.push(` ${d.line} an eslint-disable switches ${d.ruleId} off. ${keep} with its reason on the line above: // @estiva-escape: <reason>`)\n return { code: 2, message: `${lines.join('\\n')}\\n` }\n}\n\n/** The file as it will be after this tool call, or null when that cannot be known. */\nfunction proposed(tool: string | undefined, args: NonNullable<ToolCall['tool_input']>, file: string): string | null {\n if (tool === 'Write') return typeof args.content === 'string' ? args.content : null\n if (tool !== 'Edit' || typeof args.old_string !== 'string' || typeof args.new_string !== 'string') return null\n const { old_string: from, new_string: to } = args\n const current = existsSync(file) ? readFileSync(file, 'utf8') : ''\n // A checkout on Windows has CRLF on disk; an edit is written with LF.\n for (const text of [current, current.replace(/\\r\\n/g, '\\n')]) {\n if (!text.includes(from)) continue\n return args.replace_all === true ? text.split(from).join(to) : text.replace(from, () => to)\n }\n return null\n}\n", "/// <reference types=\"node\" />\n/**\n * gates:status \u2014 where the UI Guardrails project stands, read from the code.\n *\n * estiva-gates status one row per ticket\n * estiva-gates status --detail every check under every row\n * estiva-gates status --json machine output, read by estiva-ui's run\n * estiva-gates status --app web the app sits in `web/`, as Ship's does\n *\n * Every row is decided by checks on real files, a real lint run or a real\n * GitHub setting. Nothing here reads a list that someone ticks by hand.\n *\n * **One copy, in the package** (docs/GATES.md \u00A723). Until UIG-10 this engine was\n * `scripts/gates-status.mjs`, the same file copied by hand into estiva-ui, Peek\n * and Ship, with estiva-ui warning when the copies drifted. Now it ships here,\n * and a repo runs it with `estiva-gates status`. A repo still carrying its own\n * copy is named as one when estiva-ui runs it; UIG-32 moves Peek and Ship off\n * theirs.\n *\n * What differs per repo is `scripts/gates-checks.mjs`, beside the app: its\n * checks, or the checks every app runs (`appChecks`) plus its own. It sits with\n * the app because it imports the package \u2014 `web/scripts/` in Ship, whose app,\n * and whose install, are in `web/` (UIG-32).\n *\n * The rows and the reasons for each check are in estiva-ui docs/GATES.md \u00A715\n * and \u00A717.\n */\nimport { execFileSync } from 'node:child_process'\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { join, relative, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { ESLint as ESLintClass } from 'eslint'\n\nexport type CheckResult = { result: 'pass' | 'fail' | 'part' | 'unknown'; detail: string }\ntype Maybe<T> = T | Promise<T>\n\nexport interface LintProbe {\n /** The folder ESLint runs from, relative to the repo: `web` in Ship. */\n cwd?: string\n /** The config file, relative to `cwd`. */\n config: string\n /** The path the text is linted as, relative to `cwd`. Nothing is written. */\n file: string\n code: string\n expect: 'error' | 'warning' | 'none'\n /** Words the expected message must contain. */\n mentions?: string\n}\n\n/** The helpers a checks file is written with. */\nexport interface GateHelpers {\n PASS(detail: string): CheckResult\n FAIL(detail: string): CheckResult\n PART(detail: string): CheckResult\n UNKNOWN(detail: string): CheckResult\n exists(rel: string): boolean\n read(rel: string): string\n listFiles(rel: string, test: (name: string, path: string) => boolean): string[]\n file(rel: string): CheckResult\n committed(rel: string): CheckResult\n contains(rel: string, needle: string | RegExp, label?: string): CheckResult\n lacks(rel: string, needle: string | RegExp, label?: string): CheckResult\n script(pkgRel: string, name: string): CheckResult\n loads(rel: string): Promise<CheckResult>\n ci(script: string | RegExp): CheckResult\n ciJob(job: string, script: string): CheckResult\n hook(settingsRel: string, needle: string): CheckResult\n json(rel: string, test?: (data: unknown) => boolean, label?: string): CheckResult\n lint(probe: LintProbe): Promise<CheckResult>\n protectedBranch(pattern: RegExp): CheckResult\n gh(args: string[], label: string): CheckResult\n share(files: string[], test: (file: string) => boolean, label: string): CheckResult\n}\n\nexport interface GateCheck {\n what: string\n run: () => Maybe<CheckResult>\n aggregate?: boolean\n}\nexport interface GateTicket {\n ref: string\n title?: string\n owner: boolean\n checks: GateCheck[]\n}\nexport interface TicketListEntry {\n ref: string\n owner: string\n title: string\n parts?: string[]\n aggregate?: boolean\n}\nexport interface GateSpec {\n repo: string\n tickets: GateTicket[]\n /** estiva-ui only: every ticket, and the repos it joins in. */\n all?: TicketListEntry[]\n /** estiva-ui only: the repos beside it. `app` is the folder its app sits in: `web` in Ship. */\n siblings?: { name: string; path: string; env: string; app?: string }[]\n}\n\ninterface Row {\n ref: string\n title?: string\n owner?: boolean\n ownerRepo?: string\n checks: (CheckResult & { repo: string; what: string })[]\n status?: string\n aggregate?: boolean\n}\ninterface Report {\n schema: 1\n repo: string\n branch: string | null\n commit: string | null\n engine: string\n rows: Row[]\n}\n\nconst { version } = createRequire(import.meta.url)('../../package.json') as { version: string }\n/** What a report says ran it. A copy of the old script reports a hash instead. */\nexport const ENGINE = `@estiva-app/ui@${version}`\n\nconst PASS = (detail: string): CheckResult => ({ result: 'pass', detail })\nconst FAIL = (detail: string): CheckResult => ({ result: 'fail', detail })\nconst PART = (detail: string): CheckResult => ({ result: 'part', detail })\nconst UNKNOWN = (detail: string): CheckResult => ({ result: 'unknown', detail })\nconst said = (e: unknown) => {\n const err = e as { stdout?: unknown; stderr?: unknown; message?: string; code?: string }\n return { text: `${err.stdout ?? ''}${err.stderr ?? ''}`, message: String(err.message ?? e), code: err.code }\n}\n\n/** The helpers, bound to one repository. */\nexport function helpers(ROOT: string): GateHelpers {\n const abs = (rel: string) => resolve(ROOT, rel)\n const exists = (rel: string) => existsSync(abs(rel))\n const read = (rel: string) => readFileSync(abs(rel), 'utf8')\n const git = (...a: string[]) => {\n try {\n return execFileSync('git', a, { cwd: ROOT, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()\n } catch {\n return null\n }\n }\n const listFiles = (rel: string, test: (name: string, path: string) => boolean) => {\n const out: string[] = []\n const walk = (dir: string) => {\n if (!existsSync(dir)) return\n for (const name of readdirSync(dir)) {\n if (['node_modules', '.git', 'dist', 'storybook-static', '.verify-shots'].includes(name)) continue\n const p = join(dir, name)\n if (statSync(p).isDirectory()) walk(p)\n else if (test(name, p)) out.push(relative(ROOT, p).replace(/\\\\/g, '/'))\n }\n }\n walk(abs(rel))\n return out\n }\n const escape = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n return {\n PASS, FAIL, PART, UNKNOWN, exists, read, listFiles,\n\n file(rel) {\n return exists(rel) ? PASS(`${rel} exists`) : FAIL(`${rel} does not exist`)\n },\n\n committed(rel) {\n if (!exists(rel)) return FAIL(`${rel} does not exist`)\n return git('ls-files', '--error-unmatch', rel) ? PASS(`${rel} is committed`) : FAIL(`${rel} exists but is not committed`)\n },\n\n contains(rel, needle, label) {\n if (!exists(rel)) return FAIL(`${rel} does not exist`)\n const hit = typeof needle === 'string' ? read(rel).includes(needle) : needle.test(read(rel))\n return hit ? PASS(label ?? `${rel} has ${needle}`) : FAIL(label ? `not yet: ${label}` : `${rel} has no ${needle}`)\n },\n\n lacks(rel, needle, label) {\n if (!exists(rel)) return FAIL(`${rel} does not exist`)\n const hit = typeof needle === 'string' ? read(rel).includes(needle) : needle.test(read(rel))\n return hit ? FAIL(`${rel} still has ${needle}`) : PASS(label ?? `${rel} has no ${needle}`)\n },\n\n script(pkgRel, name) {\n if (!exists(pkgRel)) return FAIL(`${pkgRel} does not exist`)\n const cmd = (JSON.parse(read(pkgRel)) as { scripts?: Record<string, string> }).scripts?.[name]\n return cmd ? PASS(`${pkgRel} runs \"${name}\": ${cmd}`) : FAIL(`${pkgRel} has no \"${name}\" script`)\n },\n\n async loads(rel) {\n if (!exists(rel)) return FAIL(`${rel} does not exist`)\n try {\n const mod = (await import(pathToFileURL(abs(rel)).href)) as { default?: unknown }\n return mod.default ? PASS(`${rel} loads`) : FAIL(`${rel} has no default export`)\n } catch (e) {\n return FAIL(`${rel} does not load: ${said(e).message.split('\\n')[0]}`)\n }\n },\n\n /** A workflow step that runs `npm run <script>`; a regex matches the script name. */\n ci(script) {\n const files = listFiles('.github/workflows', (n) => /\\.ya?ml$/.test(n))\n const name = typeof script === 'string' ? escape(script) : script.source\n const step = new RegExp(`npm run (${name})(?![\\\\w:-])`)\n for (const f of files) {\n const m = read(f).match(step)\n if (m) return PASS(`${f} runs npm run ${m[1]}`)\n }\n return FAIL(`no workflow runs npm run ${script}`)\n },\n\n /**\n * A workflow job, by its id, with a step that runs `npm run <script>`. Branch\n * protection requires a whole job by its name, so a required check needs the\n * job, not the step somewhere (UIG-6). A job is a key two spaces in under\n * `jobs:`, and ends at the next line that is not indented further; a comment\n * line does not count.\n */\n ciJob(job, script) {\n const files = listFiles('.github/workflows', (n) => /\\.ya?ml$/.test(n))\n const header = new RegExp(`^ ${escape(job)}:\\\\s*(#.*)?$`)\n const step = new RegExp(`^(?!\\\\s*#).*npm run ${escape(script)}(?![\\\\w:-])`, 'm')\n for (const f of files) {\n const lines = read(f).split(/\\r?\\n/)\n const from = lines.findIndex((l) => /^jobs:\\s*$/.test(l))\n const start = from === -1 ? -1 : lines.findIndex((l, i) => i > from && header.test(l))\n if (start === -1) continue\n const end = lines.findIndex((l, i) => i > start && /^ {0,2}\\S/.test(l))\n const body = lines.slice(start + 1, end === -1 ? undefined : end).join('\\n')\n return step.test(body) ? PASS(`${f}: the job ${job} runs npm run ${script}`) : FAIL(`${f}: the job ${job} does not run npm run ${script}`)\n }\n return FAIL(`no workflow has a job ${job}`)\n },\n\n hook(settingsRel, needle) {\n if (!exists(settingsRel)) return FAIL(`${settingsRel} does not exist`)\n let s: { hooks?: { PreToolUse?: { hooks?: { command?: string }[] }[] } }\n try {\n s = JSON.parse(read(settingsRel))\n } catch {\n return FAIL(`${settingsRel} is not valid JSON`)\n }\n const commands = (s.hooks?.PreToolUse ?? []).flatMap((m) => (m.hooks ?? []).map((x) => x.command ?? ''))\n const hit = commands.find((c) => c.includes(needle))\n if (hit) return PASS(`${settingsRel} has a PreToolUse hook running ${needle}`)\n return FAIL(commands.length ? `${settingsRel} has PreToolUse hooks, none running ${needle}` : `${settingsRel} has no PreToolUse hook`)\n },\n\n json(rel, test = () => true, label) {\n if (!exists(rel)) return FAIL(`${rel} does not exist`)\n let data: unknown\n try {\n data = JSON.parse(read(rel))\n } catch {\n return FAIL(`${rel} is not valid JSON`)\n }\n if (!git('ls-files', '--error-unmatch', rel)) return FAIL(`${rel} is not committed`)\n return test(data) ? PASS(label ?? `${rel} is committed and parses`) : FAIL(`${rel} parses, but ${label ?? 'fails its test'}`)\n },\n\n /** Lints a line of code that is never written to disk, the way the hook will. */\n async lint({ cwd = '.', config, file, code, expect, mentions }) {\n const cfg = join(cwd, config)\n if (!exists(cfg)) return FAIL(`${cfg} does not exist`)\n let ESLint: typeof ESLintClass\n try {\n const req = createRequire(join(abs(cwd), 'package.json'))\n ;({ ESLint } = (await import(pathToFileURL(req.resolve('eslint')).href)) as { ESLint: typeof ESLintClass })\n } catch (e) {\n return UNKNOWN(`could not load eslint from ${cwd}: ${said(e).message.split('\\n')[0]}`)\n }\n let results: ESLintClass.LintResult[]\n try {\n const eslint = new ESLint({ cwd: abs(cwd), overrideConfigFile: config })\n results = await eslint.lintText(code, { filePath: join(abs(cwd), file), warnIgnored: true })\n } catch (e) {\n return UNKNOWN(`${cfg} could not lint ${file}: ${said(e).message.split('\\n')[0]}`)\n }\n const messages = results.flatMap((r) => r.messages)\n const ignored = messages.some((m) => /ignored/i.test(m.message) && !m.ruleId)\n const fatal = messages.find((m) => m.fatal && !/ignored/i.test(m.message))\n if (fatal) return UNKNOWN(`${file} did not parse: ${fatal.message}`)\n const want = expect === 'error' ? 2 : expect === 'warning' ? 1 : 0\n if (want === 0) {\n const any = messages.filter((m) => m.ruleId && m.severity === 2)\n return any.length ? FAIL(`${file} gets an error it should not: ${any[0].message}`) : PASS(`${file}: no error${ignored ? ' (not linted)' : ''}`)\n }\n if (ignored) return FAIL(`${cfg} does not lint ${file}`)\n const hit = messages.find((m) => m.ruleId && m.severity === want && (!mentions || m.message.includes(mentions)))\n const kind = want === 2 ? 'error' : 'warning'\n return hit ? PASS(`${file} gets ${want === 2 ? 'an' : 'a'} ${kind}: ${hit.message}`) : FAIL(`${file} gets no ${kind}${mentions ? ` naming ${mentions}` : ''}`)\n },\n\n /**\n * A GitHub setting, not a file, so it is asked of GitHub: the checks a merge\n * into the default branch must pass, from the rulesets in force on it\n * (`rules/branches/<branch>`, which anyone who can read the repository may\n * see) and from classic protection as `branches/<branch>` reports it. Not\n * `branches/<branch>/protection`: GitHub answers \"Not Found\" there to anyone\n * who is not an admin, and it knows nothing of rulesets (UIG-6).\n */\n protectedBranch(pattern) {\n const url = git('remote', 'get-url', 'origin')\n const slug = url?.match(/github\\.com[:/](.+?)(?:\\.git)?$/)?.[1]\n if (!slug) return UNKNOWN('no GitHub remote')\n const branch = (git('symbolic-ref', '--short', 'refs/remotes/origin/HEAD') ?? 'origin/main').replace(/^origin\\//, '')\n const ask = (path: string) => JSON.parse(execFileSync('gh', ['api', path], { stdio: ['ignore', 'pipe', 'pipe'], timeout: 20000 }).toString())\n let rules: { type: string; parameters?: { required_status_checks?: { context: string }[] } }[]\n let classic: { contexts?: string[]; checks?: { context: string }[] } | undefined\n try {\n rules = ask(`repos/${slug}/rules/branches/${branch}`)\n classic = ask(`repos/${slug}/branches/${branch}`).protection?.required_status_checks\n } catch (e) {\n const s = said(e)\n return UNKNOWN(`could not ask GitHub (${s.code === 'ENOENT' ? 'gh is not installed' : s.text.split('\\n')[0] || s.message})`)\n }\n const names = [\n ...rules.filter((r) => r.type === 'required_status_checks').flatMap((r) => r.parameters?.required_status_checks ?? []).map((c) => c.context),\n ...(classic?.contexts ?? []),\n ...(classic?.checks ?? []).map((c) => c.context),\n ]\n const hit = names.find((n) => pattern.test(n))\n if (hit) return PASS(`${slug} ${branch} requires \"${hit}\"`)\n return FAIL(names.length ? `${slug} ${branch} requires ${names.map((n) => `\"${n}\"`).join(', ')}, none matching ${pattern}` : `${slug} ${branch} requires no check to merge`)\n },\n\n gh(args, label) {\n try {\n execFileSync('gh', args, { stdio: ['ignore', 'pipe', 'pipe'], timeout: 20000 })\n return PASS(label)\n } catch (e) {\n const s = said(e)\n if (/could not resolve|not found|HTTP 404/i.test(s.text)) return FAIL(`not yet: ${label}`)\n return UNKNOWN(`could not ask GitHub (${s.code === 'ENOENT' ? 'gh is not installed' : s.text.split('\\n')[0] || s.message})`)\n }\n },\n\n /** How many of `files` pass `test`: all is a pass, some is a part. */\n share(files, test, label) {\n if (!files.length) return FAIL(`no files to check for: ${label}`)\n const ok = files.filter(test).length\n const detail = `${ok} of ${files.length}: ${label}`\n return ok === files.length ? PASS(detail) : ok ? PART(detail) : FAIL(detail)\n },\n }\n}\n\n// \u2500\u2500 running the checks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst STATUS: Record<string, string> = { done: '\u2705', started: '\uD83D\uDEA7', none: '\u2B1C', unknown: '\u2754' }\n\nfunction statusOf(checks: CheckResult[]) {\n const n = (r: CheckResult['result']) => checks.filter((c) => c.result === r).length\n const pass = n('pass'), part = n('part'), fail = n('fail'), unknown = n('unknown')\n if (!checks.length) return 'unknown'\n if (pass === checks.length) return 'done'\n if (pass + part > 0) return fail === 0 && unknown > 0 ? 'unknown' : 'started'\n return fail > 0 ? 'none' : 'unknown'\n}\n\nasync function runRepo(ROOT: string, app: string, h: GateHelpers): Promise<{ spec: GateSpec; report: Report }> {\n // The checks file sits with the app, because it imports `appChecks` from the\n // package: in Ship that install is `web/node_modules`, not the repo's top\n // folder (UIG-32). Everything it names is still read from the repo's top.\n const checksFile = join(ROOT, app, 'scripts', 'gates-checks.mjs')\n const define = ((await import(pathToFileURL(checksFile).href)) as { default: (h: GateHelpers) => GateSpec }).default\n const spec = define(h)\n const git = (...a: string[]) => {\n try {\n return execFileSync('git', a, { cwd: ROOT, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()\n } catch {\n return null\n }\n }\n const rows: Row[] = []\n for (const t of spec.tickets) {\n const checks: Row['checks'] = []\n for (const c of t.checks ?? []) {\n if (c.aggregate) continue\n let r: CheckResult\n try {\n r = await c.run()\n } catch (e) {\n r = UNKNOWN(`the check threw: ${said(e).message}`)\n }\n checks.push({ repo: spec.repo, what: c.what, ...r })\n }\n rows.push({ ref: t.ref, title: t.title, owner: t.owner, checks })\n }\n return { spec, report: { schema: 1, repo: spec.repo, branch: git('rev-parse', '--abbrev-ref', 'HEAD'), commit: git('rev-parse', '--short', 'HEAD'), engine: ENGINE, rows } }\n}\n\n/**\n * Another repository's report. A repo on the package's engine runs it from its\n * own install; a repo still carrying `scripts/gates-status.mjs` runs that copy.\n *\n * `app` is the folder that repo's app sits in \u2014 `web` in Ship \u2014 where its\n * install and its checks file are. The report is still of the whole repo.\n */\nfunction runSibling(dir: string, app: string, shown: string): { report?: Report; error?: string } {\n const copy = join(dir, 'scripts', 'gates-status.mjs')\n const installed = createRequire(join(dir, app, 'package.json'))\n let script = copy\n const args = ['--json']\n if (!existsSync(copy)) {\n try {\n script = join(installed.resolve('@estiva-app/ui/package.json'), '..', 'dist', 'gates', 'cli.js')\n args.unshift('status')\n if (app !== '.') args.push('--app', app)\n } catch {\n return { error: `${shown} has neither its own gates-status.mjs nor @estiva-app/ui installed${app === '.' ? '' : ` in ${app}`}` }\n }\n if (!existsSync(script)) return { error: `${shown} has an @estiva-app/ui with no gates engine: install 0.21.0 or later` }\n }\n try {\n const out = execFileSync(process.execPath, [script, ...args], { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'], timeout: 300000, maxBuffer: 16 * 1024 * 1024 })\n return { report: JSON.parse(out.toString()) as Report }\n } catch (e) {\n const err = e as { stderr?: unknown; message?: string }\n return { error: `its gates:status failed: ${String(err.stderr ?? err.message).split('\\n').filter(Boolean).slice(-1)[0]}` }\n }\n}\n\nfunction pad(s: string, n: number) {\n const len = [...s].length\n return len >= n ? s : s + ' '.repeat(n - len)\n}\n\nfunction printRows(rows: Row[], { showOwner, detail }: { showOwner: boolean; detail: boolean }) {\n const lines: string[] = []\n const width = Math.max(...rows.map((r) => [...(r.title ?? '')].length))\n for (const r of rows) {\n const pass = r.checks.filter((c) => c.result === 'pass').length\n const count = r.checks.length ? `${pass} of ${r.checks.length}` : 'no checks here'\n lines.push(`${STATUS[r.status ?? 'unknown']} ${pad(r.ref, 7)} ${pad(r.title ?? '', width)} ${showOwner ? pad(r.ownerRepo ?? '', 10) : ''}${count}`)\n if (detail || r.status === 'started' || r.status === 'unknown') {\n for (const c of r.checks) {\n const mark = { pass: '\u2713', part: '\u00BD', fail: '\u2717', unknown: '?' }[c.result]\n lines.push(` ${mark} ${showOwner ? `${pad(c.repo, 10)} ` : ''}${c.what} \u2014 ${c.detail}`)\n }\n }\n }\n return lines.join('\\n')\n}\n\nfunction summary(rows: Row[]) {\n const n = (s: string) => rows.filter((r) => r.status === s).length\n return `${STATUS.done} ${n('done')} done \u00B7 ${STATUS.started} ${n('started')} started \u00B7 ${STATUS.none} ${n('none')} not started \u00B7 ${STATUS.unknown} ${n('unknown')} could not check`\n}\n\nexport interface StatusOptions {\n /** The repository to report on. */\n root?: string\n /** The folder that holds the app, relative to `root`: `web` in Ship, `.` elsewhere. */\n app?: string\n json?: boolean\n detail?: boolean\n}\n\n/** Print where the project stands. Returns what was printed, or the JSON report. */\nexport async function runStatus({ root = process.cwd(), app = '.', json = false, detail = false }: StatusOptions = {}): Promise<string> {\n const ROOT = resolve(root)\n const h = helpers(ROOT)\n const shown = (dir: string) => (relative(ROOT, dir) || dir).replace(/\\\\/g, '/')\n const { spec, report } = await runRepo(ROOT, app, h)\n\n if (json) return JSON.stringify(report)\n\n const out: string[] = []\n out.push('UI Guardrails \u00B7 gates:status', 'Read from the code. Nothing here is a hand-ticked list.', '')\n out.push(`${pad(spec.repo, 10)} ${report.branch} @ ${report.commit}`)\n\n if (!spec.all) {\n // A sibling repo on its own: its own rows, then its parts of rows owned elsewhere.\n const own = report.rows.filter((r) => r.owner).map((r) => ({ ...r, ownerRepo: spec.repo, status: statusOf(r.checks) }))\n const parts = report.rows.filter((r) => !r.owner).map((r) => ({ ...r, ownerRepo: '', status: statusOf(r.checks) }))\n out.push('', `Tickets ${spec.repo} owns (${own.length}):`, printRows(own, { showOwner: false, detail }))\n out.push('', `Parts of tickets another repo owns, checked here (${parts.length}):`, printRows(parts, { showOwner: false, detail }))\n out.push('', 'For every ticket across the repos, run npm run gates:status in estiva-ui.')\n return out.join('\\n')\n }\n\n // estiva-ui: gather the sibling checkouts and join everything into one row per ticket.\n const reports: Report[] = [report]\n const found: { name: string; note: string; missing?: boolean }[] = []\n for (const s of spec.siblings ?? []) {\n const dir = resolve(ROOT, process.env[s.env] ?? s.path)\n if (!existsSync(join(dir, 'package.json'))) {\n found.push({ name: s.name, note: `not found at ${shown(dir)} (set ${s.env} to point at it)`, missing: true })\n continue\n }\n const r = runSibling(dir, s.app ?? '.', shown(dir))\n if (!r.report) {\n found.push({ name: s.name, note: r.error ?? 'no report', missing: true })\n continue\n }\n const copy = r.report.engine !== ENGINE ? (r.report.engine.startsWith('@estiva-app/ui@') ? ` \u00B7 on ${r.report.engine}'s engine` : ' \u00B7 runs its own copy of the status engine; UIG-32 moves it onto the package') : ''\n found.push({ name: s.name, note: `${r.report.branch} @ ${r.report.commit}, found at ${shown(dir)}${copy}` })\n reports.push(r.report)\n }\n for (const f of found) out.push(`${pad(f.name, 10)} ${f.note}`)\n\n const problems: string[] = []\n const known = new Set(spec.all.map((t) => t.ref))\n for (const rep of reports) {\n for (const r of rep.rows.filter((r) => !known.has(r.ref))) problems.push(`${rep.repo} reports ${r.ref}, which is not one of the tickets`)\n }\n const rows: Row[] = spec.all.map((t) => {\n const pieces = reports.flatMap((rep) => rep.rows.filter((r) => r.ref === t.ref).map((r) => ({ rep, r })))\n const claims = pieces.filter((p) => p.r.owner).map((p) => p.rep.repo)\n const ownerMissing = found.some((f) => f.name === t.owner && f.missing)\n if (!ownerMissing && (claims.length !== 1 || claims[0] !== t.owner)) {\n problems.push(`${t.ref}: should be owned by ${t.owner}, is claimed by ${claims.join(' and ') || 'no repo'}`)\n }\n for (const p of pieces.filter((p) => !p.r.owner && !(t.parts ?? []).includes(p.rep.repo))) {\n problems.push(`${t.ref}: ${p.rep.repo} checks a part of it, but the ticket list does not name ${p.rep.repo}`)\n }\n const checks = pieces.flatMap((p) => p.r.checks)\n for (const f of found.filter((f) => f.missing)) {\n if ((t.parts ?? []).includes(f.name) || t.owner === f.name) checks.push({ repo: f.name, what: `${f.name}'s part`, result: 'unknown', detail: f.note })\n }\n return { ref: t.ref, title: t.title, ownerRepo: t.owner, checks, aggregate: t.aggregate }\n })\n for (const row of rows) row.status = statusOf(row.checks)\n for (const row of rows.filter((r) => r.aggregate)) {\n const others = rows.filter((r) => r !== row)\n const done = others.filter((r) => r.status === 'done').length\n row.checks.push({ repo: 'all', what: 'every other ticket is done', ...(done === others.length ? PASS(`${done} of ${others.length} done`) : FAIL(`${done} of ${others.length} done`)) })\n row.status = statusOf(row.checks)\n }\n\n out.push('', printRows(rows, { showOwner: true, detail }), '', summary(rows))\n const owners = Object.entries(rows.reduce<Record<string, number>>((a, r) => ({ ...a, [r.ownerRepo ?? '']: (a[r.ownerRepo ?? ''] ?? 0) + 1 }), {}))\n .map(([k, v]) => `${k} ${v}`)\n .join(', ')\n out.push(`${rows.length} tickets. Owned by ${owners}.`)\n const unchecked = found.filter((f) => f.missing && spec.all!.some((t) => t.owner === f.name)).map((f) => f.name)\n if (problems.length) out.push(`\u26A0\uFE0F Ownership does not add up:\\n ${problems.join('\\n ')}`)\n else if (unchecked.length) out.push(`\u2754 Ownership not fully checked: ${unchecked.join(' and ')} could not be read.`)\n else out.push('\u2705 Each ticket is owned by exactly one repo, and every repo agrees.')\n if (!detail) out.push('Add -- --detail to see every check.')\n return out.join('\\n')\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["existsSync", "readFileSync", "createRequire", "join", "relative", "pathToFileURL", "countGates", "existsSync", "readFileSync", "createRequire", "join", "relative", "resolve", "pathToFileURL", "PASS", "r", "p", "f"]
|
|
7
|
-
}
|