@junejuly-lockstep/reconciler 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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/dist/boot.d.ts +51 -0
  4. package/dist/boot.d.ts.map +1 -0
  5. package/dist/boot.js +162 -0
  6. package/dist/boot.js.map +1 -0
  7. package/dist/cli.d.ts +4 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +180 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +55 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +140 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/contract.d.ts +61 -0
  16. package/dist/contract.d.ts.map +1 -0
  17. package/dist/contract.js +154 -0
  18. package/dist/contract.js.map +1 -0
  19. package/dist/determinism.d.ts +23 -0
  20. package/dist/determinism.d.ts.map +1 -0
  21. package/dist/determinism.js +102 -0
  22. package/dist/determinism.js.map +1 -0
  23. package/dist/errors.d.ts +25 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +35 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/exitCodes.d.ts +33 -0
  28. package/dist/exitCodes.d.ts.map +1 -0
  29. package/dist/exitCodes.js +49 -0
  30. package/dist/exitCodes.js.map +1 -0
  31. package/dist/extract.d.ts +30 -0
  32. package/dist/extract.d.ts.map +1 -0
  33. package/dist/extract.js +376 -0
  34. package/dist/extract.js.map +1 -0
  35. package/dist/index.d.ts +35 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +19 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/init.d.ts +17 -0
  40. package/dist/init.d.ts.map +1 -0
  41. package/dist/init.js +55 -0
  42. package/dist/init.js.map +1 -0
  43. package/dist/journeys.d.ts +34 -0
  44. package/dist/journeys.d.ts.map +1 -0
  45. package/dist/journeys.js +276 -0
  46. package/dist/journeys.js.map +1 -0
  47. package/dist/paths.d.ts +26 -0
  48. package/dist/paths.d.ts.map +1 -0
  49. package/dist/paths.js +80 -0
  50. package/dist/paths.js.map +1 -0
  51. package/dist/reconcile.d.ts +63 -0
  52. package/dist/reconcile.d.ts.map +1 -0
  53. package/dist/reconcile.js +316 -0
  54. package/dist/reconcile.js.map +1 -0
  55. package/dist/report.d.ts +33 -0
  56. package/dist/report.d.ts.map +1 -0
  57. package/dist/report.js +97 -0
  58. package/dist/report.js.map +1 -0
  59. package/dist/sync.d.ts +46 -0
  60. package/dist/sync.d.ts.map +1 -0
  61. package/dist/sync.js +110 -0
  62. package/dist/sync.js.map +1 -0
  63. package/dist/upload.d.ts +33 -0
  64. package/dist/upload.d.ts.map +1 -0
  65. package/dist/upload.js +86 -0
  66. package/dist/upload.js.map +1 -0
  67. package/dist/util.d.ts +24 -0
  68. package/dist/util.d.ts.map +1 -0
  69. package/dist/util.js +97 -0
  70. package/dist/util.js.map +1 -0
  71. package/dist/visual.d.ts +49 -0
  72. package/dist/visual.d.ts.map +1 -0
  73. package/dist/visual.js +145 -0
  74. package/dist/visual.js.map +1 -0
  75. package/dist/walk.d.ts +147 -0
  76. package/dist/walk.d.ts.map +1 -0
  77. package/dist/walk.js +433 -0
  78. package/dist/walk.js.map +1 -0
  79. package/package.json +78 -0
  80. package/templates/example.journey.ts +90 -0
  81. package/templates/lockstep-reconcile.yml +219 -0
  82. package/templates/lockstep.config.example.ts +46 -0
  83. package/templates/msw-handlers.example.ts +113 -0
  84. package/templates/scenarios.example.ts +32 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { IngestClient } from '@junejuly-lockstep/sdk';
2
+ export declare const PRESIGN_BATCH = 50;
3
+ export interface UploadFile {
4
+ /** Path relative to runs/{runId}/, e.g. shots/book-a-visit/start/nora.png */
5
+ path: string;
6
+ contentType: string;
7
+ localPath: string;
8
+ }
9
+ export interface UploadResult {
10
+ runId: string;
11
+ prefix: string;
12
+ /** Relative path -> full S3 key, as returned by presign. */
13
+ keys: Map<string, string>;
14
+ uploaded: number;
15
+ }
16
+ /** Collect every PNG and trace under the artifacts dir, sorted for deterministic batching. */
17
+ export declare function collectArtifacts(artifactsDir: string): Promise<UploadFile[]>;
18
+ export interface UploadOptions {
19
+ client: Pick<IngestClient, 'presign'>;
20
+ files: UploadFile[];
21
+ /** Reserve under an existing run id (second batch onwards); omitted for the first call. */
22
+ runId?: string;
23
+ fetchImpl?: typeof fetch;
24
+ log?: (line: string) => void;
25
+ batchSize?: number;
26
+ retries?: number;
27
+ }
28
+ /**
29
+ * Request presigned PUTs in batches of 50 and upload each file with its content type.
30
+ * The first presign response reserves the run id used for every later batch and the payload.
31
+ */
32
+ export declare function uploadArtifacts(opts: UploadOptions): Promise<UploadResult>;
33
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAI3D,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,8FAA8F;AAC9F,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAalF;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IACtC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAkEhF"}
package/dist/upload.js ADDED
@@ -0,0 +1,86 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import fg from 'fast-glob';
4
+ import { InfrastructureError } from './errors.js';
5
+ import { contentTypeFor, toPosix, withRetry } from './util.js';
6
+ export const PRESIGN_BATCH = 50;
7
+ /** Collect every PNG and trace under the artifacts dir, sorted for deterministic batching. */
8
+ export async function collectArtifacts(artifactsDir) {
9
+ const files = await fg(['shots/**/*.png', 'diffs/**/*.png', 'traces/**/*.zip'], {
10
+ cwd: artifactsDir,
11
+ onlyFiles: true,
12
+ });
13
+ return files
14
+ .map(toPosix)
15
+ .sort()
16
+ .map((rel) => ({
17
+ path: rel,
18
+ contentType: contentTypeFor(rel),
19
+ localPath: path.join(artifactsDir, rel),
20
+ }));
21
+ }
22
+ /**
23
+ * Request presigned PUTs in batches of 50 and upload each file with its content type.
24
+ * The first presign response reserves the run id used for every later batch and the payload.
25
+ */
26
+ export async function uploadArtifacts(opts) {
27
+ const fetchImpl = opts.fetchImpl ?? fetch;
28
+ const log = opts.log ?? (() => { });
29
+ const batchSize = Math.min(opts.batchSize ?? PRESIGN_BATCH, PRESIGN_BATCH);
30
+ const files = [...opts.files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
31
+ const keys = new Map();
32
+ let runId = opts.runId;
33
+ let prefix = '';
34
+ let uploaded = 0;
35
+ const batches = [];
36
+ for (let i = 0; i < files.length; i += batchSize)
37
+ batches.push(files.slice(i, i + batchSize));
38
+ if (batches.length === 0) {
39
+ // Nothing to upload, but the run still needs an id: reserve it with a presign of nothing.
40
+ throw new InfrastructureError('no artifacts to upload; the walk produced no screenshots or traces');
41
+ }
42
+ for (const [bi, batch] of batches.entries()) {
43
+ const res = await withRetry(() => opts.client.presign({
44
+ runId,
45
+ files: batch.map((f) => ({ path: f.path, contentType: f.contentType })),
46
+ }), {
47
+ retries: opts.retries ?? 3,
48
+ onRetry: (err) => log(`presign batch ${bi + 1} retry: ${describe(err)}`),
49
+ }).catch((err) => {
50
+ throw new InfrastructureError(`presign batch ${bi + 1}/${batches.length} failed`, err);
51
+ });
52
+ runId ??= res.runId;
53
+ if (res.runId !== runId)
54
+ throw new InfrastructureError(`presign returned run id ${res.runId}, expected ${runId}`);
55
+ prefix = res.prefix;
56
+ const byPath = new Map(res.urls.map((u) => [u.path, u]));
57
+ for (const file of batch) {
58
+ const target = byPath.get(file.path);
59
+ if (!target)
60
+ throw new InfrastructureError(`presign response is missing a URL for ${file.path}`);
61
+ const body = readFileSync(file.localPath);
62
+ await withRetry(async () => {
63
+ const r = await fetchImpl(target.url, {
64
+ method: 'PUT',
65
+ headers: { 'content-type': file.contentType },
66
+ body,
67
+ });
68
+ if (!r.ok)
69
+ throw new Error(`PUT ${file.path} -> ${r.status}`);
70
+ }, {
71
+ retries: opts.retries ?? 3,
72
+ onRetry: (err) => log(`upload ${file.path} retry: ${describe(err)}`),
73
+ }).catch((err) => {
74
+ throw new InfrastructureError(`upload of ${file.path} failed`, err);
75
+ });
76
+ keys.set(file.path, target.key);
77
+ uploaded++;
78
+ }
79
+ log(`uploaded batch ${bi + 1}/${batches.length} (${batch.length} files)`);
80
+ }
81
+ return { runId: runId, prefix, keys, uploaded };
82
+ }
83
+ function describe(err) {
84
+ return err instanceof Error ? err.message : String(err);
85
+ }
86
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
package/dist/util.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { type Jiti } from 'jiti';
2
+ export declare function sha256(input: string | Uint8Array): string;
3
+ /** Normalise to forward slashes so keys and payloads are identical on every OS. */
4
+ export declare function toPosix(p: string): string;
5
+ export declare function relPosix(from: string, to: string): string;
6
+ export declare function ensureDir(dir: string): void;
7
+ /** Make a journey/state/scenario name safe for an artifact path (`PresignRequest` allows [\w./-]). */
8
+ export declare function safeSegment(s: string): string;
9
+ export declare function normalizeNewlines(text: string): string;
10
+ /** Sort helper producing a deterministic order regardless of locale. */
11
+ export declare function byString<T>(key: (t: T) => string): (a: T, b: T) => number;
12
+ /** One jiti instance per process: loads TS at runtime with no build step. */
13
+ export declare function getJiti(): Jiti;
14
+ export declare function importTs<T = unknown>(file: string): Promise<T>;
15
+ export declare function packageRoot(): string;
16
+ export declare function reconcilerVersion(): string;
17
+ export declare function sleep(ms: number): Promise<void>;
18
+ export declare function withRetry<T>(fn: (attempt: number) => Promise<T>, opts: {
19
+ retries: number;
20
+ baseDelayMs?: number;
21
+ onRetry?: (err: unknown, attempt: number) => void;
22
+ }): Promise<T>;
23
+ export declare function contentTypeFor(file: string): string;
24
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,MAAM,CAAC;AAE7C,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAEzD;AAED,mFAAmF;AACnF,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,sGAAsG;AACtG,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAMzE;AAGD,6EAA6E;AAC7E,wBAAgB,OAAO,IAAI,IAAI,CAO9B;AAED,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAEpE;AAED,wBAAgB,WAAW,IAAI,MAAM,CAGpC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAS1C;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACnC,IAAI,EAAE;IACJ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD,GACA,OAAO,CAAC,CAAC,CAAC,CAaZ;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBnD"}
package/dist/util.js ADDED
@@ -0,0 +1,97 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdirSync, readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { createJiti } from 'jiti';
6
+ export function sha256(input) {
7
+ return createHash('sha256').update(input).digest('hex');
8
+ }
9
+ /** Normalise to forward slashes so keys and payloads are identical on every OS. */
10
+ export function toPosix(p) {
11
+ return p.split(path.sep).join('/').replace(/\\/g, '/');
12
+ }
13
+ export function relPosix(from, to) {
14
+ return toPosix(path.relative(from, to));
15
+ }
16
+ export function ensureDir(dir) {
17
+ mkdirSync(dir, { recursive: true });
18
+ }
19
+ /** Make a journey/state/scenario name safe for an artifact path (`PresignRequest` allows [\w./-]). */
20
+ export function safeSegment(s) {
21
+ return s.replace(/[^\w.-]+/g, '_') || '_';
22
+ }
23
+ export function normalizeNewlines(text) {
24
+ return text.replace(/\r\n?/g, '\n');
25
+ }
26
+ /** Sort helper producing a deterministic order regardless of locale. */
27
+ export function byString(key) {
28
+ return (a, b) => {
29
+ const ka = key(a);
30
+ const kb = key(b);
31
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
32
+ };
33
+ }
34
+ let jiti;
35
+ /** One jiti instance per process: loads TS at runtime with no build step. */
36
+ export function getJiti() {
37
+ jiti ??= createJiti(import.meta.url, {
38
+ interopDefault: true,
39
+ moduleCache: false,
40
+ fsCache: false,
41
+ });
42
+ return jiti;
43
+ }
44
+ export async function importTs(file) {
45
+ return getJiti().import(path.resolve(file));
46
+ }
47
+ export function packageRoot() {
48
+ // dist/util.js or src/util.ts -> package root
49
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
50
+ }
51
+ export function reconcilerVersion() {
52
+ try {
53
+ const pkg = JSON.parse(readFileSync(path.join(packageRoot(), 'package.json'), 'utf8'));
54
+ return pkg.version ?? '0.0.0';
55
+ }
56
+ catch {
57
+ return '0.0.0';
58
+ }
59
+ }
60
+ export function sleep(ms) {
61
+ return new Promise((r) => setTimeout(r, ms));
62
+ }
63
+ export async function withRetry(fn, opts) {
64
+ let last;
65
+ for (let attempt = 0; attempt <= opts.retries; attempt++) {
66
+ try {
67
+ return await fn(attempt);
68
+ }
69
+ catch (err) {
70
+ last = err;
71
+ if (attempt === opts.retries)
72
+ break;
73
+ opts.onRetry?.(err, attempt);
74
+ await sleep((opts.baseDelayMs ?? 250) * 2 ** attempt);
75
+ }
76
+ }
77
+ throw last;
78
+ }
79
+ export function contentTypeFor(file) {
80
+ const ext = path.extname(file).toLowerCase();
81
+ switch (ext) {
82
+ case '.png':
83
+ return 'image/png';
84
+ case '.zip':
85
+ return 'application/zip';
86
+ case '.json':
87
+ return 'application/json';
88
+ case '.txt':
89
+ case '.md':
90
+ return 'text/plain';
91
+ case '.html':
92
+ return 'text/html';
93
+ default:
94
+ return 'application/octet-stream';
95
+ }
96
+ }
97
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { PNG } from 'pngjs';
2
+ import { type BaselineManifest, type VisualComparison, type WalkResult } from '@junejuly-lockstep/model';
3
+ /**
4
+ * Per-pixel colour distance threshold handed to pixelmatch (0..1): how different two pixels must
5
+ * be to count as differing. Not the `visualDiffThreshold` of lockstep.config.ts, which is the
6
+ * fraction of differing pixels above which the API marks a screenshot `changed`; the reconciler
7
+ * only reports the score.
8
+ */
9
+ export declare const PIXEL_THRESHOLD = 0.1;
10
+ export interface CompareResult {
11
+ /** Differing pixels / total pixels, 0..1. Size mismatch is 1. */
12
+ score: number;
13
+ diffPixels: number;
14
+ width: number;
15
+ height: number;
16
+ /** Encoded diff PNG, or null when sizes differ (nothing to overlay). */
17
+ diff: Buffer | null;
18
+ detail: string | null;
19
+ }
20
+ export declare function decodePng(buf: Buffer): PNG;
21
+ export declare function encodePng(png: PNG): Buffer;
22
+ /** Compare two PNG buffers with pixelmatch. */
23
+ export declare function comparePng(actual: Buffer, baseline: Buffer, opts?: {
24
+ pixelThreshold?: number;
25
+ }): CompareResult;
26
+ export declare function baselineId(e: {
27
+ journeyKey: string;
28
+ stateKey: string;
29
+ scenario: string;
30
+ }): string;
31
+ /** Download every baseline in the manifest (presigned GETs) into `dir`; returns id -> local path. */
32
+ export declare function downloadBaselines(manifest: BaselineManifest, dir: string, fetchImpl?: typeof fetch, log?: (msg: string) => void): Promise<Map<string, string>>;
33
+ export interface CompareWalkOptions {
34
+ walk: WalkResult;
35
+ manifest: BaselineManifest | null;
36
+ /** Local directory the walk wrote relative screenshot paths into. */
37
+ artifactsDir: string;
38
+ /** Baseline id -> local path, from `downloadBaselines`. */
39
+ baselines: Map<string, string>;
40
+ /** pixelmatch colour threshold (see PIXEL_THRESHOLD); never the visualDiffThreshold. */
41
+ pixelThreshold?: number;
42
+ }
43
+ /**
44
+ * Compare the first screenshot of every (journey, state, scenario) against its baseline.
45
+ * `screenshotKey`/`diffKey` are artifact-relative paths here (`ARTIFACT_PATHS`); the uploader maps
46
+ * them to S3 keys under runs/{runId}/.
47
+ */
48
+ export declare function compareWalk(opts: CompareWalkOptions): VisualComparison[];
49
+ //# sourceMappingURL=visual.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual.d.ts","sourceRoot":"","sources":["../src/visual.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AAIlC;;;;;GAKG;AACH,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAE1C;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAE1C;AAED,+CAA+C;AAC/C,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GACrC,aAAa,CA2Bf;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEhG;AAED,qGAAqG;AACrG,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,gBAAgB,EAC1B,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,OAAO,KAAa,EAC/B,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe,GACpC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA4B9B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,gBAAgB,EAAE,CAgExE"}
package/dist/visual.js ADDED
@@ -0,0 +1,145 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import pixelmatch from 'pixelmatch';
4
+ import { PNG } from 'pngjs';
5
+ import { ARTIFACT_PATHS, } from '@junejuly-lockstep/model';
6
+ import { InfrastructureError } from './errors.js';
7
+ import { ensureDir, safeSegment, withRetry } from './util.js';
8
+ /**
9
+ * Per-pixel colour distance threshold handed to pixelmatch (0..1): how different two pixels must
10
+ * be to count as differing. Not the `visualDiffThreshold` of lockstep.config.ts, which is the
11
+ * fraction of differing pixels above which the API marks a screenshot `changed`; the reconciler
12
+ * only reports the score.
13
+ */
14
+ export const PIXEL_THRESHOLD = 0.1;
15
+ export function decodePng(buf) {
16
+ return PNG.sync.read(buf);
17
+ }
18
+ export function encodePng(png) {
19
+ return PNG.sync.write(png);
20
+ }
21
+ /** Compare two PNG buffers with pixelmatch. */
22
+ export function comparePng(actual, baseline, opts = {}) {
23
+ const a = decodePng(actual);
24
+ const b = decodePng(baseline);
25
+ if (a.width !== b.width || a.height !== b.height) {
26
+ return {
27
+ score: 1,
28
+ diffPixels: a.width * a.height,
29
+ width: a.width,
30
+ height: a.height,
31
+ diff: null,
32
+ detail: `size mismatch: screenshot ${a.width}x${a.height}, baseline ${b.width}x${b.height}`,
33
+ };
34
+ }
35
+ const diff = new PNG({ width: a.width, height: a.height });
36
+ const diffPixels = pixelmatch(a.data, b.data, diff.data, a.width, a.height, {
37
+ threshold: opts.pixelThreshold ?? PIXEL_THRESHOLD,
38
+ includeAA: false,
39
+ });
40
+ const total = a.width * a.height;
41
+ return {
42
+ score: total === 0 ? 0 : diffPixels / total,
43
+ diffPixels,
44
+ width: a.width,
45
+ height: a.height,
46
+ diff: diffPixels > 0 ? encodePng(diff) : null,
47
+ detail: null,
48
+ };
49
+ }
50
+ export function baselineId(e) {
51
+ return `${e.journeyKey}/${e.stateKey}/${e.scenario}`;
52
+ }
53
+ /** Download every baseline in the manifest (presigned GETs) into `dir`; returns id -> local path. */
54
+ export async function downloadBaselines(manifest, dir, fetchImpl = fetch, log = () => { }) {
55
+ ensureDir(dir);
56
+ const out = new Map();
57
+ for (const entry of [...manifest.entries].sort((a, b) => baselineId(a).localeCompare(baselineId(b)))) {
58
+ const id = baselineId(entry);
59
+ const local = path.join(dir, `${id.replace(/\//g, '__')}.png`);
60
+ await withRetry(async () => {
61
+ const res = await fetchImpl(entry.url);
62
+ if (!res.ok)
63
+ throw new InfrastructureError(`baseline download ${id} failed with ${res.status}`);
64
+ writeFileSync(local, Buffer.from(await res.arrayBuffer()));
65
+ }, {
66
+ retries: 3,
67
+ onRetry: (err) => log(`retrying baseline ${id}: ${err instanceof Error ? err.message : String(err)}`),
68
+ }).catch((err) => {
69
+ throw err instanceof InfrastructureError
70
+ ? err
71
+ : new InfrastructureError(`baseline download ${id} failed`, err);
72
+ });
73
+ out.set(id, local);
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * Compare the first screenshot of every (journey, state, scenario) against its baseline.
79
+ * `screenshotKey`/`diffKey` are artifact-relative paths here (`ARTIFACT_PATHS`); the uploader maps
80
+ * them to S3 keys under runs/{runId}/.
81
+ */
82
+ export function compareWalk(opts) {
83
+ const seen = new Set();
84
+ const out = [];
85
+ const entries = new Map((opts.manifest?.entries ?? []).map((e) => [baselineId(e), e]));
86
+ for (const p of opts.walk.paths) {
87
+ for (const step of p.steps) {
88
+ if (!step.screenshotKey || step.status !== 'pass')
89
+ continue;
90
+ const id = baselineId({
91
+ journeyKey: p.journeyKey,
92
+ stateKey: step.stateKey,
93
+ scenario: p.scenario,
94
+ });
95
+ if (seen.has(id))
96
+ continue;
97
+ seen.add(id);
98
+ const shotPath = path.join(opts.artifactsDir, step.screenshotKey);
99
+ if (!existsSync(shotPath))
100
+ continue;
101
+ const actual = readFileSync(shotPath);
102
+ const entry = entries.get(id);
103
+ const local = entry ? opts.baselines.get(id) : undefined;
104
+ if (!entry || !local || !existsSync(local)) {
105
+ const png = decodePng(actual);
106
+ out.push({
107
+ journeyKey: p.journeyKey,
108
+ stateKey: step.stateKey,
109
+ scenario: p.scenario,
110
+ screenshotKey: step.screenshotKey,
111
+ baselineKey: null,
112
+ diffKey: null,
113
+ score: 0,
114
+ width: png.width,
115
+ height: png.height,
116
+ });
117
+ continue;
118
+ }
119
+ const result = comparePng(actual, readFileSync(local), {
120
+ pixelThreshold: opts.pixelThreshold,
121
+ });
122
+ let diffKey = null;
123
+ if (result.diff) {
124
+ diffKey = ARTIFACT_PATHS.diff(safeSegment(p.journeyKey), safeSegment(step.stateKey), safeSegment(p.scenario));
125
+ const diffPath = path.join(opts.artifactsDir, diffKey);
126
+ ensureDir(path.dirname(diffPath));
127
+ writeFileSync(diffPath, result.diff);
128
+ }
129
+ out.push({
130
+ journeyKey: p.journeyKey,
131
+ stateKey: step.stateKey,
132
+ scenario: p.scenario,
133
+ screenshotKey: step.screenshotKey,
134
+ baselineKey: entry.s3Key,
135
+ diffKey,
136
+ score: result.score,
137
+ width: result.width,
138
+ height: result.height,
139
+ ...(result.detail ? { detail: result.detail } : {}),
140
+ });
141
+ }
142
+ }
143
+ return out.sort((a, b) => baselineId(a).localeCompare(baselineId(b)));
144
+ }
145
+ //# sourceMappingURL=visual.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual.js","sourceRoot":"","sources":["../src/visual.ts"],"names":[],"mappings":"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"}
package/dist/walk.d.ts ADDED
@@ -0,0 +1,147 @@
1
+ import type { JourneyModel, LockstepConfig, Scenario, WalkResult } from '@junejuly-lockstep/model';
2
+ import { type BootOptions, type BootedApp } from './boot.js';
3
+ /** 2026-01-15T09:30:00.000Z. Every walk's `Date.now()` starts here. */
4
+ export declare const FROZEN_EPOCH_MS: number;
5
+ export declare const VIEWPORT: {
6
+ readonly width: 1280;
7
+ readonly height: 800;
8
+ };
9
+ export declare const DEVICE_SCALE_FACTOR = 1;
10
+ export declare const LOCALE = "en-US";
11
+ export declare const TIMEZONE = "UTC";
12
+ export declare const COLOR_SCHEME = "light";
13
+ /** Injected on every document: no animations, no transitions, no blinking caret, no smooth scroll. */
14
+ export declare const DISABLE_MOTION_CSS: string;
15
+ /**
16
+ * Chromium flags that remove machine-dependent text rendering, plus `--disable-partial-raster`:
17
+ * with partial raster on, a tile whose content changed after first paint (a page that renders its
18
+ * state root once its data arrived) is re-rastered only inside the damage rect, and the
19
+ * anti-aliasing along that rect's edges differs by a unit from a full raster of the same pixels,
20
+ * depending on whether a frame was produced in between. Invisible, but not byte-identical.
21
+ */
22
+ export declare const CHROMIUM_ARGS: string[];
23
+ export declare const STEP_TIMEOUT_MS = 15000;
24
+ export declare const MAX_ATTEMPTS = 2;
25
+ export interface LocatorLike {
26
+ waitFor(options?: {
27
+ state?: 'visible' | 'attached' | 'hidden' | 'detached';
28
+ timeout?: number;
29
+ }): Promise<void>;
30
+ click(options?: {
31
+ timeout?: number;
32
+ }): Promise<void>;
33
+ isVisible(): Promise<boolean>;
34
+ first(): LocatorLike;
35
+ }
36
+ export interface RequestLike {
37
+ url(): string;
38
+ method(): string;
39
+ postData(): string | null;
40
+ }
41
+ export interface PageLike {
42
+ goto(url: string, options?: {
43
+ waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
44
+ timeout?: number;
45
+ }): Promise<unknown>;
46
+ url(): string;
47
+ locator(selector: string): LocatorLike;
48
+ screenshot(options: {
49
+ path?: string;
50
+ fullPage?: boolean;
51
+ animations?: 'disabled' | 'allow';
52
+ caret?: 'hide' | 'initial';
53
+ scale?: 'css' | 'device';
54
+ }): Promise<Buffer>;
55
+ waitForLoadState(state?: 'load' | 'domcontentloaded' | 'networkidle', options?: {
56
+ timeout?: number;
57
+ }): Promise<void>;
58
+ evaluate(expression: string): Promise<unknown>;
59
+ addStyleTag(options: {
60
+ content?: string;
61
+ }): Promise<unknown>;
62
+ on(event: 'request', listener: (request: RequestLike) => void): unknown;
63
+ /** Optional in fakes; the real page parks the pointer after every click (see `park`). */
64
+ mouse?: {
65
+ move(x: number, y: number): Promise<void>;
66
+ };
67
+ clock: {
68
+ install(options?: {
69
+ time?: number | string | Date;
70
+ }): Promise<void>;
71
+ setFixedTime(time: number | string | Date): Promise<void>;
72
+ };
73
+ close(): Promise<void>;
74
+ }
75
+ export interface ContextLike {
76
+ newPage(): Promise<PageLike>;
77
+ addInitScript(script: string): Promise<void>;
78
+ tracing: {
79
+ start(options?: {
80
+ screenshots?: boolean;
81
+ snapshots?: boolean;
82
+ sources?: boolean;
83
+ title?: string;
84
+ }): Promise<void>;
85
+ stop(options?: {
86
+ path?: string;
87
+ }): Promise<void>;
88
+ };
89
+ close(): Promise<void>;
90
+ }
91
+ export interface BrowserLike {
92
+ newContext(options?: {
93
+ viewport?: {
94
+ width: number;
95
+ height: number;
96
+ };
97
+ deviceScaleFactor?: number;
98
+ reducedMotion?: 'reduce' | 'no-preference';
99
+ colorScheme?: 'light' | 'dark';
100
+ locale?: string;
101
+ timezoneId?: string;
102
+ hasTouch?: boolean;
103
+ isMobile?: boolean;
104
+ }): Promise<ContextLike>;
105
+ close(): Promise<void>;
106
+ }
107
+ export interface WalkDeps {
108
+ launch(): Promise<BrowserLike>;
109
+ boot(opts: BootOptions): Promise<BootedApp>;
110
+ }
111
+ /** Default deps: real Playwright Chromium (imported lazily) and the real app boot. */
112
+ export declare const defaultWalkDeps: WalkDeps;
113
+ export interface WalkOptions {
114
+ journeys: JourneyModel[];
115
+ scenarios: Scenario[];
116
+ config: Pick<LockstepConfig, 'bootCommand' | 'port'>;
117
+ /** Repo root the boot command runs in. */
118
+ rootDir: string;
119
+ /** Local directory that mirrors `runs/{runId}/`: shots/ and traces/ are written under it. */
120
+ artifactsDir: string;
121
+ log?: (line: string) => void;
122
+ deps?: WalkDeps;
123
+ /** Override the base URL (default http://127.0.0.1:{port}). */
124
+ baseUrl?: string;
125
+ stepTimeoutMs?: number;
126
+ /** Skip booting (an already-running app on `port`). Only for local debugging. */
127
+ noBoot?: boolean;
128
+ }
129
+ export declare function screenshotPath(journeyKey: string, stateKey: string, scenario: string): string;
130
+ export declare function tracePath(journeyKey: string, scenario: string, pathIndex: number): string;
131
+ export declare function triggerTestId(journey: JourneyModel, from: string, event: string): string;
132
+ export declare function testIdSelector(testId: string): string;
133
+ /** Pull GraphQL operation names out of a request whose path ends in /graphql. */
134
+ export declare function operationNamesOf(req: RequestLike): string[];
135
+ /**
136
+ * One line for the walk result and the verdict detail: Playwright's first line plus the call-log
137
+ * lines that say what it was waiting for or navigating to (the selector lives there, not in the
138
+ * first line). Anything else in the call log is noise for a card.
139
+ */
140
+ export declare function summarizeError(err: unknown): string;
141
+ /**
142
+ * Execute every enumerated path of every journey, per scenario, and record a `WalkResult`.
143
+ * Order: scenarios by name, journeys by key, paths by target state. Boot happens once per
144
+ * scenario so `LOCKSTEP_SCENARIO`/`LOCKSTEP_SEED` reach server-side mocks too.
145
+ */
146
+ export declare function walk(opts: WalkOptions): Promise<WalkResult>;
147
+ //# sourceMappingURL=walk.d.ts.map