@brandlab-digital/conformance 3.14.0 → 3.15.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/README.md +60 -3
- package/dist/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/engineering.d.ts.map +1 -1
- package/dist/engineering.js +18 -2
- package/dist/engineering.js.map +1 -1
- package/dist/estate-snapshot.d.ts +54 -0
- package/dist/estate-snapshot.d.ts.map +1 -0
- package/dist/estate-snapshot.js +164 -0
- package/dist/estate-snapshot.js.map +1 -0
- package/dist/estate.d.ts +10 -0
- package/dist/estate.d.ts.map +1 -1
- package/dist/estate.js +36 -16
- package/dist/estate.js.map +1 -1
- package/dist/media-snapshot.d.ts +53 -0
- package/dist/media-snapshot.d.ts.map +1 -0
- package/dist/media-snapshot.js +66 -0
- package/dist/media-snapshot.js.map +1 -0
- package/dist/site-system/checks.d.ts +2 -2
- package/dist/site-system/checks.js +2 -2
- package/dist/site-system/checks.js.map +1 -1
- package/dist/site-system/config.d.ts +13 -0
- package/dist/site-system/config.d.ts.map +1 -1
- package/dist/site-system/config.js +30 -1
- package/dist/site-system/config.js.map +1 -1
- package/dist/site-system/index.d.ts +1 -1
- package/dist/site-system/index.d.ts.map +1 -1
- package/dist/site-system/index.js +1 -1
- package/dist/site-system/index.js.map +1 -1
- package/dist/site-system/journeys/conversion.d.ts.map +1 -1
- package/dist/site-system/journeys/conversion.js +15 -2
- package/dist/site-system/journeys/conversion.js.map +1 -1
- package/dist/site-system/journeys/menu.d.ts.map +1 -1
- package/dist/site-system/journeys/menu.js +24 -1
- package/dist/site-system/journeys/menu.js.map +1 -1
- package/dist/site-system/journeys/page-helpers.d.ts.map +1 -1
- package/dist/site-system/journeys/page-helpers.js +1 -1
- package/dist/site-system/journeys/page-helpers.js.map +1 -1
- package/dist/site-system/journeys/plan.d.ts +13 -3
- package/dist/site-system/journeys/plan.d.ts.map +1 -1
- package/dist/site-system/journeys/plan.js +12 -2
- package/dist/site-system/journeys/plan.js.map +1 -1
- package/dist/site-system/journeys/shell.d.ts +1 -1
- package/dist/site-system/journeys/shell.js +2 -2
- package/dist/site-system/journeys/shell.js.map +1 -1
- package/dist/site-system/report.d.ts.map +1 -1
- package/dist/site-system/report.js +24 -4
- package/dist/site-system/report.js.map +1 -1
- package/dist/site-system/rules/form/c7-thank-you.d.ts +7 -1
- package/dist/site-system/rules/form/c7-thank-you.d.ts.map +1 -1
- package/dist/site-system/rules/form/c7-thank-you.js +26 -3
- package/dist/site-system/rules/form/c7-thank-you.js.map +1 -1
- package/dist/site-system/rules/form/index.d.ts.map +1 -1
- package/dist/site-system/rules/form/index.js +11 -1
- package/dist/site-system/rules/form/index.js.map +1 -1
- package/dist/site-system/rules/full/common.d.ts +2 -0
- package/dist/site-system/rules/full/common.d.ts.map +1 -1
- package/dist/site-system/rules/full/common.js.map +1 -1
- package/dist/site-system/rules/full/estate.d.ts +10 -2
- package/dist/site-system/rules/full/estate.d.ts.map +1 -1
- package/dist/site-system/rules/full/estate.js +35 -5
- package/dist/site-system/rules/full/estate.js.map +1 -1
- package/dist/site-system/rules/full/geography.d.ts +7 -0
- package/dist/site-system/rules/full/geography.d.ts.map +1 -1
- package/dist/site-system/rules/full/geography.js +9 -0
- package/dist/site-system/rules/full/geography.js.map +1 -1
- package/dist/site-system/rules/full/index.d.ts +2 -1
- package/dist/site-system/rules/full/index.d.ts.map +1 -1
- package/dist/site-system/rules/full/index.js +5 -1
- package/dist/site-system/rules/full/index.js.map +1 -1
- package/dist/site-system/rules/full/l10-location-copy.d.ts +1 -1
- package/dist/site-system/rules/full/l10-location-copy.d.ts.map +1 -1
- package/dist/site-system/rules/full/l10-location-copy.js +5 -2
- package/dist/site-system/rules/full/l10-location-copy.js.map +1 -1
- package/dist/site-system/rules/full/l3-neighbours.d.ts +3 -2
- package/dist/site-system/rules/full/l3-neighbours.d.ts.map +1 -1
- package/dist/site-system/rules/full/l3-neighbours.js +4 -1
- package/dist/site-system/rules/full/l3-neighbours.js.map +1 -1
- package/dist/site-system/rules/full/l4-local-copy.d.ts +2 -1
- package/dist/site-system/rules/full/l4-local-copy.d.ts.map +1 -1
- package/dist/site-system/rules/full/l4-local-copy.js +8 -3
- package/dist/site-system/rules/full/l4-local-copy.js.map +1 -1
- package/dist/site-system/rules/full/l9-data-contract.js +2 -2
- package/dist/site-system/rules/full/l9-data-contract.js.map +1 -1
- package/dist/site-system/rules/full/links.d.ts +3 -1
- package/dist/site-system/rules/full/links.d.ts.map +1 -1
- package/dist/site-system/rules/full/links.js +15 -5
- package/dist/site-system/rules/full/links.js.map +1 -1
- package/dist/site-system/rules/full/route-kinds.d.ts +15 -0
- package/dist/site-system/rules/full/route-kinds.d.ts.map +1 -0
- package/dist/site-system/rules/full/route-kinds.js +52 -0
- package/dist/site-system/rules/full/route-kinds.js.map +1 -0
- package/dist/site-system/rules/full/strategy.d.ts +2 -0
- package/dist/site-system/rules/full/strategy.d.ts.map +1 -1
- package/dist/site-system/rules/full/strategy.js +86 -14
- package/dist/site-system/rules/full/strategy.js.map +1 -1
- package/dist/site-system/rules/sections/k2-skin-coverage.d.ts +2 -2
- package/dist/site-system/rules/sections/k2-skin-coverage.d.ts.map +1 -1
- package/dist/site-system/rules/sections/k2-skin-coverage.js +9 -2
- package/dist/site-system/rules/sections/k2-skin-coverage.js.map +1 -1
- package/dist/site-system/rules/sections/t6-media-entries.d.ts.map +1 -1
- package/dist/site-system/rules/sections/t6-media-entries.js +69 -5
- package/dist/site-system/rules/sections/t6-media-entries.js.map +1 -1
- package/dist/site-system-e2e.d.ts +1 -1
- package/dist/site-system-e2e.d.ts.map +1 -1
- package/dist/site-system-e2e.js +17 -4
- package/dist/site-system-e2e.js.map +1 -1
- package/package.json +10 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SiteEstatePlan, SiteReleaseWave, SiteStrategy } from "@brandlab-digital/site-core/strategy";
|
|
2
|
+
/** Where the snapshot lives, relative to the project root. */
|
|
3
|
+
export declare const ESTATE_PLAN_SNAPSHOT_FILE = "src/data/estate-plan.generated.json";
|
|
4
|
+
/** The folders hashed by default: the snapshot is stale once any file in them changes. */
|
|
5
|
+
export declare const ESTATE_PLAN_SNAPSHOT_SOURCES: readonly string[];
|
|
6
|
+
export declare const ESTATE_PLAN_SNAPSHOT_KIND = "brandlab-estate-plan-snapshot";
|
|
7
|
+
export declare const ESTATE_PLAN_SNAPSHOT_VERSION = 1;
|
|
8
|
+
export interface EstatePlanSnapshot {
|
|
9
|
+
readonly kind: typeof ESTATE_PLAN_SNAPSHOT_KIND;
|
|
10
|
+
readonly version: typeof ESTATE_PLAN_SNAPSHOT_VERSION;
|
|
11
|
+
/** The folders hashed and the hash of their files when the snapshot was written. */
|
|
12
|
+
readonly sources: {
|
|
13
|
+
readonly paths: readonly string[];
|
|
14
|
+
readonly sha256: string;
|
|
15
|
+
};
|
|
16
|
+
readonly waves?: readonly SiteReleaseWave[];
|
|
17
|
+
readonly estate?: SiteEstatePlan;
|
|
18
|
+
}
|
|
19
|
+
export interface EstatePlanSnapshotOptions {
|
|
20
|
+
readonly projectRoot: string;
|
|
21
|
+
/** Folders (or files) relative to the project root the plan is computed from. Defaults to `src/data`. */
|
|
22
|
+
readonly sources?: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The waves and estate plan in their canonical form: waves in date order (stable), each wave's routes sorted, the
|
|
26
|
+
* plan's lists sorted and its keys in a fixed order. Two plans that mean the same thing serialise the same.
|
|
27
|
+
*/
|
|
28
|
+
export declare function canonicalEstatePlan(strategy: Pick<SiteStrategy, "waves" | "estate">): Pick<EstatePlanSnapshot, "waves" | "estate">;
|
|
29
|
+
/**
|
|
30
|
+
* The hash of the files the plan is computed from: every code and data file under `sources` (tests and generated
|
|
31
|
+
* files left out), by path and content in path order. The ready gate recomputes it to tell a stale snapshot.
|
|
32
|
+
*/
|
|
33
|
+
export declare function estatePlanSourceHash(projectRoot: string, sources?: readonly string[]): string;
|
|
34
|
+
/** The snapshot for a strategy record: its waves and estate plan in canonical form, with the sources' hash. */
|
|
35
|
+
export declare function estatePlanSnapshot(strategy: Pick<SiteStrategy, "waves" | "estate">, options: EstatePlanSnapshotOptions): EstatePlanSnapshot;
|
|
36
|
+
/** The snapshot as the file holds it: two-space JSON and a final newline. */
|
|
37
|
+
export declare function serialiseEstatePlanSnapshot(snapshot: EstatePlanSnapshot): string;
|
|
38
|
+
/**
|
|
39
|
+
* Writes `src/data/estate-plan.generated.json` when its content differs; `changed` says whether it did. Run it from the
|
|
40
|
+
* site's tests or build so the snapshot follows the strategy record, and commit the file.
|
|
41
|
+
*/
|
|
42
|
+
export declare function writeEstatePlanSnapshot(strategy: Pick<SiteStrategy, "waves" | "estate">, options: EstatePlanSnapshotOptions): {
|
|
43
|
+
readonly path: string;
|
|
44
|
+
readonly changed: boolean;
|
|
45
|
+
readonly snapshot: EstatePlanSnapshot;
|
|
46
|
+
};
|
|
47
|
+
/** Writes a snapshot file when its text differs; `changed` says whether it did. Shared with the media snapshot. */
|
|
48
|
+
export declare function writeSnapshotFile(projectRoot: string, file: string, text: string): {
|
|
49
|
+
readonly path: string;
|
|
50
|
+
readonly changed: boolean;
|
|
51
|
+
};
|
|
52
|
+
/** The folder list a snapshot records, checked and sorted. */
|
|
53
|
+
export declare function snapshotSourcePaths(sources?: readonly string[]): string[];
|
|
54
|
+
//# sourceMappingURL=estate-snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estate-snapshot.d.ts","sourceRoot":"","sources":["../src/estate-snapshot.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAE1G,8DAA8D;AAC9D,eAAO,MAAM,yBAAyB,wCAAwC,CAAC;AAC/E,0FAA0F;AAC1F,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAAgC,CAAC;AAC3F,eAAO,MAAM,yBAAyB,kCAAkC,CAAC;AACzE,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAM9C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,OAAO,yBAAyB,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,4BAA4B,CAAC;IACtD,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yGAAyG;IACzG,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAUD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,QAAQ,CAAC,CAsClI;AA8BD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,SAAS,MAAM,EAAiC,GAAG,MAAM,CAU3H;AAED,+GAA+G;AAC/G,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,QAAQ,CAAC,EAAE,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CAQ3I;AAED,6EAA6E;AAC7E,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAEhF;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,QAAQ,CAAC,EAChD,OAAO,EAAE,yBAAyB,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAA;CAAE,CAG7F;AAED,mHAAmH;AACnH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAOvI;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,SAAS,MAAM,EAAiC,GAAG,MAAM,EAAE,CAEvG"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The estate plan snapshot (3.15.0): a site whose waves or estate plan are computed (from its location catalogue,
|
|
3
|
+
* for example) records them as plain JSON, so the ready gate's LOC-INDEX can read them without running the site's
|
|
4
|
+
* code. The snapshot is deterministic (sorted routes and keys, no timestamp) and carries a hash of the source files it
|
|
5
|
+
* was written from (`src/data` by default), so the gate fails a snapshot older than those files.
|
|
6
|
+
*
|
|
7
|
+
* The site writes it from its own tests, where its module aliases resolve:
|
|
8
|
+
*
|
|
9
|
+
* // tests/data/estatePlanSnapshot.test.ts
|
|
10
|
+
* import { writeEstatePlanSnapshot } from '@brandlab-digital/conformance/estate-snapshot'
|
|
11
|
+
* import { siteStrategy } from '@/data/strategy'
|
|
12
|
+
*
|
|
13
|
+
* it('records the estate plan for the ready gate', () => {
|
|
14
|
+
* const { changed } = writeEstatePlanSnapshot(siteStrategy, { projectRoot: process.cwd() })
|
|
15
|
+
* if (process.env.CI) expect(changed).toBe(false)
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* Node only: it reads and writes files.
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { dirname, isAbsolute, join, normalize, relative, sep } from "node:path";
|
|
23
|
+
/** Where the snapshot lives, relative to the project root. */
|
|
24
|
+
export const ESTATE_PLAN_SNAPSHOT_FILE = "src/data/estate-plan.generated.json";
|
|
25
|
+
/** The folders hashed by default: the snapshot is stale once any file in them changes. */
|
|
26
|
+
export const ESTATE_PLAN_SNAPSHOT_SOURCES = Object.freeze(["src/data"]);
|
|
27
|
+
export const ESTATE_PLAN_SNAPSHOT_KIND = "brandlab-estate-plan-snapshot";
|
|
28
|
+
export const ESTATE_PLAN_SNAPSHOT_VERSION = 1;
|
|
29
|
+
/** The source files hashed: code and data, never tests or generated files. */
|
|
30
|
+
const SOURCE_FILE = /\.(?:ts|tsx|mts|cts|js|mjs|cjs|json|mdx?|csv)$/;
|
|
31
|
+
const SKIPPED_FILE = /(?:\.generated\.json|\.(?:test|spec)\.[a-z]+)$/;
|
|
32
|
+
function sortedUnique(values) {
|
|
33
|
+
return [...new Set(values)].sort();
|
|
34
|
+
}
|
|
35
|
+
function sortedRecord(record) {
|
|
36
|
+
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The waves and estate plan in their canonical form: waves in date order (stable), each wave's routes sorted, the
|
|
40
|
+
* plan's lists sorted and its keys in a fixed order. Two plans that mean the same thing serialise the same.
|
|
41
|
+
*/
|
|
42
|
+
export function canonicalEstatePlan(strategy) {
|
|
43
|
+
const result = {};
|
|
44
|
+
if (strategy.waves !== undefined) {
|
|
45
|
+
result.waves = [...strategy.waves]
|
|
46
|
+
.map((wave, index) => ({ wave, index }))
|
|
47
|
+
.sort((left, right) => (left.wave.releaseOn < right.wave.releaseOn ? -1 : left.wave.releaseOn > right.wave.releaseOn ? 1 : left.index - right.index))
|
|
48
|
+
.map(({ wave }) => ({
|
|
49
|
+
releaseOn: wave.releaseOn,
|
|
50
|
+
...(wave.kind !== undefined ? { kind: wave.kind } : {}),
|
|
51
|
+
routes: sortedUnique(wave.routes),
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
const plan = strategy.estate;
|
|
55
|
+
if (plan !== undefined) {
|
|
56
|
+
const paths = plan.paths;
|
|
57
|
+
const slugs = plan.slugs;
|
|
58
|
+
result.estate = {
|
|
59
|
+
indexedNow: sortedUnique(plan.indexedNow),
|
|
60
|
+
...(paths !== undefined
|
|
61
|
+
? {
|
|
62
|
+
paths: {
|
|
63
|
+
...(paths.region !== undefined ? { region: paths.region } : {}),
|
|
64
|
+
...(paths.suburb !== undefined ? { suburb: paths.suburb } : {}),
|
|
65
|
+
...(paths.serviceInSuburb !== undefined ? { serviceInSuburb: sortedUnique(paths.serviceInSuburb) } : {}),
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
: {}),
|
|
69
|
+
...(slugs !== undefined
|
|
70
|
+
? {
|
|
71
|
+
slugs: {
|
|
72
|
+
...(slugs.regions !== undefined ? { regions: sortedRecord(slugs.regions) } : {}),
|
|
73
|
+
...(slugs.suburbs !== undefined ? { suburbs: sortedRecord(slugs.suburbs) } : {}),
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
function assertSourcePath(path) {
|
|
82
|
+
if (typeof path !== "string" || path === "" || isAbsolute(path)) {
|
|
83
|
+
throw new Error(`The snapshot source "${String(path)}" must be a folder or file inside the project.`);
|
|
84
|
+
}
|
|
85
|
+
const clean = normalize(path).split(sep).join("/").replace(/\/$/, "");
|
|
86
|
+
if (clean === "." || clean === ".." || clean.startsWith("../")) {
|
|
87
|
+
throw new Error(`The snapshot source "${path}" must be a folder or file inside the project.`);
|
|
88
|
+
}
|
|
89
|
+
return clean;
|
|
90
|
+
}
|
|
91
|
+
function collectFiles(root, path, files) {
|
|
92
|
+
const absolute = join(root, path);
|
|
93
|
+
if (!existsSync(absolute))
|
|
94
|
+
return;
|
|
95
|
+
if (statSync(absolute).isFile()) {
|
|
96
|
+
if (!SKIPPED_FILE.test(path))
|
|
97
|
+
files.push(path);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
for (const name of readdirSync(absolute)) {
|
|
101
|
+
if (name === "node_modules" || name.startsWith("."))
|
|
102
|
+
continue;
|
|
103
|
+
const child = `${path}/${name}`;
|
|
104
|
+
// statSync follows symbolic links, so a linked file or folder is hashed by what it points to.
|
|
105
|
+
const stats = statSync(join(root, child), { throwIfNoEntry: false });
|
|
106
|
+
if (stats?.isDirectory())
|
|
107
|
+
collectFiles(root, child, files);
|
|
108
|
+
else if (stats?.isFile() && SOURCE_FILE.test(name) && !SKIPPED_FILE.test(name))
|
|
109
|
+
files.push(child);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The hash of the files the plan is computed from: every code and data file under `sources` (tests and generated
|
|
114
|
+
* files left out), by path and content in path order. The ready gate recomputes it to tell a stale snapshot.
|
|
115
|
+
*/
|
|
116
|
+
export function estatePlanSourceHash(projectRoot, sources = ESTATE_PLAN_SNAPSHOT_SOURCES) {
|
|
117
|
+
const files = [];
|
|
118
|
+
for (const source of sortedUnique(sources.map(assertSourcePath)))
|
|
119
|
+
collectFiles(projectRoot, source, files);
|
|
120
|
+
const hash = createHash("sha256");
|
|
121
|
+
for (const file of sortedUnique(files)) {
|
|
122
|
+
hash.update(`${file}\n`);
|
|
123
|
+
hash.update(readFileSync(join(projectRoot, file)));
|
|
124
|
+
hash.update("\n");
|
|
125
|
+
}
|
|
126
|
+
return hash.digest("hex");
|
|
127
|
+
}
|
|
128
|
+
/** The snapshot for a strategy record: its waves and estate plan in canonical form, with the sources' hash. */
|
|
129
|
+
export function estatePlanSnapshot(strategy, options) {
|
|
130
|
+
const paths = sortedUnique((options.sources ?? ESTATE_PLAN_SNAPSHOT_SOURCES).map(assertSourcePath));
|
|
131
|
+
return {
|
|
132
|
+
kind: ESTATE_PLAN_SNAPSHOT_KIND,
|
|
133
|
+
version: ESTATE_PLAN_SNAPSHOT_VERSION,
|
|
134
|
+
sources: { paths, sha256: estatePlanSourceHash(options.projectRoot, paths) },
|
|
135
|
+
...canonicalEstatePlan(strategy),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** The snapshot as the file holds it: two-space JSON and a final newline. */
|
|
139
|
+
export function serialiseEstatePlanSnapshot(snapshot) {
|
|
140
|
+
return `${JSON.stringify(snapshot, null, 2)}\n`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Writes `src/data/estate-plan.generated.json` when its content differs; `changed` says whether it did. Run it from the
|
|
144
|
+
* site's tests or build so the snapshot follows the strategy record, and commit the file.
|
|
145
|
+
*/
|
|
146
|
+
export function writeEstatePlanSnapshot(strategy, options) {
|
|
147
|
+
const snapshot = estatePlanSnapshot(strategy, options);
|
|
148
|
+
return { ...writeSnapshotFile(options.projectRoot, ESTATE_PLAN_SNAPSHOT_FILE, serialiseEstatePlanSnapshot(snapshot)), snapshot };
|
|
149
|
+
}
|
|
150
|
+
/** Writes a snapshot file when its text differs; `changed` says whether it did. Shared with the media snapshot. */
|
|
151
|
+
export function writeSnapshotFile(projectRoot, file, text) {
|
|
152
|
+
const path = join(projectRoot, file);
|
|
153
|
+
const current = existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
154
|
+
if (current === text)
|
|
155
|
+
return { path: relative(projectRoot, path), changed: false };
|
|
156
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
157
|
+
writeFileSync(path, text);
|
|
158
|
+
return { path: relative(projectRoot, path), changed: true };
|
|
159
|
+
}
|
|
160
|
+
/** The folder list a snapshot records, checked and sorted. */
|
|
161
|
+
export function snapshotSourcePaths(sources = ESTATE_PLAN_SNAPSHOT_SOURCES) {
|
|
162
|
+
return sortedUnique(sources.map(assertSourcePath));
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=estate-snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estate-snapshot.js","sourceRoot":"","sources":["../src/estate-snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The estate plan snapshot (3.15.0): a site whose waves or estate plan are computed (from its location catalogue,\n * for example) records them as plain JSON, so the ready gate's LOC-INDEX can read them without running the site's\n * code. The snapshot is deterministic (sorted routes and keys, no timestamp) and carries a hash of the source files it\n * was written from (`src/data` by default), so the gate fails a snapshot older than those files.\n *\n * The site writes it from its own tests, where its module aliases resolve:\n *\n * // tests/data/estatePlanSnapshot.test.ts\n * import { writeEstatePlanSnapshot } from '@brandlab-digital/conformance/estate-snapshot'\n * import { siteStrategy } from '@/data/strategy'\n *\n * it('records the estate plan for the ready gate', () => {\n * const { changed } = writeEstatePlanSnapshot(siteStrategy, { projectRoot: process.cwd() })\n * if (process.env.CI) expect(changed).toBe(false)\n * })\n *\n * Node only: it reads and writes files.\n */\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, normalize, relative, sep } from \"node:path\";\nimport type { SiteEstatePlan, SiteReleaseWave, SiteStrategy } from \"@brandlab-digital/site-core/strategy\";\n\n/** Where the snapshot lives, relative to the project root. */\nexport const ESTATE_PLAN_SNAPSHOT_FILE = \"src/data/estate-plan.generated.json\";\n/** The folders hashed by default: the snapshot is stale once any file in them changes. */\nexport const ESTATE_PLAN_SNAPSHOT_SOURCES: readonly string[] = Object.freeze([\"src/data\"]);\nexport const ESTATE_PLAN_SNAPSHOT_KIND = \"brandlab-estate-plan-snapshot\";\nexport const ESTATE_PLAN_SNAPSHOT_VERSION = 1;\n\n/** The source files hashed: code and data, never tests or generated files. */\nconst SOURCE_FILE = /\\.(?:ts|tsx|mts|cts|js|mjs|cjs|json|mdx?|csv)$/;\nconst SKIPPED_FILE = /(?:\\.generated\\.json|\\.(?:test|spec)\\.[a-z]+)$/;\n\nexport interface EstatePlanSnapshot {\n readonly kind: typeof ESTATE_PLAN_SNAPSHOT_KIND;\n readonly version: typeof ESTATE_PLAN_SNAPSHOT_VERSION;\n /** The folders hashed and the hash of their files when the snapshot was written. */\n readonly sources: { readonly paths: readonly string[]; readonly sha256: string };\n readonly waves?: readonly SiteReleaseWave[];\n readonly estate?: SiteEstatePlan;\n}\n\nexport interface EstatePlanSnapshotOptions {\n readonly projectRoot: string;\n /** Folders (or files) relative to the project root the plan is computed from. Defaults to `src/data`. */\n readonly sources?: readonly string[];\n}\n\nfunction sortedUnique(values: readonly string[]): string[] {\n return [...new Set(values)].sort();\n}\n\nfunction sortedRecord(record: Readonly<Record<string, string>>): Record<string, string> {\n return Object.fromEntries(Object.entries(record).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)));\n}\n\n/**\n * The waves and estate plan in their canonical form: waves in date order (stable), each wave's routes sorted, the\n * plan's lists sorted and its keys in a fixed order. Two plans that mean the same thing serialise the same.\n */\nexport function canonicalEstatePlan(strategy: Pick<SiteStrategy, \"waves\" | \"estate\">): Pick<EstatePlanSnapshot, \"waves\" | \"estate\"> {\n const result: { waves?: SiteReleaseWave[]; estate?: SiteEstatePlan } = {};\n if (strategy.waves !== undefined) {\n result.waves = [...strategy.waves]\n .map((wave, index) => ({ wave, index }))\n .sort((left, right) => (left.wave.releaseOn < right.wave.releaseOn ? -1 : left.wave.releaseOn > right.wave.releaseOn ? 1 : left.index - right.index))\n .map(({ wave }) => ({\n releaseOn: wave.releaseOn,\n ...(wave.kind !== undefined ? { kind: wave.kind } : {}),\n routes: sortedUnique(wave.routes),\n }));\n }\n const plan = strategy.estate;\n if (plan !== undefined) {\n const paths = plan.paths;\n const slugs = plan.slugs;\n result.estate = {\n indexedNow: sortedUnique(plan.indexedNow),\n ...(paths !== undefined\n ? {\n paths: {\n ...(paths.region !== undefined ? { region: paths.region } : {}),\n ...(paths.suburb !== undefined ? { suburb: paths.suburb } : {}),\n ...(paths.serviceInSuburb !== undefined ? { serviceInSuburb: sortedUnique(paths.serviceInSuburb) } : {}),\n },\n }\n : {}),\n ...(slugs !== undefined\n ? {\n slugs: {\n ...(slugs.regions !== undefined ? { regions: sortedRecord(slugs.regions) } : {}),\n ...(slugs.suburbs !== undefined ? { suburbs: sortedRecord(slugs.suburbs) } : {}),\n },\n }\n : {}),\n };\n }\n return result;\n}\n\nfunction assertSourcePath(path: string): string {\n if (typeof path !== \"string\" || path === \"\" || isAbsolute(path)) {\n throw new Error(`The snapshot source \"${String(path)}\" must be a folder or file inside the project.`);\n }\n const clean = normalize(path).split(sep).join(\"/\").replace(/\\/$/, \"\");\n if (clean === \".\" || clean === \"..\" || clean.startsWith(\"../\")) {\n throw new Error(`The snapshot source \"${path}\" must be a folder or file inside the project.`);\n }\n return clean;\n}\n\nfunction collectFiles(root: string, path: string, files: string[]): void {\n const absolute = join(root, path);\n if (!existsSync(absolute)) return;\n if (statSync(absolute).isFile()) {\n if (!SKIPPED_FILE.test(path)) files.push(path);\n return;\n }\n for (const name of readdirSync(absolute)) {\n if (name === \"node_modules\" || name.startsWith(\".\")) continue;\n const child = `${path}/${name}`;\n // statSync follows symbolic links, so a linked file or folder is hashed by what it points to.\n const stats = statSync(join(root, child), { throwIfNoEntry: false });\n if (stats?.isDirectory()) collectFiles(root, child, files);\n else if (stats?.isFile() && SOURCE_FILE.test(name) && !SKIPPED_FILE.test(name)) files.push(child);\n }\n}\n\n/**\n * The hash of the files the plan is computed from: every code and data file under `sources` (tests and generated\n * files left out), by path and content in path order. The ready gate recomputes it to tell a stale snapshot.\n */\nexport function estatePlanSourceHash(projectRoot: string, sources: readonly string[] = ESTATE_PLAN_SNAPSHOT_SOURCES): string {\n const files: string[] = [];\n for (const source of sortedUnique(sources.map(assertSourcePath))) collectFiles(projectRoot, source, files);\n const hash = createHash(\"sha256\");\n for (const file of sortedUnique(files)) {\n hash.update(`${file}\\n`);\n hash.update(readFileSync(join(projectRoot, file)));\n hash.update(\"\\n\");\n }\n return hash.digest(\"hex\");\n}\n\n/** The snapshot for a strategy record: its waves and estate plan in canonical form, with the sources' hash. */\nexport function estatePlanSnapshot(strategy: Pick<SiteStrategy, \"waves\" | \"estate\">, options: EstatePlanSnapshotOptions): EstatePlanSnapshot {\n const paths = sortedUnique((options.sources ?? ESTATE_PLAN_SNAPSHOT_SOURCES).map(assertSourcePath));\n return {\n kind: ESTATE_PLAN_SNAPSHOT_KIND,\n version: ESTATE_PLAN_SNAPSHOT_VERSION,\n sources: { paths, sha256: estatePlanSourceHash(options.projectRoot, paths) },\n ...canonicalEstatePlan(strategy),\n };\n}\n\n/** The snapshot as the file holds it: two-space JSON and a final newline. */\nexport function serialiseEstatePlanSnapshot(snapshot: EstatePlanSnapshot): string {\n return `${JSON.stringify(snapshot, null, 2)}\\n`;\n}\n\n/**\n * Writes `src/data/estate-plan.generated.json` when its content differs; `changed` says whether it did. Run it from the\n * site's tests or build so the snapshot follows the strategy record, and commit the file.\n */\nexport function writeEstatePlanSnapshot(\n strategy: Pick<SiteStrategy, \"waves\" | \"estate\">,\n options: EstatePlanSnapshotOptions\n): { readonly path: string; readonly changed: boolean; readonly snapshot: EstatePlanSnapshot } {\n const snapshot = estatePlanSnapshot(strategy, options);\n return { ...writeSnapshotFile(options.projectRoot, ESTATE_PLAN_SNAPSHOT_FILE, serialiseEstatePlanSnapshot(snapshot)), snapshot };\n}\n\n/** Writes a snapshot file when its text differs; `changed` says whether it did. Shared with the media snapshot. */\nexport function writeSnapshotFile(projectRoot: string, file: string, text: string): { readonly path: string; readonly changed: boolean } {\n const path = join(projectRoot, file);\n const current = existsSync(path) ? readFileSync(path, \"utf8\") : null;\n if (current === text) return { path: relative(projectRoot, path), changed: false };\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, text);\n return { path: relative(projectRoot, path), changed: true };\n}\n\n/** The folder list a snapshot records, checked and sorted. */\nexport function snapshotSourcePaths(sources: readonly string[] = ESTATE_PLAN_SNAPSHOT_SOURCES): string[] {\n return sortedUnique(sources.map(assertSourcePath));\n}\n"]}
|
package/dist/estate.d.ts
CHANGED
|
@@ -212,6 +212,16 @@ export declare function pageQualityRunFromEnv(env?: NodeJS.ProcessEnv): {
|
|
|
212
212
|
reason: string;
|
|
213
213
|
directory?: string;
|
|
214
214
|
};
|
|
215
|
+
/**
|
|
216
|
+
* The internal pages a page's server HTML links, once each, without the page itself. Only anchors count: an area named
|
|
217
|
+
* as plain text (a `Coverage` entry with no `href`, 3.15.0) is no link.
|
|
218
|
+
*/
|
|
219
|
+
export declare function pageLinkTargets(html: string, path: string, origin: string, base?: string): Set<string>;
|
|
220
|
+
/**
|
|
221
|
+
* Indexable pages that link a noindex location page (D4: list a place whose page waits for its wave as plain text,
|
|
222
|
+
* as site-core's `coverageEntries` does). `linksFrom` maps each page to the pages it links.
|
|
223
|
+
*/
|
|
224
|
+
export declare function noindexLinkFindings(linksFrom: ReadonlyMap<string, ReadonlySet<string>>, routes: readonly Pick<BrandlabEstateRoute, 'path' | 'indexing' | 'kind'>[]): BrandlabPageQualityFinding[];
|
|
215
225
|
/**
|
|
216
226
|
* Absolute (`https://host`) and protocol-relative (`//host`) destinations leave
|
|
217
227
|
* the site; browsers also read `/\host` and `\\host` as protocol-relative.
|
package/dist/estate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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"}
|
package/dist/estate.js
CHANGED
|
@@ -659,26 +659,14 @@ export function defineBrandlabPageQualityTests(options) {
|
|
|
659
659
|
const place = placeOfRoute(route)?.slug;
|
|
660
660
|
if (indexable && place)
|
|
661
661
|
similarityPages.push({ path: route.path, place, blocks: textBlocks(html).map(geography.maskPlaces) });
|
|
662
|
-
|
|
663
|
-
for (const href of extractLinks(html)) {
|
|
664
|
-
const path = internalPath(href, expectedCanonical, origin);
|
|
665
|
-
if (path && path !== normalisePath(route.path))
|
|
666
|
-
targets.add(path);
|
|
667
|
-
}
|
|
668
|
-
linksFrom.set(normalisePath(route.path), targets);
|
|
662
|
+
linksFrom.set(normalisePath(route.path), pageLinkTargets(html, route.path, origin, expectedCanonical));
|
|
669
663
|
});
|
|
670
664
|
// Internal links: every indexable page needs a link from another page, and indexable pages link no noindex location page.
|
|
671
665
|
const inbound = new Map();
|
|
672
|
-
for (const
|
|
673
|
-
const
|
|
674
|
-
for (const target of targets) {
|
|
666
|
+
for (const targets of linksFrom.values())
|
|
667
|
+
for (const target of targets)
|
|
675
668
|
inbound.set(target, (inbound.get(target) ?? 0) + 1);
|
|
676
|
-
|
|
677
|
-
if (source?.indexing === 'index' && linked && linked.indexing !== 'index' && LOCATION_KINDS.has(linked.kind ?? '')) {
|
|
678
|
-
findings.push({ path: from, kind: 'noindex-link', detail: `links to ${target}, a noindex location page (list unindexed places as plain text)` });
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
669
|
+
findings.push(...noindexLinkFindings(linksFrom, routes));
|
|
682
670
|
for (const route of routes) {
|
|
683
671
|
const path = normalisePath(route.path);
|
|
684
672
|
if (route.indexing === 'index' && route.kind !== 'home' && path !== '/' && linksFrom.has(path) && !inbound.get(path)) {
|
|
@@ -789,6 +777,38 @@ function inspectStructuredData(route, payloads, businessId) {
|
|
|
789
777
|
failures.push(`${route.path}: JSON-LD does not reference ${businessId}`);
|
|
790
778
|
return failures;
|
|
791
779
|
}
|
|
780
|
+
/**
|
|
781
|
+
* The internal pages a page's server HTML links, once each, without the page itself. Only anchors count: an area named
|
|
782
|
+
* as plain text (a `Coverage` entry with no `href`, 3.15.0) is no link.
|
|
783
|
+
*/
|
|
784
|
+
export function pageLinkTargets(html, path, origin, base = new URL(path, `${origin}/`).href) {
|
|
785
|
+
const targets = new Set();
|
|
786
|
+
for (const href of extractLinks(html)) {
|
|
787
|
+
const target = internalPath(href, base, origin);
|
|
788
|
+
if (target && target !== normalisePath(path))
|
|
789
|
+
targets.add(target);
|
|
790
|
+
}
|
|
791
|
+
return targets;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Indexable pages that link a noindex location page (D4: list a place whose page waits for its wave as plain text,
|
|
795
|
+
* as site-core's `coverageEntries` does). `linksFrom` maps each page to the pages it links.
|
|
796
|
+
*/
|
|
797
|
+
export function noindexLinkFindings(linksFrom, routes) {
|
|
798
|
+
const routeByPath = new Map(routes.map((route) => [normalisePath(route.path), route]));
|
|
799
|
+
const findings = [];
|
|
800
|
+
for (const [from, targets] of linksFrom) {
|
|
801
|
+
if (routeByPath.get(from)?.indexing !== 'index')
|
|
802
|
+
continue;
|
|
803
|
+
for (const target of targets) {
|
|
804
|
+
const linked = routeByPath.get(target);
|
|
805
|
+
if (linked && linked.indexing !== 'index' && LOCATION_KINDS.has(linked.kind ?? '')) {
|
|
806
|
+
findings.push({ path: from, kind: 'noindex-link', detail: `links to ${target}, a noindex location page (list unindexed places as plain text)` });
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return findings;
|
|
811
|
+
}
|
|
792
812
|
function extractLinks(html) {
|
|
793
813
|
return [...html.matchAll(/<a\b[^>]*\bhref=["']([^"']+)["']/gi)].map((match) => decodeXml(match[1]));
|
|
794
814
|
}
|