@variance-authority/unit-test 0.1.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.
@@ -0,0 +1,65 @@
1
+ import { captureFiles, readCapture } from './archive.js';
2
+ import { operatorError } from './operator.js';
3
+ /** Read browserless unit-test captures in a later `variance run` process. */
4
+ export function captureCollector(options) {
5
+ return async () => {
6
+ const artifacts = new Map();
7
+ const load = async () => {
8
+ if (artifacts.size > 0)
9
+ return;
10
+ for (const path of await captureFiles(options.directory)) {
11
+ const artifact = await readCapture(path);
12
+ if (artifacts.has(artifact.subject.id)) {
13
+ throw operatorError(`capture directory contains duplicate subject ${artifact.subject.id}`);
14
+ }
15
+ artifacts.set(artifact.subject.id, artifact);
16
+ }
17
+ if (artifacts.size === 0) {
18
+ throw operatorError(`capture directory ${options.directory} contains no capture artifacts`);
19
+ }
20
+ };
21
+ return {
22
+ async plan() {
23
+ await load();
24
+ return {
25
+ subjects: [...artifacts.values()].map((artifact) => ({
26
+ subject: artifact.subject,
27
+ ...(artifact.material.kind === 'document'
28
+ ? { viewport: artifact.material.document.viewport }
29
+ : {}),
30
+ })),
31
+ notObserved: [],
32
+ warnings: [],
33
+ };
34
+ },
35
+ async collect(subject) {
36
+ await load();
37
+ const artifact = artifacts.get(subject.subject.id);
38
+ if (artifact === undefined) {
39
+ return { ok: false, because: `no capture artifact for ${subject.subject.id}` };
40
+ }
41
+ if (artifact.material.kind !== 'document') {
42
+ // FIXME: `Collected` carries a render document, so a value capture has
43
+ // nowhere to land. Spec 0031 gives it a value arm and an observation
44
+ // that files changes rather than regions; until then the run says so.
45
+ return {
46
+ ok: false,
47
+ because: `${subject.subject.id} is a ${artifact.material.kind} capture, and this run ` +
48
+ 'compares rendered documents',
49
+ };
50
+ }
51
+ return {
52
+ ok: true,
53
+ document: artifact.material.document,
54
+ ...(artifact.snapshot === undefined ? {} : { snapshot: artifact.snapshot }),
55
+ ...(artifact.source === undefined ? {} : { source: artifact.source }),
56
+ ...(artifact.stabilization === undefined
57
+ ? {}
58
+ : { stabilization: artifact.stabilization }),
59
+ };
60
+ },
61
+ async close() { },
62
+ };
63
+ };
64
+ }
65
+ //# sourceMappingURL=collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collector.js","sourceRoot":"","sources":["../src/collector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CaptureArtifact } from '@variance-authority/core';\nimport { captureFiles, readCapture } from './archive.js';\nimport type { Collector, SubjectSource } from './contract.js';\nimport { operatorError } from './operator.js';\n\nexport interface CaptureCollectorOptions {\n readonly directory: string;\n}\n\n/** Read browserless unit-test captures in a later `variance run` process. */\nexport function captureCollector(options: CaptureCollectorOptions): SubjectSource {\n return async (): Promise<Collector> => {\n const artifacts = new Map<string, CaptureArtifact>();\n\n const load = async (): Promise<void> => {\n if (artifacts.size > 0) return;\n for (const path of await captureFiles(options.directory)) {\n const artifact = await readCapture(path);\n if (artifacts.has(artifact.subject.id)) {\n throw operatorError(`capture directory contains duplicate subject ${artifact.subject.id}`);\n }\n artifacts.set(artifact.subject.id, artifact);\n }\n if (artifacts.size === 0) {\n throw operatorError(`capture directory ${options.directory} contains no capture artifacts`);\n }\n };\n\n return {\n async plan() {\n await load();\n return {\n subjects: [...artifacts.values()].map((artifact) => ({\n subject: artifact.subject,\n ...(artifact.material.kind === 'document'\n ? { viewport: artifact.material.document.viewport }\n : {}),\n })),\n notObserved: [],\n warnings: [],\n };\n },\n\n async collect(subject) {\n await load();\n const artifact = artifacts.get(subject.subject.id);\n if (artifact === undefined) {\n return { ok: false, because: `no capture artifact for ${subject.subject.id}` };\n }\n if (artifact.material.kind !== 'document') {\n // FIXME: `Collected` carries a render document, so a value capture has\n // nowhere to land. Spec 0031 gives it a value arm and an observation\n // that files changes rather than regions; until then the run says so.\n return {\n ok: false,\n because:\n `${subject.subject.id} is a ${artifact.material.kind} capture, and this run ` +\n 'compares rendered documents',\n };\n }\n return {\n ok: true,\n document: artifact.material.document,\n ...(artifact.snapshot === undefined ? {} : { snapshot: artifact.snapshot }),\n ...(artifact.source === undefined ? {} : { source: artifact.source }),\n ...(artifact.stabilization === undefined\n ? {}\n : { stabilization: artifact.stabilization }),\n };\n },\n\n async close() {},\n };\n };\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import type { CallSiteResolver, RenderDocument, SemanticSnapshot, SourceIndex, SubjectRef, Viewport } from '@variance-authority/core';
2
+ /** Structural copy of the CLI collector boundary; this surface does not import the binary. */
3
+ export interface PlannedSubject {
4
+ readonly subject: SubjectRef;
5
+ readonly viewport?: Viewport;
6
+ readonly tags?: readonly string[];
7
+ }
8
+ export interface Plan {
9
+ readonly subjects: readonly PlannedSubject[];
10
+ readonly notObserved: readonly unknown[];
11
+ readonly warnings: readonly string[];
12
+ }
13
+ export type Collected = {
14
+ readonly ok: true;
15
+ readonly document: RenderDocument;
16
+ readonly snapshot?: SemanticSnapshot;
17
+ readonly source?: SourceIndex;
18
+ readonly stabilization?: readonly string[];
19
+ } | {
20
+ readonly ok: false;
21
+ readonly because: string;
22
+ };
23
+ export interface Collector {
24
+ plan(): Promise<Plan>;
25
+ collect(subject: PlannedSubject): Promise<Collected>;
26
+ readonly callSites?: CallSiteResolver;
27
+ close(): Promise<void>;
28
+ }
29
+ export interface CollectorContext {
30
+ readonly config: {
31
+ readonly viewport: Viewport;
32
+ };
33
+ readonly plan?: Plan;
34
+ }
35
+ export type SubjectSource = (context: CollectorContext) => Promise<Collector>;
36
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n CallSiteResolver,\n RenderDocument,\n SemanticSnapshot,\n SourceIndex,\n SubjectRef,\n Viewport,\n} from '@variance-authority/core';\n\n/** Structural copy of the CLI collector boundary; this surface does not import the binary. */\nexport interface PlannedSubject {\n readonly subject: SubjectRef;\n readonly viewport?: Viewport;\n readonly tags?: readonly string[];\n}\n\nexport interface Plan {\n readonly subjects: readonly PlannedSubject[];\n readonly notObserved: readonly unknown[];\n readonly warnings: readonly string[];\n}\n\nexport type Collected =\n | {\n readonly ok: true;\n readonly document: RenderDocument;\n readonly snapshot?: SemanticSnapshot;\n readonly source?: SourceIndex;\n readonly stabilization?: readonly string[];\n }\n | { readonly ok: false; readonly because: string };\n\nexport interface Collector {\n plan(): Promise<Plan>;\n collect(subject: PlannedSubject): Promise<Collected>;\n readonly callSites?: CallSiteResolver;\n close(): Promise<void>;\n}\n\nexport interface CollectorContext {\n readonly config: { readonly viewport: Viewport };\n readonly plan?: Plan;\n}\n\nexport type SubjectSource = (context: CollectorContext) => Promise<Collector>;\n"]}
@@ -0,0 +1,9 @@
1
+ export { capture } from './capture.js';
2
+ export type { ResolvedResource, UnitCaptureOptions, } from './capture.js';
3
+ export { snapshotValue } from './value.js';
4
+ export type { SnapshotValueOptions } from './value.js';
5
+ export { CAPTURE_SUFFIX, captureFiles, readCapture, resetCaptures, writeCapture, } from './archive.js';
6
+ export { captureCollector } from './collector.js';
7
+ export type { CaptureCollectorOptions } from './collector.js';
8
+ export type { Collected, Collector, CollectorContext, Plan, PlannedSubject, SubjectSource, } from './contract.js';
9
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { capture } from './capture.js';
2
+ export { snapshotValue } from './value.js';
3
+ export { CAPTURE_SUFFIX, captureFiles, readCapture, resetCaptures, writeCapture, } from './archive.js';
4
+ export { captureCollector } from './collector.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAKvC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EACL,cAAc,EACd,YAAY,EACZ,WAAW,EACX,aAAa,EACb,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { capture } from './capture.js';\nexport type {\n ResolvedResource,\n UnitCaptureOptions,\n} from './capture.js';\nexport { snapshotValue } from './value.js';\nexport type { SnapshotValueOptions } from './value.js';\nexport {\n CAPTURE_SUFFIX,\n captureFiles,\n readCapture,\n resetCaptures,\n writeCapture,\n} from './archive.js';\nexport { captureCollector } from './collector.js';\nexport type { CaptureCollectorOptions } from './collector.js';\nexport type {\n Collected,\n Collector,\n CollectorContext,\n Plan,\n PlannedSubject,\n SubjectSource,\n} from './contract.js';\n"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Say that a failure is the operator's to fix, not a bug in the tool.
3
+ *
4
+ * A collector is loaded by dynamic import from the adopter's own
5
+ * `node_modules`, so it cannot share an error *class* with whatever runs it —
6
+ * `instanceof` across two installed copies is `false`, and the misconfigured
7
+ * suite would be told it has found a defect. The contract is therefore a
8
+ * property name, which survives the boundary the way a header name does.
9
+ *
10
+ * Use it for anything the reader can act on: a config that names two plans, a
11
+ * sitemap that answered 404, a route with no id. Leave a bare `Error` where the
12
+ * cause really is this package — a missing bundle it was supposed to build is
13
+ * not something an adopter can repair, and dressing it up as their problem
14
+ * sends them to edit a config that was never wrong.
15
+ */
16
+ export declare function operatorError(message: string, options?: ErrorOptions): Error;
17
+ //# sourceMappingURL=operator.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Say that a failure is the operator's to fix, not a bug in the tool.
3
+ *
4
+ * A collector is loaded by dynamic import from the adopter's own
5
+ * `node_modules`, so it cannot share an error *class* with whatever runs it —
6
+ * `instanceof` across two installed copies is `false`, and the misconfigured
7
+ * suite would be told it has found a defect. The contract is therefore a
8
+ * property name, which survives the boundary the way a header name does.
9
+ *
10
+ * Use it for anything the reader can act on: a config that names two plans, a
11
+ * sitemap that answered 404, a route with no id. Leave a bare `Error` where the
12
+ * cause really is this package — a missing bundle it was supposed to build is
13
+ * not something an adopter can repair, and dressing it up as their problem
14
+ * sends them to edit a config that was never wrong.
15
+ */
16
+ export function operatorError(message, options) {
17
+ return Object.assign(new Error(message, options), { varianceOperatorError: true });
18
+ }
19
+ //# sourceMappingURL=operator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operator.js","sourceRoot":"","sources":["../src/operator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,OAAsB;IACnE,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC,CAAC;AACrF,CAAC","sourcesContent":["/**\n * Say that a failure is the operator's to fix, not a bug in the tool.\n *\n * A collector is loaded by dynamic import from the adopter's own\n * `node_modules`, so it cannot share an error *class* with whatever runs it —\n * `instanceof` across two installed copies is `false`, and the misconfigured\n * suite would be told it has found a defect. The contract is therefore a\n * property name, which survives the boundary the way a header name does.\n *\n * Use it for anything the reader can act on: a config that names two plans, a\n * sitemap that answered 404, a route with no id. Leave a bare `Error` where the\n * cause really is this package — a missing bundle it was supposed to build is\n * not something an adopter can repair, and dressing it up as their problem\n * sends them to edit a config that was never wrong.\n */\nexport function operatorError(message: string, options?: ErrorOptions): Error {\n return Object.assign(new Error(message, options), { varianceOperatorError: true });\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { type CaptureArtifact } from '@variance-authority/core';
2
+ /**
3
+ * What a capture file has to be before anything is believed about it.
4
+ *
5
+ * Separate from the directory protocol in [`archive.ts`](./archive.ts) because
6
+ * the two answer different questions and change for different reasons: that one
7
+ * owns which files exist and who may replace them, this one owns whether the
8
+ * bytes in one of them are a capture. The split is also where the size is — a
9
+ * document carries a semantic snapshot, a resource closure and a source index,
10
+ * and every one of those is checked field by field here.
11
+ *
12
+ * Everything is re-derived rather than believed. A digest that arrived in the
13
+ * file is recomputed from the content it claims to describe, so a baseline
14
+ * edited by hand between runs fails here instead of comparing as though the edit
15
+ * had been captured.
16
+ */
17
+ export declare function captureArtifactFrom(value: unknown, path: string): CaptureArtifact;
18
+ //# sourceMappingURL=shape.d.ts.map
package/dist/shape.js ADDED
@@ -0,0 +1,365 @@
1
+ import { digestBytes, digestCombine, digestString, } from '@variance-authority/core';
2
+ /**
3
+ * What a capture file has to be before anything is believed about it.
4
+ *
5
+ * Separate from the directory protocol in [`archive.ts`](./archive.ts) because
6
+ * the two answer different questions and change for different reasons: that one
7
+ * owns which files exist and who may replace them, this one owns whether the
8
+ * bytes in one of them are a capture. The split is also where the size is — a
9
+ * document carries a semantic snapshot, a resource closure and a source index,
10
+ * and every one of those is checked field by field here.
11
+ *
12
+ * Everything is re-derived rather than believed. A digest that arrived in the
13
+ * file is recomputed from the content it claims to describe, so a baseline
14
+ * edited by hand between runs fails here instead of comparing as though the edit
15
+ * had been captured.
16
+ */
17
+ export function captureArtifactFrom(value, path) {
18
+ const artifact = record(value);
19
+ if (artifact === null || artifact.artifactVersion !== 1) {
20
+ throw new Error(`${path} is not a capture artifact version 1`);
21
+ }
22
+ const subject = subjectFrom(artifact.subject);
23
+ const material = record(artifact.material);
24
+ if (subject === null || material === null) {
25
+ throw new Error(`${path} is not a unit-test capture`);
26
+ }
27
+ if (material.kind === 'value') {
28
+ checkValue(material.value, path);
29
+ checkTail(artifact, subject.id, path);
30
+ return value;
31
+ }
32
+ if (material.kind !== 'document') {
33
+ throw new Error(`${path} is not a unit-test document capture`);
34
+ }
35
+ const document = record(material.document);
36
+ if (document === null ||
37
+ !documentShape(document) ||
38
+ record(document.resources) === null) {
39
+ throw new Error(`${path} does not contain a resource-closed render document`);
40
+ }
41
+ const documentSubject = subjectFrom(document.subject);
42
+ if (documentSubject === null || documentSubject.id !== subject.id) {
43
+ throw new Error(`${path} has different artifact and document subjects`);
44
+ }
45
+ for (const [url, value] of Object.entries(document.resources)) {
46
+ const resource = resourceFrom(value);
47
+ if (resource === null)
48
+ throw new Error(`${path} contains an invalid resource for ${url}`);
49
+ const actual = digestBytes(Buffer.from(resource.bytes, 'base64'));
50
+ if (actual !== resource.digest) {
51
+ throw new Error(`${path} contains resource bytes that do not match ${url}`);
52
+ }
53
+ }
54
+ const assets = record(document.assets);
55
+ if (assets === null)
56
+ throw new Error(`${path} does not declare its resource digests`);
57
+ for (const [url, expected] of Object.entries(assets)) {
58
+ const resource = document.resources[url];
59
+ const parsed = resourceFrom(resource);
60
+ if (typeof expected !== 'string' || parsed === null || parsed.digest !== expected) {
61
+ throw new Error(`${path} has no matching archived resource for ${url}`);
62
+ }
63
+ }
64
+ checkTail(artifact, subject.id, path);
65
+ return value;
66
+ }
67
+ function checkTail(artifact, subjectId, path) {
68
+ if (artifact.snapshot !== undefined && !snapshotShape(artifact.snapshot, subjectId)) {
69
+ throw new Error(`${path} contains an invalid semantic snapshot`);
70
+ }
71
+ if (artifact.source !== undefined && !sourceShape(artifact.source)) {
72
+ throw new Error(`${path} contains an invalid source index`);
73
+ }
74
+ if (artifact.stabilization !== undefined && !stringArray(artifact.stabilization)) {
75
+ throw new Error(`${path} contains an invalid stabilization record`);
76
+ }
77
+ if (artifact.attempt !== undefined && !attemptShape(artifact.attempt)) {
78
+ throw new Error(`${path} contains an invalid capture attempt`);
79
+ }
80
+ }
81
+ /**
82
+ * A value capture, re-derived rather than believed.
83
+ *
84
+ * The digest is recomputed from the text on the way in, exactly as a resource's
85
+ * bytes are. A baseline edited by hand between runs would otherwise pass its own
86
+ * identity check and compare as though the edit had been captured.
87
+ */
88
+ function checkValue(value, path) {
89
+ const captured = record(value);
90
+ if (captured === null ||
91
+ typeof captured.dialect !== 'string' ||
92
+ typeof captured.text !== 'string' ||
93
+ typeof captured.digest !== 'string' ||
94
+ typeof captured.recipe !== 'string' ||
95
+ (captured.keyed !== undefined && !stringArray(captured.keyed)) ||
96
+ (captured.generator !== undefined && !generatorShape(captured.generator))) {
97
+ throw new Error(`${path} does not contain a captured value`);
98
+ }
99
+ const actual = digestCombine('value/v1', [
100
+ digestString(captured.text),
101
+ digestString(captured.recipe),
102
+ ]);
103
+ if (actual !== captured.digest) {
104
+ throw new Error(`${path} contains value text that does not match its digest`);
105
+ }
106
+ }
107
+ function generatorShape(value) {
108
+ const generator = record(value);
109
+ return (generator !== null &&
110
+ typeof generator.name === 'string' &&
111
+ typeof generator.version === 'string');
112
+ }
113
+ function resourceFrom(value) {
114
+ const resource = record(value);
115
+ return resource !== null &&
116
+ typeof resource.contentType === 'string' &&
117
+ typeof resource.bytes === 'string' &&
118
+ base64(resource.bytes) &&
119
+ typeof resource.digest === 'string'
120
+ ? resource
121
+ : null;
122
+ }
123
+ function record(value) {
124
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
125
+ ? value
126
+ : null;
127
+ }
128
+ function subjectFrom(value) {
129
+ const subject = record(value);
130
+ if (subject === null ||
131
+ typeof subject.id !== 'string' ||
132
+ !['story', 'route', 'fixture', 'value'].includes(String(subject.kind)) ||
133
+ (subject.title !== undefined && typeof subject.title !== 'string'))
134
+ return null;
135
+ return { id: subject.id, kind: String(subject.kind) };
136
+ }
137
+ function documentShape(document) {
138
+ const frame = record(document.frame);
139
+ const viewport = record(document.viewport);
140
+ return (document.documentVersion === 1 &&
141
+ subjectFrom(document.subject) !== null &&
142
+ typeof document.html === 'string' &&
143
+ frame !== null &&
144
+ stringRecord(frame.html) &&
145
+ stringRecord(frame.body) &&
146
+ Array.isArray(frame.ancestors) &&
147
+ frame.ancestors.every(frameElementShape) &&
148
+ (frame.containerWidth === undefined || finiteNumber(frame.containerWidth)) &&
149
+ stringArray(document.css) &&
150
+ viewport !== null &&
151
+ finiteNumber(viewport.width) &&
152
+ finiteNumber(viewport.height) &&
153
+ finiteNumber(viewport.deviceScaleFactor) &&
154
+ (viewport.colorScheme === 'light' || viewport.colorScheme === 'dark') &&
155
+ stringRecord(document.inherited) &&
156
+ stringArray(document.fonts) &&
157
+ (document.assets === undefined || stringRecord(document.assets)) &&
158
+ (document.baseUrl === undefined || typeof document.baseUrl === 'string') &&
159
+ diagnosticArray(document.diagnostics));
160
+ }
161
+ function frameElementShape(value) {
162
+ const element = record(value);
163
+ return element !== null && typeof element.tag === 'string' && stringRecord(element.attributes);
164
+ }
165
+ function snapshotShape(value, subjectId) {
166
+ const snapshot = record(value);
167
+ const profile = snapshot === null ? null : record(snapshot.profile);
168
+ const environment = snapshot === null ? null : record(snapshot.environment);
169
+ const root = snapshot === null ? null : record(snapshot.root);
170
+ return (snapshot !== null &&
171
+ snapshot.formatVersion === 1 &&
172
+ subjectFrom(snapshot.subject)?.id === subjectId &&
173
+ profile !== null &&
174
+ (profile.id === 'jsdom' || profile.id === 'chromium') &&
175
+ ['ariaTree', 'declaredStyle', 'computedStyle', 'layout', 'raster'].every((field) => typeof profile[field] === 'boolean') &&
176
+ environment !== null &&
177
+ typeof environment.digest === 'string' &&
178
+ typeof environment.semanticDigest === 'string' &&
179
+ environmentInputsShape(environment.inputs) &&
180
+ typeof snapshot.renderHash === 'string' &&
181
+ typeof snapshot.structureHash === 'string' &&
182
+ typeof snapshot.styleHash === 'string' &&
183
+ root !== null &&
184
+ semanticNodeShape(root) &&
185
+ Array.isArray(snapshot.styleProvenance) &&
186
+ snapshot.styleProvenance.every(styleProvenanceShape) &&
187
+ (snapshot.ignoreSites === undefined ||
188
+ (Array.isArray(snapshot.ignoreSites) && snapshot.ignoreSites.every(ignoreSiteShape))) &&
189
+ diagnosticArray(snapshot.diagnostics));
190
+ }
191
+ function environmentInputsShape(value) {
192
+ const inputs = record(value);
193
+ const viewport = inputs === null ? null : record(inputs.viewport);
194
+ return inputs !== null &&
195
+ (inputs.profile === 'jsdom' || inputs.profile === 'chromium') &&
196
+ typeof inputs.engine === 'string' &&
197
+ typeof inputs.ruleset === 'string' &&
198
+ typeof inputs.allowlist === 'string' &&
199
+ viewport !== null &&
200
+ finiteNumber(viewport.width) &&
201
+ finiteNumber(viewport.height) &&
202
+ finiteNumber(viewport.deviceScaleFactor) &&
203
+ (viewport.colorScheme === 'light' || viewport.colorScheme === 'dark') &&
204
+ stringArray(inputs.fonts) &&
205
+ scalarRecord(inputs.conditions) &&
206
+ stringRecord(inputs.assets) &&
207
+ (inputs.stabilization === undefined || typeof inputs.stabilization === 'string');
208
+ }
209
+ function styleProvenanceShape(value) {
210
+ const entry = record(value);
211
+ if (entry === null ||
212
+ typeof entry.path !== 'string' ||
213
+ typeof entry.property !== 'string' ||
214
+ typeof entry.sheet !== 'string' ||
215
+ typeof entry.selector !== 'string' ||
216
+ (entry.tokenName !== undefined && typeof entry.tokenName !== 'string'))
217
+ return false;
218
+ if (entry.source === undefined)
219
+ return true;
220
+ const source = record(entry.source);
221
+ return source !== null && typeof source.file === 'string' && finiteNumber(source.line);
222
+ }
223
+ function semanticNodeShape(node) {
224
+ return (typeof node.path === 'string' &&
225
+ typeof node.tag === 'string' &&
226
+ optionalString(node.alias) &&
227
+ optionalString(node.role) &&
228
+ optionalString(node.name) &&
229
+ optionalString(node.description) &&
230
+ (node.state === undefined || scalarRecord(node.state)) &&
231
+ stringRecord(node.attributes) &&
232
+ stringRecord(node.style) &&
233
+ (node.tokens === undefined || stringRecord(node.tokens)) &&
234
+ (node.styleTokens === undefined || stringRecord(node.styleTokens)) &&
235
+ (node.rect === undefined || rectShape(node.rect)) &&
236
+ optionalString(node.text) &&
237
+ (node.provenance === undefined || provenanceShape(node.provenance)) &&
238
+ (node.wiring === undefined || wiringShape(node.wiring)) &&
239
+ (node.portalled === undefined || typeof node.portalled === 'boolean') &&
240
+ (node.ignoredBy === undefined || stringArray(node.ignoredBy)) &&
241
+ Array.isArray(node.children) &&
242
+ node.children.every((child) => {
243
+ const parsed = record(child);
244
+ return parsed !== null && semanticNodeShape(parsed);
245
+ }));
246
+ }
247
+ function ignoreSiteShape(value) {
248
+ const site = record(value);
249
+ return site !== null &&
250
+ typeof site.path === 'string' &&
251
+ typeof site.rule === 'string' &&
252
+ (site.rect === undefined || rectShape(site.rect));
253
+ }
254
+ function rectShape(value) {
255
+ const rect = record(value);
256
+ return rect !== null &&
257
+ finiteNumber(rect.x) &&
258
+ finiteNumber(rect.y) &&
259
+ finiteNumber(rect.width) &&
260
+ finiteNumber(rect.height);
261
+ }
262
+ function provenanceShape(value) {
263
+ const provenance = record(value);
264
+ return provenance !== null &&
265
+ Array.isArray(provenance.owners) &&
266
+ provenance.owners.every(ownerShape) &&
267
+ optionalString(provenance.createdBy) &&
268
+ (provenance.source === undefined || sourceLocationShape(provenance.source)) &&
269
+ (provenance.stack === undefined ||
270
+ (Array.isArray(provenance.stack) && provenance.stack.every(stackFrameShape)));
271
+ }
272
+ function ownerShape(value) {
273
+ const owner = record(value);
274
+ return owner !== null &&
275
+ typeof owner.name === 'string' &&
276
+ typeof owner.propsDigest === 'string' &&
277
+ optionalString(owner.createdBy);
278
+ }
279
+ function sourceLocationShape(value) {
280
+ const source = record(value);
281
+ return source !== null &&
282
+ typeof source.file === 'string' &&
283
+ finiteNumber(source.line) &&
284
+ finiteNumber(source.column);
285
+ }
286
+ function stackFrameShape(value) {
287
+ const frame = record(value);
288
+ return frame !== null &&
289
+ typeof frame.url === 'string' &&
290
+ finiteNumber(frame.line) &&
291
+ finiteNumber(frame.column) &&
292
+ optionalString(frame.function);
293
+ }
294
+ function wiringShape(value) {
295
+ const wiring = record(value);
296
+ return wiring !== null &&
297
+ (wiring.hooks === undefined || stringArray(wiring.hooks)) &&
298
+ (wiring.wrappers === undefined ||
299
+ (Array.isArray(wiring.wrappers) &&
300
+ wiring.wrappers.every((entry) => entry === 'memo' || entry === 'forwardRef'))) &&
301
+ (wiring.contexts === undefined || stringArray(wiring.contexts)) &&
302
+ optionalString(wiring.key);
303
+ }
304
+ function sourceShape(value) {
305
+ const source = record(value);
306
+ return source !== null && Object.values(source).every((refs) => Array.isArray(refs) && refs.every((ref) => {
307
+ const parsed = record(ref);
308
+ return parsed !== null &&
309
+ typeof parsed.file === 'string' &&
310
+ finiteNumber(parsed.line) &&
311
+ ['function', 'const', 'class', 'declared'].includes(String(parsed.via));
312
+ }));
313
+ }
314
+ function attemptShape(value) {
315
+ const attempt = record(value);
316
+ if (attempt === null ||
317
+ !nonNegativeInteger(attempt.retry) ||
318
+ !nonNegativeInteger(attempt.repeat))
319
+ return false;
320
+ if (attempt.shard === undefined)
321
+ return true;
322
+ const shard = record(attempt.shard);
323
+ return shard !== null &&
324
+ nonNegativeInteger(shard.index) &&
325
+ nonNegativeInteger(shard.total) &&
326
+ Number(shard.total) > 0 &&
327
+ Number(shard.index) < Number(shard.total);
328
+ }
329
+ function diagnosticArray(value) {
330
+ return Array.isArray(value) && value.every((entry) => {
331
+ const diagnostic = record(entry);
332
+ return diagnostic !== null &&
333
+ (diagnostic.severity === 'warn' || diagnostic.severity === 'error') &&
334
+ typeof diagnostic.code === 'string' &&
335
+ typeof diagnostic.message === 'string' &&
336
+ (diagnostic.nodePath === undefined || typeof diagnostic.nodePath === 'string');
337
+ });
338
+ }
339
+ function stringRecord(value) {
340
+ const parsed = record(value);
341
+ return parsed !== null && Object.values(parsed).every((entry) => typeof entry === 'string');
342
+ }
343
+ function scalarRecord(value) {
344
+ const parsed = record(value);
345
+ return parsed !== null && Object.values(parsed).every((entry) => ['string', 'boolean', 'number'].includes(typeof entry) &&
346
+ (typeof entry !== 'number' || Number.isFinite(entry)));
347
+ }
348
+ function stringArray(value) {
349
+ return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
350
+ }
351
+ function optionalString(value) {
352
+ return value === undefined || typeof value === 'string';
353
+ }
354
+ function finiteNumber(value) {
355
+ return typeof value === 'number' && Number.isFinite(value);
356
+ }
357
+ function nonNegativeInteger(value) {
358
+ return finiteNumber(value) && Number.isInteger(value) && Number(value) >= 0;
359
+ }
360
+ function base64(value) {
361
+ if (value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value))
362
+ return false;
363
+ return Buffer.from(value, 'base64').toString('base64') === value;
364
+ }
365
+ //# sourceMappingURL=shape.js.map