@brandlab-digital/conformance 0.1.0-alpha.9 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { type BrandlabConformanceMode } from "./project.js";
3
+ interface CliOptions {
4
+ root: string;
5
+ installedRelease?: string | null;
6
+ targetRelease?: string | null;
7
+ supportPolicyPath: string | null;
8
+ mode: BrandlabConformanceMode;
9
+ json: boolean;
10
+ help: boolean;
11
+ }
12
+ interface CliIo {
13
+ stdout: (value: string) => void;
14
+ stderr: (value: string) => void;
15
+ }
16
+ export declare function parseBrandlabConformanceArgs(argv: readonly string[]): CliOptions;
17
+ export declare function runBrandlabConformanceCli(argv: readonly string[], io?: CliIo): number;
18
+ export {};
19
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAIA,OAAO,EAGL,KAAK,uBAAuB,EAE7B,MAAM,cAAc,CAAC;AAGtB,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,UAAU,KAAK;IACb,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAcD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CA2ChF;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,GAAE,KAGH,GACA,MAAM,CAqBR"}
package/dist/cli.js ADDED
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { evaluateBrandlabProject, shouldFailBrandlabConformance, } from "./project.js";
6
+ const HELP = `Brandlab version-aware conformance
7
+
8
+ Usage:
9
+ brandlab-conformance --support-policy <path> [--root <path>]
10
+ [--installed-release <version|unadopted>]
11
+ [--target-release <version|none>] [--mode <local|ci|preflight>] [--json]
12
+
13
+ Exit status:
14
+ local/ci fail only for installed-release maintenance blockers
15
+ preflight also fails for target-release migration blockers
16
+ `;
17
+ export function parseBrandlabConformanceArgs(argv) {
18
+ const parsed = {
19
+ root: process.cwd(),
20
+ supportPolicyPath: null,
21
+ mode: "local",
22
+ json: false,
23
+ help: false,
24
+ };
25
+ for (let index = 0; index < argv.length; index += 1) {
26
+ const argument = argv[index];
27
+ if (argument === "--help" || argument === "-h") {
28
+ parsed.help = true;
29
+ }
30
+ else if (argument === "--json") {
31
+ parsed.json = true;
32
+ }
33
+ else if (argument === "--root") {
34
+ parsed.root = resolve(requireValue(argv, ++index, "--root"));
35
+ }
36
+ else if (argument === "--support-policy") {
37
+ parsed.supportPolicyPath = resolve(requireValue(argv, ++index, "--support-policy"));
38
+ }
39
+ else if (argument === "--installed-release") {
40
+ const value = requireValue(argv, ++index, "--installed-release");
41
+ parsed.installedRelease = value === "unadopted" ? null : value;
42
+ }
43
+ else if (argument === "--target-release") {
44
+ const value = requireValue(argv, ++index, "--target-release");
45
+ parsed.targetRelease = value === "none" ? null : value;
46
+ }
47
+ else if (argument === "--mode") {
48
+ const value = requireValue(argv, ++index, "--mode");
49
+ if (value !== "local" && value !== "ci" && value !== "preflight") {
50
+ throw new Error(`Invalid --mode ${value}.`);
51
+ }
52
+ parsed.mode = value;
53
+ }
54
+ else {
55
+ throw new Error(`Unknown argument ${argument}.`);
56
+ }
57
+ }
58
+ if (!parsed.help && parsed.supportPolicyPath === null) {
59
+ throw new Error("--support-policy is required.");
60
+ }
61
+ return parsed;
62
+ }
63
+ export function runBrandlabConformanceCli(argv, io = {
64
+ stdout: (value) => process.stdout.write(value),
65
+ stderr: (value) => process.stderr.write(value),
66
+ }) {
67
+ try {
68
+ const options = parseBrandlabConformanceArgs(argv);
69
+ if (options.help) {
70
+ io.stdout(HELP);
71
+ return 0;
72
+ }
73
+ const result = evaluateBrandlabProject({
74
+ root: options.root,
75
+ installedRelease: options.installedRelease,
76
+ targetRelease: options.targetRelease,
77
+ mode: options.mode,
78
+ supportPolicy: loadSupportPolicy(options.supportPolicyPath),
79
+ });
80
+ io.stdout(options.json ? `${JSON.stringify(result, null, 2)}\n` : formatResult(result));
81
+ return shouldFailBrandlabConformance(result) ? 1 : 0;
82
+ }
83
+ catch (error) {
84
+ io.stderr(`${error instanceof Error ? error.message : String(error)}\n\n${HELP}`);
85
+ return 2;
86
+ }
87
+ }
88
+ function loadSupportPolicy(path) {
89
+ if (path === null)
90
+ throw new Error("--support-policy is required.");
91
+ try {
92
+ return JSON.parse(readFileSync(path, "utf8"));
93
+ }
94
+ catch (error) {
95
+ const detail = error instanceof Error ? error.message : String(error);
96
+ throw new Error(`Unable to load support policy from ${path}: ${detail}`);
97
+ }
98
+ }
99
+ function formatResult(result) {
100
+ const lines = [
101
+ "Brandlab conformance",
102
+ `installed: ${result.installedRelease ?? "unadopted"}`,
103
+ `target: ${result.targetRelease ?? "none"}`,
104
+ `maintenance: ${formatSummary(result.maintenance)}`,
105
+ `migration: ${formatSummary(result.migration)}`,
106
+ `advisory: ${formatSummary(result.advisory)}`,
107
+ ];
108
+ for (const finding of result.findings) {
109
+ const location = finding.path
110
+ ? ` (${finding.path}${finding.line ? `:${finding.line}` : ""})`
111
+ : "";
112
+ lines.push(`[${finding.classification}/${finding.severity}] ${finding.ruleId}${location}: ${finding.actual}`);
113
+ }
114
+ return `${lines.join("\n")}\n`;
115
+ }
116
+ function formatSummary(summary) {
117
+ return `${summary.status}; ${summary.blockers} blocker(s), ${summary.warnings} warning(s), ${summary.info} info`;
118
+ }
119
+ function requireValue(argv, index, argument) {
120
+ const value = argv[index];
121
+ if (!value || value.startsWith("--"))
122
+ throw new Error(`${argument} requires a value.`);
123
+ return value;
124
+ }
125
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
126
+ process.exitCode = runBrandlabConformanceCli(process.argv.slice(2));
127
+ }
128
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Location-page conformance rules (Brandlab Location Pages Standard §11).
3
+ *
4
+ * These checks are heuristics over project source text and sitemap output.
5
+ * They are deliberately conservative: each rule documents its escape hatch so
6
+ * a compliant pattern is never impossible, only explicit.
7
+ */
8
+ import type { SourcePattern, SourcePatternMatch, SourceTextFile } from "./vitest.js";
9
+ export declare const BRANDLAB_LOCATION_FORBIDDEN_SOURCE_PATTERNS: readonly SourcePattern[];
10
+ /**
11
+ * Flags `approvedIndexable*` assignments whose value is produced by code
12
+ * (call/map/flatMap) instead of an explicit literal list (standard §7.4).
13
+ *
14
+ * Escape hatch: statements that reference an identifier containing "frozen"
15
+ * are allowed — that is the documented snapshot pattern (an explicit,
16
+ * human-edited literal module filtered into record shape).
17
+ */
18
+ export declare function findGeneratedServiceLocationApprovals(files: readonly SourceTextFile[]): SourcePatternMatch[];
19
+ export interface LocationLinkDumpOptions {
20
+ files: readonly SourceTextFile[];
21
+ /** Path prefixes that identify location URLs, e.g. ['/locations/']. */
22
+ locationPathPrefixes: readonly string[];
23
+ /** Only files whose path matches are checked. Default: footer/nav components. */
24
+ filePathPattern?: RegExp;
25
+ /** Maximum location links allowed per matching file. Default 20 (≈ regions, never all suburbs). */
26
+ maxLinks?: number;
27
+ }
28
+ export interface LocationLinkDumpFinding {
29
+ file: string;
30
+ linkCount: number;
31
+ maxLinks: number;
32
+ }
33
+ /**
34
+ * Flags footer/nav components that link a suburb dump (standard §6.6 — nav
35
+ * and footer carry region links only; "blocks of text that list cities and
36
+ * regions" is Google's own keyword-stuffing example).
37
+ */
38
+ export declare function findLocationLinkDumps({ files, locationPathPrefixes, filePathPattern, maxLinks, }: LocationLinkDumpOptions): LocationLinkDumpFinding[];
39
+ /**
40
+ * Copy-hygiene patterns for location data modules (standard §3.8). Applied to
41
+ * `src/data/locations/**` and `src/data/serviceLocations/**` rendered-string
42
+ * sources: unresolved tokens and template wording must never reach a
43
+ * customer-facing surface.
44
+ */
45
+ export declare const BRANDLAB_LOCATION_COPY_PATTERNS: readonly SourcePattern[];
46
+ export interface LocationUrlShareOptions {
47
+ /** All sitemap entry URLs (absolute or root-relative). */
48
+ urls: readonly string[];
49
+ /** Predicate identifying location-family URLs. */
50
+ isLocationUrl: (url: string) => boolean;
51
+ /** Warn when location URLs exceed this share of all URLs. Default 0.4 (standard §8.8). */
52
+ maxShare?: number;
53
+ }
54
+ export interface LocationUrlShareResult {
55
+ locationUrlCount: number;
56
+ totalUrlCount: number;
57
+ share: number;
58
+ maxShare: number;
59
+ exceeded: boolean;
60
+ }
61
+ /** Volume alarm for location-heavy sitemaps (standard §8.8). */
62
+ export declare function evaluateLocationUrlShare({ urls, isLocationUrl, maxShare, }: LocationUrlShareOptions): LocationUrlShareResult;
63
+ //# sourceMappingURL=locations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAErF,eAAO,MAAM,2CAA2C,EAAE,SAAS,aAAa,EAkB/E,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,kBAAkB,EAAE,CAuBtB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IACjC,uEAAuE;IACvE,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,oBAAoB,EACpB,eAAiC,EACjC,QAAa,GACd,EAAE,uBAAuB,GAAG,uBAAuB,EAAE,CAiBrD;AAED;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,EAAE,SAAS,aAAa,EAcnE,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,0DAA0D;IAC1D,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,kDAAkD;IAClD,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,gEAAgE;AAChE,wBAAgB,wBAAwB,CAAC,EACvC,IAAI,EACJ,aAAa,EACb,QAAc,GACf,EAAE,uBAAuB,GAAG,sBAAsB,CAYlD"}
@@ -0,0 +1,105 @@
1
+ export const BRANDLAB_LOCATION_FORBIDDEN_SOURCE_PATTERNS = [
2
+ {
3
+ // Rating markup about the business/its services is never emitted
4
+ // (standard §6.2). Matches emission sites, not prose/comments.
5
+ name: "self-serving rating schema emission",
6
+ pattern: /['"]@type['"]\s*:\s*['"](?:AggregateRating|Review)['"]/,
7
+ },
8
+ {
9
+ name: "self-serving rating schema builder",
10
+ pattern: /\bbuild(?:Service)?(?:AggregateRating|Rating|Review)Schema\b/,
11
+ },
12
+ {
13
+ // Named constant reused as every sitemap entry's lastModified defeats
14
+ // the freshness signal (standard §6.3). Use per-record
15
+ // lastSubstantialUpdate dates instead.
16
+ name: "constant sitemap lastModified",
17
+ pattern: /lastModified\s*:\s*SITEMAP_LAST_MODIFIED\b/,
18
+ },
19
+ ];
20
+ /**
21
+ * Flags `approvedIndexable*` assignments whose value is produced by code
22
+ * (call/map/flatMap) instead of an explicit literal list (standard §7.4).
23
+ *
24
+ * Escape hatch: statements that reference an identifier containing "frozen"
25
+ * are allowed — that is the documented snapshot pattern (an explicit,
26
+ * human-edited literal module filtered into record shape).
27
+ */
28
+ export function findGeneratedServiceLocationApprovals(files) {
29
+ const matches = [];
30
+ const assignment = /\bapprovedIndexable\w*\s*[:=]/;
31
+ const generated = /[:=][^;\n]*(?:\.flatMap\(|\.map\(|\w+\(\s*\))/;
32
+ for (const file of files) {
33
+ const lines = file.text.split("\n");
34
+ lines.forEach((line, index) => {
35
+ if (!assignment.test(line))
36
+ return;
37
+ if (/frozen/i.test(line))
38
+ return;
39
+ if (!generated.test(line))
40
+ return;
41
+ matches.push({
42
+ file: file.path,
43
+ patternName: "generated service-location approvals",
44
+ line: index + 1,
45
+ column: line.search(assignment) + 1,
46
+ text: line.trim(),
47
+ });
48
+ });
49
+ }
50
+ return matches;
51
+ }
52
+ /**
53
+ * Flags footer/nav components that link a suburb dump (standard §6.6 — nav
54
+ * and footer carry region links only; "blocks of text that list cities and
55
+ * regions" is Google's own keyword-stuffing example).
56
+ */
57
+ export function findLocationLinkDumps({ files, locationPathPrefixes, filePathPattern = /(footer|nav)/i, maxLinks = 20, }) {
58
+ const findings = [];
59
+ for (const file of files) {
60
+ if (!filePathPattern.test(file.path))
61
+ continue;
62
+ let linkCount = 0;
63
+ for (const prefix of locationPathPrefixes) {
64
+ linkCount += file.text.split(prefix).length - 1;
65
+ }
66
+ if (linkCount > maxLinks) {
67
+ findings.push({ file: file.path, linkCount, maxLinks });
68
+ }
69
+ }
70
+ return findings;
71
+ }
72
+ /**
73
+ * Copy-hygiene patterns for location data modules (standard §3.8). Applied to
74
+ * `src/data/locations/**` and `src/data/serviceLocations/**` rendered-string
75
+ * sources: unresolved tokens and template wording must never reach a
76
+ * customer-facing surface.
77
+ */
78
+ export const BRANDLAB_LOCATION_COPY_PATTERNS = [
79
+ {
80
+ name: "unresolved bracket token",
81
+ pattern: /\[(?:Suburb|Region|Location|Service|Business Name|Phone|Email)\]/,
82
+ },
83
+ {
84
+ name: "unresolved curly token in string",
85
+ pattern: /['"`][^'"`\n]*\{(?:suburb|region|location|service)\}[^'"`\n]*['"`]/,
86
+ },
87
+ {
88
+ name: "template wording",
89
+ pattern: /\b(?:lorem ipsum|placeholder copy|copy goes here|content goes here|insert copy|TODO copy|sample copy|starter copy|boilerplate copy)\b/i,
90
+ },
91
+ ];
92
+ /** Volume alarm for location-heavy sitemaps (standard §8.8). */
93
+ export function evaluateLocationUrlShare({ urls, isLocationUrl, maxShare = 0.4, }) {
94
+ const totalUrlCount = urls.length;
95
+ const locationUrlCount = urls.filter((url) => isLocationUrl(url)).length;
96
+ const share = totalUrlCount === 0 ? 0 : locationUrlCount / totalUrlCount;
97
+ return {
98
+ locationUrlCount,
99
+ totalUrlCount,
100
+ share,
101
+ maxShare,
102
+ exceeded: share > maxShare,
103
+ };
104
+ }
105
+ //# sourceMappingURL=locations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locations.js","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export declare const BRANDLAB_CONFIG_V2_PROFILES: readonly ["next-local-service", "next-root-level", "next-static-legacy", "legacy-php"];
2
+ export interface InspectedBrandlabProjectConfig {
3
+ projectId: string;
4
+ profile: (typeof BRANDLAB_CONFIG_V2_PROFILES)[number];
5
+ routes: {
6
+ families: readonly string[];
7
+ };
8
+ capabilities: {
9
+ blogAdapter: string | null;
10
+ contactEnabled: boolean;
11
+ };
12
+ }
13
+ export interface BrandlabConfigV2Issue {
14
+ ruleId: string;
15
+ surface: string;
16
+ severity: "blocker" | "warning" | "info";
17
+ evidenceKind: "static" | "behavioural" | "runtime";
18
+ path?: string;
19
+ line?: number;
20
+ expected: string;
21
+ actual: string;
22
+ remediation: string;
23
+ }
24
+ export interface BrandlabConfigV2Inspection {
25
+ config: InspectedBrandlabProjectConfig | null;
26
+ imports: ReadonlySet<string>;
27
+ executableImports: ReadonlySet<string>;
28
+ issues: BrandlabConfigV2Issue[];
29
+ }
30
+ export declare function inspectBrandlabConfigV2(root: string): BrandlabConfigV2Inspection;
31
+ //# sourceMappingURL=project-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-config.d.ts","sourceRoot":"","sources":["../src/project-config.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,2BAA2B,wFAK9B,CAAC;AA0CX,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,EAAE;QACN,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;KAC7B,CAAC;IACF,YAAY,EAAE;QACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACzC,YAAY,EAAE,QAAQ,GAAG,aAAa,GAAG,SAAS,CAAC;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,8BAA8B,GAAG,IAAI,CAAC;IAC9C,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,EAAE,qBAAqB,EAAE,CAAC;CACjC;AAqDD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,0BAA0B,CA2FhF"}