@brandlab-digital/conformance 3.11.0 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/current-release.js +6 -6
- package/dist/current-release.js.map +1 -1
- package/dist/ready-cli.d.ts.map +1 -1
- package/dist/ready-cli.js +1 -0
- package/dist/ready-cli.js.map +1 -1
- package/dist/site-system/checks.d.ts +1 -1
- package/dist/site-system/checks.js +1 -1
- package/dist/site-system/checks.js.map +1 -1
- package/dist/site-system/rule-modules.d.ts.map +1 -1
- package/dist/site-system/rule-modules.js +5 -0
- package/dist/site-system/rule-modules.js.map +1 -1
- package/dist/site-system/rules/full/common.d.ts +37 -0
- package/dist/site-system/rules/full/common.d.ts.map +1 -0
- package/dist/site-system/rules/full/common.js +51 -0
- package/dist/site-system/rules/full/common.js.map +1 -0
- package/dist/site-system/rules/full/e10-phrases.d.ts +14 -0
- package/dist/site-system/rules/full/e10-phrases.d.ts.map +1 -0
- package/dist/site-system/rules/full/e10-phrases.js +69 -0
- package/dist/site-system/rules/full/e10-phrases.js.map +1 -0
- package/dist/site-system/rules/full/estate.d.ts +118 -0
- package/dist/site-system/rules/full/estate.d.ts.map +1 -0
- package/dist/site-system/rules/full/estate.js +447 -0
- package/dist/site-system/rules/full/estate.js.map +1 -0
- package/dist/site-system/rules/full/geography.d.ts +60 -0
- package/dist/site-system/rules/full/geography.d.ts.map +1 -0
- package/dist/site-system/rules/full/geography.js +111 -0
- package/dist/site-system/rules/full/geography.js.map +1 -0
- package/dist/site-system/rules/full/index.d.ts +33 -0
- package/dist/site-system/rules/full/index.d.ts.map +1 -0
- package/dist/site-system/rules/full/index.js +126 -0
- package/dist/site-system/rules/full/index.js.map +1 -0
- package/dist/site-system/rules/full/k12-manifests.d.ts +19 -0
- package/dist/site-system/rules/full/k12-manifests.d.ts.map +1 -0
- package/dist/site-system/rules/full/k12-manifests.js +130 -0
- package/dist/site-system/rules/full/k12-manifests.js.map +1 -0
- package/dist/site-system/rules/full/l10-location-copy.d.ts +15 -0
- package/dist/site-system/rules/full/l10-location-copy.d.ts.map +1 -0
- package/dist/site-system/rules/full/l10-location-copy.js +59 -0
- package/dist/site-system/rules/full/l10-location-copy.js.map +1 -0
- package/dist/site-system/rules/full/l3-neighbours.d.ts +16 -0
- package/dist/site-system/rules/full/l3-neighbours.d.ts.map +1 -0
- package/dist/site-system/rules/full/l3-neighbours.js +78 -0
- package/dist/site-system/rules/full/l3-neighbours.js.map +1 -0
- package/dist/site-system/rules/full/l4-local-copy.d.ts +27 -0
- package/dist/site-system/rules/full/l4-local-copy.d.ts.map +1 -0
- package/dist/site-system/rules/full/l4-local-copy.js +150 -0
- package/dist/site-system/rules/full/l4-local-copy.js.map +1 -0
- package/dist/site-system/rules/full/l9-data-contract.d.ts +15 -0
- package/dist/site-system/rules/full/l9-data-contract.d.ts.map +1 -0
- package/dist/site-system/rules/full/l9-data-contract.js +65 -0
- package/dist/site-system/rules/full/l9-data-contract.js.map +1 -0
- package/dist/site-system/rules/full/links.d.ts +23 -0
- package/dist/site-system/rules/full/links.d.ts.map +1 -0
- package/dist/site-system/rules/full/links.js +131 -0
- package/dist/site-system/rules/full/links.js.map +1 -0
- package/dist/site-system/rules/full/loc-index.d.ts +16 -0
- package/dist/site-system/rules/full/loc-index.d.ts.map +1 -0
- package/dist/site-system/rules/full/loc-index.js +59 -0
- package/dist/site-system/rules/full/loc-index.js.map +1 -0
- package/dist/site-system/rules/full/s4-route-files.d.ts +4 -0
- package/dist/site-system/rules/full/s4-route-files.d.ts.map +1 -0
- package/dist/site-system/rules/full/s4-route-files.js +125 -0
- package/dist/site-system/rules/full/s4-route-files.js.map +1 -0
- package/dist/site-system/rules/full/strategy.d.ts +43 -0
- package/dist/site-system/rules/full/strategy.d.ts.map +1 -0
- package/dist/site-system/rules/full/strategy.js +147 -0
- package/dist/site-system/rules/full/strategy.js.map +1 -0
- package/dist/site-system/runners.d.ts +2 -0
- package/dist/site-system/runners.d.ts.map +1 -1
- package/dist/site-system/runners.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { fail, outcome, plural } from "./common.js";
|
|
2
|
+
import { FULL_STRATEGY_FILE, normalisePhrase } from "./strategy.js";
|
|
3
|
+
/** Words that join a phrase to its place in a heading ("Plumber in Ryde"). */
|
|
4
|
+
const PLACE_JOINERS = /\b(?:in|near|around|across|servicing|serving|for|of|the|nsw)\b/g;
|
|
5
|
+
/** Words that qualify a trade without making it another service ("Local plumber", "Licensed emergency plumbers"). */
|
|
6
|
+
const QUALIFIERS = /\b(?:local|residential|commercial|domestic|emergency|licensed|licenced|qualified|certified|professional|expert|experienced|trusted|reliable|affordable|best|top|24\/7|24 hour|24-hour|same day|same-day)\b/g;
|
|
7
|
+
/** A heading's phrase without its place: "Blocked drains in Ryde" reads "blocked drain". */
|
|
8
|
+
export function headingPhrase(heading, mask) {
|
|
9
|
+
return normalisePhrase(mask(heading.toLowerCase()).replace(/\{place\}|\{postcode\}/g, " ").replace(QUALIFIERS, " ").replace(PLACE_JOINERS, " "));
|
|
10
|
+
}
|
|
11
|
+
/** The service slug in a stem, as a phrase: `/services/plumber/{place}` and `/plumber-{place}` read "plumber". */
|
|
12
|
+
export function stemPhrase(stem) {
|
|
13
|
+
const before = stem.slice(0, stem.indexOf("{place}")).replace(/[/-]+$/, "");
|
|
14
|
+
return normalisePhrase((before.split("/").pop() ?? "").replace(/-/g, " ").replace(QUALIFIERS, " "));
|
|
15
|
+
}
|
|
16
|
+
export function checkE10(context) {
|
|
17
|
+
const { strategy, estate, geography } = context;
|
|
18
|
+
if (!strategy.found)
|
|
19
|
+
return [fail("E10", FULL_STRATEGY_FILE, `The site has no ${FULL_STRATEGY_FILE}, so phrase ownership is not recorded.`)];
|
|
20
|
+
if (strategy.problems.phrases.length > 0)
|
|
21
|
+
return strategy.problems.phrases.map((problem) => fail("E10", FULL_STRATEGY_FILE, problem));
|
|
22
|
+
if (!strategy.phrases) {
|
|
23
|
+
return [fail("E10", FULL_STRATEGY_FILE, `${FULL_STRATEGY_FILE} records no phrases (the main trade and the owner of each primary phrase).`)];
|
|
24
|
+
}
|
|
25
|
+
const failures = [];
|
|
26
|
+
const owners = new Map();
|
|
27
|
+
for (const owner of strategy.phrases.owners) {
|
|
28
|
+
const key = normalisePhrase(owner.phrase);
|
|
29
|
+
const other = owners.get(key);
|
|
30
|
+
if (other && other.route !== owner.route) {
|
|
31
|
+
failures.push(fail("E10", FULL_STRATEGY_FILE, `The phrase "${owner.phrase}" has two owners in ${FULL_STRATEGY_FILE}: ${other.route} and ${owner.route}.`));
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
owners.set(key, owner);
|
|
35
|
+
}
|
|
36
|
+
if (estate.complete) {
|
|
37
|
+
for (const owner of strategy.phrases.owners) {
|
|
38
|
+
const page = estate.byPath.get(owner.route);
|
|
39
|
+
if (!page)
|
|
40
|
+
failures.push(fail("E10", owner.route, `The phrase "${owner.phrase}" is owned by ${owner.route}, which the build has no page for.`));
|
|
41
|
+
else if (!page.indexable)
|
|
42
|
+
failures.push(fail("E10", owner.route, `The phrase "${owner.phrase}" is owned by ${owner.route}, which is not indexable.`));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const mainTrade = normalisePhrase(strategy.phrases.mainTrade.toLowerCase().replace(QUALIFIERS, " "));
|
|
46
|
+
let compared = 0;
|
|
47
|
+
if (geography) {
|
|
48
|
+
const serviceStems = new Set(estate.pages.flatMap((page) => (page.family === "service-in-suburb" && page.place ? [page.place.stem] : [])));
|
|
49
|
+
for (const page of estate.pages) {
|
|
50
|
+
if (!page.place)
|
|
51
|
+
continue;
|
|
52
|
+
const serviceInSuburb = page.family === "service-in-suburb" && page.place.kind === "suburb";
|
|
53
|
+
const serviceRegionHub = page.family === "region" && page.place.kind === "region" && serviceStems.has(page.place.stem);
|
|
54
|
+
if (!serviceInSuburb && !serviceRegionHub)
|
|
55
|
+
continue;
|
|
56
|
+
compared += 1;
|
|
57
|
+
if (headingPhrase(page.h1, geography.mask) !== mainTrade && stemPhrase(page.place.stem) !== mainTrade)
|
|
58
|
+
continue;
|
|
59
|
+
failures.push(fail("E10", page.path, serviceInSuburb
|
|
60
|
+
? `${page.path} is a service-in-suburb page for "${page.h1 || page.path}", the main trade in a suburb, which the suburb page owns.`
|
|
61
|
+
: `${page.path} is a service-region hub for "${page.h1 || page.path}", the main trade in a region, which the region page owns.`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (estate.pages.some((page) => page.family === "service-in-suburb" || page.family === "region")) {
|
|
65
|
+
failures.push(fail("E10", undefined, `The place names could not be read (${context.geographyProblem}), so service pages were not compared with the main trade.`));
|
|
66
|
+
}
|
|
67
|
+
return outcome("E10", failures, `Each of ${plural(owners.size, "primary phrase")} has one indexable owner, and none of ${plural(compared, "service-in-suburb page and hub", "service-in-suburb pages and hubs")} competes for the main trade "${strategy.phrases.mainTrade}".`);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=e10-phrases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"e10-phrases.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/e10-phrases.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * E10, phrase ownership (Site System v2 §4.2, §4.4 step 3, §5.3). strategy.ts records the main trade and one owning\n * page per primary phrase; no phrase has two owners and every owner is an indexable page. The suburb page owns\n * \"{main trade} {suburb}\" and the region page \"{main trade} {region}\", so no service-in-suburb page and no\n * service-region hub carries the main trade as its own phrase (read from its H1 with the place taken out).\n */\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, outcome, plural, type FullContext } from \"./common.js\";\nimport { FULL_STRATEGY_FILE, normalisePhrase } from \"./strategy.js\";\n\n/** Words that join a phrase to its place in a heading (\"Plumber in Ryde\"). */\nconst PLACE_JOINERS = /\\b(?:in|near|around|across|servicing|serving|for|of|the|nsw)\\b/g;\n/** Words that qualify a trade without making it another service (\"Local plumber\", \"Licensed emergency plumbers\"). */\nconst QUALIFIERS = /\\b(?:local|residential|commercial|domestic|emergency|licensed|licenced|qualified|certified|professional|expert|experienced|trusted|reliable|affordable|best|top|24\\/7|24 hour|24-hour|same day|same-day)\\b/g;\n\n/** A heading's phrase without its place: \"Blocked drains in Ryde\" reads \"blocked drain\". */\nexport function headingPhrase(heading: string, mask: (text: string) => string): string {\n return normalisePhrase(mask(heading.toLowerCase()).replace(/\\{place\\}|\\{postcode\\}/g, \" \").replace(QUALIFIERS, \" \").replace(PLACE_JOINERS, \" \"));\n}\n\n/** The service slug in a stem, as a phrase: `/services/plumber/{place}` and `/plumber-{place}` read \"plumber\". */\nexport function stemPhrase(stem: string): string {\n const before = stem.slice(0, stem.indexOf(\"{place}\")).replace(/[/-]+$/, \"\");\n return normalisePhrase((before.split(\"/\").pop() ?? \"\").replace(/-/g, \" \").replace(QUALIFIERS, \" \"));\n}\n\nexport function checkE10(context: FullContext): SiteSystemFinding[] {\n const { strategy, estate, geography } = context;\n if (!strategy.found) return [fail(\"E10\", FULL_STRATEGY_FILE, `The site has no ${FULL_STRATEGY_FILE}, so phrase ownership is not recorded.`)];\n if (strategy.problems.phrases.length > 0) return strategy.problems.phrases.map((problem) => fail(\"E10\", FULL_STRATEGY_FILE, problem));\n if (!strategy.phrases) {\n return [fail(\"E10\", FULL_STRATEGY_FILE, `${FULL_STRATEGY_FILE} records no phrases (the main trade and the owner of each primary phrase).`)];\n }\n const failures: SiteSystemFinding[] = [];\n const owners = new Map<string, { phrase: string; route: string }>();\n for (const owner of strategy.phrases.owners) {\n const key = normalisePhrase(owner.phrase);\n const other = owners.get(key);\n if (other && other.route !== owner.route) {\n failures.push(fail(\"E10\", FULL_STRATEGY_FILE, `The phrase \"${owner.phrase}\" has two owners in ${FULL_STRATEGY_FILE}: ${other.route} and ${owner.route}.`));\n } else owners.set(key, owner);\n }\n if (estate.complete) {\n for (const owner of strategy.phrases.owners) {\n const page = estate.byPath.get(owner.route);\n if (!page) failures.push(fail(\"E10\", owner.route, `The phrase \"${owner.phrase}\" is owned by ${owner.route}, which the build has no page for.`));\n else if (!page.indexable) failures.push(fail(\"E10\", owner.route, `The phrase \"${owner.phrase}\" is owned by ${owner.route}, which is not indexable.`));\n }\n }\n\n const mainTrade = normalisePhrase(strategy.phrases.mainTrade.toLowerCase().replace(QUALIFIERS, \" \"));\n let compared = 0;\n if (geography) {\n const serviceStems = new Set(estate.pages.flatMap((page) => (page.family === \"service-in-suburb\" && page.place ? [page.place.stem] : [])));\n for (const page of estate.pages) {\n if (!page.place) continue;\n const serviceInSuburb = page.family === \"service-in-suburb\" && page.place.kind === \"suburb\";\n const serviceRegionHub = page.family === \"region\" && page.place.kind === \"region\" && serviceStems.has(page.place.stem);\n if (!serviceInSuburb && !serviceRegionHub) continue;\n compared += 1;\n if (headingPhrase(page.h1, geography.mask) !== mainTrade && stemPhrase(page.place.stem) !== mainTrade) continue;\n failures.push(fail(\n \"E10\",\n page.path,\n serviceInSuburb\n ? `${page.path} is a service-in-suburb page for \"${page.h1 || page.path}\", the main trade in a suburb, which the suburb page owns.`\n : `${page.path} is a service-region hub for \"${page.h1 || page.path}\", the main trade in a region, which the region page owns.`,\n ));\n }\n } else if (estate.pages.some((page) => page.family === \"service-in-suburb\" || page.family === \"region\")) {\n failures.push(fail(\"E10\", undefined, `The place names could not be read (${context.geographyProblem}), so service pages were not compared with the main trade.`));\n }\n return outcome(\n \"E10\",\n failures,\n `Each of ${plural(owners.size, \"primary phrase\")} has one indexable owner, and none of ${plural(compared, \"service-in-suburb page and hub\", \"service-in-suburb pages and hubs\")} competes for the main trade \"${strategy.phrases.mainTrade}\".`,\n );\n}\n"]}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { SiteSystemPage } from "../../samples.js";
|
|
2
|
+
import { type FullGeography, type PathPlace } from "./geography.js";
|
|
3
|
+
/** The section manifest as SectionSequence writes it (site-core `BrandlabSectionManifest`). */
|
|
4
|
+
export interface ManifestData {
|
|
5
|
+
readonly version: number;
|
|
6
|
+
readonly family: string;
|
|
7
|
+
readonly page?: string;
|
|
8
|
+
readonly cap: number;
|
|
9
|
+
readonly order: "default" | "recorded";
|
|
10
|
+
readonly planned: readonly {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly requirement: string;
|
|
13
|
+
}[];
|
|
14
|
+
readonly rendered: readonly {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly section: string;
|
|
17
|
+
readonly requirement: string;
|
|
18
|
+
readonly tone?: string;
|
|
19
|
+
}[];
|
|
20
|
+
readonly skipped: readonly {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly section: string;
|
|
23
|
+
readonly requirement: string;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
}[];
|
|
26
|
+
readonly counted: number;
|
|
27
|
+
readonly flags: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
export type ManifestState = {
|
|
30
|
+
readonly kind: "none";
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: "invalid";
|
|
33
|
+
readonly reason: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: "many";
|
|
36
|
+
readonly count: number;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: "one";
|
|
39
|
+
readonly manifest: ManifestData;
|
|
40
|
+
};
|
|
41
|
+
export declare const LOCATION_FAMILIES: readonly ["suburb", "service-in-suburb", "region"];
|
|
42
|
+
export type LocationFamily = (typeof LOCATION_FAMILIES)[number];
|
|
43
|
+
export declare function isLocationFamily(family: string | undefined): family is LocationFamily;
|
|
44
|
+
/** The copy and parts of a location page the L4, L9 and L10 rules read. */
|
|
45
|
+
export interface LocationCopy {
|
|
46
|
+
/** Readable lines of the page content, with the section they sit in ("page" outside any section). */
|
|
47
|
+
readonly lines: readonly {
|
|
48
|
+
readonly section: string;
|
|
49
|
+
readonly text: string;
|
|
50
|
+
}[];
|
|
51
|
+
readonly faqs: readonly {
|
|
52
|
+
readonly question: string;
|
|
53
|
+
readonly answer: string;
|
|
54
|
+
}[];
|
|
55
|
+
/** The job cards of the nearby-jobs section; null when the page has none. */
|
|
56
|
+
readonly jobs: readonly string[] | null;
|
|
57
|
+
/** The price-guide section's text; null when the page has none. */
|
|
58
|
+
readonly prices: string | null;
|
|
59
|
+
readonly reviews: {
|
|
60
|
+
readonly written: number;
|
|
61
|
+
readonly googleLink: boolean;
|
|
62
|
+
readonly rating: boolean;
|
|
63
|
+
} | null;
|
|
64
|
+
readonly quoteForm: {
|
|
65
|
+
readonly suburbField: boolean;
|
|
66
|
+
readonly suburbValue: string;
|
|
67
|
+
} | null;
|
|
68
|
+
readonly telLink: boolean;
|
|
69
|
+
readonly callBar: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface EstatePage {
|
|
72
|
+
readonly path: string;
|
|
73
|
+
/** The app route Next built the page from (`/locations/[suburb]`), when known. */
|
|
74
|
+
readonly srcRoute: string | undefined;
|
|
75
|
+
readonly indexable: boolean;
|
|
76
|
+
readonly inSitemap: boolean;
|
|
77
|
+
/** The sitemap file that lists the page, without its number and extension (`service-locations`). */
|
|
78
|
+
readonly sitemapFile: string | undefined;
|
|
79
|
+
/** Internal paths the page links to, once each. */
|
|
80
|
+
readonly links: readonly string[];
|
|
81
|
+
/** The main content's link groups (lists), as internal paths, for the neighbour order. */
|
|
82
|
+
readonly linkGroups: readonly (readonly string[])[];
|
|
83
|
+
readonly manifest: ManifestState;
|
|
84
|
+
/** Top-level `data-bl-section` ids, in document order. */
|
|
85
|
+
readonly sections: readonly string[];
|
|
86
|
+
readonly h1: string;
|
|
87
|
+
readonly place: PathPlace | undefined;
|
|
88
|
+
family: string | undefined;
|
|
89
|
+
familySource: "manifest" | "sitemap" | "route" | "stem" | "path" | undefined;
|
|
90
|
+
copy: LocationCopy | undefined;
|
|
91
|
+
}
|
|
92
|
+
export interface Estate {
|
|
93
|
+
readonly pages: readonly EstatePage[];
|
|
94
|
+
readonly byPath: ReadonlyMap<string, EstatePage>;
|
|
95
|
+
/** True when the pages are the whole production build, not just a sample. */
|
|
96
|
+
readonly complete: boolean;
|
|
97
|
+
/** Paths listed in the build's sitemap files; null when the build holds no sitemap. */
|
|
98
|
+
readonly sitemap: ReadonlySet<string> | null;
|
|
99
|
+
/** Dynamic app routes in the build and how many pages each produced. */
|
|
100
|
+
readonly dynamicRoutes: ReadonlyMap<string, number>;
|
|
101
|
+
readonly buildDir: string;
|
|
102
|
+
/** Routes the prerender manifest lists with no HTML in the build; `location` when the route is a location page's. */
|
|
103
|
+
readonly missingHtml: readonly {
|
|
104
|
+
readonly path: string;
|
|
105
|
+
readonly location: boolean;
|
|
106
|
+
}[];
|
|
107
|
+
}
|
|
108
|
+
/** Reads the estate: the whole build when `buildDir` holds one, otherwise the sampled pages. */
|
|
109
|
+
export declare function readEstate(input: {
|
|
110
|
+
readonly pages: readonly SiteSystemPage[];
|
|
111
|
+
readonly buildDir: string;
|
|
112
|
+
readonly geography: FullGeography | null;
|
|
113
|
+
/** Families known from elsewhere, by path (the representative service route). */
|
|
114
|
+
readonly seeds?: Readonly<Record<string, string>>;
|
|
115
|
+
}): Estate;
|
|
116
|
+
/** An internal link's normalised path, or null for other sites, anchors on the same page, mail and phone links. */
|
|
117
|
+
export declare function internalPath(href: string, from: string, hosts: ReadonlySet<string>): string | null;
|
|
118
|
+
//# sourceMappingURL=estate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/estate.ts"],"names":[],"mappings":"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"}
|