@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,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* brandlab-prefetch-probe (3.13.0): measures one page's Next prefetches, run locally against a URL (a local
|
|
4
|
+
* production build, a protected preview or a live page). For each viewport it lists the pages prefetched on landing
|
|
5
|
+
* and after a full scroll, with their compressed and decoded bytes and the edge cache result, and says whether the
|
|
6
|
+
* page would pass W2 (prefetch by link role).
|
|
7
|
+
*
|
|
8
|
+
* npx brandlab-prefetch-probe https://www.example.com.au/areas/hills-district
|
|
9
|
+
* npx brandlab-prefetch-probe http://localhost:3000/ --viewport 390x844 --json
|
|
10
|
+
*
|
|
11
|
+
* It loads the page once per viewport in headless Chromium (`@playwright/test`, an optional peer) and lets no request
|
|
12
|
+
* leave for another site, so analytics never counts it. It reads one page; it never crawls.
|
|
13
|
+
*/
|
|
14
|
+
import { realpathSync } from "node:fs";
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { measurePrefetch } from "./site-system/journeys/prefetch-measure.js";
|
|
18
|
+
import { evaluatePrefetch } from "./site-system/journeys/prefetch-policy.js";
|
|
19
|
+
export const PROBE_DEFAULT_VIEWPORTS = Object.freeze([
|
|
20
|
+
{ width: 390, height: 844 },
|
|
21
|
+
{ width: 1440, height: 900 },
|
|
22
|
+
]);
|
|
23
|
+
const HELP = `brandlab-prefetch-probe <url> [--viewport WIDTHxHEIGHT ...] [--json] [--strict]
|
|
24
|
+
|
|
25
|
+
Lists the pages one page prefetches on landing and after a full scroll, with bytes, at 390 x 844 and 1440 x 900
|
|
26
|
+
(or the viewports named), and whether it would pass W2. --strict exits 1 when it would not.`;
|
|
27
|
+
const MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Mobile Safari/537.36";
|
|
28
|
+
export function parseProbeArgs(argv) {
|
|
29
|
+
let url;
|
|
30
|
+
const viewports = [];
|
|
31
|
+
let json = false;
|
|
32
|
+
let strict = false;
|
|
33
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
34
|
+
const argument = argv[index];
|
|
35
|
+
if (argument === "--json")
|
|
36
|
+
json = true;
|
|
37
|
+
else if (argument === "--strict")
|
|
38
|
+
strict = true;
|
|
39
|
+
else if (argument === "--viewport") {
|
|
40
|
+
const value = argv[++index] ?? "";
|
|
41
|
+
const match = /^(\d{3,4})x(\d{3,4})$/.exec(value);
|
|
42
|
+
if (!match)
|
|
43
|
+
throw new Error(`--viewport takes WIDTHxHEIGHT, for example 390x844 (got ${JSON.stringify(value)}).`);
|
|
44
|
+
viewports.push({ width: Number(match[1]), height: Number(match[2]) });
|
|
45
|
+
}
|
|
46
|
+
else if (argument.startsWith("--"))
|
|
47
|
+
throw new Error(`Unknown option ${argument}.`);
|
|
48
|
+
else if (url)
|
|
49
|
+
throw new Error("Name one page URL.");
|
|
50
|
+
else
|
|
51
|
+
url = argument;
|
|
52
|
+
}
|
|
53
|
+
if (!url)
|
|
54
|
+
throw new Error("Name the page URL to probe.");
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = new URL(url);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new Error(`${url} is not a full URL; include http:// or https://.`);
|
|
61
|
+
}
|
|
62
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
63
|
+
throw new Error(`${url} is not an http or https URL.`);
|
|
64
|
+
return { url: parsed.href, viewports: viewports.length > 0 ? viewports : [...PROBE_DEFAULT_VIEWPORTS], json, strict };
|
|
65
|
+
}
|
|
66
|
+
function kilobytes(bytes) {
|
|
67
|
+
return `${Math.round(bytes / 102.4) / 10} KB`;
|
|
68
|
+
}
|
|
69
|
+
/** Edge cache results by count, for example "HIT 3, MISS 9". */
|
|
70
|
+
export function cacheSummary(requests) {
|
|
71
|
+
const counts = new Map();
|
|
72
|
+
for (const request of requests)
|
|
73
|
+
counts.set(request.cache ?? "none", (counts.get(request.cache ?? "none") ?? 0) + 1);
|
|
74
|
+
return [...counts].map(([cache, count]) => `${cache} ${count}`).join(", ") || "none";
|
|
75
|
+
}
|
|
76
|
+
/** The plain-text report: per viewport, the counts, bytes, pages and the W2 result. */
|
|
77
|
+
export function formatProbeReport(url, results) {
|
|
78
|
+
const lines = [`Prefetch probe: ${url}`];
|
|
79
|
+
for (const { viewport, measurement, verdict } of results) {
|
|
80
|
+
const scrollOnly = measurement.scrolled.filter((page) => !measurement.landing.includes(page));
|
|
81
|
+
lines.push("", `${viewport}`, ` On landing: ${measurement.landing.length} page(s)${measurement.landing.length ? `: ${measurement.landing.join(", ")}` : ""}`, ` After a full scroll: ${measurement.scrolled.length} page(s)${scrollOnly.length ? `; added ${scrollOnly.join(", ")}` : ""}`, ` Bytes: ${kilobytes(measurement.bytes.prefetchCompressed)} compressed, ${kilobytes(measurement.bytes.prefetchDecoded)} decoded, in ${measurement.requests.length} request(s); the page's HTML ${kilobytes(measurement.bytes.htmlCompressed)} compressed`, ` Edge cache: ${cacheSummary(measurement.requests)}`, verdict.failures.length === 0 ? " W2: would pass" : ` W2: would fail\n${verdict.failures.map((failure) => ` - ${failure}`).join("\n")}`, ...verdict.warnings.map((warning) => ` ${warning}`));
|
|
82
|
+
}
|
|
83
|
+
return `${lines.join("\n")}\n`;
|
|
84
|
+
}
|
|
85
|
+
/** The JSON report: the same results, with each request. */
|
|
86
|
+
export function probeJson(url, results) {
|
|
87
|
+
return `${JSON.stringify({ url, viewports: results.map(({ viewport, measurement, verdict }) => ({ viewport, ...measurement, verdict })) }, null, 2)}\n`;
|
|
88
|
+
}
|
|
89
|
+
async function loadChromium() {
|
|
90
|
+
const specifier = "@playwright/test";
|
|
91
|
+
try {
|
|
92
|
+
return (await import(specifier)).chromium;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new Error("brandlab-prefetch-probe needs @playwright/test with Chromium: npm install --save-dev @playwright/test && npx playwright install chromium");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export async function probePrefetch(options, chromium) {
|
|
99
|
+
const browser = await (chromium ?? (await loadChromium())).launch({ headless: true });
|
|
100
|
+
const target = new URL(options.url);
|
|
101
|
+
const results = [];
|
|
102
|
+
try {
|
|
103
|
+
for (const viewport of options.viewports) {
|
|
104
|
+
const mobile = viewport.width < 768;
|
|
105
|
+
const context = await browser.newContext({
|
|
106
|
+
viewport: { ...viewport },
|
|
107
|
+
isMobile: mobile,
|
|
108
|
+
hasTouch: mobile,
|
|
109
|
+
serviceWorkers: "block",
|
|
110
|
+
...(mobile ? { userAgent: MOBILE_USER_AGENT } : {}),
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
// Only the site under test: analytics, tags and other sites never see the probe.
|
|
114
|
+
await context.route("**/*", (route) => (new URL(route.request().url()).origin === target.origin ? route.continue() : route.abort()));
|
|
115
|
+
const page = await context.newPage();
|
|
116
|
+
const measurement = await measurePrefetch(page, `${target.pathname}${target.search}`, {
|
|
117
|
+
open: (opened) => opened.goto(target.href, { waitUntil: "load", timeout: 60_000 }),
|
|
118
|
+
});
|
|
119
|
+
const label = `${viewport.width} x ${viewport.height}`;
|
|
120
|
+
results.push({ viewport: label, measurement, verdict: evaluatePrefetch(measurement, label) });
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await context.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
await browser.close();
|
|
129
|
+
}
|
|
130
|
+
return results;
|
|
131
|
+
}
|
|
132
|
+
export async function runPrefetchProbeCli(argv, io, probe = probePrefetch) {
|
|
133
|
+
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
134
|
+
io.stdout(`${HELP}\n`);
|
|
135
|
+
return argv.length === 0 ? 1 : 0;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const options = parseProbeArgs(argv);
|
|
139
|
+
const results = await probe(options);
|
|
140
|
+
io.stdout(options.json ? probeJson(options.url, results) : formatProbeReport(options.url, results));
|
|
141
|
+
return options.strict && results.some((result) => result.verdict.failures.length > 0) ? 1 : 0;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
io.stderr(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function isEntrypoint(moduleUrl, argvPath) {
|
|
149
|
+
if (!argvPath)
|
|
150
|
+
return false;
|
|
151
|
+
try {
|
|
152
|
+
return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(resolve(argvPath));
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (isEntrypoint(import.meta.url, process.argv[1])) {
|
|
159
|
+
runPrefetchProbeCli(process.argv.slice(2), { stdout: (value) => process.stdout.write(value), stderr: (value) => process.stderr.write(value) }).then((code) => {
|
|
160
|
+
process.exitCode = code;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=prefetch-probe-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefetch-probe-cli.js","sourceRoot":"","sources":["../src/prefetch-probe-cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * brandlab-prefetch-probe (3.13.0): measures one page's Next prefetches, run locally against a URL (a local\n * production build, a protected preview or a live page). For each viewport it lists the pages prefetched on landing\n * and after a full scroll, with their compressed and decoded bytes and the edge cache result, and says whether the\n * page would pass W2 (prefetch by link role).\n *\n * npx brandlab-prefetch-probe https://www.example.com.au/areas/hills-district\n * npx brandlab-prefetch-probe http://localhost:3000/ --viewport 390x844 --json\n *\n * It loads the page once per viewport in headless Chromium (`@playwright/test`, an optional peer) and lets no request\n * leave for another site, so analytics never counts it. It reads one page; it never crawls.\n */\nimport { realpathSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { measurePrefetch, type PrefetchMeasureResult } from \"./site-system/journeys/prefetch-measure.js\";\nimport { evaluatePrefetch, type PrefetchVerdict } from \"./site-system/journeys/prefetch-policy.js\";\n\nexport interface ProbeViewport {\n readonly width: number;\n readonly height: number;\n}\n\nexport interface ProbeOptions {\n readonly url: string;\n readonly viewports: readonly ProbeViewport[];\n readonly json: boolean;\n /** Exit 1 when the page would fail W2. */\n readonly strict: boolean;\n}\n\nexport interface ProbeViewportResult {\n readonly viewport: string;\n readonly measurement: PrefetchMeasureResult;\n readonly verdict: PrefetchVerdict;\n}\n\nexport const PROBE_DEFAULT_VIEWPORTS: readonly ProbeViewport[] = Object.freeze([\n { width: 390, height: 844 },\n { width: 1440, height: 900 },\n]);\n\nconst HELP = `brandlab-prefetch-probe <url> [--viewport WIDTHxHEIGHT ...] [--json] [--strict]\n\nLists the pages one page prefetches on landing and after a full scroll, with bytes, at 390 x 844 and 1440 x 900\n(or the viewports named), and whether it would pass W2. --strict exits 1 when it would not.`;\n\nconst MOBILE_USER_AGENT = \"Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Mobile Safari/537.36\";\n\nexport function parseProbeArgs(argv: readonly string[]): ProbeOptions {\n let url: string | undefined;\n const viewports: ProbeViewport[] = [];\n let json = false;\n let strict = false;\n for (let index = 0; index < argv.length; index += 1) {\n const argument = argv[index]!;\n if (argument === \"--json\") json = true;\n else if (argument === \"--strict\") strict = true;\n else if (argument === \"--viewport\") {\n const value = argv[++index] ?? \"\";\n const match = /^(\\d{3,4})x(\\d{3,4})$/.exec(value);\n if (!match) throw new Error(`--viewport takes WIDTHxHEIGHT, for example 390x844 (got ${JSON.stringify(value)}).`);\n viewports.push({ width: Number(match[1]), height: Number(match[2]) });\n } else if (argument.startsWith(\"--\")) throw new Error(`Unknown option ${argument}.`);\n else if (url) throw new Error(\"Name one page URL.\");\n else url = argument;\n }\n if (!url) throw new Error(\"Name the page URL to probe.\");\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`${url} is not a full URL; include http:// or https://.`);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") throw new Error(`${url} is not an http or https URL.`);\n return { url: parsed.href, viewports: viewports.length > 0 ? viewports : [...PROBE_DEFAULT_VIEWPORTS], json, strict };\n}\n\nfunction kilobytes(bytes: number): string {\n return `${Math.round(bytes / 102.4) / 10} KB`;\n}\n\n/** Edge cache results by count, for example \"HIT 3, MISS 9\". */\nexport function cacheSummary(requests: PrefetchMeasureResult[\"requests\"]): string {\n const counts = new Map<string, number>();\n for (const request of requests) counts.set(request.cache ?? \"none\", (counts.get(request.cache ?? \"none\") ?? 0) + 1);\n return [...counts].map(([cache, count]) => `${cache} ${count}`).join(\", \") || \"none\";\n}\n\n/** The plain-text report: per viewport, the counts, bytes, pages and the W2 result. */\nexport function formatProbeReport(url: string, results: readonly ProbeViewportResult[]): string {\n const lines = [`Prefetch probe: ${url}`];\n for (const { viewport, measurement, verdict } of results) {\n const scrollOnly = measurement.scrolled.filter((page) => !measurement.landing.includes(page));\n lines.push(\n \"\",\n `${viewport}`,\n ` On landing: ${measurement.landing.length} page(s)${measurement.landing.length ? `: ${measurement.landing.join(\", \")}` : \"\"}`,\n ` After a full scroll: ${measurement.scrolled.length} page(s)${scrollOnly.length ? `; added ${scrollOnly.join(\", \")}` : \"\"}`,\n ` Bytes: ${kilobytes(measurement.bytes.prefetchCompressed)} compressed, ${kilobytes(measurement.bytes.prefetchDecoded)} decoded, in ${measurement.requests.length} request(s); the page's HTML ${kilobytes(measurement.bytes.htmlCompressed)} compressed`,\n ` Edge cache: ${cacheSummary(measurement.requests)}`,\n verdict.failures.length === 0 ? \" W2: would pass\" : ` W2: would fail\\n${verdict.failures.map((failure) => ` - ${failure}`).join(\"\\n\")}`,\n ...verdict.warnings.map((warning) => ` ${warning}`),\n );\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n\n/** The JSON report: the same results, with each request. */\nexport function probeJson(url: string, results: readonly ProbeViewportResult[]): string {\n return `${JSON.stringify({ url, viewports: results.map(({ viewport, measurement, verdict }) => ({ viewport, ...measurement, verdict })) }, null, 2)}\\n`;\n}\n\ntype Chromium = typeof import(\"@playwright/test\").chromium;\n\nasync function loadChromium(): Promise<Chromium> {\n const specifier: string = \"@playwright/test\";\n try {\n return ((await import(specifier)) as { chromium: Chromium }).chromium;\n } catch {\n throw new Error(\"brandlab-prefetch-probe needs @playwright/test with Chromium: npm install --save-dev @playwright/test && npx playwright install chromium\");\n }\n}\n\nexport async function probePrefetch(options: ProbeOptions, chromium?: Chromium): Promise<ProbeViewportResult[]> {\n const browser = await (chromium ?? (await loadChromium())).launch({ headless: true });\n const target = new URL(options.url);\n const results: ProbeViewportResult[] = [];\n try {\n for (const viewport of options.viewports) {\n const mobile = viewport.width < 768;\n const context = await browser.newContext({\n viewport: { ...viewport },\n isMobile: mobile,\n hasTouch: mobile,\n serviceWorkers: \"block\",\n ...(mobile ? { userAgent: MOBILE_USER_AGENT } : {}),\n });\n try {\n // Only the site under test: analytics, tags and other sites never see the probe.\n await context.route(\"**/*\", (route) => (new URL(route.request().url()).origin === target.origin ? route.continue() : route.abort()));\n const page = await context.newPage();\n const measurement = await measurePrefetch(page, `${target.pathname}${target.search}`, {\n open: (opened) => opened.goto(target.href, { waitUntil: \"load\", timeout: 60_000 }),\n });\n const label = `${viewport.width} x ${viewport.height}`;\n results.push({ viewport: label, measurement, verdict: evaluatePrefetch(measurement, label) });\n } finally {\n await context.close();\n }\n }\n } finally {\n await browser.close();\n }\n return results;\n}\n\ninterface CliIo {\n stdout(value: string): void;\n stderr(value: string): void;\n}\n\nexport async function runPrefetchProbeCli(argv: readonly string[], io: CliIo, probe: typeof probePrefetch = probePrefetch): Promise<number> {\n if (argv.length === 0 || argv.includes(\"--help\") || argv.includes(\"-h\")) {\n io.stdout(`${HELP}\\n`);\n return argv.length === 0 ? 1 : 0;\n }\n try {\n const options = parseProbeArgs(argv);\n const results = await probe(options);\n io.stdout(options.json ? probeJson(options.url, results) : formatProbeReport(options.url, results));\n return options.strict && results.some((result) => result.verdict.failures.length > 0) ? 1 : 0;\n } catch (error) {\n io.stderr(`${error instanceof Error ? error.message : String(error)}\\n`);\n return 1;\n }\n}\n\nfunction isEntrypoint(moduleUrl: string, argvPath: string | undefined): boolean {\n if (!argvPath) return false;\n try {\n return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(resolve(argvPath));\n } catch {\n return false;\n }\n}\n\nif (isEntrypoint(import.meta.url, process.argv[1])) {\n runPrefetchProbeCli(process.argv.slice(2), { stdout: (value) => process.stdout.write(value), stderr: (value) => process.stderr.write(value) }).then((code) => {\n process.exitCode = code;\n });\n}\n"]}
|
package/dist/project.d.ts
CHANGED
|
@@ -38,11 +38,24 @@ export interface BrandlabConformanceResult {
|
|
|
38
38
|
implementationFingerprint: string;
|
|
39
39
|
mode: BrandlabConformanceMode;
|
|
40
40
|
installedRelease: string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Set only when all four packages are one canary prerelease (`X.Y.Z-canary.N`). A canary is never an
|
|
43
|
+
* installed release: `installedRelease` stays null, so it cannot stand as release evidence.
|
|
44
|
+
*/
|
|
45
|
+
canaryRelease?: BrandlabCanaryRelease;
|
|
41
46
|
findings: BrandlabFinding[];
|
|
42
47
|
operationalFindings: BrandlabOperationalFinding[];
|
|
43
48
|
verification: BrandlabVerificationResult;
|
|
44
49
|
summary: ConformanceSummary;
|
|
45
50
|
}
|
|
51
|
+
export interface BrandlabCanaryRelease {
|
|
52
|
+
/** The canary version, e.g. `3.14.0-canary.2`. */
|
|
53
|
+
version: string;
|
|
54
|
+
/** The release line it previews, e.g. `3.14.0`. */
|
|
55
|
+
line: string;
|
|
56
|
+
/** `embedded`: checked against this checker's contract for the line; `not-cut`: the line has no contract yet. */
|
|
57
|
+
contract: "embedded" | "not-cut";
|
|
58
|
+
}
|
|
46
59
|
export interface EvaluateBrandlabProjectOptions {
|
|
47
60
|
packedReleaseReceipt?: string;
|
|
48
61
|
root: string;
|
|
@@ -62,6 +75,13 @@ interface ProjectPackageJsonLike {
|
|
|
62
75
|
export declare function evaluateBrandlabProject(options: EvaluateBrandlabProjectOptions): BrandlabConformanceResult;
|
|
63
76
|
export declare function shouldFailBrandlabConformance(result: BrandlabConformanceResult): boolean;
|
|
64
77
|
export declare function detectInstalledBrandlabRelease(packageJson: ProjectPackageJsonLike | null): string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Reads one canary prerelease across all four packages, pinned exactly (`3.14.0-canary.2`) or installed from
|
|
80
|
+
* the packed tarball (`file:.canary/brandlab-digital-site-core-3.14.0-canary.2.tgz`). A canary previews the line
|
|
81
|
+
* `X.Y.Z`: it is checked against that line's contract when this checker carries it, and is otherwise reported
|
|
82
|
+
* as a canary notice. Lines older than the checker's release are not supported. Never a published release.
|
|
83
|
+
*/
|
|
84
|
+
export declare function detectBrandlabCanaryRelease(packageJson: ProjectPackageJsonLike | null, release?: Pick<BrandlabRuntimeReleaseContract, "releaseVersion">): BrandlabCanaryRelease | null;
|
|
65
85
|
/** Variable names documented in an env example file (`KEY=`, `export KEY=`); values are ignored. */
|
|
66
86
|
export declare function parseEnvironmentKeys(text: string): Set<string>;
|
|
67
87
|
export type { BrandlabContractVersions };
|
package/dist/project.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
|
package/dist/project.js
CHANGED
|
@@ -160,7 +160,10 @@ export function evaluateBrandlabProject(options) {
|
|
|
160
160
|
? verifyPackedPreflight(root, resolve(options.packedReleaseReceipt)) : undefined;
|
|
161
161
|
const detectedRelease = packedPreflight?.version ?? detectInstalledBrandlabRelease(packageJson);
|
|
162
162
|
const installedRelease = options.installedRelease === undefined ? detectedRelease : options.installedRelease;
|
|
163
|
-
const
|
|
163
|
+
const canary = !detectedRelease && options.installedRelease === undefined
|
|
164
|
+
? detectBrandlabCanaryRelease(packageJson, release)
|
|
165
|
+
: null;
|
|
166
|
+
const manifest = release.releaseVersion === installedRelease || canary?.contract === "embedded" ? release : null;
|
|
164
167
|
const attestation = options.releaseManifest?.releaseVersion === installedRelease
|
|
165
168
|
? options.releaseManifest
|
|
166
169
|
: null;
|
|
@@ -174,6 +177,8 @@ export function evaluateBrandlabProject(options) {
|
|
|
174
177
|
packageLock,
|
|
175
178
|
installedRelease,
|
|
176
179
|
detectedRelease,
|
|
180
|
+
canary,
|
|
181
|
+
releaseVersion: release.releaseVersion,
|
|
177
182
|
manifest,
|
|
178
183
|
attestation,
|
|
179
184
|
packedPreflight,
|
|
@@ -225,6 +230,7 @@ export function evaluateBrandlabProject(options) {
|
|
|
225
230
|
implementationFingerprint,
|
|
226
231
|
mode,
|
|
227
232
|
installedRelease,
|
|
233
|
+
...(canary ? { canaryRelease: canary } : {}),
|
|
228
234
|
findings: operationalFindings,
|
|
229
235
|
operationalFindings,
|
|
230
236
|
verification: policyResult.verification,
|
|
@@ -234,16 +240,88 @@ export function evaluateBrandlabProject(options) {
|
|
|
234
240
|
export function shouldFailBrandlabConformance(result) {
|
|
235
241
|
return result.verification.summary.blockers > 0;
|
|
236
242
|
}
|
|
243
|
+
const CANARY_VERSION_PATTERN = /^((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))-canary\.(?:0|[1-9]\d*)$/;
|
|
237
244
|
export function detectInstalledBrandlabRelease(packageJson) {
|
|
238
245
|
if (!packageJson)
|
|
239
246
|
return null;
|
|
240
247
|
const versions = BRANDLAB_SHARED_PACKAGE_NAMES.map((packageName) => packageJson.dependencies?.[packageName] ??
|
|
241
248
|
packageJson.devDependencies?.[packageName] ??
|
|
242
249
|
null);
|
|
243
|
-
|
|
250
|
+
// A canary (X.Y.Z-canary.N) is never an installed release; detectBrandlabCanaryRelease reads it.
|
|
251
|
+
if (versions.some((version) => !version || !isBrandlabSemver(version) || CANARY_VERSION_PATTERN.test(version))) {
|
|
244
252
|
return null;
|
|
253
|
+
}
|
|
245
254
|
return new Set(versions).size === 1 ? versions[0] : null;
|
|
246
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Reads one canary prerelease across all four packages, pinned exactly (`3.14.0-canary.2`) or installed from
|
|
258
|
+
* the packed tarball (`file:.canary/brandlab-digital-site-core-3.14.0-canary.2.tgz`). A canary previews the line
|
|
259
|
+
* `X.Y.Z`: it is checked against that line's contract when this checker carries it, and is otherwise reported
|
|
260
|
+
* as a canary notice. Lines older than the checker's release are not supported. Never a published release.
|
|
261
|
+
*/
|
|
262
|
+
export function detectBrandlabCanaryRelease(packageJson, release = BRANDLAB_CURRENT_RUNTIME_RELEASE) {
|
|
263
|
+
if (!packageJson)
|
|
264
|
+
return null;
|
|
265
|
+
const versions = BRANDLAB_SHARED_PACKAGE_NAMES.map((packageName) => {
|
|
266
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName];
|
|
267
|
+
if (!spec)
|
|
268
|
+
return null;
|
|
269
|
+
if (CANARY_VERSION_PATTERN.test(spec))
|
|
270
|
+
return spec;
|
|
271
|
+
if (!spec.startsWith("file:"))
|
|
272
|
+
return null;
|
|
273
|
+
const tarballPrefix = `brandlab-digital-${packageName.split("/")[1]}-`;
|
|
274
|
+
const fileName = spec.slice("file:".length).split("/").at(-1) ?? "";
|
|
275
|
+
if (!fileName.startsWith(tarballPrefix) || !fileName.endsWith(".tgz"))
|
|
276
|
+
return null;
|
|
277
|
+
const version = fileName.slice(tarballPrefix.length, -".tgz".length);
|
|
278
|
+
return CANARY_VERSION_PATTERN.test(version) ? version : null;
|
|
279
|
+
});
|
|
280
|
+
const [version] = versions;
|
|
281
|
+
if (!version || versions.some((candidate) => candidate !== version))
|
|
282
|
+
return null;
|
|
283
|
+
const line = CANARY_VERSION_PATTERN.exec(version)[1];
|
|
284
|
+
const contract = line === release.releaseVersion ? "embedded" : "not-cut";
|
|
285
|
+
return { version, line, contract };
|
|
286
|
+
}
|
|
287
|
+
function inspectCanary(options) {
|
|
288
|
+
const { canary } = options;
|
|
289
|
+
recordEvaluation(options.evaluations, "release", "brandlab-release-unsupported");
|
|
290
|
+
if (compareBrandlabSemver(canary.line, options.release) < 0) {
|
|
291
|
+
options.findings.push(releaseFinding("brandlab-release-unsupported", `A published release, or a canary of ${options.release} or later`, `${canary.version} is a canary of an older line than ${options.release}.`, "Install the published release or a current canary."));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
recordEvaluation(options.evaluations, "release", "brandlab-release-canary");
|
|
295
|
+
options.findings.push({
|
|
296
|
+
...releaseFinding("brandlab-release-canary", "A published Brandlab release", canary.contract === "embedded"
|
|
297
|
+
? `${canary.version} is a canary of ${canary.line}, not a published release; it was checked against the ${canary.line} contract.`
|
|
298
|
+
: `${canary.version} is a canary of ${canary.line}, not a published release; ${canary.line} has no release contract yet.`, `Install the published ${canary.line} before release or launch.`, "package.json"),
|
|
299
|
+
severity: "warning",
|
|
300
|
+
});
|
|
301
|
+
recordEvaluation(options.evaluations, "release", "brandlab-release-package-lock");
|
|
302
|
+
if (options.packageLock.state !== "valid") {
|
|
303
|
+
options.findings.push(releaseFinding(`brandlab-release-package-lock-${options.packageLock.state}`, "A valid npm lockfile v2 or v3", `package-lock.json state is ${options.packageLock.state}.`, "Regenerate the lockfile from the exact package declarations.", "package-lock.json"));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
recordEvaluation(options.evaluations, "release", "brandlab-release-package-pin-mismatch");
|
|
307
|
+
for (const packageName of BRANDLAB_SHARED_PACKAGE_NAMES) {
|
|
308
|
+
const declared = declaredPackageSpec(options.packageJson, packageName);
|
|
309
|
+
const locked = lockedRootSpec(options.packageLock, packageName);
|
|
310
|
+
const resolved = options.packageLock.resolvedPackages.get(packageName);
|
|
311
|
+
if (locked !== declared || resolved?.version !== canary.version || resolved.linked) {
|
|
312
|
+
options.findings.push(releaseFinding("brandlab-release-package-pin-mismatch", `${packageName}@${canary.version} declared once and locked exactly`, `package.json=${declared ?? "missing"}; lockfile=${locked ?? "missing"}; resolved=${resolved?.version ?? "missing"}.`, "Reinstall the four canary packages together and regenerate the lockfile.", "package.json"));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (options.manifest) {
|
|
316
|
+
inspectRuntime({
|
|
317
|
+
findings: options.findings,
|
|
318
|
+
evaluations: options.evaluations,
|
|
319
|
+
packageJson: options.packageJson,
|
|
320
|
+
packageLock: options.packageLock,
|
|
321
|
+
manifest: options.manifest,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
247
325
|
function inspectRelease(options) {
|
|
248
326
|
recordEvaluation(options.evaluations, "release", "brandlab-release-package-json-missing");
|
|
249
327
|
if (!options.packageJson) {
|
|
@@ -251,6 +329,18 @@ function inspectRelease(options) {
|
|
|
251
329
|
return;
|
|
252
330
|
}
|
|
253
331
|
recordEvaluation(options.evaluations, "release", "brandlab-release-installed-version-unresolved");
|
|
332
|
+
if (!options.installedRelease && options.canary) {
|
|
333
|
+
inspectCanary({
|
|
334
|
+
findings: options.findings,
|
|
335
|
+
evaluations: options.evaluations,
|
|
336
|
+
packageJson: options.packageJson,
|
|
337
|
+
packageLock: options.packageLock,
|
|
338
|
+
canary: options.canary,
|
|
339
|
+
release: options.releaseVersion,
|
|
340
|
+
manifest: options.manifest,
|
|
341
|
+
});
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
254
344
|
if (!options.installedRelease) {
|
|
255
345
|
options.findings.push(releaseFinding("brandlab-release-installed-version-unresolved", "All four Brandlab packages pinned to one exact release", "The installed Brandlab release could not be resolved.", "Pin all four packages to one exact supported version."));
|
|
256
346
|
return;
|