my-frontend-observer 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.md +52 -11
- package/dist/application/frontendContractEvaluationService.d.ts +49 -0
- package/dist/application/frontendContractEvaluationService.js +112 -0
- package/dist/application/frontendContractEvaluationService.js.map +1 -0
- package/dist/application/frontendContractPersistenceService.d.ts +56 -0
- package/dist/application/frontendContractPersistenceService.js +91 -0
- package/dist/application/frontendContractPersistenceService.js.map +1 -0
- package/dist/artifacts/comparisonArtifactReader.d.ts +18 -0
- package/dist/artifacts/comparisonArtifactReader.js +35 -0
- package/dist/artifacts/comparisonArtifactReader.js.map +1 -0
- package/dist/artifacts/frontendContractArtifactReader.d.ts +24 -0
- package/dist/artifacts/frontendContractArtifactReader.js +47 -0
- package/dist/artifacts/frontendContractArtifactReader.js.map +1 -0
- package/dist/artifacts/frontendContractArtifactWriter.d.ts +34 -0
- package/dist/artifacts/frontendContractArtifactWriter.js +70 -0
- package/dist/artifacts/frontendContractArtifactWriter.js.map +1 -0
- package/dist/artifacts/frontendContractEvaluationArtifactReader.d.ts +17 -0
- package/dist/artifacts/frontendContractEvaluationArtifactReader.js +34 -0
- package/dist/artifacts/frontendContractEvaluationArtifactReader.js.map +1 -0
- package/dist/artifacts/frontendContractEvaluationArtifactWriter.d.ts +32 -0
- package/dist/artifacts/frontendContractEvaluationArtifactWriter.js +58 -0
- package/dist/artifacts/frontendContractEvaluationArtifactWriter.js.map +1 -0
- package/dist/cli.js +496 -6
- package/dist/cli.js.map +1 -1
- package/dist/domain/boundedAgentContext.d.ts +174 -0
- package/dist/domain/boundedAgentContext.js +312 -0
- package/dist/domain/boundedAgentContext.js.map +1 -0
- package/dist/domain/boundedAgentContextCorrelation.d.ts +74 -0
- package/dist/domain/boundedAgentContextCorrelation.js +441 -0
- package/dist/domain/boundedAgentContextCorrelation.js.map +1 -0
- package/dist/domain/boundedAgentContextIdentity.d.ts +16 -0
- package/dist/domain/boundedAgentContextIdentity.js +53 -0
- package/dist/domain/boundedAgentContextIdentity.js.map +1 -0
- package/dist/domain/boundedAgentContextProjection.d.ts +60 -0
- package/dist/domain/boundedAgentContextProjection.js +449 -0
- package/dist/domain/boundedAgentContextProjection.js.map +1 -0
- package/dist/domain/frontendContractEvaluation.d.ts +57 -0
- package/dist/domain/frontendContractEvaluation.js +454 -0
- package/dist/domain/frontendContractEvaluation.js.map +1 -0
- package/dist/domain/frontendContractEvaluationArtifact.d.ts +65 -0
- package/dist/domain/frontendContractEvaluationArtifact.js +108 -0
- package/dist/domain/frontendContractEvaluationArtifact.js.map +1 -0
- package/dist/domain/frontendContractIdentity.d.ts +39 -0
- package/dist/domain/frontendContractIdentity.js +70 -0
- package/dist/domain/frontendContractIdentity.js.map +1 -0
- package/dist/domain/frontendContracts.d.ts +188 -0
- package/dist/domain/frontendContracts.js +260 -0
- package/dist/domain/frontendContracts.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +169 -10
- package/docs/CI_CD.md +68 -0
- package/docs/COMMANDS.md +162 -0
- package/docs/CONTRACTS.md +245 -5
- package/docs/CURRENT_STATE.md +152 -14
- package/docs/DEVELOPMENT.md +61 -11
- package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +33 -33
- package/docs/PROJECT_DESCRIPTION.md +1770 -1770
- package/docs/PROJECT_MILESTONES.md +2073 -2073
- package/docs/PROJECT_OVERVIEW.md +29 -16
- package/docs/QUICKSTART.md +5 -0
- package/docs/RELEASE.md +17 -10
- package/docs/ROADMAP.md +9 -0
- package/docs/SECURITY.md +30 -1
- package/docs/WORKFLOWS.md +98 -9
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { isValidPersistentBaselineContract, isValidPerChangeContract } from '../domain/frontendContracts.js';
|
|
3
|
+
async function readJson(manifestPath) {
|
|
4
|
+
let raw;
|
|
5
|
+
try {
|
|
6
|
+
raw = await readFile(manifestPath, 'utf8');
|
|
7
|
+
}
|
|
8
|
+
catch (err) {
|
|
9
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10
|
+
return { ok: false, reason: `failed to read artifact manifest at "${manifestPath}": ${message}` };
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17
|
+
return { ok: false, reason: `artifact manifest at "${manifestPath}" is not valid JSON: ${message}` };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Narrow canonical reader counterpart to `frontendContractArtifactWriter.ts#writePersistentBaselineContract`:
|
|
22
|
+
* reads one `manifest.json`, parses it, and validates it through the same
|
|
23
|
+
* `isValidPersistentBaselineContract` structural gate the writer uses - never
|
|
24
|
+
* a second validator, never a schema upgrade, never a guessed missing field.
|
|
25
|
+
*/
|
|
26
|
+
export async function readPersistentBaselineContract(manifestPath) {
|
|
27
|
+
const parsed = await readJson(manifestPath);
|
|
28
|
+
if (!parsed.ok)
|
|
29
|
+
return parsed;
|
|
30
|
+
const validation = isValidPersistentBaselineContract(parsed.value);
|
|
31
|
+
if (!validation.valid) {
|
|
32
|
+
return { ok: false, reason: `baseline contract manifest at "${manifestPath}" failed structural validation: ${validation.reason}` };
|
|
33
|
+
}
|
|
34
|
+
return { ok: true, contract: parsed.value };
|
|
35
|
+
}
|
|
36
|
+
/** Narrow canonical reader counterpart to `frontendContractArtifactWriter.ts#writePerChangeContract`. */
|
|
37
|
+
export async function readPerChangeContract(manifestPath) {
|
|
38
|
+
const parsed = await readJson(manifestPath);
|
|
39
|
+
if (!parsed.ok)
|
|
40
|
+
return parsed;
|
|
41
|
+
const validation = isValidPerChangeContract(parsed.value);
|
|
42
|
+
if (!validation.valid) {
|
|
43
|
+
return { ok: false, reason: `per-change contract manifest at "${manifestPath}" failed structural validation: ${validation.reason}` };
|
|
44
|
+
}
|
|
45
|
+
return { ok: true, contract: parsed.value };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=frontendContractArtifactReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontendContractArtifactReader.js","sourceRoot":"","sources":["../../src/artifacts/frontendContractArtifactReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,iCAAiC,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAK7G,KAAK,UAAU,QAAQ,CAAC,YAAoB;IAC1C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wCAAwC,YAAY,MAAM,OAAO,EAAE,EAAE,CAAC;IACpG,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,YAAY,wBAAwB,OAAO,EAAE,EAAE,CAAC;IACvG,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,YAAoB;IACvE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC;IAC9B,MAAM,UAAU,GAAG,iCAAiC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,YAAY,mCAAmC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;IACrI,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAmC,EAAE,CAAC;AAC5E,CAAC;AAED,yGAAyG;AACzG,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,YAAoB;IAC9D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC;IAC9B,MAAM,UAAU,GAAG,wBAAwB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,YAAY,mCAAmC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;IACvI,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,KAA0B,EAAE,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PersistentBaselineContract, PerChangeContract } from '../domain/frontendContracts.js';
|
|
2
|
+
import type { Diagnostic } from '../domain/diagnostics.js';
|
|
3
|
+
export declare const FRONTEND_CONTRACT_MANIFEST_FILENAME = "manifest.json";
|
|
4
|
+
/**
|
|
5
|
+
* Mirrors `PersistedComparisonResult`'s shape/semantics - a frontend contract
|
|
6
|
+
* directory has no screenshot file either, and this writer never touches
|
|
7
|
+
* `artifacts/artifactWriter.ts` or `artifacts/comparisonArtifactWriter.ts`.
|
|
8
|
+
*/
|
|
9
|
+
export type PersistedFrontendContractResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
artifactRoot: string;
|
|
12
|
+
manifestPath: string;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
diagnostics: Diagnostic[];
|
|
16
|
+
};
|
|
17
|
+
export interface WriteFrontendContractArtifactOptions {
|
|
18
|
+
/** Base directory that `outputLocation` is resolved against. Defaults to `process.cwd()`. */
|
|
19
|
+
cwd?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Persists one already-valid `PersistentBaselineContract` as
|
|
23
|
+
* `<outputLocation>/<baselineId>/manifest.json`. Never mutates or removes any
|
|
24
|
+
* other baseline artifact - `supersedesBaselineId` (if present) is written
|
|
25
|
+
* exactly as supplied; this writer never inspects, rewrites, or deletes the
|
|
26
|
+
* baseline it refers to.
|
|
27
|
+
*/
|
|
28
|
+
export declare function writePersistentBaselineContract(contract: PersistentBaselineContract, outputLocation: string, options?: WriteFrontendContractArtifactOptions): Promise<PersistedFrontendContractResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Persists one already-valid `PerChangeContract` as
|
|
31
|
+
* `<outputLocation>/<contractId>/manifest.json`, symmetric with baseline
|
|
32
|
+
* persistence above (both share `CONTRACT_ARTIFACT_KIND`/`CONTRACT_SCHEMA_VERSION`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function writePerChangeContract(contract: PerChangeContract, outputLocation: string, options?: WriteFrontendContractArtifactOptions): Promise<PersistedFrontendContractResult>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { mkdir, writeFile, rename, rm, access } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isValidPersistentBaselineContract, isValidPerChangeContract } from '../domain/frontendContracts.js';
|
|
4
|
+
import { DIAGNOSTIC_SEVERITY } from '../domain/diagnostics.js';
|
|
5
|
+
export const FRONTEND_CONTRACT_MANIFEST_FILENAME = 'manifest.json';
|
|
6
|
+
function writeFailure(message, details) {
|
|
7
|
+
const diagnostic = {
|
|
8
|
+
code: 'artifact-write-failure',
|
|
9
|
+
severity: DIAGNOSTIC_SEVERITY['artifact-write-failure'],
|
|
10
|
+
message,
|
|
11
|
+
...(details === undefined ? {} : { details }),
|
|
12
|
+
};
|
|
13
|
+
return { ok: false, diagnostics: [diagnostic] };
|
|
14
|
+
}
|
|
15
|
+
async function pathExists(candidate) {
|
|
16
|
+
return access(candidate).then(() => true, () => false);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Shared atomic-write body for both contract classes, following the same
|
|
20
|
+
* discipline as `writeObservationArtifact`/`writeComparisonArtifact`: sibling
|
|
21
|
+
* temporary directory, single `manifest.json`, then one atomic rename. An
|
|
22
|
+
* existing directory at the final identity is a genuine collision (never a
|
|
23
|
+
* destructive overwrite of prior baseline/contract history) and is rejected.
|
|
24
|
+
*/
|
|
25
|
+
async function writeContractArtifact(value, id, kindLabel, outputLocation, options) {
|
|
26
|
+
const cwd = options.cwd ?? process.cwd();
|
|
27
|
+
const outputRoot = path.resolve(cwd, outputLocation);
|
|
28
|
+
const finalRoot = path.join(outputRoot, id);
|
|
29
|
+
const tempRoot = path.join(outputRoot, `.tmp-${id}`);
|
|
30
|
+
if (await pathExists(finalRoot)) {
|
|
31
|
+
return writeFailure(`a ${kindLabel} artifact already exists at "${id}"; refusing to overwrite it`, { artifactRoot: finalRoot });
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
35
|
+
await mkdir(tempRoot, { recursive: true });
|
|
36
|
+
await writeFile(path.join(tempRoot, FRONTEND_CONTRACT_MANIFEST_FILENAME), JSON.stringify(value, null, 2), 'utf8');
|
|
37
|
+
await rename(tempRoot, finalRoot);
|
|
38
|
+
return { ok: true, artifactRoot: finalRoot, manifestPath: path.join(finalRoot, FRONTEND_CONTRACT_MANIFEST_FILENAME) };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
await rm(tempRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
42
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
43
|
+
return writeFailure(`failed to persist ${kindLabel} artifact: ${message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Persists one already-valid `PersistentBaselineContract` as
|
|
48
|
+
* `<outputLocation>/<baselineId>/manifest.json`. Never mutates or removes any
|
|
49
|
+
* other baseline artifact - `supersedesBaselineId` (if present) is written
|
|
50
|
+
* exactly as supplied; this writer never inspects, rewrites, or deletes the
|
|
51
|
+
* baseline it refers to.
|
|
52
|
+
*/
|
|
53
|
+
export async function writePersistentBaselineContract(contract, outputLocation, options = {}) {
|
|
54
|
+
const validation = isValidPersistentBaselineContract(contract);
|
|
55
|
+
if (!validation.valid)
|
|
56
|
+
return writeFailure(`refusing to persist an invalid PersistentBaselineContract: ${validation.reason}`);
|
|
57
|
+
return writeContractArtifact(contract, contract.baselineId, 'baseline contract', outputLocation, options);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Persists one already-valid `PerChangeContract` as
|
|
61
|
+
* `<outputLocation>/<contractId>/manifest.json`, symmetric with baseline
|
|
62
|
+
* persistence above (both share `CONTRACT_ARTIFACT_KIND`/`CONTRACT_SCHEMA_VERSION`).
|
|
63
|
+
*/
|
|
64
|
+
export async function writePerChangeContract(contract, outputLocation, options = {}) {
|
|
65
|
+
const validation = isValidPerChangeContract(contract);
|
|
66
|
+
if (!validation.valid)
|
|
67
|
+
return writeFailure(`refusing to persist an invalid PerChangeContract: ${validation.reason}`);
|
|
68
|
+
return writeContractArtifact(contract, contract.contractId, 'per-change contract', outputLocation, options);
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=frontendContractArtifactWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontendContractArtifactWriter.js","sourceRoot":"","sources":["../../src/artifacts/frontendContractArtifactWriter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FrontendContractEvaluationArtifact } from '../domain/frontendContractEvaluationArtifact.js';
|
|
2
|
+
export type ReadFrontendContractEvaluationArtifactResult = {
|
|
3
|
+
ok: true;
|
|
4
|
+
artifact: FrontendContractEvaluationArtifact;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
reason: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Narrow canonical reader counterpart to
|
|
11
|
+
* `frontendContractEvaluationArtifactWriter.ts#writeFrontendContractEvaluationArtifact`:
|
|
12
|
+
* reads one `manifest.json`, parses it, and validates it through the same
|
|
13
|
+
* `isValidFrontendContractEvaluationArtifact` structural gate the writer
|
|
14
|
+
* uses. Read-only: never mutates the file, never recomputes or reinterprets
|
|
15
|
+
* the evaluation, never repairs a malformed artifact.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readFrontendContractEvaluationArtifact(manifestPath: string): Promise<ReadFrontendContractEvaluationArtifactResult>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { isValidFrontendContractEvaluationArtifact } from '../domain/frontendContractEvaluationArtifact.js';
|
|
3
|
+
/**
|
|
4
|
+
* Narrow canonical reader counterpart to
|
|
5
|
+
* `frontendContractEvaluationArtifactWriter.ts#writeFrontendContractEvaluationArtifact`:
|
|
6
|
+
* reads one `manifest.json`, parses it, and validates it through the same
|
|
7
|
+
* `isValidFrontendContractEvaluationArtifact` structural gate the writer
|
|
8
|
+
* uses. Read-only: never mutates the file, never recomputes or reinterprets
|
|
9
|
+
* the evaluation, never repairs a malformed artifact.
|
|
10
|
+
*/
|
|
11
|
+
export async function readFrontendContractEvaluationArtifact(manifestPath) {
|
|
12
|
+
let raw;
|
|
13
|
+
try {
|
|
14
|
+
raw = await readFile(manifestPath, 'utf8');
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18
|
+
return { ok: false, reason: `failed to read evaluation artifact manifest at "${manifestPath}": ${message}` };
|
|
19
|
+
}
|
|
20
|
+
let parsed;
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
26
|
+
return { ok: false, reason: `evaluation artifact manifest at "${manifestPath}" is not valid JSON: ${message}` };
|
|
27
|
+
}
|
|
28
|
+
const validation = isValidFrontendContractEvaluationArtifact(parsed);
|
|
29
|
+
if (!validation.valid) {
|
|
30
|
+
return { ok: false, reason: `evaluation artifact manifest at "${manifestPath}" failed structural validation: ${validation.reason}` };
|
|
31
|
+
}
|
|
32
|
+
return { ok: true, artifact: parsed };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=frontendContractEvaluationArtifactReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontendContractEvaluationArtifactReader.js","sourceRoot":"","sources":["../../src/artifacts/frontendContractEvaluationArtifactReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,yCAAyC,EAAE,MAAM,iDAAiD,CAAC;AAI5G;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,sCAAsC,CAAC,YAAoB;IAC/E,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mDAAmD,YAAY,MAAM,OAAO,EAAE,EAAE,CAAC;IAC/G,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,YAAY,wBAAwB,OAAO,EAAE,EAAE,CAAC;IAClH,CAAC;IAED,MAAM,UAAU,GAAG,yCAAyC,CAAC,MAAM,CAAC,CAAC;IACrE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,YAAY,mCAAmC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;IACvI,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAA4C,EAAE,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FrontendContractEvaluationArtifact } from '../domain/frontendContractEvaluationArtifact.js';
|
|
2
|
+
import type { Diagnostic } from '../domain/diagnostics.js';
|
|
3
|
+
export declare const EVALUATION_MANIFEST_FILENAME = "manifest.json";
|
|
4
|
+
export type PersistedFrontendContractEvaluationResult = {
|
|
5
|
+
ok: true;
|
|
6
|
+
artifactRoot: string;
|
|
7
|
+
manifestPath: string;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
diagnostics: Diagnostic[];
|
|
11
|
+
};
|
|
12
|
+
export interface WriteFrontendContractEvaluationArtifactOptions {
|
|
13
|
+
/** Base directory that `outputLocation` is resolved against. Defaults to `process.cwd()`. */
|
|
14
|
+
cwd?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Persists one already-built, already-valid `FrontendContractEvaluationArtifact`
|
|
18
|
+
* as `<outputLocation>/<evaluationId>/manifest.json` only - no screenshot is
|
|
19
|
+
* copied (the artifact's `before`/`after` fields already point back to the
|
|
20
|
+
* source observations). Same atomic-write discipline as
|
|
21
|
+
* `artifactWriter.ts#writeObservationArtifact`/`comparisonArtifactWriter.ts#writeComparisonArtifact`
|
|
22
|
+
* without sharing their implementation: sibling temporary directory, then one
|
|
23
|
+
* atomic rename. `evaluationId` is fresh per execution (see
|
|
24
|
+
* `frontendContractIdentity.ts#buildFrontendContractInstanceIdentity`), so an
|
|
25
|
+
* existing directory at the final path is a genuine collision and is
|
|
26
|
+
* rejected rather than overwritten - a legitimate PASS or FAIL evaluation
|
|
27
|
+
* result is never silently replaced. A `FAIL` verdict is a legitimate,
|
|
28
|
+
* persistable evaluation result, not a write failure; only an unpersistable
|
|
29
|
+
* artifact (invalid shape, or a real filesystem error) is reported as
|
|
30
|
+
* `ok: false`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeFrontendContractEvaluationArtifact(artifact: FrontendContractEvaluationArtifact, outputLocation: string, options?: WriteFrontendContractEvaluationArtifactOptions): Promise<PersistedFrontendContractEvaluationResult>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { mkdir, writeFile, rename, rm, access } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isValidFrontendContractEvaluationArtifact } from '../domain/frontendContractEvaluationArtifact.js';
|
|
4
|
+
import { DIAGNOSTIC_SEVERITY } from '../domain/diagnostics.js';
|
|
5
|
+
export const EVALUATION_MANIFEST_FILENAME = 'manifest.json';
|
|
6
|
+
function writeFailure(message, details) {
|
|
7
|
+
const diagnostic = {
|
|
8
|
+
code: 'artifact-write-failure',
|
|
9
|
+
severity: DIAGNOSTIC_SEVERITY['artifact-write-failure'],
|
|
10
|
+
message,
|
|
11
|
+
...(details === undefined ? {} : { details }),
|
|
12
|
+
};
|
|
13
|
+
return { ok: false, diagnostics: [diagnostic] };
|
|
14
|
+
}
|
|
15
|
+
async function pathExists(candidate) {
|
|
16
|
+
return access(candidate).then(() => true, () => false);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Persists one already-built, already-valid `FrontendContractEvaluationArtifact`
|
|
20
|
+
* as `<outputLocation>/<evaluationId>/manifest.json` only - no screenshot is
|
|
21
|
+
* copied (the artifact's `before`/`after` fields already point back to the
|
|
22
|
+
* source observations). Same atomic-write discipline as
|
|
23
|
+
* `artifactWriter.ts#writeObservationArtifact`/`comparisonArtifactWriter.ts#writeComparisonArtifact`
|
|
24
|
+
* without sharing their implementation: sibling temporary directory, then one
|
|
25
|
+
* atomic rename. `evaluationId` is fresh per execution (see
|
|
26
|
+
* `frontendContractIdentity.ts#buildFrontendContractInstanceIdentity`), so an
|
|
27
|
+
* existing directory at the final path is a genuine collision and is
|
|
28
|
+
* rejected rather than overwritten - a legitimate PASS or FAIL evaluation
|
|
29
|
+
* result is never silently replaced. A `FAIL` verdict is a legitimate,
|
|
30
|
+
* persistable evaluation result, not a write failure; only an unpersistable
|
|
31
|
+
* artifact (invalid shape, or a real filesystem error) is reported as
|
|
32
|
+
* `ok: false`.
|
|
33
|
+
*/
|
|
34
|
+
export async function writeFrontendContractEvaluationArtifact(artifact, outputLocation, options = {}) {
|
|
35
|
+
const validation = isValidFrontendContractEvaluationArtifact(artifact);
|
|
36
|
+
if (!validation.valid)
|
|
37
|
+
return writeFailure(`refusing to persist an invalid FrontendContractEvaluationArtifact: ${validation.reason}`);
|
|
38
|
+
const cwd = options.cwd ?? process.cwd();
|
|
39
|
+
const outputRoot = path.resolve(cwd, outputLocation);
|
|
40
|
+
const finalRoot = path.join(outputRoot, artifact.evaluationId);
|
|
41
|
+
const tempRoot = path.join(outputRoot, `.tmp-${artifact.evaluationId}`);
|
|
42
|
+
if (await pathExists(finalRoot)) {
|
|
43
|
+
return writeFailure(`an evaluation artifact already exists at "${artifact.evaluationId}"; refusing to overwrite it`, { artifactRoot: finalRoot });
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
47
|
+
await mkdir(tempRoot, { recursive: true });
|
|
48
|
+
await writeFile(path.join(tempRoot, EVALUATION_MANIFEST_FILENAME), JSON.stringify(artifact, null, 2), 'utf8');
|
|
49
|
+
await rename(tempRoot, finalRoot);
|
|
50
|
+
return { ok: true, artifactRoot: finalRoot, manifestPath: path.join(finalRoot, EVALUATION_MANIFEST_FILENAME) };
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
await rm(tempRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
54
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
+
return writeFailure(`failed to persist evaluation artifact: ${message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=frontendContractEvaluationArtifactWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontendContractEvaluationArtifactWriter.js","sourceRoot":"","sources":["../../src/artifacts/frontendContractEvaluationArtifactWriter.ts"],"names":[],"mappings":"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"}
|