@invarn/cibuild 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +86 -8
- package/dist/src/lib.d.ts +2 -0
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +2 -0
- package/dist/src/yaml/fidelity-manifest.d.ts +126 -0
- package/dist/src/yaml/fidelity-manifest.d.ts.map +1 -0
- package/dist/src/yaml/fidelity-manifest.js +134 -0
- package/dist/src/yaml/fidelity-manifest.test.d.ts +12 -0
- package/dist/src/yaml/fidelity-manifest.test.d.ts.map +1 -0
- package/dist/src/yaml/fidelity-manifest.test.js +251 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts +53 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts.map +1 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.js +72 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts +11 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.js +50 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts +45 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts.map +1 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.js +69 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts +14 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.js +54 -0
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-preview-android.js +2 -9
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts +26 -0
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-preview.js +21 -9
- package/dist/src/yaml/steps/ui-fidelity-preview.test.js +31 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts +9 -0
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.js +88 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +98 -0
- package/package.json +1 -1
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
* 1. Reads `.ci/inputs/params.json`
|
|
10
10
|
* ({ "screens": { "<ScreenName>": "<referenceFileBasename>" } });
|
|
11
11
|
* reference images live at `.ci/inputs/<basename>`.
|
|
12
|
+
* Optional reference-provenance sidecar (manifest-driven multi-state path):
|
|
13
|
+
* `"render_locale": "<locale>"` plus
|
|
14
|
+
* `"provenance": { "<ScreenName>": { figmaNode, locale, resolved, exportedAt } }`.
|
|
15
|
+
* When `render_locale` is present, each screen's reference is gated on its
|
|
16
|
+
* provenance METADATA (never pixels/OCR): absent, unprovenanced,
|
|
17
|
+
* `resolved == false`, or `locale != render_locale` → status
|
|
18
|
+
* `unverifiable` for that screen, so a rotten reference fails loud instead
|
|
19
|
+
* of yielding a confident-but-false PASS. Content differences (copy,
|
|
20
|
+
* geometry) are NOT gated here — they stay soft, judged findings.
|
|
12
21
|
* 2. Extracts and validates the shipped Gradle project shipped as
|
|
13
22
|
* `.ci/inputs/package.tar.gz` (single project root carrying a settings
|
|
14
23
|
* script, bounded extracted size, safe member paths).
|
|
@@ -136,6 +145,54 @@ function setError(entry, code, message) {
|
|
|
136
145
|
entry.error = { code: code, message: sanitizeMessage(message) };
|
|
137
146
|
}
|
|
138
147
|
|
|
148
|
+
// A reference the gate cannot honestly judge: a hard, mechanical metadata
|
|
149
|
+
// verdict (never pixels/OCR). Distinct status so a rotten reference fails loud
|
|
150
|
+
// instead of producing a confident-but-false PASS.
|
|
151
|
+
function setUnverifiable(entry, code, message) {
|
|
152
|
+
entry.status = 'unverifiable';
|
|
153
|
+
entry.error = { code: code, message: sanitizeMessage(message) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Inlined twin of src/yaml/steps/fidelity-reference-provenance.ts (this script
|
|
157
|
+
// ships self-contained and cannot import it). The render-step integration tests
|
|
158
|
+
// exercise BOTH against the same fixtures so they cannot silently drift. Returns
|
|
159
|
+
// a { code, message } fault, or null when the reference is verifiable.
|
|
160
|
+
function provenanceFault(screen, provenance, renderLocale, referenceExists) {
|
|
161
|
+
var hasProvenance =
|
|
162
|
+
provenance != null &&
|
|
163
|
+
typeof provenance === 'object' &&
|
|
164
|
+
typeof provenance.locale === 'string' &&
|
|
165
|
+
typeof provenance.resolved === 'boolean';
|
|
166
|
+
if (!hasProvenance) {
|
|
167
|
+
return {
|
|
168
|
+
code: 'NO_PROVENANCE',
|
|
169
|
+
message:
|
|
170
|
+
'reference for ' + screen + ' has no export provenance; a reference is ' +
|
|
171
|
+
'verifiable only when exported through the deterministic path',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (provenance.resolved === false) {
|
|
175
|
+
return {
|
|
176
|
+
code: 'UNRESOLVED_REFERENCE',
|
|
177
|
+
message:
|
|
178
|
+
'reference for ' + screen + ' still has unresolved copy placeholders ' +
|
|
179
|
+
'(resolved=false) — export it with the copy filled in',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (provenance.locale !== renderLocale) {
|
|
183
|
+
return {
|
|
184
|
+
code: 'LOCALE_MISMATCH',
|
|
185
|
+
message:
|
|
186
|
+
'reference for ' + screen + ' is locale "' + provenance.locale +
|
|
187
|
+
'" but the render locale is "' + renderLocale + '"',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (!referenceExists) {
|
|
191
|
+
return { code: 'REFERENCE_ABSENT', message: 'reference image for ' + screen + ' is absent' };
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
139
196
|
function outputTail(result) {
|
|
140
197
|
var combined = ((result.stdout || '') + '\n' + (result.stderr || '')).trim();
|
|
141
198
|
if (combined.length > 2000) combined = '...' + combined.slice(-2000);
|
|
@@ -549,6 +606,18 @@ function main() {
|
|
|
549
606
|
return 1;
|
|
550
607
|
}
|
|
551
608
|
|
|
609
|
+
// Reference-provenance sidecar. Enforcement is ON only when the run declares
|
|
610
|
+
// its render locale (the manifest-driven multi-state path always does);
|
|
611
|
+
// legacy single-reference runs without it keep their existing behaviour.
|
|
612
|
+
var renderLocale =
|
|
613
|
+
typeof params.render_locale === 'string' && params.render_locale !== ''
|
|
614
|
+
? params.render_locale
|
|
615
|
+
: null;
|
|
616
|
+
var provenanceMap =
|
|
617
|
+
params.provenance && typeof params.provenance === 'object' && !Array.isArray(params.provenance)
|
|
618
|
+
? params.provenance
|
|
619
|
+
: {};
|
|
620
|
+
|
|
552
621
|
var screens = Object.keys(params.screens);
|
|
553
622
|
currentEntries = screens.map(function (screen) {
|
|
554
623
|
return {
|
|
@@ -600,7 +669,25 @@ function main() {
|
|
|
600
669
|
} catch (statError) {
|
|
601
670
|
sourceStat = null;
|
|
602
671
|
}
|
|
603
|
-
|
|
672
|
+
var referenceExists = !!(sourceStat && sourceStat.isFile());
|
|
673
|
+
|
|
674
|
+
if (renderLocale) {
|
|
675
|
+
// Provenance enforcement: a reference that is absent, unprovenanced,
|
|
676
|
+
// unresolved, or in the wrong locale is UNVERIFIABLE (hard, loud) — not a
|
|
677
|
+
// render_failed and never a silent PASS.
|
|
678
|
+
var fault = provenanceFault(
|
|
679
|
+
entry.screen,
|
|
680
|
+
provenanceMap[entry.screen],
|
|
681
|
+
renderLocale,
|
|
682
|
+
referenceExists
|
|
683
|
+
);
|
|
684
|
+
if (fault) {
|
|
685
|
+
setUnverifiable(entry, fault.code, fault.message);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
} else if (!referenceExists) {
|
|
689
|
+
// Legacy (no render locale declared): an absent reference stays a
|
|
690
|
+
// render_failed REFERENCE_MISSING, exactly as before.
|
|
604
691
|
setError(
|
|
605
692
|
entry,
|
|
606
693
|
'REFERENCE_MISSING',
|
|
@@ -286,6 +286,104 @@ describe('android render runtime — render + trim (happy path)', () => {
|
|
|
286
286
|
expect(doc.screens.map((s) => s.status)).toEqual(['rendered', 'rendered']);
|
|
287
287
|
});
|
|
288
288
|
});
|
|
289
|
+
describe('android render runtime — reference provenance (UNVERIFIABLE)', () => {
|
|
290
|
+
const GOOD_PROV = {
|
|
291
|
+
figmaNode: '12:345',
|
|
292
|
+
locale: 'en',
|
|
293
|
+
resolved: true,
|
|
294
|
+
exportedAt: '2026-06-18T10:00:00Z',
|
|
295
|
+
};
|
|
296
|
+
// Stage a valid project so per-build archive validation passes and the only
|
|
297
|
+
// signal under test is the per-screen provenance verdict.
|
|
298
|
+
function provProject(params) {
|
|
299
|
+
const project = makeProject(params);
|
|
300
|
+
stageArchive(project, gradleProjectEntries());
|
|
301
|
+
return project;
|
|
302
|
+
}
|
|
303
|
+
test('a reference with no provenance is unverifiable', async () => {
|
|
304
|
+
const project = provProject({
|
|
305
|
+
screens: { XProof: 'x.png' },
|
|
306
|
+
render_locale: 'en',
|
|
307
|
+
provenance: {},
|
|
308
|
+
});
|
|
309
|
+
writeReference(project, 'x.png');
|
|
310
|
+
runScript(project, await buildScript());
|
|
311
|
+
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
312
|
+
expect(screen?.status).toBe('unverifiable');
|
|
313
|
+
expect(screen?.error?.code).toBe('NO_PROVENANCE');
|
|
314
|
+
});
|
|
315
|
+
test('a placeholder reference (resolved=false) is unverifiable', async () => {
|
|
316
|
+
const project = provProject({
|
|
317
|
+
screens: { XProof: 'x.png' },
|
|
318
|
+
render_locale: 'en',
|
|
319
|
+
provenance: { XProof: { ...GOOD_PROV, resolved: false } },
|
|
320
|
+
});
|
|
321
|
+
writeReference(project, 'x.png');
|
|
322
|
+
runScript(project, await buildScript());
|
|
323
|
+
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
324
|
+
expect(screen?.status).toBe('unverifiable');
|
|
325
|
+
expect(screen?.error?.code).toBe('UNRESOLVED_REFERENCE');
|
|
326
|
+
});
|
|
327
|
+
test('an Italian reference against an English render is unverifiable', async () => {
|
|
328
|
+
const project = provProject({
|
|
329
|
+
screens: { XProof: 'x.png' },
|
|
330
|
+
render_locale: 'en',
|
|
331
|
+
provenance: { XProof: { ...GOOD_PROV, locale: 'it' } },
|
|
332
|
+
});
|
|
333
|
+
writeReference(project, 'x.png');
|
|
334
|
+
runScript(project, await buildScript());
|
|
335
|
+
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
336
|
+
expect(screen?.status).toBe('unverifiable');
|
|
337
|
+
expect(screen?.error?.code).toBe('LOCALE_MISMATCH');
|
|
338
|
+
});
|
|
339
|
+
test('an absent reference with good provenance is unverifiable', async () => {
|
|
340
|
+
const project = provProject({
|
|
341
|
+
screens: { XProof: 'x.png' },
|
|
342
|
+
render_locale: 'en',
|
|
343
|
+
provenance: { XProof: GOOD_PROV },
|
|
344
|
+
});
|
|
345
|
+
// No writeReference — the file is absent.
|
|
346
|
+
runScript(project, await buildScript());
|
|
347
|
+
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
348
|
+
expect(screen?.status).toBe('unverifiable');
|
|
349
|
+
expect(screen?.error?.code).toBe('REFERENCE_ABSENT');
|
|
350
|
+
});
|
|
351
|
+
test('a good provenanced reference renders normally (a content diff is left to the judge)', async () => {
|
|
352
|
+
const project = provProject({
|
|
353
|
+
screens: { XProof: 'x.png' },
|
|
354
|
+
render_locale: 'en',
|
|
355
|
+
provenance: { XProof: GOOD_PROV },
|
|
356
|
+
});
|
|
357
|
+
writeReference(project, 'x.png');
|
|
358
|
+
const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
|
|
359
|
+
expect(run.status).toBe(0);
|
|
360
|
+
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
361
|
+
expect(screen?.status).toBe('rendered');
|
|
362
|
+
expect(screen?.error).toBeNull();
|
|
363
|
+
});
|
|
364
|
+
test('independent verdicts: a sibling stays rendered while one state is unverifiable', async () => {
|
|
365
|
+
const project = provProject({
|
|
366
|
+
screens: { XProof: 'x.png', YProof: 'y.png' },
|
|
367
|
+
render_locale: 'en',
|
|
368
|
+
provenance: { XProof: GOOD_PROV, YProof: { ...GOOD_PROV, resolved: false } },
|
|
369
|
+
});
|
|
370
|
+
writeReference(project, 'x.png');
|
|
371
|
+
writeReference(project, 'y.png');
|
|
372
|
+
runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
|
|
373
|
+
const doc = readResult(project);
|
|
374
|
+
expect(doc.screens.find((s) => s.screen === 'XProof')?.status).toBe('rendered');
|
|
375
|
+
const y = doc.screens.find((s) => s.screen === 'YProof');
|
|
376
|
+
expect(y?.status).toBe('unverifiable');
|
|
377
|
+
expect(y?.error?.code).toBe('UNRESOLVED_REFERENCE');
|
|
378
|
+
});
|
|
379
|
+
test('without a render_locale, provenance is not enforced (legacy behaviour)', async () => {
|
|
380
|
+
const project = provProject({ screens: { XProof: 'x.png' } });
|
|
381
|
+
writeReference(project, 'x.png');
|
|
382
|
+
runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
|
|
383
|
+
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
384
|
+
expect(screen?.status).toBe('rendered');
|
|
385
|
+
});
|
|
386
|
+
});
|
|
289
387
|
describe('android render runtime — error isolation', () => {
|
|
290
388
|
function twoScreenProject() {
|
|
291
389
|
const project = makeProject({ screens: { XProof: 'x.png', YProof: 'y.png' } });
|