@brandlab-digital/conformance 3.12.1 → 3.14.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/README.md +41 -1
- package/dist/analytics-e2e.d.ts +10 -16
- package/dist/analytics-e2e.d.ts.map +1 -1
- package/dist/analytics-e2e.js +61 -3
- package/dist/analytics-e2e.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/edge-probe-cli.d.ts +57 -0
- package/dist/edge-probe-cli.d.ts.map +1 -0
- package/dist/edge-probe-cli.js +134 -0
- package/dist/edge-probe-cli.js.map +1 -0
- package/dist/engineering.d.ts +4 -1
- package/dist/engineering.d.ts.map +1 -1
- package/dist/engineering.js +19 -14
- package/dist/engineering.js.map +1 -1
- package/dist/estate-sample.d.ts +60 -0
- package/dist/estate-sample.d.ts.map +1 -0
- package/dist/estate-sample.js +144 -0
- package/dist/estate-sample.js.map +1 -0
- package/dist/estate.d.ts +45 -0
- package/dist/estate.d.ts.map +1 -1
- package/dist/estate.js +82 -22
- package/dist/estate.js.map +1 -1
- package/dist/origin-region.d.ts +14 -0
- package/dist/origin-region.d.ts.map +1 -0
- package/dist/origin-region.js +46 -0
- package/dist/origin-region.js.map +1 -0
- package/dist/prefetch-probe-cli.d.ts +36 -0
- package/dist/prefetch-probe-cli.d.ts.map +1 -0
- package/dist/prefetch-probe-cli.js +163 -0
- package/dist/prefetch-probe-cli.js.map +1 -0
- package/dist/project.d.ts +20 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +92 -2
- package/dist/project.js.map +1 -1
- package/dist/ready-cli.d.ts.map +1 -1
- package/dist/ready-cli.js +8 -3
- package/dist/ready-cli.js.map +1 -1
- package/dist/rule-registry.d.ts +5 -0
- package/dist/rule-registry.d.ts.map +1 -1
- package/dist/rule-registry.js +1 -0
- package/dist/rule-registry.js.map +1 -1
- package/dist/site-system/checks.d.ts +62 -1
- package/dist/site-system/checks.d.ts.map +1 -1
- package/dist/site-system/checks.js +9 -0
- package/dist/site-system/checks.js.map +1 -1
- package/dist/site-system/config.d.ts +27 -0
- package/dist/site-system/config.d.ts.map +1 -1
- package/dist/site-system/config.js +50 -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/plan.d.ts +9 -2
- package/dist/site-system/journeys/plan.d.ts.map +1 -1
- package/dist/site-system/journeys/plan.js +11 -1
- package/dist/site-system/journeys/plan.js.map +1 -1
- package/dist/site-system/journeys/prefetch-measure.d.ts +28 -0
- package/dist/site-system/journeys/prefetch-measure.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch-measure.js +94 -0
- package/dist/site-system/journeys/prefetch-measure.js.map +1 -0
- package/dist/site-system/journeys/prefetch-policy.d.ts +85 -0
- package/dist/site-system/journeys/prefetch-policy.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch-policy.js +152 -0
- package/dist/site-system/journeys/prefetch-policy.js.map +1 -0
- package/dist/site-system/journeys/prefetch.d.ts +3 -0
- package/dist/site-system/journeys/prefetch.d.ts.map +1 -0
- package/dist/site-system/journeys/prefetch.js +37 -0
- package/dist/site-system/journeys/prefetch.js.map +1 -0
- package/dist/site-system/journeys/variants.d.ts +34 -0
- package/dist/site-system/journeys/variants.d.ts.map +1 -0
- package/dist/site-system/journeys/variants.js +139 -0
- package/dist/site-system/journeys/variants.js.map +1 -0
- package/dist/site-system/report.d.ts.map +1 -1
- package/dist/site-system/report.js +19 -5
- package/dist/site-system/report.js.map +1 -1
- package/dist/site-system/rule-modules.d.ts.map +1 -1
- package/dist/site-system/rule-modules.js +10 -0
- package/dist/site-system/rule-modules.js.map +1 -1
- package/dist/site-system/rules/delivery/h13-inbox.d.ts +23 -0
- package/dist/site-system/rules/delivery/h13-inbox.d.ts.map +1 -0
- package/dist/site-system/rules/delivery/h13-inbox.js +208 -0
- package/dist/site-system/rules/delivery/h13-inbox.js.map +1 -0
- package/dist/site-system/rules/delivery/index.d.ts +12 -0
- package/dist/site-system/rules/delivery/index.d.ts.map +1 -0
- package/dist/site-system/rules/delivery/index.js +7 -0
- package/dist/site-system/rules/delivery/index.js.map +1 -0
- package/dist/site-system/rules/full/common.d.ts +3 -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/e1-pattern.d.ts +4 -0
- package/dist/site-system/rules/full/e1-pattern.d.ts.map +1 -0
- package/dist/site-system/rules/full/e1-pattern.js +47 -0
- package/dist/site-system/rules/full/e1-pattern.js.map +1 -0
- package/dist/site-system/rules/full/estate.d.ts +20 -1
- package/dist/site-system/rules/full/estate.d.ts.map +1 -1
- package/dist/site-system/rules/full/estate.js +33 -5
- package/dist/site-system/rules/full/estate.js.map +1 -1
- package/dist/site-system/rules/full/geography.d.ts +8 -2
- package/dist/site-system/rules/full/geography.d.ts.map +1 -1
- package/dist/site-system/rules/full/geography.js +6 -2
- package/dist/site-system/rules/full/geography.js.map +1 -1
- package/dist/site-system/rules/full/index.d.ts +3 -2
- package/dist/site-system/rules/full/index.d.ts.map +1 -1
- package/dist/site-system/rules/full/index.js +27 -9
- package/dist/site-system/rules/full/index.js.map +1 -1
- package/dist/site-system/rules/full/k12-slots.d.ts +4 -0
- package/dist/site-system/rules/full/k12-slots.d.ts.map +1 -0
- package/dist/site-system/rules/full/k12-slots.js +40 -0
- package/dist/site-system/rules/full/k12-slots.js.map +1 -0
- package/dist/site-system/rules/full/loc-index.d.ts +5 -0
- package/dist/site-system/rules/full/loc-index.d.ts.map +1 -1
- package/dist/site-system/rules/full/loc-index.js +43 -2
- package/dist/site-system/rules/full/loc-index.js.map +1 -1
- package/dist/site-system/rules/full/strategy.d.ts +4 -0
- package/dist/site-system/rules/full/strategy.d.ts.map +1 -1
- package/dist/site-system/rules/full/strategy.js +19 -3
- package/dist/site-system/rules/full/strategy.js.map +1 -1
- package/dist/site-system/rules/full/w3-refresh.d.ts +22 -0
- package/dist/site-system/rules/full/w3-refresh.d.ts.map +1 -0
- package/dist/site-system/rules/full/w3-refresh.js +70 -0
- package/dist/site-system/rules/full/w3-refresh.js.map +1 -0
- package/dist/site-system/rules/guards/index.d.ts +22 -0
- package/dist/site-system/rules/guards/index.d.ts.map +1 -0
- package/dist/site-system/rules/guards/index.js +109 -0
- package/dist/site-system/rules/guards/index.js.map +1 -0
- package/dist/site-system/rules/guards/w10-crawlers.d.ts +55 -0
- package/dist/site-system/rules/guards/w10-crawlers.d.ts.map +1 -0
- package/dist/site-system/rules/guards/w10-crawlers.js +182 -0
- package/dist/site-system/rules/guards/w10-crawlers.js.map +1 -0
- package/dist/site-system/rules/guards/w9-params.d.ts +19 -0
- package/dist/site-system/rules/guards/w9-params.d.ts.map +1 -0
- package/dist/site-system/rules/guards/w9-params.js +84 -0
- package/dist/site-system/rules/guards/w9-params.js.map +1 -0
- 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 +6 -2
- package/dist/site-system/rules/sections/k2-skin-coverage.js.map +1 -1
- package/dist/site-system/runners.js +1 -1
- package/dist/site-system/runners.js.map +1 -1
- package/dist/site-system-e2e.d.ts +1 -0
- package/dist/site-system-e2e.d.ts.map +1 -1
- package/dist/site-system-e2e.js +8 -2
- package/dist/site-system-e2e.js.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"w10-crawlers.js","sourceRoot":"","sources":["../../../../src/site-system/rules/guards/w10-crawlers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * W10, the crawler guard (3.13.0): robots.txt and the recorded firewall rules never block a search or answer crawler\n * (site-core `BRANDLAB_SEARCH_CRAWLERS`), and robots.txt gives none of them a Crawl-delay. The robots.txt is the\n * built one (or the running site's with --base-url); the firewall rules are vercel.json `routes` with `mitigate` and\n * the rules recorded in brandlab.config.ts `hosting.firewall`.\n */\nimport { BRANDLAB_SEARCH_CRAWLERS, type BrandlabSearchCrawler } from \"@brandlab-digital/site-core/site\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, pass } from \"../enquiry/common.js\";\n\nexport interface RobotsGroup {\n /** The user-agent tokens, lower-cased. */\n readonly agents: readonly string[];\n readonly rules: readonly { readonly allow: boolean; readonly path: string }[];\n readonly crawlDelay: string | null;\n}\n\n/** robots.txt as groups (RFC 9309): consecutive user-agent lines, then their rules. Comments and unknown lines are skipped. */\nexport function parseRobotsTxt(text: string): RobotsGroup[] {\n const groups: { agents: string[]; rules: { allow: boolean; path: string }[]; crawlDelay: string | null }[] = [];\n let current: (typeof groups)[number] | null = null;\n let collectingAgents = false;\n for (const raw of text.split(/\\r?\\n/)) {\n const line = raw.replace(/#.*$/, \"\").trim();\n const colon = line.indexOf(\":\");\n if (colon === -1) continue;\n const key = line.slice(0, colon).trim().toLowerCase();\n const value = line.slice(colon + 1).trim();\n if (key === \"user-agent\") {\n if (!collectingAgents || !current) {\n current = { agents: [], rules: [], crawlDelay: null };\n groups.push(current);\n }\n current.agents.push(value.toLowerCase());\n collectingAgents = true;\n continue;\n }\n if (!current) continue;\n collectingAgents = false;\n if (key === \"allow\" || key === \"disallow\") {\n // An empty Disallow allows everything; it adds no rule.\n if (value) current.rules.push({ allow: key === \"allow\", path: value });\n } else if (key === \"crawl-delay\") {\n current.crawlDelay = value;\n }\n }\n return groups;\n}\n\n/** The groups that apply to a crawler: those naming its token, or else the `*` groups. */\nexport function robotsGroupsFor(groups: readonly RobotsGroup[], crawler: string): RobotsGroup[] {\n const token = crawler.toLowerCase();\n const named = groups.filter((group) => group.agents.includes(token));\n return named.length > 0 ? named : groups.filter((group) => group.agents.includes(\"*\"));\n}\n\nfunction patternMatches(pattern: string, path: string): boolean {\n const anchored = pattern.endsWith(\"$\");\n const body = (anchored ? pattern.slice(0, -1) : pattern).split(\"*\").map((part) => part.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\")).join(\".*\");\n return new RegExp(`^${body}${anchored ? \"$\" : \"\"}`).test(path);\n}\n\n/** Whether a crawler may fetch `path` under these groups: the longest matching rule wins, and Allow wins a tie. */\nexport function robotsAllows(groups: readonly RobotsGroup[], crawler: string, path: string): boolean {\n let best: { allow: boolean; length: number } | null = null;\n for (const group of robotsGroupsFor(groups, crawler)) {\n for (const rule of group.rules) {\n if (!patternMatches(rule.path, path)) continue;\n const length = rule.path.length;\n if (!best || length > best.length || (length === best.length && rule.allow)) best = { allow: rule.allow, length };\n }\n }\n return best ? best.allow : true;\n}\n\n/** A user-agent condition from a firewall rule, in the forms Vercel and the brandlab.config record write. */\nexport type UserAgentCondition =\n | { readonly match: \"contains\" | \"equals\" | \"regex\"; readonly value: string }\n | { readonly eq?: string; readonly neq?: string; readonly inc?: readonly string[]; readonly ninc?: readonly string[]; readonly pre?: string; readonly suf?: string; readonly re?: string };\n\n/** The user agents a crawler sends, as a rule would see them: its bare token and a typical full string. */\nfunction crawlerUserAgents(crawler: string): string[] {\n return [crawler, `Mozilla/5.0 (compatible; ${crawler}/1.0)`];\n}\n\nfunction safeRegExp(source: string): RegExp | null {\n try {\n return new RegExp(source, \"i\");\n } catch {\n return null;\n }\n}\n\n/** Whether a user-agent condition matches the crawler (case-insensitive, so a doubtful rule is flagged). */\nexport function userAgentConditionMatches(condition: UserAgentCondition | string, crawler: string): boolean {\n return crawlerUserAgents(crawler).some((agent) => {\n const ua = agent.toLowerCase();\n if (typeof condition === \"string\") return safeRegExp(condition)?.test(agent) ?? false;\n if (\"match\" in condition) {\n const value = condition.value.toLowerCase();\n if (condition.match === \"contains\") return ua.includes(value);\n if (condition.match === \"equals\") return ua === value;\n return safeRegExp(condition.value)?.test(agent) ?? false;\n }\n const checks: boolean[] = [];\n if (condition.eq !== undefined) checks.push(ua === condition.eq.toLowerCase());\n if (condition.neq !== undefined) checks.push(ua !== condition.neq.toLowerCase());\n if (condition.inc !== undefined) checks.push(condition.inc.some((value) => ua === value.toLowerCase()));\n if (condition.ninc !== undefined) checks.push(!condition.ninc.some((value) => ua === value.toLowerCase()));\n if (condition.pre !== undefined) checks.push(ua.startsWith(condition.pre.toLowerCase()));\n if (condition.suf !== undefined) checks.push(ua.endsWith(condition.suf.toLowerCase()));\n if (condition.re !== undefined) checks.push(safeRegExp(condition.re)?.test(agent) ?? false);\n return checks.length > 0 && checks.every(Boolean);\n });\n}\n\nexport interface FirewallRule {\n /** Where the rule is recorded, for messages: `vercel.json routes[2]` or `hosting.firewall \"block-scrapers\"`. */\n readonly source: string;\n readonly action: string;\n readonly userAgent: UserAgentCondition | string;\n}\n\n/** The firewall rules that match on the user agent: vercel.json `routes` with `mitigate`, and `hosting.firewall`. */\nexport function firewallRules(vercelJson: unknown, hosting: unknown): FirewallRule[] {\n const rules: FirewallRule[] = [];\n const routes = isRecord(vercelJson) && Array.isArray(vercelJson.routes) ? vercelJson.routes : [];\n routes.forEach((route: unknown, index) => {\n if (!isRecord(route) || !isRecord(route.mitigate) || typeof route.mitigate.action !== \"string\") return;\n const has = Array.isArray(route.has) ? route.has : [];\n for (const condition of has) {\n if (!isRecord(condition) || condition.type !== \"header\" || String(condition.key).toLowerCase() !== \"user-agent\") continue;\n const value = condition.value;\n if (typeof value === \"string\" || isRecord(value)) {\n rules.push({ source: `vercel.json routes[${index}]`, action: route.mitigate.action, userAgent: value as UserAgentCondition | string });\n }\n }\n });\n const recorded = isRecord(hosting) && Array.isArray(hosting.firewall) ? hosting.firewall : [];\n for (const rule of recorded) {\n if (!isRecord(rule) || !isRecord(rule.userAgent) || typeof rule.action !== \"string\") continue;\n const { match, value } = rule.userAgent;\n if ((match === \"contains\" || match === \"equals\" || match === \"regex\") && typeof value === \"string\") {\n rules.push({ source: `hosting.firewall \"${String(rule.id)}\"`, action: rule.action, userAgent: { match, value } });\n }\n }\n return rules;\n}\n\nconst BLOCKING_ACTIONS = new Set([\"deny\", \"challenge\"]);\n\nexport interface W10Input {\n /** robots.txt text, or null when it could not be read; `robotsProblem` says why. */\n readonly robotsTxt: string | null;\n readonly robotsProblem?: string;\n /**\n * True when the robots.txt came from a Preview, closed on purpose: a running deployment that answers noindex, or a\n * build made for a non-production environment whose robots.txt disallows everything.\n */\n readonly closedDeployment?: boolean;\n /** Paths each crawler must be able to fetch: home and the representative routes. */\n readonly paths: readonly string[];\n readonly vercelJson: unknown;\n readonly hosting: unknown;\n}\n\nexport function checkW10(input: W10Input): SiteSystemFinding[] {\n const findings: SiteSystemFinding[] = [];\n const crawlers: readonly BrandlabSearchCrawler[] = BRANDLAB_SEARCH_CRAWLERS;\n if (input.closedDeployment) {\n findings.push(notApplicable(\"W10\", \"robots.txt came from a Preview deployment or a build made for one, which stays closed to every crawler; W10 reads the production build (VERCEL_ENV=production next build, or --base-url on production).\"));\n } else if (input.robotsTxt === null) {\n findings.push(fail(\"W10\", \"/robots.txt\", input.robotsProblem ?? \"robots.txt could not be read, so the crawler guard could not check it.\"));\n } else {\n const groups = parseRobotsTxt(input.robotsTxt);\n for (const crawler of crawlers) {\n const blocked = input.paths.filter((path) => !robotsAllows(groups, crawler, path));\n if (blocked.length > 0) {\n findings.push(fail(\"W10\", \"/robots.txt\", `robots.txt blocks ${crawler} from ${blocked.join(\", \")}; search and answer crawlers stay allowed (no blanket robots rule).`));\n }\n const delay = robotsGroupsFor(groups, crawler).find((group) => group.crawlDelay !== null);\n if (delay) {\n findings.push(fail(\"W10\", \"/robots.txt\", `robots.txt gives ${crawler} a Crawl-delay of ${delay.crawlDelay}; Google ignores it and Bing honours it, so it only slows Bing indexing. Keep one only with a recorded decision and a Romie-owned W10 exception.`));\n }\n }\n }\n for (const rule of firewallRules(input.vercelJson, input.hosting)) {\n const hit = crawlers.filter((crawler) => userAgentConditionMatches(rule.userAgent, crawler));\n if (hit.length === 0) continue;\n if (BLOCKING_ACTIONS.has(rule.action)) {\n findings.push(fail(\"W10\", rule.source.startsWith(\"vercel.json\") ? \"vercel.json\" : \"brandlab.config.ts\", `The firewall rule ${rule.source} (${rule.action}) matches ${hit.join(\", \")}; search and answer crawlers must not be denied or challenged.`));\n } else if (rule.action === \"rate-limit\") {\n findings.push({ checkId: \"W10\", status: \"pass\", severity: \"warning\", target: \"brandlab.config.ts\", message: `The firewall rule ${rule.source} rate-limits ${hit.join(\", \")}; check it never stops their normal crawl.` });\n }\n }\n if (findings.some((finding) => finding.status === \"fail\" || finding.status === \"not-applicable\")) return findings;\n return [...findings, pass(\"W10\", `robots.txt and the recorded firewall rules leave ${crawlers.join(\", \")} open on ${input.paths.join(\", \")}, with no Crawl-delay.`)];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { SiteSystemFinding } from "../../checks.js";
|
|
3
|
+
/** The page files under a dynamic segment, relative to the project, sorted. */
|
|
4
|
+
export declare function dynamicRoutePageFiles(projectRoot: string): string[] | null;
|
|
5
|
+
/** True when the module's directive prologue says `'use client'`. */
|
|
6
|
+
export declare function isClientModule(source: ts.SourceFile): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Where a source file names `searchParams` or `useSearchParams` in code (not in comments or strings), by 1-based line.
|
|
9
|
+
* `useSearchParams` in a `'use client'` module is the client-side reading the rule recommends, so it does not count
|
|
10
|
+
* there; the `searchParams` page prop always does.
|
|
11
|
+
*/
|
|
12
|
+
export declare function searchParamReads(fileName: string, text: string): {
|
|
13
|
+
line: number;
|
|
14
|
+
name: string;
|
|
15
|
+
}[];
|
|
16
|
+
/** The 1-based lines `searchParamReads` reports. */
|
|
17
|
+
export declare function searchParamLines(fileName: string, text: string): number[];
|
|
18
|
+
export declare function checkW9Params(projectRoot: string): SiteSystemFinding[];
|
|
19
|
+
//# sourceMappingURL=w9-params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"w9-params.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/guards/w9-params.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAMzD,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAgB1E;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAM7D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAajG;AAED,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzE;AAED,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAkBtE"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W9-params (3.13.0): a page under a dynamic route (`[slug]`, `[...slug]`) never reads `searchParams`. Reading it
|
|
3
|
+
* makes the page render per request, so every tracking query (`?utm_source=`, `?gclid=`) becomes an origin render and
|
|
4
|
+
* a separate cache entry instead of the prerendered page. A source scan of the page files under the app directory.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join, relative } from "node:path";
|
|
8
|
+
import ts from "typescript";
|
|
9
|
+
import { fail, notApplicable, pass } from "../enquiry/common.js";
|
|
10
|
+
const PAGE_FILE = /^page\.(?:tsx|ts|jsx|js)$/;
|
|
11
|
+
const SEARCH_PARAM_NAMES = new Set(["searchParams", "useSearchParams"]);
|
|
12
|
+
/** The page files under a dynamic segment, relative to the project, sorted. */
|
|
13
|
+
export function dynamicRoutePageFiles(projectRoot) {
|
|
14
|
+
const appDir = ["src/app", "app"].map((path) => join(projectRoot, path)).find((path) => existsSync(path));
|
|
15
|
+
if (!appDir)
|
|
16
|
+
return null;
|
|
17
|
+
const files = [];
|
|
18
|
+
const visit = (directory, dynamic) => {
|
|
19
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
20
|
+
if (entry.isDirectory()) {
|
|
21
|
+
if (entry.name === "node_modules" || entry.name.startsWith("_"))
|
|
22
|
+
continue;
|
|
23
|
+
visit(join(directory, entry.name), dynamic || /^\[.+\]$/.test(entry.name));
|
|
24
|
+
}
|
|
25
|
+
else if (dynamic && entry.isFile() && PAGE_FILE.test(entry.name)) {
|
|
26
|
+
files.push(relative(projectRoot, join(directory, entry.name)));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
visit(appDir, false);
|
|
31
|
+
return files;
|
|
32
|
+
}
|
|
33
|
+
/** True when the module's directive prologue says `'use client'`. */
|
|
34
|
+
export function isClientModule(source) {
|
|
35
|
+
for (const statement of source.statements) {
|
|
36
|
+
if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression))
|
|
37
|
+
return false;
|
|
38
|
+
if (statement.expression.text === "use client")
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Where a source file names `searchParams` or `useSearchParams` in code (not in comments or strings), by 1-based line.
|
|
45
|
+
* `useSearchParams` in a `'use client'` module is the client-side reading the rule recommends, so it does not count
|
|
46
|
+
* there; the `searchParams` page prop always does.
|
|
47
|
+
*/
|
|
48
|
+
export function searchParamReads(fileName, text) {
|
|
49
|
+
const source = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
50
|
+
const client = isClientModule(source);
|
|
51
|
+
const reads = new Map();
|
|
52
|
+
const visit = (node) => {
|
|
53
|
+
if (ts.isIdentifier(node) && SEARCH_PARAM_NAMES.has(node.text) && !(client && node.text === "useSearchParams")) {
|
|
54
|
+
const line = source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
55
|
+
reads.set(`${line}:${node.text}`, { line, name: node.text });
|
|
56
|
+
}
|
|
57
|
+
ts.forEachChild(node, visit);
|
|
58
|
+
};
|
|
59
|
+
visit(source);
|
|
60
|
+
return [...reads.values()].sort((left, right) => left.line - right.line || left.name.localeCompare(right.name));
|
|
61
|
+
}
|
|
62
|
+
/** The 1-based lines `searchParamReads` reports. */
|
|
63
|
+
export function searchParamLines(fileName, text) {
|
|
64
|
+
return [...new Set(searchParamReads(fileName, text).map((read) => read.line))];
|
|
65
|
+
}
|
|
66
|
+
export function checkW9Params(projectRoot) {
|
|
67
|
+
const files = dynamicRoutePageFiles(projectRoot);
|
|
68
|
+
if (files === null)
|
|
69
|
+
return [notApplicable("W9-params", "The project has no app directory to scan.")];
|
|
70
|
+
if (files.length === 0)
|
|
71
|
+
return [notApplicable("W9-params", "The project has no page under a dynamic route.")];
|
|
72
|
+
const failures = files.flatMap((file) => {
|
|
73
|
+
const reads = searchParamReads(file, readFileSync(join(projectRoot, file), "utf8"));
|
|
74
|
+
if (reads.length === 0)
|
|
75
|
+
return [];
|
|
76
|
+
const names = [...new Set(reads.map((read) => read.name))].join(" and ");
|
|
77
|
+
const lines = [...new Set(reads.map((read) => read.line))];
|
|
78
|
+
return [
|
|
79
|
+
fail("W9-params", file, `${file} reads ${names} (line ${lines.join(", ")}): a page under a dynamic route that reads the query renders at the origin for every tracking link (?utm_source=, ?gclid=) instead of serving its prerendered copy. Read the query in a 'use client' component with useSearchParams, inside a Suspense boundary, or move the page out of the dynamic route.`),
|
|
80
|
+
];
|
|
81
|
+
});
|
|
82
|
+
return failures.length > 0 ? failures : [pass("W9-params", `None of the ${files.length} pages under a dynamic route reads searchParams.`)];
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=w9-params.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"w9-params.js","sourceRoot":"","sources":["../../../../src/site-system/rules/guards/w9-params.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEjE,MAAM,SAAS,GAAG,2BAA2B,CAAC;AAC9C,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAExE,+EAA+E;AAC/E,MAAM,UAAU,qBAAqB,CAAC,WAAmB;IACvD,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1G,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,SAAiB,EAAE,OAAgB,EAAE,EAAE;QACpD,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC/H,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC1E,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7E,CAAC;iBAAM,IAAI,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACrB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,MAAqB;IAClD,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC1C,IAAI,CAAC,EAAE,CAAC,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC,UAAU,CAAC;YAAE,OAAO,KAAK,CAAC;QACpG,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,KAAK,YAAY;YAAE,OAAO,IAAI,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,IAAY;IAC7D,MAAM,MAAM,GAAG,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAChJ,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0C,CAAC;IAChE,MAAM,KAAK,GAAG,CAAC,IAAa,EAAE,EAAE;QAC9B,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,CAAC,EAAE,CAAC;YAC/G,MAAM,IAAI,GAAG,MAAM,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;YAClF,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC;IACF,KAAK,CAAC,MAAM,CAAC,CAAC;IACd,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AAClH,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,IAAY;IAC7D,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,MAAM,KAAK,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;IACjD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,CAAC,aAAa,CAAC,WAAW,EAAE,2CAA2C,CAAC,CAAC,CAAC;IACrG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,aAAa,CAAC,WAAW,EAAE,gDAAgD,CAAC,CAAC,CAAC;IAC9G,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACtC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzE,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3D,OAAO;YACL,IAAI,CACF,WAAW,EACX,IAAI,EACJ,GAAG,IAAI,UAAU,KAAK,UAAU,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,6SAA6S,CAC9V;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,KAAK,CAAC,MAAM,kDAAkD,CAAC,CAAC,CAAC;AAC7I,CAAC","sourcesContent":["/**\n * W9-params (3.13.0): a page under a dynamic route (`[slug]`, `[...slug]`) never reads `searchParams`. Reading it\n * makes the page render per request, so every tracking query (`?utm_source=`, `?gclid=`) becomes an origin render and\n * a separate cache entry instead of the prerendered page. A source scan of the page files under the app directory.\n */\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport ts from \"typescript\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, pass } from \"../enquiry/common.js\";\n\nconst PAGE_FILE = /^page\\.(?:tsx|ts|jsx|js)$/;\nconst SEARCH_PARAM_NAMES = new Set([\"searchParams\", \"useSearchParams\"]);\n\n/** The page files under a dynamic segment, relative to the project, sorted. */\nexport function dynamicRoutePageFiles(projectRoot: string): string[] | null {\n const appDir = [\"src/app\", \"app\"].map((path) => join(projectRoot, path)).find((path) => existsSync(path));\n if (!appDir) return null;\n const files: string[] = [];\n const visit = (directory: string, dynamic: boolean) => {\n for (const entry of readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\"_\")) continue;\n visit(join(directory, entry.name), dynamic || /^\\[.+\\]$/.test(entry.name));\n } else if (dynamic && entry.isFile() && PAGE_FILE.test(entry.name)) {\n files.push(relative(projectRoot, join(directory, entry.name)));\n }\n }\n };\n visit(appDir, false);\n return files;\n}\n\n/** True when the module's directive prologue says `'use client'`. */\nexport function isClientModule(source: ts.SourceFile): boolean {\n for (const statement of source.statements) {\n if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) return false;\n if (statement.expression.text === \"use client\") return true;\n }\n return false;\n}\n\n/**\n * Where a source file names `searchParams` or `useSearchParams` in code (not in comments or strings), by 1-based line.\n * `useSearchParams` in a `'use client'` module is the client-side reading the rule recommends, so it does not count\n * there; the `searchParams` page prop always does.\n */\nexport function searchParamReads(fileName: string, text: string): { line: number; name: string }[] {\n const source = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true, fileName.endsWith(\"x\") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);\n const client = isClientModule(source);\n const reads = new Map<string, { line: number; name: string }>();\n const visit = (node: ts.Node) => {\n if (ts.isIdentifier(node) && SEARCH_PARAM_NAMES.has(node.text) && !(client && node.text === \"useSearchParams\")) {\n const line = source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;\n reads.set(`${line}:${node.text}`, { line, name: node.text });\n }\n ts.forEachChild(node, visit);\n };\n visit(source);\n return [...reads.values()].sort((left, right) => left.line - right.line || left.name.localeCompare(right.name));\n}\n\n/** The 1-based lines `searchParamReads` reports. */\nexport function searchParamLines(fileName: string, text: string): number[] {\n return [...new Set(searchParamReads(fileName, text).map((read) => read.line))];\n}\n\nexport function checkW9Params(projectRoot: string): SiteSystemFinding[] {\n const files = dynamicRoutePageFiles(projectRoot);\n if (files === null) return [notApplicable(\"W9-params\", \"The project has no app directory to scan.\")];\n if (files.length === 0) return [notApplicable(\"W9-params\", \"The project has no page under a dynamic route.\")];\n const failures = files.flatMap((file) => {\n const reads = searchParamReads(file, readFileSync(join(projectRoot, file), \"utf8\"));\n if (reads.length === 0) return [];\n const names = [...new Set(reads.map((read) => read.name))].join(\" and \");\n const lines = [...new Set(reads.map((read) => read.line))];\n return [\n fail(\n \"W9-params\",\n file,\n `${file} reads ${names} (line ${lines.join(\", \")}): a page under a dynamic route that reads the query renders at the origin for every tracking link (?utm_source=, ?gclid=) instead of serving its prerendered copy. Read the query in a 'use client' component with useSearchParams, inside a Suspense boundary, or move the page out of the dynamic route.`,\n ),\n ];\n });\n return failures.length > 0 ? failures : [pass(\"W9-params\", `None of the ${files.length} pages under a dynamic route reads searchParams.`)];\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { SiteSystemFinding } from "../../checks.js";
|
|
2
2
|
import { type ParsedPage } from "../enquiry/common.js";
|
|
3
3
|
/** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */
|
|
4
|
-
export declare const K2_SKINNED_HOOKS: readonly ["header", "drawer", "utility", "callbar", "back-to-top", "quote-button", "phone-link", "text-link", "button", "container", "quote-form", "photo-upload", "dialog", "field", "thank-you", "rating-chip", "footer", "accordion", "price-table"];
|
|
4
|
+
export declare const K2_SKINNED_HOOKS: readonly ["header", "drawer", "utility", "callbar", "back-to-top", "quote-button", "phone-link", "text-link", "button", "container", "quote-form", "photo-upload", "dialog", "field", "thank-you", "rating-chip", "footer", "accordion", "price-table", "eyebrow", "card", "badge", "chip", "stat", "tick-list", "link-list", "media", "breadcrumbs"];
|
|
5
5
|
/** `data-bl-section` ids of the sections the library renders and skins. */
|
|
6
|
-
export declare const K2_SKINNED_SECTIONS: readonly ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact"];
|
|
6
|
+
export declare const K2_SKINNED_SECTIONS: readonly ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services"];
|
|
7
7
|
export declare const K2_REFERENCE_CLASS: RegExp;
|
|
8
8
|
/** Hook names the project's own stylesheets select: `[data-bl="x"]` and `[data-bl-section="x"]`. */
|
|
9
9
|
export declare function hooksStyledBySite(projectRoot: string): Set<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"k2-skin-coverage.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"k2-skin-coverage.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAI1F,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,uVAKnB,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,iHAAkH,CAAC;AAEnJ,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAqB9D,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA0BlE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAqC9F"}
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* What the HTML can show, defined exactly:
|
|
6
6
|
* - A component is a rendered element with `data-bl="<name>"` or `data-bl-section="<id>"`, for the names the library
|
|
7
7
|
* skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star
|
|
8
|
-
* row) and the site's own compositions
|
|
8
|
+
* row) and the site's own compositions are not components in this sense. From 3.14.0 the library's hero and the
|
|
9
|
+
* primitives (button actions share `button`; cards, badges, chips and stats take no classes, so a site covers them
|
|
10
|
+
* from its skin stylesheet) join the lists.
|
|
9
11
|
* - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an
|
|
10
12
|
* element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a
|
|
11
13
|
* component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the
|
|
@@ -21,9 +23,11 @@ import { PageProblems } from "../library/common.js";
|
|
|
21
23
|
export const K2_SKINNED_HOOKS = [
|
|
22
24
|
"header", "drawer", "utility", "callbar", "back-to-top", "quote-button", "phone-link", "text-link", "button", "container",
|
|
23
25
|
"quote-form", "photo-upload", "dialog", "field", "thank-you", "rating-chip", "footer", "accordion", "price-table",
|
|
26
|
+
// 3.14.0 primitives
|
|
27
|
+
"eyebrow", "card", "badge", "chip", "stat", "tick-list", "link-list", "media", "breadcrumbs",
|
|
24
28
|
];
|
|
25
29
|
/** `data-bl-section` ids of the sections the library renders and skins. */
|
|
26
|
-
export const K2_SKINNED_SECTIONS = ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact"];
|
|
30
|
+
export const K2_SKINNED_SECTIONS = ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services"];
|
|
27
31
|
export const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;
|
|
28
32
|
function classesOf(element) {
|
|
29
33
|
return (attr(element, "class") ?? "").split(/\s+/).filter(Boolean);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"k2-skin-coverage.js","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K2: skin coverage (Site System v2 §13.2, §8.4). Every shared component a page renders has a skin entry, so a site\n * never ships an unstyled look-alike, and no reference-skin class (the test skins the library's own tests use) ships.\n *\n * What the HTML can show, defined exactly:\n * - A component is a rendered element with `data-bl=\"<name>\"` or `data-bl-section=\"<id>\"`, for the names the library\n * skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star\n * row) and the site's own compositions (the hero) are not components in this sense.\n * - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an\n * element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a\n * component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the\n * component by its hook (`[data-bl=\"footer\"]`, `[data-bl-section=\"faqs\"]`), which is how a site styles slots in plain CSS.\n * - A class token starting `ref-heavy-`, `ref-calm-` or `ref-pool-` on any element is a reference-skin class and fails.\n */\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, pass, plural, type ParsedPage } from \"../enquiry/common.js\";\nimport { attr, descendants, type HtmlElement } from \"../enquiry/html.js\";\nimport { PageProblems } from \"../library/common.js\";\n\n/** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */\nexport const K2_SKINNED_HOOKS = [\n \"header\", \"drawer\", \"utility\", \"callbar\", \"back-to-top\", \"quote-button\", \"phone-link\", \"text-link\", \"button\", \"container\",\n \"quote-form\", \"photo-upload\", \"dialog\", \"field\", \"thank-you\", \"rating-chip\", \"footer\", \"accordion\", \"price-table\",\n] as const;\n\n/** `data-bl-section` ids of the sections the library renders and skins. */\nexport const K2_SKINNED_SECTIONS = [\"reviews\", \"cta\", \"faqs\", \"price-guide\", \"proof-strip\", \"page-header\", \"contact\"] as const;\n\nexport const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;\n\nfunction classesOf(element: HtmlElement): string[] {\n return (attr(element, \"class\") ?? \"\").split(/\\s+/).filter(Boolean);\n}\n\n/** The root and slot elements of one component, leaving out components nested inside it. */\nfunction skinSurface(root: HtmlElement): HtmlElement[] {\n const surface = [root];\n const visit = (node: HtmlElement) => {\n for (const child of node.children) {\n if (child.type !== \"element\") continue;\n if (attr(child, \"data-bl\") !== undefined || attr(child, \"data-bl-section\") !== undefined) continue;\n if (attr(child, \"data-slot\") !== undefined) surface.push(child);\n visit(child);\n }\n };\n visit(root);\n return surface;\n}\n\n/** Hook names the project's own stylesheets select: `[data-bl=\"x\"]` and `[data-bl-section=\"x\"]`. */\nexport function hooksStyledBySite(projectRoot: string): Set<string> {\n const styled = new Set<string>();\n const selector = /\\[\\s*data-bl(?:-section)?\\s*=\\s*[\"']?([a-z0-9-]+)[\"']?\\s*\\]/gi;\n const visit = (directory: string, depth: number) => {\n if (depth > 6) return;\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path, depth + 1);\n else if (entry.isFile() && /\\.(?:css|scss)$/i.test(entry.name)) {\n try {\n for (const match of readFileSync(path, \"utf8\").matchAll(selector)) styled.add(match[1]!.toLowerCase());\n } catch {\n // an unreadable stylesheet styles nothing we can see\n }\n }\n }\n };\n for (const folder of [\"src\", \"app\", \"sites\"]) if (existsSync(join(projectRoot, folder))) visit(join(projectRoot, folder), 0);\n return styled;\n}\n\nexport function checkK2(pages: readonly ParsedPage[], projectRoot: string): SiteSystemFinding[] {\n const hooks = new Set<string>(K2_SKINNED_HOOKS);\n const sections = new Set<string>(K2_SKINNED_SECTIONS);\n /** Component key (\"footer\", \"section:faqs\") to whether any instance carries a skin class, and the first page. */\n const components = new Map<string, { covered: boolean; page: string; label: string }>();\n const references = new PageProblems();\n\n for (const page of pages) {\n for (const element of descendants(page.root)) {\n const reference = classesOf(element).find((token) => K2_REFERENCE_CLASS.test(token));\n if (reference) references.add(page.path, `A reference-skin class (${reference}) ships; those belong to the library's own tests`);\n\n const hook = attr(element, \"data-bl\");\n const section = attr(element, \"data-bl-section\");\n const key = hook !== undefined && hooks.has(hook) ? `hook:${hook}` : section !== undefined && sections.has(section) ? `section:${section}` : null;\n if (!key) continue;\n const label = hook !== undefined ? `data-bl=\"${hook}\"` : `data-bl-section=\"${section}\"`;\n const carries = skinSurface(element).some((candidate) => classesOf(candidate).length > 0);\n const known = components.get(key);\n if (known) known.covered ||= carries;\n else components.set(key, { covered: carries, page: page.path, label });\n }\n }\n\n const failures = references.findings(\"K2\");\n if (components.size === 0 && failures.length === 0) return [notApplicable(\"K2\", \"None of the sampled pages renders a shared component.\")];\n const uncovered = [...components].filter(([, component]) => !component.covered);\n if (uncovered.length > 0) {\n const styled = hooksStyledBySite(projectRoot);\n for (const [key, component] of uncovered) {\n const name = key.slice(key.indexOf(\":\") + 1);\n if (styled.has(name)) continue;\n failures.push(fail(\"K2\", component.page, `${component.label} is rendered (first on ${component.page}) with no skin class on its root or slots and no selector for it in the site's stylesheets, so it has no skin entry.`));\n }\n }\n if (failures.length > 0) return failures;\n return [pass(\"K2\", `Checked ${plural(components.size, \"shared component\")} on ${plural(pages.length, \"page\")}: each carries a skin class or is styled by the site's own CSS, and no reference-skin class ships.`)];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"k2-skin-coverage.js","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAmB,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,IAAI,EAAE,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,sFAAsF;AACtF,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW;IACzH,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa;IACjH,oBAAoB;IACpB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;CACpF,CAAC;AAEX,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAEnJ,MAAM,CAAC,MAAM,kBAAkB,GAAG,2BAA2B,CAAC;AAE9D,SAAS,SAAS,CAAC,OAAoB;IACrC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACrE,CAAC;AAED,4FAA4F;AAC5F,SAAS,WAAW,CAAC,IAAiB;IACpC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,KAAK,GAAG,CAAC,IAAiB,EAAE,EAAE;QAClC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;gBAAE,SAAS;YACvC,IAAI,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,SAAS;gBAAE,SAAS;YACnG,IAAI,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAChE,KAAK,CAAC,KAAK,CAAC,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,MAAM,QAAQ,GAAG,+DAA+D,CAAC;IACjF,MAAM,KAAK,GAAG,CAAC,SAAiB,EAAE,KAAa,EAAE,EAAE;QACjD,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO;QACtB,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,WAAW,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;gBAAE,SAAS;YAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,KAAK,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;iBAC3C,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/D,IAAI,CAAC;oBACH,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;wBAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC,CAAC;gBACzG,CAAC;gBAAC,MAAM,CAAC;oBACP,qDAAqD;gBACvD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,KAAK,MAAM,MAAM,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC;QAAE,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7H,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAA4B,EAAE,WAAmB;IACvE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS,gBAAgB,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,mBAAmB,CAAC,CAAC;IACtD,iHAAiH;IACjH,MAAM,UAAU,GAAG,IAAI,GAAG,EAA6D,CAAC;IACxF,MAAM,UAAU,GAAG,IAAI,YAAY,EAAE,CAAC;IAEtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACrF,IAAI,SAAS;gBAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,2BAA2B,SAAS,kDAAkD,CAAC,CAAC;YAEjI,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YACtC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;YACjD,MAAM,GAAG,GAAG,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAClJ,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,oBAAoB,OAAO,GAAG,CAAC;YACxF,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC1F,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,KAAK;gBAAE,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC;;gBAChC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,uDAAuD,CAAC,CAAC,CAAC;IAC1I,MAAM,SAAS,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAChF,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC9C,KAAK,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,SAAS,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7C,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC/B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,SAAS,CAAC,KAAK,0BAA0B,SAAS,CAAC,IAAI,sHAAsH,CAAC,CAAC,CAAC;QAC9N,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACzC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,kBAAkB,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,oGAAoG,CAAC,CAAC,CAAC;AACrN,CAAC","sourcesContent":["/**\n * K2: skin coverage (Site System v2 §13.2, §8.4). Every shared component a page renders has a skin entry, so a site\n * never ships an unstyled look-alike, and no reference-skin class (the test skins the library's own tests use) ships.\n *\n * What the HTML can show, defined exactly:\n * - A component is a rendered element with `data-bl=\"<name>\"` or `data-bl-section=\"<id>\"`, for the names the library\n * skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star\n * row) and the site's own compositions are not components in this sense. From 3.14.0 the library's hero and the\n * primitives (button actions share `button`; cards, badges, chips and stats take no classes, so a site covers them\n * from its skin stylesheet) join the lists.\n * - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an\n * element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a\n * component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the\n * component by its hook (`[data-bl=\"footer\"]`, `[data-bl-section=\"faqs\"]`), which is how a site styles slots in plain CSS.\n * - A class token starting `ref-heavy-`, `ref-calm-` or `ref-pool-` on any element is a reference-skin class and fails.\n */\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, pass, plural, type ParsedPage } from \"../enquiry/common.js\";\nimport { attr, descendants, type HtmlElement } from \"../enquiry/html.js\";\nimport { PageProblems } from \"../library/common.js\";\n\n/** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */\nexport const K2_SKINNED_HOOKS = [\n \"header\", \"drawer\", \"utility\", \"callbar\", \"back-to-top\", \"quote-button\", \"phone-link\", \"text-link\", \"button\", \"container\",\n \"quote-form\", \"photo-upload\", \"dialog\", \"field\", \"thank-you\", \"rating-chip\", \"footer\", \"accordion\", \"price-table\",\n // 3.14.0 primitives\n \"eyebrow\", \"card\", \"badge\", \"chip\", \"stat\", \"tick-list\", \"link-list\", \"media\", \"breadcrumbs\",\n] as const;\n\n/** `data-bl-section` ids of the sections the library renders and skins. */\nexport const K2_SKINNED_SECTIONS = [\"reviews\", \"cta\", \"faqs\", \"price-guide\", \"proof-strip\", \"page-header\", \"contact\", \"hero\", \"services\"] as const;\n\nexport const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;\n\nfunction classesOf(element: HtmlElement): string[] {\n return (attr(element, \"class\") ?? \"\").split(/\\s+/).filter(Boolean);\n}\n\n/** The root and slot elements of one component, leaving out components nested inside it. */\nfunction skinSurface(root: HtmlElement): HtmlElement[] {\n const surface = [root];\n const visit = (node: HtmlElement) => {\n for (const child of node.children) {\n if (child.type !== \"element\") continue;\n if (attr(child, \"data-bl\") !== undefined || attr(child, \"data-bl-section\") !== undefined) continue;\n if (attr(child, \"data-slot\") !== undefined) surface.push(child);\n visit(child);\n }\n };\n visit(root);\n return surface;\n}\n\n/** Hook names the project's own stylesheets select: `[data-bl=\"x\"]` and `[data-bl-section=\"x\"]`. */\nexport function hooksStyledBySite(projectRoot: string): Set<string> {\n const styled = new Set<string>();\n const selector = /\\[\\s*data-bl(?:-section)?\\s*=\\s*[\"']?([a-z0-9-]+)[\"']?\\s*\\]/gi;\n const visit = (directory: string, depth: number) => {\n if (depth > 6) return;\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path, depth + 1);\n else if (entry.isFile() && /\\.(?:css|scss)$/i.test(entry.name)) {\n try {\n for (const match of readFileSync(path, \"utf8\").matchAll(selector)) styled.add(match[1]!.toLowerCase());\n } catch {\n // an unreadable stylesheet styles nothing we can see\n }\n }\n }\n };\n for (const folder of [\"src\", \"app\", \"sites\"]) if (existsSync(join(projectRoot, folder))) visit(join(projectRoot, folder), 0);\n return styled;\n}\n\nexport function checkK2(pages: readonly ParsedPage[], projectRoot: string): SiteSystemFinding[] {\n const hooks = new Set<string>(K2_SKINNED_HOOKS);\n const sections = new Set<string>(K2_SKINNED_SECTIONS);\n /** Component key (\"footer\", \"section:faqs\") to whether any instance carries a skin class, and the first page. */\n const components = new Map<string, { covered: boolean; page: string; label: string }>();\n const references = new PageProblems();\n\n for (const page of pages) {\n for (const element of descendants(page.root)) {\n const reference = classesOf(element).find((token) => K2_REFERENCE_CLASS.test(token));\n if (reference) references.add(page.path, `A reference-skin class (${reference}) ships; those belong to the library's own tests`);\n\n const hook = attr(element, \"data-bl\");\n const section = attr(element, \"data-bl-section\");\n const key = hook !== undefined && hooks.has(hook) ? `hook:${hook}` : section !== undefined && sections.has(section) ? `section:${section}` : null;\n if (!key) continue;\n const label = hook !== undefined ? `data-bl=\"${hook}\"` : `data-bl-section=\"${section}\"`;\n const carries = skinSurface(element).some((candidate) => classesOf(candidate).length > 0);\n const known = components.get(key);\n if (known) known.covered ||= carries;\n else components.set(key, { covered: carries, page: page.path, label });\n }\n }\n\n const failures = references.findings(\"K2\");\n if (components.size === 0 && failures.length === 0) return [notApplicable(\"K2\", \"None of the sampled pages renders a shared component.\")];\n const uncovered = [...components].filter(([, component]) => !component.covered);\n if (uncovered.length > 0) {\n const styled = hooksStyledBySite(projectRoot);\n for (const [key, component] of uncovered) {\n const name = key.slice(key.indexOf(\":\") + 1);\n if (styled.has(name)) continue;\n failures.push(fail(\"K2\", component.page, `${component.label} is rendered (first on ${component.page}) with no skin class on its root or slots and no selector for it in the site's stylesheets, so it has no skin entry.`));\n }\n }\n if (failures.length > 0) return failures;\n return [pass(\"K2\", `Checked ${plural(components.size, \"shared component\")} on ${plural(pages.length, \"page\")}: each carries a skin class or is styled by the site's own CSS, and no reference-skin class ships.`)];\n}\n"]}
|
|
@@ -40,7 +40,7 @@ export function isSiteSystemFinding(value) {
|
|
|
40
40
|
const finding = value;
|
|
41
41
|
return (typeof finding.checkId === "string" &&
|
|
42
42
|
KNOWN_IDS.has(finding.checkId) &&
|
|
43
|
-
(finding.status === "pass" || finding.status === "fail" || finding.status === "not-applicable") &&
|
|
43
|
+
(finding.status === "pass" || finding.status === "fail" || finding.status === "not-run" || finding.status === "not-applicable") &&
|
|
44
44
|
typeof finding.message === "string" &&
|
|
45
45
|
(finding.target === undefined || typeof finding.target === "string") &&
|
|
46
46
|
(finding.severity === undefined || finding.severity === "warning"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runners.js","sourceRoot":"","sources":["../../src/site-system/runners.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAA0B,MAAM,aAAa,CAAC;AAwBzE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAS,kBAAkB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAA4C,EAC5C,KAA8B;IAE9B,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,eAAe,CAAC;QAC5C,IAAI,OAAqC,CAAC;QAC1C,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,2BAA2B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACvI,SAAS;QACX,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC9B,IAAI,mBAAmB,CAAC,OAAO,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;gBACpD,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,uFAAuF,CAAC,CAAC;QACrH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,OAAO,GAAG,KAAgC,CAAC;IACjD,OAAO,CACL,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QACnC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;QAC9B,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,gBAAgB,CAAC;QAC/
|
|
1
|
+
{"version":3,"file":"runners.js","sourceRoot":"","sources":["../../src/site-system/runners.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAA0B,MAAM,aAAa,CAAC;AAwBzE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAS,kBAAkB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAA4C,EAC5C,KAA8B;IAE9B,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,eAAe,CAAC;QAC5C,IAAI,OAAqC,CAAC;QAC1C,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,2BAA2B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACvI,SAAS;QACX,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC9B,IAAI,mBAAmB,CAAC,OAAO,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;gBACpD,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,uFAAuF,CAAC,CAAC;QACrH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,OAAO,GAAG,KAAgC,CAAC;IACjD,OAAO,CACL,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QACnC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;QAC9B,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,gBAAgB,CAAC;QAC/H,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QACnC,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;QACpE,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CACnE,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The rule-module interface of the ready gate. Each module under ./rules/ exports a runner, listed in\n * ./rule-modules.ts; `brandlab-ready` calls every runner with the sampled pages and merges what they find. Static\n * rules use the same shape and read the project through `projectRoot`. Every runner is called even when no page\n * could be read: static rules still run, and rendered rules return not-applicable for an empty page list (the gate\n * fails the run itself when home or the contact page is missing).\n */\nimport { SITE_SYSTEM_CHECKS, type SiteSystemFinding } from \"./checks.js\";\nimport type { SiteSystemSettings } from \"./config.js\";\nimport type { SiteSystemPage } from \"./samples.js\";\n\nexport interface SiteSystemRenderedInput {\n /** The running site's base URL with --base-url; empty when the pages were read from the build output. */\n readonly baseUrl: string;\n readonly pages: readonly SiteSystemPage[];\n /** Planned sample paths with no HTML, so rules can say what they could not check. */\n readonly missing: readonly string[];\n /** The project directory, for rules that read its data files. */\n readonly projectRoot: string;\n /**\n * The gate's parsed `siteSystem` settings from brandlab.config.ts (null when the site declares none). Rules use these\n * rather than reading the config themselves; a caller that leaves it out lets each rule read the config.\n */\n readonly siteSystem?: SiteSystemSettings | null;\n /** The production build directory (`.next` unless `--build-dir` says otherwise), for rules that read the whole build. */\n readonly buildDir?: string;\n}\n\n/** A rule module: one finding per check result (pass, fail or not-applicable), never a throw for a failing site. */\nexport type SiteSystemRenderedRunner = (input: SiteSystemRenderedInput) => Promise<readonly SiteSystemFinding[]>;\n\nconst KNOWN_IDS = new Set<string>(SITE_SYSTEM_CHECKS.map((check) => check.id));\n\n/**\n * Runs each runner in turn. A runner that throws, or returns a finding for an unknown check, is reported as a\n * problem; the checks it would have covered then show as not run, which fails them where they block.\n */\nexport async function runSiteSystemRenderedRunners(\n runners: readonly SiteSystemRenderedRunner[],\n input: SiteSystemRenderedInput,\n): Promise<{ findings: SiteSystemFinding[]; problems: string[] }> {\n const findings: SiteSystemFinding[] = [];\n const problems: string[] = [];\n for (const runner of runners) {\n const name = runner.name || \"A rule module\";\n let results: readonly SiteSystemFinding[];\n try {\n results = await runner(input);\n } catch (error) {\n problems.push(`${name} stopped with an error (${error instanceof Error ? error.message : String(error)}), so its checks did not run.`);\n continue;\n }\n for (const finding of results) {\n if (isSiteSystemFinding(finding)) findings.push(finding);\n else problems.push(`${name} returned a result for an unknown check or with an unknown status, so it was ignored.`);\n }\n }\n return { findings, problems };\n}\n\n/** A finding with a known check id, a known status and a message; anything else is ignored. */\nexport function isSiteSystemFinding(value: unknown): value is SiteSystemFinding {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const finding = value as Record<string, unknown>;\n return (\n typeof finding.checkId === \"string\" &&\n KNOWN_IDS.has(finding.checkId) &&\n (finding.status === \"pass\" || finding.status === \"fail\" || finding.status === \"not-run\" || finding.status === \"not-applicable\") &&\n typeof finding.message === \"string\" &&\n (finding.target === undefined || typeof finding.target === \"string\") &&\n (finding.severity === undefined || finding.severity === \"warning\")\n );\n}\n"]}
|
|
@@ -2,5 +2,6 @@ import { type BrandlabSiteSystemTestOptions } from './site-system/journeys/plan.
|
|
|
2
2
|
export { SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_DESKTOP_WIDTHS, SITE_SYSTEM_JOURNEY_IDS, SITE_SYSTEM_PHONE_VIEWPORT, SITE_SYSTEM_QUOTE_NAME, planBrandlabSiteSystemJourneys, siteSystemJourneyTitle, siteSystemJourneysFor, type BrandlabSiteSystemTestOptions, type SiteSystemActionOrder, type SiteSystemJourneyId, type SiteSystemJourneyPlan, } from './site-system/journeys/plan.js';
|
|
3
3
|
export { HOVER_OPEN_WINDOW_MS, MOUSE_OUT_CLOSE_WINDOW_MS } from './site-system/journeys/menu.js';
|
|
4
4
|
export { SITE_SYSTEM_PHONE_PLACEMENTS } from './site-system/journeys/page-helpers.js';
|
|
5
|
+
export { W9_CACHE_HEADERS, W9_TRACKING_QUERY, W9_UNKNOWN_SLUG, notFoundVariantProblems, trackingVariantProblems, urlVariants, type W9Response, type W9Variants, } from './site-system/journeys/variants.js';
|
|
5
6
|
export declare function defineBrandlabSiteSystemTests(options: BrandlabSiteSystemTestOptions): void;
|
|
6
7
|
//# sourceMappingURL=site-system-e2e.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-system-e2e.d.ts","sourceRoot":"","sources":["../src/site-system-e2e.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"site-system-e2e.d.ts","sourceRoot":"","sources":["../src/site-system-e2e.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAkC,KAAK,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAKpH,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,EAC1B,sBAAsB,EACtB,8BAA8B,EAC9B,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AACjG,OAAO,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,WAAW,EACX,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,oCAAoC,CAAC;AAI5C,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,6BAA6B,GAAG,IAAI,CAuB1F"}
|
package/dist/site-system-e2e.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Site System v2 browser journeys for a Brandlab production build (release 3.8.0): the header and its menu,
|
|
3
3
|
* the phone drawer and call bar, contrast, targets, focus, the action order, the money-page first screen and phone
|
|
4
|
-
* placements.
|
|
5
|
-
* `brandlab-ready` can read the
|
|
4
|
+
* placements, from 3.13.0 prefetch by link role (W2) and (full tier) the URL variants of each page family (W9). Every
|
|
5
|
+
* test title starts with its ready-gate check id in square brackets ("[N3] ...") so `brandlab-ready` can read the
|
|
6
|
+
* Playwright JSON report.
|
|
6
7
|
*
|
|
7
8
|
* // tests/e2e/site-system.spec.ts
|
|
8
9
|
* import { defineBrandlabSiteSystemTests } from '@brandlab-digital/conformance/site-system-e2e'
|
|
@@ -26,10 +27,13 @@ import { installPageHelpers } from './site-system/journeys/browser.js';
|
|
|
26
27
|
import { defineConversionJourneys } from './site-system/journeys/conversion.js';
|
|
27
28
|
import { defineMenuJourneys } from './site-system/journeys/menu.js';
|
|
28
29
|
import { planBrandlabSiteSystemJourneys } from './site-system/journeys/plan.js';
|
|
30
|
+
import { definePrefetchJourneys } from './site-system/journeys/prefetch.js';
|
|
29
31
|
import { defineShellJourneys } from './site-system/journeys/shell.js';
|
|
32
|
+
import { defineVariantJourneys } from './site-system/journeys/variants.js';
|
|
30
33
|
export { SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_DESKTOP_WIDTHS, SITE_SYSTEM_JOURNEY_IDS, SITE_SYSTEM_PHONE_VIEWPORT, SITE_SYSTEM_QUOTE_NAME, planBrandlabSiteSystemJourneys, siteSystemJourneyTitle, siteSystemJourneysFor, } from './site-system/journeys/plan.js';
|
|
31
34
|
export { HOVER_OPEN_WINDOW_MS, MOUSE_OUT_CLOSE_WINDOW_MS } from './site-system/journeys/menu.js';
|
|
32
35
|
export { SITE_SYSTEM_PHONE_PLACEMENTS } from './site-system/journeys/page-helpers.js';
|
|
36
|
+
export { W9_CACHE_HEADERS, W9_TRACKING_QUERY, W9_UNKNOWN_SLUG, notFoundVariantProblems, trackingVariantProblems, urlVariants, } from './site-system/journeys/variants.js';
|
|
33
37
|
const CHROMIUM_ONLY = 'The site-system journeys run in Chromium; K5 runs in every browser project.';
|
|
34
38
|
export function defineBrandlabSiteSystemTests(options) {
|
|
35
39
|
const plan = planBrandlabSiteSystemJourneys(options);
|
|
@@ -46,7 +50,9 @@ export function defineBrandlabSiteSystemTests(options) {
|
|
|
46
50
|
chromiumOnly();
|
|
47
51
|
defineMenuJourneys(plan, runs);
|
|
48
52
|
defineShellJourneys(plan, runs);
|
|
53
|
+
definePrefetchJourneys(plan, runs);
|
|
49
54
|
defineConversionJourneys(plan, runs);
|
|
55
|
+
defineVariantJourneys(plan, runs);
|
|
50
56
|
});
|
|
51
57
|
defineAccessJourneys(plan, runs, { chromiumOnly });
|
|
52
58
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-system-e2e.js","sourceRoot":"","sources":["../src/site-system-e2e.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"site-system-e2e.js","sourceRoot":"","sources":["../src/site-system-e2e.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,8BAA8B,EAAsC,MAAM,gCAAgC,CAAC;AACpH,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAE3E,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,EAC1B,sBAAsB,EACtB,8BAA8B,EAC9B,sBAAsB,EACtB,qBAAqB,GAKtB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AACjG,OAAO,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,WAAW,GAGZ,MAAM,oCAAoC,CAAC;AAE5C,MAAM,aAAa,GAAG,6EAA6E,CAAC;AAEpG,MAAM,UAAU,6BAA6B,CAAC,OAAsC;IAClF,MAAM,IAAI,GAAG,8BAA8B,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,WAAW,KAAK,UAAU,EAAE,aAAa,CAAC,CAAC;IAErG,IAAI,CAAC,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACzC,0EAA0E;QAC1E,IAAI,CAAC,GAAG,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YAC1C,MAAM,kBAAkB,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;YAC/D,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;YACtC,YAAY,EAAE,CAAC;YACf,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC/B,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAChC,sBAAsB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACnC,wBAAwB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACrC,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QACH,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The Site System v2 browser journeys for a Brandlab production build (release 3.8.0): the header and its menu,\n * the phone drawer and call bar, contrast, targets, focus, the action order, the money-page first screen and phone\n * placements, from 3.13.0 prefetch by link role (W2) and (full tier) the URL variants of each page family (W9). Every\n * test title starts with its ready-gate check id in square brackets (\"[N3] ...\") so `brandlab-ready` can read the\n * Playwright JSON report.\n *\n * // tests/e2e/site-system.spec.ts\n * import { defineBrandlabSiteSystemTests } from '@brandlab-digital/conformance/site-system-e2e'\n *\n * defineBrandlabSiteSystemTests({\n * tier: 'enquiry',\n * pages: { home: '/', money: ['/services/blocked-drains'], contact: '/contact' },\n * })\n *\n * The journeys find the header parts by role, so they test a bespoke header and the shared one alike: the header is\n * the banner landmark, the menu the navigation named \"Main\", each submenu a `button[aria-expanded][aria-controls]`\n * and its panel the element that names. The tier decides which journeys register (C1 from `enquiry`). They run in\n * Chromium; K5 (focus not hidden) runs in every browser project the site's Playwright config defines, so add a\n * WebKit project to cover Safari. axe comes from `@axe-core/playwright` when the site has it, else `axe-core`.\n * Every request leaves the browser only as a GET or HEAD to the build under test.\n */\nimport { test } from '@playwright/test';\nimport { isolateFromNetwork } from './e2e-support.js';\nimport { defineAccessJourneys } from './site-system/journeys/access.js';\nimport { installPageHelpers } from './site-system/journeys/browser.js';\nimport { defineConversionJourneys } from './site-system/journeys/conversion.js';\nimport { defineMenuJourneys } from './site-system/journeys/menu.js';\nimport { planBrandlabSiteSystemJourneys, type BrandlabSiteSystemTestOptions } from './site-system/journeys/plan.js';\nimport { definePrefetchJourneys } from './site-system/journeys/prefetch.js';\nimport { defineShellJourneys } from './site-system/journeys/shell.js';\nimport { defineVariantJourneys } from './site-system/journeys/variants.js';\n\nexport {\n SITE_SYSTEM_DESKTOP_VIEWPORT,\n SITE_SYSTEM_DESKTOP_WIDTHS,\n SITE_SYSTEM_JOURNEY_IDS,\n SITE_SYSTEM_PHONE_VIEWPORT,\n SITE_SYSTEM_QUOTE_NAME,\n planBrandlabSiteSystemJourneys,\n siteSystemJourneyTitle,\n siteSystemJourneysFor,\n type BrandlabSiteSystemTestOptions,\n type SiteSystemActionOrder,\n type SiteSystemJourneyId,\n type SiteSystemJourneyPlan,\n} from './site-system/journeys/plan.js';\nexport { HOVER_OPEN_WINDOW_MS, MOUSE_OUT_CLOSE_WINDOW_MS } from './site-system/journeys/menu.js';\nexport { SITE_SYSTEM_PHONE_PLACEMENTS } from './site-system/journeys/page-helpers.js';\nexport {\n W9_CACHE_HEADERS,\n W9_TRACKING_QUERY,\n W9_UNKNOWN_SLUG,\n notFoundVariantProblems,\n trackingVariantProblems,\n urlVariants,\n type W9Response,\n type W9Variants,\n} from './site-system/journeys/variants.js';\n\nconst CHROMIUM_ONLY = 'The site-system journeys run in Chromium; K5 runs in every browser project.';\n\nexport function defineBrandlabSiteSystemTests(options: BrandlabSiteSystemTestOptions): void {\n const plan = planBrandlabSiteSystemJourneys(options);\n const runs = new Set<string>(plan.journeys);\n const chromiumOnly = () => test.skip(({ browserName }) => browserName !== 'chromium', CHROMIUM_ONLY);\n\n test.describe('Brandlab site system', () => {\n // Service workers could answer requests outside page and context routing.\n test.use({ serviceWorkers: 'block' });\n test.beforeEach(async ({ page, baseURL }) => {\n await isolateFromNetwork(page, { baseURL, googleTag: 'stub' });\n await installPageHelpers(page);\n });\n\n test.describe('Chromium journeys', () => {\n chromiumOnly();\n defineMenuJourneys(plan, runs);\n defineShellJourneys(plan, runs);\n definePrefetchJourneys(plan, runs);\n defineConversionJourneys(plan, runs);\n defineVariantJourneys(plan, runs);\n });\n defineAccessJourneys(plan, runs, { chromiumOnly });\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandlab-digital/conformance",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"bin": {
|
|
14
14
|
"brandlab-verify": "dist/cli.js",
|
|
15
15
|
"brandlab-area-map": "dist/area-map-cli.js",
|
|
16
|
-
"brandlab-ready": "dist/ready-cli.js"
|
|
16
|
+
"brandlab-ready": "dist/ready-cli.js",
|
|
17
|
+
"brandlab-prefetch-probe": "dist/prefetch-probe-cli.js",
|
|
18
|
+
"brandlab-edge-probe": "dist/edge-probe-cli.js"
|
|
17
19
|
},
|
|
18
20
|
"files": [
|
|
19
21
|
"dist"
|
|
@@ -22,12 +24,12 @@
|
|
|
22
24
|
"access": "public"
|
|
23
25
|
},
|
|
24
26
|
"scripts": {
|
|
25
|
-
"build": "rm -rf dist && tsc -p tsconfig.json && chmod +x dist/cli.js dist/area-map-cli.js dist/ready-cli.js",
|
|
27
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && chmod +x dist/cli.js dist/area-map-cli.js dist/ready-cli.js dist/prefetch-probe-cli.js dist/edge-probe-cli.js",
|
|
26
28
|
"test": "vitest run",
|
|
27
29
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
|
-
"@brandlab-digital/site-core": "3.
|
|
32
|
+
"@brandlab-digital/site-core": "3.14.0",
|
|
31
33
|
"typescript": "6.0.3"
|
|
32
34
|
},
|
|
33
35
|
"peerDependencies": {
|