reflection-check 0.0.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.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/adapters/route-manifest.d.ts +3 -0
  4. package/dist/adapters/route-manifest.js +98 -0
  5. package/dist/adapters/route-manifest.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.js +93 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/doctor.d.ts +4 -0
  10. package/dist/commands/doctor.js +5 -0
  11. package/dist/commands/doctor.js.map +1 -0
  12. package/dist/commands/gc.d.ts +8 -0
  13. package/dist/commands/gc.js +45 -0
  14. package/dist/commands/gc.js.map +1 -0
  15. package/dist/commands/review.d.ts +7 -0
  16. package/dist/commands/review.js +149 -0
  17. package/dist/commands/review.js.map +1 -0
  18. package/dist/commands/run.d.ts +10 -0
  19. package/dist/commands/run.js +168 -0
  20. package/dist/commands/run.js.map +1 -0
  21. package/dist/commands/update.d.ts +11 -0
  22. package/dist/commands/update.js +183 -0
  23. package/dist/commands/update.js.map +1 -0
  24. package/dist/contracts/browser/assertions.d.ts +34 -0
  25. package/dist/contracts/browser/assertions.js +87 -0
  26. package/dist/contracts/browser/assertions.js.map +1 -0
  27. package/dist/contracts/browser/browser-contract.d.ts +13 -0
  28. package/dist/contracts/browser/browser-contract.js +35 -0
  29. package/dist/contracts/browser/browser-contract.js.map +1 -0
  30. package/dist/contracts/browser/console-observer.d.ts +6 -0
  31. package/dist/contracts/browser/console-observer.js +14 -0
  32. package/dist/contracts/browser/console-observer.js.map +1 -0
  33. package/dist/contracts/browser/overflow-check.d.ts +6 -0
  34. package/dist/contracts/browser/overflow-check.js +15 -0
  35. package/dist/contracts/browser/overflow-check.js.map +1 -0
  36. package/dist/contracts/browser/route-runner.d.ts +21 -0
  37. package/dist/contracts/browser/route-runner.js +98 -0
  38. package/dist/contracts/browser/route-runner.js.map +1 -0
  39. package/dist/contracts/component/component-visual-contract.d.ts +30 -0
  40. package/dist/contracts/component/component-visual-contract.js +147 -0
  41. package/dist/contracts/component/component-visual-contract.js.map +1 -0
  42. package/dist/contracts/design/command-adapter.d.ts +17 -0
  43. package/dist/contracts/design/command-adapter.js +60 -0
  44. package/dist/contracts/design/command-adapter.js.map +1 -0
  45. package/dist/contracts/design/design-contract.d.ts +8 -0
  46. package/dist/contracts/design/design-contract.js +149 -0
  47. package/dist/contracts/design/design-contract.js.map +1 -0
  48. package/dist/contracts/visual/baseline-compare.d.ts +19 -0
  49. package/dist/contracts/visual/baseline-compare.js +94 -0
  50. package/dist/contracts/visual/baseline-compare.js.map +1 -0
  51. package/dist/contracts/visual/image-diff.d.ts +27 -0
  52. package/dist/contracts/visual/image-diff.js +58 -0
  53. package/dist/contracts/visual/image-diff.js.map +1 -0
  54. package/dist/contracts/visual/thresholds.d.ts +15 -0
  55. package/dist/contracts/visual/thresholds.js +11 -0
  56. package/dist/contracts/visual/thresholds.js.map +1 -0
  57. package/dist/contracts/visual/visual-contract.d.ts +11 -0
  58. package/dist/contracts/visual/visual-contract.js +32 -0
  59. package/dist/contracts/visual/visual-contract.js.map +1 -0
  60. package/dist/core/artifact-store.d.ts +18 -0
  61. package/dist/core/artifact-store.js +105 -0
  62. package/dist/core/artifact-store.js.map +1 -0
  63. package/dist/core/baseline-store.d.ts +18 -0
  64. package/dist/core/baseline-store.js +56 -0
  65. package/dist/core/baseline-store.js.map +1 -0
  66. package/dist/core/config.d.ts +129 -0
  67. package/dist/core/config.js +159 -0
  68. package/dist/core/config.js.map +1 -0
  69. package/dist/core/define-reflection.d.ts +2 -0
  70. package/dist/core/define-reflection.js +4 -0
  71. package/dist/core/define-reflection.js.map +1 -0
  72. package/dist/core/exit-codes.d.ts +7 -0
  73. package/dist/core/exit-codes.js +9 -0
  74. package/dist/core/exit-codes.js.map +1 -0
  75. package/dist/core/failure-classifier.d.ts +3 -0
  76. package/dist/core/failure-classifier.js +19 -0
  77. package/dist/core/failure-classifier.js.map +1 -0
  78. package/dist/core/gc.d.ts +19 -0
  79. package/dist/core/gc.js +161 -0
  80. package/dist/core/gc.js.map +1 -0
  81. package/dist/core/manifest.d.ts +23 -0
  82. package/dist/core/manifest.js +21 -0
  83. package/dist/core/manifest.js.map +1 -0
  84. package/dist/core/redaction.d.ts +3 -0
  85. package/dist/core/redaction.js +63 -0
  86. package/dist/core/redaction.js.map +1 -0
  87. package/dist/core/report-schema.d.ts +262 -0
  88. package/dist/core/report-schema.js +112 -0
  89. package/dist/core/report-schema.js.map +1 -0
  90. package/dist/core/report-writer.d.ts +4 -0
  91. package/dist/core/report-writer.js +77 -0
  92. package/dist/core/report-writer.js.map +1 -0
  93. package/dist/core/server-manager.d.ts +23 -0
  94. package/dist/core/server-manager.js +64 -0
  95. package/dist/core/server-manager.js.map +1 -0
  96. package/dist/core/target-ir.d.ts +64 -0
  97. package/dist/core/target-ir.js +85 -0
  98. package/dist/core/target-ir.js.map +1 -0
  99. package/dist/index.d.ts +2 -0
  100. package/dist/index.js +2 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/integrations/playwright/browser-manager.d.ts +2 -0
  103. package/dist/integrations/playwright/browser-manager.js +5 -0
  104. package/dist/integrations/playwright/browser-manager.js.map +1 -0
  105. package/dist/integrations/playwright/context-factory.d.ts +7 -0
  106. package/dist/integrations/playwright/context-factory.js +19 -0
  107. package/dist/integrations/playwright/context-factory.js.map +1 -0
  108. package/dist/integrations/playwright/trace-policy.d.ts +5 -0
  109. package/dist/integrations/playwright/trace-policy.js +7 -0
  110. package/dist/integrations/playwright/trace-policy.js.map +1 -0
  111. package/dist/integrations/storybook/index-json.d.ts +21 -0
  112. package/dist/integrations/storybook/index-json.js +44 -0
  113. package/dist/integrations/storybook/index-json.js.map +1 -0
  114. package/dist/integrations/storybook/server.d.ts +8 -0
  115. package/dist/integrations/storybook/server.js +23 -0
  116. package/dist/integrations/storybook/server.js.map +1 -0
  117. package/dist/integrations/storybook/story-url.d.ts +2 -0
  118. package/dist/integrations/storybook/story-url.js +13 -0
  119. package/dist/integrations/storybook/story-url.js.map +1 -0
  120. package/dist/utils/process.d.ts +9 -0
  121. package/dist/utils/process.js +69 -0
  122. package/dist/utils/process.js.map +1 -0
  123. package/docs/agent-workflows.md +146 -0
  124. package/docs/artifacts-and-gc.md +125 -0
  125. package/docs/browser-contract.md +98 -0
  126. package/docs/ci.md +44 -0
  127. package/docs/configuration.md +210 -0
  128. package/docs/getting-started.md +166 -0
  129. package/docs/plans/reflection-implementation-plan.md +898 -0
  130. package/docs/target-ir-and-adapters.md +111 -0
  131. package/docs/validation-process.md +172 -0
  132. package/docs/visual-contract.md +174 -0
  133. package/package.json +62 -0
@@ -0,0 +1,168 @@
1
+ import { CommanderError } from 'commander';
2
+ import { availableParallelism } from 'node:os';
3
+ import { basename, join } from 'node:path';
4
+ import { runBrowserContract } from '../contracts/browser/browser-contract.js';
5
+ import { runComponentVisualContract } from '../contracts/component/component-visual-contract.js';
6
+ import { runDesignContract } from '../contracts/design/design-contract.js';
7
+ import { createArtifactStore } from '../core/artifact-store.js';
8
+ import { isRunMode, loadReflectionConfig } from '../core/config.js';
9
+ import { ExitCode } from '../core/exit-codes.js';
10
+ import { createRunManifest } from '../core/manifest.js';
11
+ import { createReport, deriveExitCode } from '../core/report-schema.js';
12
+ import { writeReports } from '../core/report-writer.js';
13
+ import { startManagedServer } from '../core/server-manager.js';
14
+ export function parseRunMode(value) {
15
+ if (!isRunMode(value)) {
16
+ throw new CommanderError(ExitCode.InvalidUsage, 'reflection.invalidMode', `Invalid mode "${value}". Expected one of: smoke, design, visual, full.`);
17
+ }
18
+ return value;
19
+ }
20
+ export async function runCommand(options) {
21
+ let mode;
22
+ try {
23
+ mode = parseRunMode(options.mode);
24
+ }
25
+ catch (error) {
26
+ const message = error instanceof Error ? error.message : String(error);
27
+ console.error(message);
28
+ throw error;
29
+ }
30
+ let config;
31
+ if (options.config) {
32
+ try {
33
+ config = await loadReflectionConfig(options.config);
34
+ }
35
+ catch (error) {
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ console.error(message);
38
+ throw new CommanderError(ExitCode.ToolOrConfigError, 'reflection.config', message);
39
+ }
40
+ }
41
+ const startedAt = new Date();
42
+ const runId = createRunId(startedAt);
43
+ const ci = options.ci === true;
44
+ const rootDir = options.reportDir ?? (ci ? 'artifacts/reflection' : '.reflection');
45
+ let workers;
46
+ try {
47
+ workers = resolveWorkerCount(options.workers, ci);
48
+ }
49
+ catch (error) {
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ console.error(message);
52
+ throw error;
53
+ }
54
+ const store = await createArtifactStore({ rootDir, runId });
55
+ const checks = [];
56
+ let server;
57
+ try {
58
+ const browserConfig = config?.contracts.browser;
59
+ const designConfig = config?.contracts.design;
60
+ const componentConfig = config?.contracts.component;
61
+ const shouldRunBrowser = (mode === 'smoke' || mode === 'full') && browserConfig?.enabled !== false && browserConfig !== undefined;
62
+ const shouldRunDesign = (mode === 'design' || mode === 'full') && designConfig?.enabled !== false && designConfig !== undefined;
63
+ const shouldRunComponent = (mode === 'visual' || mode === 'full') && componentConfig?.enabled !== false && componentConfig !== undefined;
64
+ if (shouldRunBrowser) {
65
+ if (browserConfig.server) {
66
+ server = await startManagedServer(browserConfig.server, {
67
+ cwd: process.cwd(),
68
+ logPath: store.resolveRunPath('server/app.log')
69
+ });
70
+ }
71
+ checks.push(await createEnvironmentCheck(mode, server));
72
+ checks.push(...(await runBrowserContract(browserConfig, store)));
73
+ }
74
+ if (shouldRunDesign) {
75
+ checks.push(...(await runDesignContract(designConfig, store)));
76
+ }
77
+ if (shouldRunComponent) {
78
+ checks.push(...(await runComponentVisualContract(componentConfig, store)));
79
+ }
80
+ if (!shouldRunBrowser && !shouldRunDesign && !shouldRunComponent) {
81
+ checks.push(...createPhasePlaceholderChecks(mode));
82
+ }
83
+ }
84
+ finally {
85
+ await server?.stop();
86
+ }
87
+ const finishedAt = new Date();
88
+ const report = createReport({
89
+ runId,
90
+ project: config?.project ?? basename(process.cwd()),
91
+ startedAt,
92
+ finishedAt,
93
+ mode,
94
+ ci,
95
+ environment: {
96
+ profile: ci ? 'ci' : 'local',
97
+ platform: process.platform,
98
+ nodeVersion: process.version,
99
+ workers
100
+ },
101
+ checks,
102
+ suggestedNextSteps: [{ kind: 'implementation', summary: 'Implement the next contract runner phase.' }]
103
+ });
104
+ const reportArtifacts = await writeReports(store, report);
105
+ const manifest = createRunManifest({ report: { ...report, artifacts: reportArtifacts }, files: reportArtifacts });
106
+ await store.writeJson('manifest.json', manifest);
107
+ console.log('Reflection');
108
+ console.log('');
109
+ for (const check of checks) {
110
+ const icon = check.status === 'pass' ? '✓' : check.status === 'fail' ? '✕' : check.status === 'warn' ? '⚠' : '-';
111
+ console.log(`${icon} ${check.id}`);
112
+ }
113
+ console.log('');
114
+ console.log(`Status: ${report.status}`);
115
+ console.log(`Report: ${join(rootDir, 'runs', runId, 'report.md')}`);
116
+ const exitCode = deriveExitCode(report.status);
117
+ if (exitCode !== ExitCode.Success) {
118
+ throw new CommanderError(exitCode, 'reflection.run', `Reflection finished with status: ${report.status}`);
119
+ }
120
+ }
121
+ function createRunId(date) {
122
+ const stamp = date.toISOString().replaceAll(':', '-').replace(/\.\d{3}Z$/, 'Z');
123
+ return `${stamp}-local`;
124
+ }
125
+ function resolveWorkerCount(value, ci) {
126
+ if (value === undefined) {
127
+ return ci ? 1 : Math.max(1, Math.min(4, availableParallelism()));
128
+ }
129
+ if (value === true || value === false) {
130
+ throw new CommanderError(ExitCode.InvalidUsage, 'reflection.invalidWorkers', `Invalid workers "${String(value)}". Expected a positive integer.`);
131
+ }
132
+ const parsed = typeof value === 'number' ? value : Number(value);
133
+ if (!Number.isInteger(parsed) || parsed < 1 || (typeof value === 'string' && !/^[1-9]\d*$/.test(value))) {
134
+ throw new CommanderError(ExitCode.InvalidUsage, 'reflection.invalidWorkers', `Invalid workers "${value}". Expected a positive integer.`);
135
+ }
136
+ return parsed;
137
+ }
138
+ async function createEnvironmentCheck(mode, server) {
139
+ return {
140
+ id: `environment.${mode}.server`,
141
+ suite: 'environment',
142
+ target: server?.readyUrl ?? 'external-browser-target',
143
+ status: 'pass',
144
+ severity: 'info',
145
+ summary: server ? `Browser target server ready at ${server.readyUrl}.` : 'Browser target uses configured base URL without managed server.',
146
+ artifacts: [],
147
+ metadata: {
148
+ serverStarted: server?.started ?? false,
149
+ serverReused: server?.reused ?? false,
150
+ pid: server?.pid
151
+ }
152
+ };
153
+ }
154
+ function createPhasePlaceholderChecks(mode) {
155
+ return [
156
+ {
157
+ id: `environment.${mode}.phase-placeholder`,
158
+ suite: 'environment',
159
+ target: mode,
160
+ status: 'pass',
161
+ severity: 'info',
162
+ summary: 'Phase 1.2 artifact/report pipeline is available; real contract runners come next.',
163
+ artifacts: [],
164
+ metadata: {}
165
+ }
166
+ ];
167
+ }
168
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export type UpdateCommandOptions = {
2
+ config?: string;
3
+ reportDir?: string;
4
+ fromRun?: string;
5
+ route?: string;
6
+ case?: string;
7
+ all?: boolean;
8
+ dryRun?: boolean;
9
+ ci?: boolean;
10
+ };
11
+ export declare function updateCommand(options?: UpdateCommandOptions): Promise<void>;
@@ -0,0 +1,183 @@
1
+ import { copyFile, lstat, mkdir, readFile, realpath } from 'node:fs/promises';
2
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
3
+ import { CommanderError } from 'commander';
4
+ import { createBaselineStore } from '../core/baseline-store.js';
5
+ import { loadReflectionConfig } from '../core/config.js';
6
+ import { ExitCode } from '../core/exit-codes.js';
7
+ import { validateReport } from '../core/report-schema.js';
8
+ export async function updateCommand(options = {}) {
9
+ try {
10
+ if (options.ci === true || isTruthyCi(process.env.CI)) {
11
+ throw new CommanderError(ExitCode.ToolOrConfigError, 'reflection.update', 'Reflection update refuses to mutate baselines in CI mode.');
12
+ }
13
+ if (!options.route && !options.case && options.all !== true) {
14
+ throw new CommanderError(ExitCode.InvalidUsage, 'reflection.update', 'Specify --route, --case, or --all before updating baselines.');
15
+ }
16
+ if (options.all === true && (options.route || options.case)) {
17
+ throw new CommanderError(ExitCode.InvalidUsage, 'reflection.update', 'Use --all by itself, or target a specific --route/--case.');
18
+ }
19
+ const config = await loadReflectionConfig(options.config ?? 'reflection.config.ts');
20
+ const reportRoot = resolve(options.reportDir ?? '.reflection');
21
+ const runsDir = resolve(reportRoot, 'runs');
22
+ const runId = options.fromRun === undefined || options.fromRun === 'latest' ? await readLatestRunId(reportRoot) : options.fromRun;
23
+ assertSafeRunId(runId);
24
+ const runDir = resolve(runsDir, runId);
25
+ ensureInside(runsDir, runDir);
26
+ await ensureRealPathInside(runsDir, runDir);
27
+ const reportPath = resolve(runDir, 'report.json');
28
+ ensureInside(runDir, reportPath);
29
+ await ensureRealPathInside(runDir, reportPath);
30
+ const report = validateReport(JSON.parse(await readFile(reportPath, 'utf8')));
31
+ if (report.runId !== runId) {
32
+ throw new Error(`Report run id mismatch: expected ${runId}, found ${report.runId}`);
33
+ }
34
+ const plan = await createUpdatePlan({ config, report, runDir, route: options.route, caseId: options.case, all: options.all === true });
35
+ if (plan.length === 0) {
36
+ throw new Error('No matching visual baseline updates found for the selected target.');
37
+ }
38
+ for (const item of plan) {
39
+ if (options.dryRun !== true) {
40
+ await ensureBaselineDestinationInside(item.baselineRoot, item.baselinePath);
41
+ await mkdir(dirname(item.baselinePath), { recursive: true });
42
+ await copyFile(item.sourcePath, item.baselinePath);
43
+ }
44
+ }
45
+ console.log(renderUpdateSummary(plan, options.dryRun === true));
46
+ }
47
+ catch (error) {
48
+ if (error instanceof CommanderError) {
49
+ throw error;
50
+ }
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ throw new CommanderError(ExitCode.ToolOrConfigError, 'reflection.update', message);
53
+ }
54
+ }
55
+ async function createUpdatePlan(input) {
56
+ const visualCases = input.config.contracts.browser?.visualSmoke ?? [];
57
+ const selectedCases = visualCases.filter((visualCase) => {
58
+ if (input.all) {
59
+ return true;
60
+ }
61
+ if (input.caseId && visualCase.id !== input.caseId) {
62
+ return false;
63
+ }
64
+ if (input.route && visualCase.route !== input.route) {
65
+ return false;
66
+ }
67
+ return true;
68
+ });
69
+ const plan = [];
70
+ for (const visualCase of selectedCases) {
71
+ const check = findVisualCheck(input.report, visualCase.id);
72
+ const actualArtifact = check.artifacts.find((artifact) => artifact.role === 'actual' && artifact.path.endsWith('.png'));
73
+ if (!actualArtifact) {
74
+ throw new Error(`Visual check ${check.id} has no actual PNG artifact to promote.`);
75
+ }
76
+ const sourcePath = resolve(input.runDir, actualArtifact.path);
77
+ ensureInside(input.runDir, sourcePath);
78
+ await ensureRealPathInside(input.runDir, sourcePath);
79
+ const baselineStore = createBaselineStore(visualCase.baselineRoot ? { rootDir: visualCase.baselineRoot } : {});
80
+ plan.push({
81
+ checkId: check.id,
82
+ caseId: visualCase.id,
83
+ routeId: visualCase.route,
84
+ sourcePath,
85
+ baselineRoot: baselineStore.rootDir,
86
+ baselinePath: baselineStore.resolveBaselinePath(visualCase.baseline)
87
+ });
88
+ }
89
+ return plan;
90
+ }
91
+ function findVisualCheck(report, caseId) {
92
+ const check = report.checks.find((candidate) => candidate.id === `visual.${caseId}` && candidate.suite === 'visual');
93
+ if (!check) {
94
+ throw new Error(`No visual check found in report for case ${caseId}.`);
95
+ }
96
+ return check;
97
+ }
98
+ async function readLatestRunId(rootDir) {
99
+ const latestPath = resolve(rootDir, 'runs', 'latest');
100
+ const runsDir = resolve(rootDir, 'runs');
101
+ ensureInside(runsDir, latestPath);
102
+ await ensureRealPathInside(runsDir, latestPath);
103
+ const value = (await readFile(latestPath, 'utf8')).trim();
104
+ if (value.length === 0) {
105
+ throw new Error(`Latest Reflection run pointer is empty: ${latestPath}`);
106
+ }
107
+ return value;
108
+ }
109
+ function renderUpdateSummary(plan, dryRun) {
110
+ const lines = ['Reflection update', '', `Dry run: ${dryRun ? 'yes' : 'no'}`, ''];
111
+ lines.push(dryRun ? 'Would update:' : 'Updated:');
112
+ for (const item of plan) {
113
+ lines.push(`- ${item.checkId}: ${relative(process.cwd(), item.sourcePath)} -> ${relative(process.cwd(), item.baselinePath)}`);
114
+ }
115
+ return lines.join('\n');
116
+ }
117
+ function assertSafeRunId(runId) {
118
+ if (!/^[A-Za-z0-9._:-]+$/.test(runId) || isAbsolute(runId) || runId.split(/[\\/]/).includes('..')) {
119
+ throw new Error(`Invalid Reflection run id: ${runId}`);
120
+ }
121
+ }
122
+ function isTruthyCi(value) {
123
+ return value !== undefined && value !== '' && value !== 'false' && value !== '0';
124
+ }
125
+ async function ensureBaselineDestinationInside(baselineRoot, baselinePath) {
126
+ ensureInside(baselineRoot, baselinePath);
127
+ const realRoot = await realpath(baselineRoot);
128
+ await ensureDirectoryInsideBaselineRoot(baselineRoot, dirname(baselinePath), realRoot);
129
+ try {
130
+ const stats = await lstat(baselinePath);
131
+ if (stats.isSymbolicLink()) {
132
+ throw new Error(`Refusing to overwrite symlinked baseline path: ${baselinePath}`);
133
+ }
134
+ const realDestination = await realpath(baselinePath);
135
+ ensureInside(realRoot, realDestination);
136
+ }
137
+ catch (error) {
138
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
139
+ return;
140
+ }
141
+ throw error;
142
+ }
143
+ }
144
+ async function ensureDirectoryInsideBaselineRoot(baselineRoot, directoryPath, realRoot) {
145
+ ensureInside(baselineRoot, directoryPath);
146
+ const relativeDirectory = relative(baselineRoot, directoryPath);
147
+ if (relativeDirectory === '') {
148
+ return;
149
+ }
150
+ let current = baselineRoot;
151
+ for (const segment of relativeDirectory.split(/[\\/]+/).filter(Boolean)) {
152
+ current = resolve(current, segment);
153
+ try {
154
+ const stats = await lstat(current);
155
+ if (stats.isSymbolicLink()) {
156
+ throw new Error(`Refusing to create baseline directory through symlink: ${current}`);
157
+ }
158
+ if (!stats.isDirectory()) {
159
+ throw new Error(`Refusing to create baseline directory through non-directory path: ${current}`);
160
+ }
161
+ ensureInside(realRoot, await realpath(current));
162
+ }
163
+ catch (error) {
164
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
165
+ await mkdir(current);
166
+ ensureInside(realRoot, await realpath(current));
167
+ continue;
168
+ }
169
+ throw error;
170
+ }
171
+ }
172
+ }
173
+ async function ensureRealPathInside(parent, child) {
174
+ const [realParent, realChild] = await Promise.all([realpath(parent), realpath(child)]);
175
+ ensureInside(realParent, realChild);
176
+ }
177
+ function ensureInside(parent, child) {
178
+ const relation = relative(parent, child);
179
+ if (relation.startsWith('..') || isAbsolute(relation)) {
180
+ throw new Error(`Refusing to update baselines using a path outside the expected directory: ${child}`);
181
+ }
182
+ }
183
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { Page } from 'playwright';
2
+ export type BrowserExpectation = {
3
+ urlIncludes: string;
4
+ } | {
5
+ urlEquals: string;
6
+ } | {
7
+ role: string;
8
+ name?: string | undefined;
9
+ } | {
10
+ label: string;
11
+ } | {
12
+ text: string;
13
+ } | {
14
+ noText: string;
15
+ } | {
16
+ selector: string;
17
+ } | {
18
+ elementVisible: string;
19
+ } | {
20
+ elementNotVisible: string;
21
+ } | {
22
+ noHorizontalOverflow: true;
23
+ } | {
24
+ noConsoleErrors: true;
25
+ } | {
26
+ screenshot: string;
27
+ };
28
+ export type AssertionFailure = {
29
+ classification: string;
30
+ summary: string;
31
+ details?: string;
32
+ };
33
+ export declare function evaluateExpectation(page: Page, expectation: BrowserExpectation, consoleErrors: string[]): Promise<AssertionFailure | undefined>;
34
+ export declare function shouldCaptureScreenshot(expectations: BrowserExpectation[]): boolean;
@@ -0,0 +1,87 @@
1
+ import { getHorizontalOverflow } from './overflow-check.js';
2
+ export async function evaluateExpectation(page, expectation, consoleErrors) {
3
+ if ('screenshot' in expectation) {
4
+ return undefined;
5
+ }
6
+ if ('urlIncludes' in expectation) {
7
+ if (!page.url().includes(expectation.urlIncludes)) {
8
+ return { classification: 'route-failure', summary: `Expected URL to include ${expectation.urlIncludes}.`, details: page.url() };
9
+ }
10
+ return undefined;
11
+ }
12
+ if ('urlEquals' in expectation) {
13
+ if (page.url() !== expectation.urlEquals) {
14
+ return { classification: 'route-failure', summary: `Expected URL to equal ${expectation.urlEquals}.`, details: page.url() };
15
+ }
16
+ return undefined;
17
+ }
18
+ if ('role' in expectation) {
19
+ const locator = page.getByRole(expectation.role, expectation.name ? { name: expectation.name } : undefined);
20
+ if ((await locator.count()) === 0) {
21
+ return { classification: 'dom-contract-failure', summary: `Expected role ${expectation.role}${expectation.name ? ` named ${expectation.name}` : ''}.` };
22
+ }
23
+ return undefined;
24
+ }
25
+ if ('label' in expectation) {
26
+ if ((await page.getByLabel(expectation.label).count()) === 0) {
27
+ return { classification: 'dom-contract-failure', summary: `Expected label ${expectation.label}.` };
28
+ }
29
+ return undefined;
30
+ }
31
+ if ('text' in expectation) {
32
+ if ((await page.getByText(expectation.text).count()) === 0) {
33
+ return { classification: 'dom-contract-failure', summary: `Expected text ${expectation.text}.` };
34
+ }
35
+ return undefined;
36
+ }
37
+ if ('noText' in expectation) {
38
+ if ((await page.getByText(expectation.noText).count()) > 0) {
39
+ return { classification: 'dom-contract-failure', summary: `Unexpected text ${expectation.noText}.` };
40
+ }
41
+ return undefined;
42
+ }
43
+ if ('selector' in expectation) {
44
+ if ((await page.locator(expectation.selector).count()) === 0) {
45
+ return { classification: 'dom-contract-failure', summary: `Expected selector ${expectation.selector}.` };
46
+ }
47
+ return undefined;
48
+ }
49
+ if ('elementVisible' in expectation) {
50
+ if (!(await page.locator(expectation.elementVisible).first().isVisible().catch(() => false))) {
51
+ return { classification: 'dom-contract-failure', summary: `Expected visible element ${expectation.elementVisible}.` };
52
+ }
53
+ return undefined;
54
+ }
55
+ if ('elementNotVisible' in expectation) {
56
+ if (await page.locator(expectation.elementNotVisible).first().isVisible().catch(() => false)) {
57
+ return { classification: 'dom-contract-failure', summary: `Expected hidden or absent element ${expectation.elementNotVisible}.` };
58
+ }
59
+ return undefined;
60
+ }
61
+ if ('noHorizontalOverflow' in expectation) {
62
+ const overflow = await getHorizontalOverflow(page);
63
+ if (overflow.hasOverflow) {
64
+ return {
65
+ classification: 'layout-overflow',
66
+ summary: `Page has horizontal overflow: scroll width ${overflow.scrollWidth}px exceeds viewport ${overflow.viewportWidth}px.`,
67
+ details: JSON.stringify(overflow)
68
+ };
69
+ }
70
+ return undefined;
71
+ }
72
+ if ('noConsoleErrors' in expectation) {
73
+ if (consoleErrors.length > 0) {
74
+ return {
75
+ classification: 'console-error',
76
+ summary: `Page emitted ${consoleErrors.length} console error${consoleErrors.length === 1 ? '' : 's'}.`,
77
+ details: consoleErrors.join('\n')
78
+ };
79
+ }
80
+ return undefined;
81
+ }
82
+ return undefined;
83
+ }
84
+ export function shouldCaptureScreenshot(expectations) {
85
+ return expectations.some((expectation) => 'screenshot' in expectation);
86
+ }
87
+ //# sourceMappingURL=assertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.js","sourceRoot":"","sources":["../../../src/contracts/browser/assertions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { ArtifactStore } from '../../core/artifact-store.js';
2
+ import type { CheckResult } from '../../core/report-schema.js';
3
+ import type { RouteVisualBaselineCase } from '../visual/baseline-compare.js';
4
+ import { type BrowserRoute } from './route-runner.js';
5
+ export type BrowserContractConfig = {
6
+ enabled?: boolean;
7
+ blocking?: boolean;
8
+ baseUrl: string;
9
+ routes: BrowserRoute[];
10
+ visualSmoke?: RouteVisualBaselineCase[];
11
+ maskSelectors?: string[];
12
+ };
13
+ export declare function runBrowserContract(config: BrowserContractConfig, store: ArtifactStore): Promise<CheckResult[]>;
@@ -0,0 +1,35 @@
1
+ import { launchBrowser } from '../../integrations/playwright/browser-manager.js';
2
+ import { runRouteVisualSmoke } from '../visual/visual-contract.js';
3
+ import { runBrowserRoute } from './route-runner.js';
4
+ export async function runBrowserContract(config, store) {
5
+ if (config.enabled === false) {
6
+ return [];
7
+ }
8
+ const browser = await launchBrowser();
9
+ const checks = [];
10
+ try {
11
+ for (const route of config.routes) {
12
+ for (const viewport of route.viewports) {
13
+ checks.push(await runBrowserRoute({
14
+ browser,
15
+ store,
16
+ baseUrl: config.baseUrl,
17
+ route,
18
+ viewport,
19
+ blocking: config.blocking ?? true,
20
+ maskSelectors: config.maskSelectors ?? []
21
+ }));
22
+ }
23
+ }
24
+ }
25
+ finally {
26
+ await browser.close();
27
+ }
28
+ checks.push(...(await runRouteVisualSmoke({
29
+ visualSmoke: config.visualSmoke,
30
+ browserChecks: checks,
31
+ store
32
+ })));
33
+ return checks;
34
+ }
35
+ //# sourceMappingURL=browser-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-contract.js","sourceRoot":"","sources":["../../../src/contracts/browser/browser-contract.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,kDAAkD,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAAE,eAAe,EAAqB,MAAM,mBAAmB,CAAC;AAWvE,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAA6B,EAAE,KAAoB;IAC1F,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC;IACtC,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,IAAI,CAAC;QACH,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBACvC,MAAM,CAAC,IAAI,CACT,MAAM,eAAe,CAAC;oBACpB,OAAO;oBACP,KAAK;oBACL,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,KAAK;oBACL,QAAQ;oBACR,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;oBACjC,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,EAAE;iBAC1C,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,MAAM,CAAC,IAAI,CACT,GAAG,CAAC,MAAM,mBAAmB,CAAC;QAC5B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,aAAa,EAAE,MAAM;QACrB,KAAK;KACN,CAAC,CAAC,CACJ,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Page } from 'playwright';
2
+ export type ConsoleErrorObserver = {
3
+ errors: string[];
4
+ dispose: () => void;
5
+ };
6
+ export declare function observeConsoleErrors(page: Page): ConsoleErrorObserver;
@@ -0,0 +1,14 @@
1
+ export function observeConsoleErrors(page) {
2
+ const errors = [];
3
+ const handler = (message) => {
4
+ if (message.type() === 'error') {
5
+ errors.push(message.text());
6
+ }
7
+ };
8
+ page.on('console', handler);
9
+ return {
10
+ errors,
11
+ dispose: () => page.off('console', handler)
12
+ };
13
+ }
14
+ //# sourceMappingURL=console-observer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-observer.js","sourceRoot":"","sources":["../../../src/contracts/browser/console-observer.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,oBAAoB,CAAC,IAAU;IAC7C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,CAAC,OAA2C,EAAE,EAAE;QAC9D,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAE5B,OAAO;QACL,MAAM;QACN,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC;KAC5C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Page } from 'playwright';
2
+ export declare function getHorizontalOverflow(page: Page): Promise<{
3
+ hasOverflow: boolean;
4
+ viewportWidth: number;
5
+ scrollWidth: number;
6
+ }>;
@@ -0,0 +1,15 @@
1
+ export async function getHorizontalOverflow(page) {
2
+ return page.evaluate(() => {
3
+ const global = globalThis;
4
+ const root = global.document.documentElement;
5
+ const body = global.document.body;
6
+ const viewportWidth = global.innerWidth;
7
+ const scrollWidth = Math.max(root.scrollWidth, body?.scrollWidth ?? 0);
8
+ return {
9
+ hasOverflow: scrollWidth > viewportWidth + 1,
10
+ viewportWidth,
11
+ scrollWidth
12
+ };
13
+ });
14
+ }
15
+ //# sourceMappingURL=overflow-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overflow-check.js","sourceRoot":"","sources":["../../../src/contracts/browser/overflow-check.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU;IACpD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACxB,MAAM,MAAM,GAAG,UAMd,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClC,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC;QACxC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,WAAW,IAAI,CAAC,CAAC,CAAC;QAEvE,OAAO;YACL,WAAW,EAAE,WAAW,GAAG,aAAa,GAAG,CAAC;YAC5C,aAAa;YACb,WAAW;SACZ,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}