@brandlab-digital/conformance 1.0.0-rc.9 → 1.0.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/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +35 -93
- package/dist/cli.js.map +1 -1
- package/dist/current-release.d.ts +3 -0
- package/dist/current-release.d.ts.map +1 -0
- package/dist/current-release.js +54 -0
- package/dist/current-release.js.map +1 -0
- package/dist/project.d.ts +19 -24
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +444 -1070
- package/dist/project.js.map +1 -1
- package/dist/release-manifest.d.ts +29 -82
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js +141 -348
- package/dist/release-manifest.js.map +1 -1
- package/dist/rendered-content.d.ts +36 -0
- package/dist/rendered-content.d.ts.map +1 -0
- package/dist/rendered-content.js +146 -0
- package/dist/rendered-content.js.map +1 -0
- package/dist/verification-policy.d.ts +162 -0
- package/dist/verification-policy.d.ts.map +1 -0
- package/dist/verification-policy.js +309 -0
- package/dist/verification-policy.js.map +1 -0
- package/package.json +6 -22
- package/dist/legacy-architecture-contracts.d.ts +0 -31
- package/dist/legacy-architecture-contracts.d.ts.map +0 -1
- package/dist/legacy-architecture-contracts.js +0 -49
- package/dist/legacy-architecture-contracts.js.map +0 -1
- package/dist/locations.d.ts +0 -63
- package/dist/locations.d.ts.map +0 -1
- package/dist/locations.js +0 -105
- package/dist/locations.js.map +0 -1
- package/dist/project-config-v2-contract.d.ts +0 -32
- package/dist/project-config-v2-contract.d.ts.map +0 -1
- package/dist/project-config-v2-contract.js +0 -926
- package/dist/project-config-v2-contract.js.map +0 -1
- package/dist/project-config-v3.d.ts +0 -43
- package/dist/project-config-v3.d.ts.map +0 -1
- package/dist/project-config-v3.js +0 -2876
- package/dist/project-config-v3.js.map +0 -1
- package/dist/project-config.d.ts +0 -3
- package/dist/project-config.d.ts.map +0 -1
- package/dist/project-config.js +0 -3
- package/dist/project-config.js.map +0 -1
- package/dist/release-catalog.d.ts +0 -4
- package/dist/release-catalog.d.ts.map +0 -1
- package/dist/release-catalog.js +0 -787
- package/dist/release-catalog.js.map +0 -1
- package/dist/release-contract-compatibility.d.ts +0 -104
- package/dist/release-contract-compatibility.d.ts.map +0 -1
- package/dist/release-contract-compatibility.js +0 -119
- package/dist/release-contract-compatibility.js.map +0 -1
- package/dist/schema-hygiene.d.ts +0 -26
- package/dist/schema-hygiene.d.ts.map +0 -1
- package/dist/schema-hygiene.js +0 -28
- package/dist/schema-hygiene.js.map +0 -1
- package/dist/source-safety-contracts.d.ts +0 -20
- package/dist/source-safety-contracts.d.ts.map +0 -1
- package/dist/source-safety-contracts.js +0 -60
- package/dist/source-safety-contracts.js.map +0 -1
- package/dist/vitest.d.ts +0 -110
- package/dist/vitest.d.ts.map +0 -1
- package/dist/vitest.js +0 -2678
- package/dist/vitest.js.map +0 -1
package/dist/project.js
CHANGED
|
@@ -1,195 +1,147 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import { inspectLegacyArchitectureContract, legacyArchitectureRulesetId, targetOnlyLegacyArchitectureIssues, } from "./legacy-architecture-contracts.js";
|
|
10
|
-
import { BRANDLAB_SHARED_PACKAGE_NAMES, compareBrandlabSemver, digestBrandlabAuthority, isBrandlabSemver, validateBrandlabReleaseCatalog, validateBrandlabRuntimeReleaseCatalog, validateBrandlabRuntimeReleaseManifestSubsetAgainstCatalog, validateBrandlabSupportPolicy, } from "./release-manifest.js";
|
|
11
|
-
export const BRANDLAB_CONFORMANCE_SCHEMA_VERSION = "brandlab.conformance.v1";
|
|
12
|
-
export const BRANDLAB_CONFORMANCE_EVALUATOR_VERSION = "control-plane-v1";
|
|
13
|
-
export const BRANDLAB_ARCHITECTURE_DISPATCH_RULESET_ID = "version-aware-architecture-dispatch-v1";
|
|
14
|
-
const LEGACY_ARCHITECTURE_EVALUATOR_CONTRACT = "architecture-v2";
|
|
15
|
-
const LEGACY_CONFIGURATION_CONTRACT = "legacy-config-v1";
|
|
16
|
-
const CONFIGURATION_V2_CONTRACT = "project-config-v2";
|
|
17
|
-
const CONFIGURATION_V3_CONTRACT = "project-config-v3";
|
|
18
|
-
const SEO_V3_CONTRACT = "seo-v3";
|
|
3
|
+
import { BRANDLAB_CURRENT_RUNTIME_RELEASE } from "./current-release.js";
|
|
4
|
+
import { BRANDLAB_SHARED_PACKAGE_NAMES, compareBrandlabSemver, digestBrandlabAuthority, isBrandlabSemver, projectBrandlabRuntimeReleaseContract, validateBrandlabReleaseManifest, validateBrandlabRuntimeReleaseContract, } from "./release-manifest.js";
|
|
5
|
+
import { applyBrandlabVerificationPolicy, BRANDLAB_VERIFICATION_POLICY_VERSION, } from "./verification-policy.js";
|
|
6
|
+
export { BRANDLAB_VERIFICATION_CHECKS, BRANDLAB_VERIFICATION_FAMILIES, BRANDLAB_VERIFICATION_POLICY_VERSION, } from "./verification-policy.js";
|
|
7
|
+
export const BRANDLAB_CONFORMANCE_SCHEMA_VERSION = "brandlab.conformance.v2";
|
|
8
|
+
export const BRANDLAB_CONFORMANCE_EVALUATOR_VERSION = "current-only-v2";
|
|
19
9
|
const RUNTIME_PACKAGE_FIELDS = [
|
|
20
10
|
["next", "next"],
|
|
21
11
|
["react", "react"],
|
|
22
12
|
["react-dom", "reactDom"],
|
|
23
13
|
["typescript", "typescript"],
|
|
24
14
|
];
|
|
15
|
+
const SOURCE_EXTENSIONS = new Set([".js", ".jsx", ".mjs", ".ts", ".tsx"]);
|
|
16
|
+
const SOURCE_RULES = [
|
|
17
|
+
{
|
|
18
|
+
ruleId: "brandlab-source-retired-analytics-event",
|
|
19
|
+
surface: "analytics",
|
|
20
|
+
pattern: /\b(?:contact_form_submission|phone_calls|form_start|cta_click)\b/,
|
|
21
|
+
expected: "Only the current Brandlab analytics event contract is used.",
|
|
22
|
+
remediation: "Use the canonical phone_call and contact_form events.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
ruleId: "brandlab-source-local-google-tag-bootstrap",
|
|
26
|
+
surface: "analytics",
|
|
27
|
+
pattern: /googletagmanager\.com\/gtag\/js|window\.dataLayer\s*=\s*window\.dataLayer|function\s+gtag\s*\(/,
|
|
28
|
+
expected: "Google tag bootstrap is owned by the shared analytics runtime.",
|
|
29
|
+
remediation: "Remove the project-local bootstrap and supply only approved analytics identifiers.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
ruleId: "brandlab-source-local-contact-delivery",
|
|
33
|
+
surface: "contact",
|
|
34
|
+
pattern: /\.emails\.send\s*\(|resend\.emails\.send\s*\(/,
|
|
35
|
+
expected: "Email delivery is owned by the shared contact runtime.",
|
|
36
|
+
remediation: "Pass the Resend client to the shared contact adapter instead of sending locally.",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
ruleId: "brandlab-source-local-contact-email-renderer",
|
|
40
|
+
surface: "contact",
|
|
41
|
+
pattern: /\bContactFormEmail\b|\brenderContactEmail\b/,
|
|
42
|
+
expected: "Contact email rendering is owned by the shared contact runtime.",
|
|
43
|
+
remediation: "Remove the local email renderer and configure shared brand variation only.",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
ruleId: "brandlab-source-dangerously-set-inner-html",
|
|
47
|
+
surface: "security",
|
|
48
|
+
pattern: /\bdangerouslySetInnerHTML\b/,
|
|
49
|
+
expected: "No project-owned raw HTML injection.",
|
|
50
|
+
remediation: "Use structured React output or the shared sanitised renderer.",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
ruleId: "brandlab-source-raw-json-ld",
|
|
54
|
+
surface: "schema",
|
|
55
|
+
pattern: /application\/ld\+json/,
|
|
56
|
+
expected: "Structured data is rendered through the shared schema component.",
|
|
57
|
+
remediation: "Replace raw JSON-LD output with the shared schema graph renderer.",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
ruleId: "brandlab-source-self-serving-rating-schema",
|
|
61
|
+
surface: "schema",
|
|
62
|
+
pattern: /['"]AggregateRating['"]/,
|
|
63
|
+
expected: "No self-serving aggregate rating schema.",
|
|
64
|
+
remediation: "Show verified review proof visibly without LocalBusiness or Organization rating schema.",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
ruleId: "brandlab-source-synthetic-offer-schema",
|
|
68
|
+
surface: "schema",
|
|
69
|
+
pattern: /\bpriceValidUntil\b|['"]@type['"]\s*:\s*['"]Offer['"]/,
|
|
70
|
+
expected: "No synthetic Offer markup on service pages.",
|
|
71
|
+
remediation: "Remove invented Offer data and use the shared service schema.",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
ruleId: "brandlab-source-local-google-reviews-fetch",
|
|
75
|
+
surface: "reviews",
|
|
76
|
+
pattern: /places\.googleapis\.com|maps\.googleapis\.com\/maps\/api\/place/,
|
|
77
|
+
expected: "Google Places fetching is owned by the shared review runtime.",
|
|
78
|
+
remediation: "Provide only the verified Place ID, Maps URL and server-side API key.",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
ruleId: "brandlab-source-public-secret",
|
|
82
|
+
surface: "security",
|
|
83
|
+
pattern: /NEXT_PUBLIC_[A-Z0-9_]*(?:API_KEY|SECRET|TOKEN)/,
|
|
84
|
+
expected: "Secrets remain server-only.",
|
|
85
|
+
remediation: "Move the credential to an approved server-only environment variable.",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
ruleId: "brandlab-source-retired-contact-state",
|
|
89
|
+
surface: "contact",
|
|
90
|
+
pattern: /\bCONTACT_CONTEXT_SECRET\b|\bKV_REST_API_(?:URL|TOKEN)\b/,
|
|
91
|
+
expected: "The simplified contact runtime has no project-level state-service requirement.",
|
|
92
|
+
remediation: "Remove the retired contact state variables and use the shared stateless contact flow.",
|
|
93
|
+
},
|
|
94
|
+
];
|
|
25
95
|
export function evaluateBrandlabProject(options) {
|
|
26
96
|
const root = resolve(options.root);
|
|
27
97
|
const mode = options.mode ?? "local";
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
98
|
+
const release = options.release ?? BRANDLAB_CURRENT_RUNTIME_RELEASE;
|
|
99
|
+
validateBrandlabRuntimeReleaseContract(release);
|
|
100
|
+
if (options.releaseManifest) {
|
|
101
|
+
validateBrandlabReleaseManifest(options.releaseManifest);
|
|
102
|
+
if (digestBrandlabAuthority(projectBrandlabRuntimeReleaseContract(options.releaseManifest)) !==
|
|
103
|
+
digestBrandlabAuthority(release)) {
|
|
104
|
+
throw new Error("The release manifest differs from the embedded current release.");
|
|
105
|
+
}
|
|
34
106
|
}
|
|
35
|
-
validateBrandlabSupportPolicy(supportPolicy, releaseCatalog);
|
|
36
107
|
const packageJson = readPackageJson(root);
|
|
37
108
|
const packageLock = readPackageLock(root);
|
|
38
109
|
const detectedRelease = detectInstalledBrandlabRelease(packageJson);
|
|
39
|
-
const installedRelease = options.installedRelease === undefined
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const releaseByVersion = new Map(releaseCatalog.map((manifest) => [manifest.releaseVersion, manifest]));
|
|
44
|
-
const installedManifest = installedRelease
|
|
45
|
-
? releaseByVersion.get(installedRelease) ?? null
|
|
46
|
-
: null;
|
|
47
|
-
const targetManifest = targetRelease
|
|
48
|
-
? releaseByVersion.get(targetRelease) ?? null
|
|
110
|
+
const installedRelease = options.installedRelease === undefined ? detectedRelease : options.installedRelease;
|
|
111
|
+
const manifest = release.releaseVersion === installedRelease ? release : null;
|
|
112
|
+
const attestation = options.releaseManifest?.releaseVersion === installedRelease
|
|
113
|
+
? options.releaseManifest
|
|
49
114
|
: null;
|
|
50
|
-
const releaseManifestByVersion = new Map((options.releaseManifests ?? []).map((manifest) => [manifest.releaseVersion, manifest]));
|
|
51
115
|
const findings = [];
|
|
52
|
-
|
|
116
|
+
inspectRelease({
|
|
53
117
|
findings,
|
|
54
118
|
packageJson,
|
|
55
119
|
packageLock,
|
|
56
120
|
installedRelease,
|
|
57
121
|
detectedRelease,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
? releaseManifestByVersion.get(installedRelease) ?? null
|
|
61
|
-
: null,
|
|
62
|
-
releaseAttestationsRequired: options.releaseManifests !== undefined,
|
|
63
|
-
supportPolicy,
|
|
64
|
-
});
|
|
65
|
-
addMigrationFindings({
|
|
66
|
-
findings,
|
|
67
|
-
installedRelease,
|
|
68
|
-
targetRelease,
|
|
69
|
-
targetManifest,
|
|
70
|
-
supportPolicy,
|
|
71
|
-
packageJson,
|
|
72
|
-
packageLock,
|
|
122
|
+
manifest,
|
|
123
|
+
attestation,
|
|
73
124
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
findings,
|
|
77
|
-
installedManifest,
|
|
78
|
-
targetManifest,
|
|
79
|
-
targetRelease,
|
|
80
|
-
architecture: options.architecture,
|
|
81
|
-
});
|
|
82
|
-
const sourceRoot = join(root, "src");
|
|
83
|
-
const installedSourceSafety = sourceSafetyRulesetForArchitecture(installedManifest?.contracts.architecture);
|
|
84
|
-
const targetSourceSafety = sourceSafetyRulesetForArchitecture(targetManifest?.contracts.architecture);
|
|
85
|
-
if (existsSync(sourceRoot)) {
|
|
86
|
-
if (installedSourceSafety) {
|
|
87
|
-
addSourcePatternFindings({
|
|
88
|
-
root,
|
|
89
|
-
sourceRoot,
|
|
90
|
-
findings,
|
|
91
|
-
patterns: installedSourceSafety.patterns,
|
|
92
|
-
classification: "maintenance",
|
|
93
|
-
contract: {
|
|
94
|
-
name: "source-safety",
|
|
95
|
-
version: installedSourceSafety.id,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
if (!targetSourceSafety &&
|
|
99
|
-
installedSourceSafety.id !== BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID) {
|
|
100
|
-
addSourcePatternFindings({
|
|
101
|
-
root,
|
|
102
|
-
sourceRoot,
|
|
103
|
-
findings,
|
|
104
|
-
patterns: targetOnlySourceSafetyPatterns(installedSourceSafety, {
|
|
105
|
-
id: BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID,
|
|
106
|
-
patterns: BRANDLAB_SOURCE_SAFETY_V2_PATTERNS,
|
|
107
|
-
}),
|
|
108
|
-
classification: "advisory",
|
|
109
|
-
contract: {
|
|
110
|
-
name: "source-safety",
|
|
111
|
-
version: BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID,
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
else if (!installedManifest && !targetSourceSafety) {
|
|
117
|
-
addSourcePatternFindings({
|
|
118
|
-
root,
|
|
119
|
-
sourceRoot,
|
|
120
|
-
findings,
|
|
121
|
-
patterns: BRANDLAB_SOURCE_SAFETY_V2_PATTERNS,
|
|
122
|
-
classification: "advisory",
|
|
123
|
-
contract: {
|
|
124
|
-
name: "source-safety",
|
|
125
|
-
version: BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID,
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
if (targetSourceSafety &&
|
|
130
|
-
targetManifest?.releaseVersion !== installedManifest?.releaseVersion) {
|
|
131
|
-
addSourcePatternFindings({
|
|
132
|
-
root,
|
|
133
|
-
sourceRoot,
|
|
134
|
-
findings,
|
|
135
|
-
patterns: targetOnlySourceSafetyPatterns(installedSourceSafety, targetSourceSafety),
|
|
136
|
-
classification: "migration",
|
|
137
|
-
contract: {
|
|
138
|
-
name: "source-safety",
|
|
139
|
-
version: targetSourceSafety.id,
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
if (installedManifest?.contracts.seo === SEO_V3_CONTRACT) {
|
|
144
|
-
addSourcePatternFindings({
|
|
145
|
-
root,
|
|
146
|
-
sourceRoot,
|
|
147
|
-
findings,
|
|
148
|
-
patterns: BRANDLAB_DEPRECATED_SCHEMA_SOURCE_PATTERNS,
|
|
149
|
-
classification: "maintenance",
|
|
150
|
-
contract: { name: "seo", version: SEO_V3_CONTRACT },
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
if (targetManifest?.contracts.seo === SEO_V3_CONTRACT &&
|
|
154
|
-
targetManifest.releaseVersion !== installedManifest?.releaseVersion) {
|
|
155
|
-
addSourcePatternFindings({
|
|
156
|
-
root,
|
|
157
|
-
sourceRoot,
|
|
158
|
-
findings,
|
|
159
|
-
patterns: BRANDLAB_DEPRECATED_SCHEMA_SOURCE_PATTERNS,
|
|
160
|
-
classification: "migration",
|
|
161
|
-
contract: { name: "seo", version: SEO_V3_CONTRACT },
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
125
|
+
inspectProjectConfig(root, findings);
|
|
126
|
+
inspectProjectSource(root, findings);
|
|
165
127
|
const sortedFindings = findings.sort(compareFindings);
|
|
128
|
+
const policyResult = applyBrandlabVerificationPolicy(sortedFindings, options.verificationEvidence);
|
|
129
|
+
const operationalFindings = policyResult.operationalFindings.sort(compareFindings);
|
|
166
130
|
const authorityDigest = digestBrandlabAuthority({
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
releaseManifests: options.releaseManifests ?? null,
|
|
131
|
+
release,
|
|
132
|
+
releaseManifest: options.releaseManifest ?? null,
|
|
170
133
|
});
|
|
171
134
|
const contractDigest = digestBrandlabAuthority({
|
|
172
|
-
installed:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
rulesets: {
|
|
176
|
-
configurationV2: BRANDLAB_CONFIG_V2_RULESET_ID,
|
|
177
|
-
configurationV3: BRANDLAB_CONFIG_V3_RULESET_ID,
|
|
178
|
-
seoV3: BRANDLAB_SCHEMA_HYGIENE_V3_RULESET_ID,
|
|
179
|
-
installedSourceSafety: installedSourceSafety?.id ?? null,
|
|
180
|
-
targetSourceSafety: targetSourceSafety?.id ?? null,
|
|
181
|
-
installedLegacyArchitecture: legacyArchitectureRulesetId(installedManifest?.contracts.architecture),
|
|
182
|
-
targetLegacyArchitecture: legacyArchitectureRulesetId(targetManifest?.contracts.architecture),
|
|
183
|
-
},
|
|
135
|
+
installed: manifest?.contracts ?? null,
|
|
136
|
+
evaluator: BRANDLAB_CONFORMANCE_EVALUATOR_VERSION,
|
|
137
|
+
verificationPolicy: BRANDLAB_VERIFICATION_POLICY_VERSION,
|
|
184
138
|
});
|
|
185
139
|
const implementationFingerprint = digestBrandlabAuthority({
|
|
186
140
|
packageJson: packageJson ?? null,
|
|
187
141
|
packageLock: packageLock.fingerprint,
|
|
188
142
|
brandlabConfig: readOptionalText(join(root, "brandlab.config.ts")),
|
|
189
|
-
|
|
190
|
-
evidence: sortedFindings.map(({ ruleId, classification, severity, path, line }) => ({
|
|
143
|
+
findings: sortedFindings.map(({ ruleId, severity, path, line }) => ({
|
|
191
144
|
ruleId,
|
|
192
|
-
classification,
|
|
193
145
|
severity,
|
|
194
146
|
path: path ?? null,
|
|
195
147
|
line: line ?? null,
|
|
@@ -203,37 +155,14 @@ export function evaluateBrandlabProject(options) {
|
|
|
203
155
|
implementationFingerprint,
|
|
204
156
|
mode,
|
|
205
157
|
installedRelease,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
advisory: summarize(sortedFindings, "advisory"),
|
|
158
|
+
findings: operationalFindings,
|
|
159
|
+
operationalFindings,
|
|
160
|
+
verification: policyResult.verification,
|
|
161
|
+
summary: policyResult.verification.summary,
|
|
211
162
|
};
|
|
212
163
|
}
|
|
213
|
-
function addSourcePatternFindings(options) {
|
|
214
|
-
for (const match of findForbiddenBrandlabPatterns({
|
|
215
|
-
roots: [options.sourceRoot],
|
|
216
|
-
patterns: options.patterns,
|
|
217
|
-
})) {
|
|
218
|
-
options.findings.push({
|
|
219
|
-
ruleId: `brandlab-source-${slugify(match.patternName)}`,
|
|
220
|
-
surface: surfaceForRule(match.patternName),
|
|
221
|
-
classification: options.classification,
|
|
222
|
-
severity: options.classification === "advisory" ? "warning" : "blocker",
|
|
223
|
-
contract: options.contract,
|
|
224
|
-
evidenceKind: "static",
|
|
225
|
-
path: normalizePath(relative(options.root, match.file)),
|
|
226
|
-
line: match.line,
|
|
227
|
-
expected: `No ${match.patternName} source pattern`,
|
|
228
|
-
actual: `A prohibited source pattern was detected at line ${match.line}.`,
|
|
229
|
-
remediation: `Remove the ${match.patternName} implementation and use the supported shared-platform path.`,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
164
|
export function shouldFailBrandlabConformance(result) {
|
|
234
|
-
|
|
235
|
-
return true;
|
|
236
|
-
return result.mode === "preflight" && result.migration.blockers > 0;
|
|
165
|
+
return result.verification.summary.blockers > 0;
|
|
237
166
|
}
|
|
238
167
|
export function detectInstalledBrandlabRelease(packageJson) {
|
|
239
168
|
if (!packageJson)
|
|
@@ -245,759 +174,261 @@ export function detectInstalledBrandlabRelease(packageJson) {
|
|
|
245
174
|
return null;
|
|
246
175
|
return new Set(versions).size === 1 ? versions[0] : null;
|
|
247
176
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return
|
|
252
|
-
const architectureSource = extractDelimitedProperty(source, "architecture", "{", "}");
|
|
253
|
-
if (!architectureSource)
|
|
254
|
-
return null;
|
|
255
|
-
const architecture = {};
|
|
256
|
-
assignString(architecture, "profile", extractStringProperty(architectureSource, "profile"));
|
|
257
|
-
assignString(architecture, "status", extractStringProperty(architectureSource, "status"));
|
|
258
|
-
assignString(architecture, "targetProfile", extractStringProperty(architectureSource, "targetProfile"));
|
|
259
|
-
assignString(architecture, "migrationRationale", extractStringProperty(architectureSource, "migrationRationale"));
|
|
260
|
-
assignNumber(architecture, "staticRouteFileLimit", extractNumberProperty(architectureSource, "staticRouteFileLimit"));
|
|
261
|
-
assignNumber(architecture, "largeRouteFileLineLimit", extractNumberProperty(architectureSource, "largeRouteFileLineLimit"));
|
|
262
|
-
assignNumber(architecture, "routeFileContentCharLimit", extractNumberProperty(architectureSource, "routeFileContentCharLimit"));
|
|
263
|
-
const routeFamiliesSource = extractDelimitedProperty(architectureSource, "routeFamilies", "[", "]");
|
|
264
|
-
if (routeFamiliesSource) {
|
|
265
|
-
architecture.routeFamilies = extractObjectBodies(routeFamiliesSource).map((routeFamily) => ({
|
|
266
|
-
name: extractStringProperty(routeFamily, "name") ?? undefined,
|
|
267
|
-
model: extractStringProperty(routeFamily, "model") ?? undefined,
|
|
268
|
-
route: extractStringProperty(routeFamily, "route") ?? undefined,
|
|
269
|
-
dataSource: extractStringProperty(routeFamily, "dataSource") ?? undefined,
|
|
270
|
-
}));
|
|
271
|
-
}
|
|
272
|
-
const contentOwnershipSource = extractDelimitedProperty(architectureSource, "contentOwnership", "{", "}");
|
|
273
|
-
if (contentOwnershipSource) {
|
|
274
|
-
architecture.contentOwnership = {
|
|
275
|
-
routeFiles: extractStringProperty(contentOwnershipSource, "routeFiles") ?? undefined,
|
|
276
|
-
pageData: extractStringProperty(contentOwnershipSource, "pageData") ?? undefined,
|
|
277
|
-
componentCopy: extractStringProperty(contentOwnershipSource, "componentCopy") ?? undefined,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
const topologySource = extractDelimitedProperty(architectureSource, "topology", "{", "}");
|
|
281
|
-
if (topologySource) {
|
|
282
|
-
architecture.topology = {
|
|
283
|
-
allowedRootDirs: extractStringArray(topologySource, "allowedRootDirs"),
|
|
284
|
-
allowedComponentDirs: extractStringArray(topologySource, "allowedComponentDirs"),
|
|
285
|
-
allowedLibDirs: extractStringArray(topologySource, "allowedLibDirs"),
|
|
286
|
-
allowedTypeFiles: extractStringArray(topologySource, "allowedTypeFiles"),
|
|
287
|
-
legacyAllowedRootDirs: extractStringArray(topologySource, "legacyAllowedRootDirs"),
|
|
288
|
-
legacyAllowedComponentDirs: extractStringArray(topologySource, "legacyAllowedComponentDirs"),
|
|
289
|
-
legacyAllowedLibDirs: extractStringArray(topologySource, "legacyAllowedLibDirs"),
|
|
290
|
-
legacyAllowedTypeFiles: extractStringArray(topologySource, "legacyAllowedTypeFiles"),
|
|
291
|
-
targetTopology: extractStringProperty(topologySource, "targetTopology") ?? undefined,
|
|
292
|
-
};
|
|
177
|
+
function inspectRelease(options) {
|
|
178
|
+
if (!options.packageJson) {
|
|
179
|
+
options.findings.push(releaseFinding("brandlab-release-package-json-missing", "A readable package.json", "package.json is missing or invalid.", "Restore a valid package.json."));
|
|
180
|
+
return;
|
|
293
181
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
function addReleaseMaintenanceFindings(options) {
|
|
297
|
-
const packageJson = options.packageJson;
|
|
298
|
-
if (!packageJson) {
|
|
299
|
-
options.findings.push(releaseFinding({
|
|
300
|
-
ruleId: "brandlab-release-package-json-missing",
|
|
301
|
-
severity: "blocker",
|
|
302
|
-
expected: "A readable project package.json",
|
|
303
|
-
actual: "package.json is missing or invalid.",
|
|
304
|
-
remediation: "Restore a valid package.json before evaluating Brandlab conformance.",
|
|
305
|
-
}));
|
|
182
|
+
if (!options.installedRelease) {
|
|
183
|
+
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."));
|
|
306
184
|
return;
|
|
307
185
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
options.findings.push(releaseFinding({
|
|
311
|
-
ruleId: "brandlab-release-installed-version-unresolved",
|
|
312
|
-
severity: "blocker",
|
|
313
|
-
expected: "Four exact, internally aligned Brandlab package versions",
|
|
314
|
-
actual: "The installed Brandlab release could not be resolved.",
|
|
315
|
-
remediation: "Pin all four Brandlab packages to one exact supported release.",
|
|
316
|
-
}));
|
|
186
|
+
if (!options.manifest) {
|
|
187
|
+
options.findings.push(releaseFinding("brandlab-release-unsupported", "The release selected by the active Brandlab channel", `${options.installedRelease} is not supported by the active platform.`, "Upgrade the project to the current candidate before further work."));
|
|
317
188
|
return;
|
|
318
189
|
}
|
|
319
|
-
if (
|
|
320
|
-
options.findings.push(releaseFinding(
|
|
321
|
-
ruleId: "brandlab-release-unsupported",
|
|
322
|
-
severity: "blocker",
|
|
323
|
-
expected: "An installed release with an embedded runtime contract",
|
|
324
|
-
actual: `${options.installedRelease} has no runtime release contract.`,
|
|
325
|
-
remediation: "Use a supported exact release or add and verify its external manifest and runtime projection.",
|
|
326
|
-
}));
|
|
190
|
+
if (options.detectedRelease !== options.installedRelease) {
|
|
191
|
+
options.findings.push(releaseFinding("brandlab-release-declared-installed-mismatch", options.installedRelease, options.detectedRelease ?? "unresolved", "Restore one exact package version across all four Brandlab packages.", "package.json"));
|
|
327
192
|
}
|
|
328
|
-
|
|
329
|
-
options.installedManifest &&
|
|
330
|
-
!options.installedAttestation) {
|
|
331
|
-
options.findings.push(releaseFinding({
|
|
332
|
-
ruleId: "brandlab-release-attestation-missing",
|
|
333
|
-
severity: "blocker",
|
|
334
|
-
expected: `Immutable registry attestation for ${installedRelease}`,
|
|
335
|
-
actual: "The operational release-manifest set does not attest the installed release.",
|
|
336
|
-
remediation: "Load the installed release manifest referenced by support policy before accepting package provenance.",
|
|
337
|
-
}));
|
|
338
|
-
}
|
|
339
|
-
addInstalledPackageLockFindings({
|
|
193
|
+
inspectBrandlabPackages({
|
|
340
194
|
findings: options.findings,
|
|
341
|
-
packageJson,
|
|
195
|
+
packageJson: options.packageJson,
|
|
342
196
|
packageLock: options.packageLock,
|
|
343
|
-
installedRelease,
|
|
344
|
-
|
|
345
|
-
});
|
|
346
|
-
const versionIssues = evaluateSharedPackageVersionSpecs({
|
|
347
|
-
packageJson,
|
|
348
|
-
expectedVersion: installedRelease,
|
|
349
|
-
packageNames: BRANDLAB_SHARED_PACKAGE_NAMES,
|
|
350
|
-
});
|
|
351
|
-
for (const issue of versionIssues) {
|
|
352
|
-
options.findings.push(releaseFinding({
|
|
353
|
-
ruleId: `brandlab-release-package-${issue.issue}`,
|
|
354
|
-
severity: "blocker",
|
|
355
|
-
expected: `${issue.packageName}@${installedRelease}`,
|
|
356
|
-
actual: `${issue.packageName} is ${issue.installedVersion ?? "missing"}.`,
|
|
357
|
-
remediation: "Restore exact lockstep package pins; do not use ranges, tags or mixed releases.",
|
|
358
|
-
path: "package.json",
|
|
359
|
-
}));
|
|
360
|
-
}
|
|
361
|
-
if (options.detectedRelease && options.detectedRelease !== installedRelease) {
|
|
362
|
-
options.findings.push(releaseFinding({
|
|
363
|
-
ruleId: "brandlab-release-declared-installed-mismatch",
|
|
364
|
-
severity: "blocker",
|
|
365
|
-
expected: `Detected package release ${installedRelease}`,
|
|
366
|
-
actual: `package.json resolves to ${options.detectedRelease}.`,
|
|
367
|
-
remediation: "Use the detected exact release or correct all four package pins.",
|
|
368
|
-
path: "package.json",
|
|
369
|
-
}));
|
|
370
|
-
}
|
|
371
|
-
const lifecycle = options.supportPolicy.releases[installedRelease]?.lifecycle;
|
|
372
|
-
if (options.installedManifest && !lifecycle) {
|
|
373
|
-
options.findings.push(releaseFinding({
|
|
374
|
-
ruleId: "brandlab-release-not-operationally-supported",
|
|
375
|
-
severity: "blocker",
|
|
376
|
-
expected: "An installed release listed in mutable support policy",
|
|
377
|
-
actual: `${installedRelease} has a runtime contract but is not approved for operational use.`,
|
|
378
|
-
remediation: "Use the current supported release or supply the synthetic candidate policy only inside packed release fixtures.",
|
|
379
|
-
}));
|
|
380
|
-
}
|
|
381
|
-
if (lifecycle === "security-blocked" || lifecycle === "retired") {
|
|
382
|
-
options.findings.push(releaseFinding({
|
|
383
|
-
ruleId: `brandlab-release-${lifecycle}`,
|
|
384
|
-
severity: "blocker",
|
|
385
|
-
expected: "A current or tolerated installed release",
|
|
386
|
-
actual: `${installedRelease} is ${lifecycle}.`,
|
|
387
|
-
remediation: "Prepare an owner-approved migration to a supported release.",
|
|
388
|
-
}));
|
|
389
|
-
}
|
|
390
|
-
if (options.installedManifest) {
|
|
391
|
-
addInstalledRuntimeFindings({
|
|
392
|
-
findings: options.findings,
|
|
393
|
-
packageJson,
|
|
394
|
-
packageLock: options.packageLock,
|
|
395
|
-
manifest: options.installedManifest,
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
function addMigrationFindings(options) {
|
|
400
|
-
if (!options.targetRelease) {
|
|
401
|
-
if (options.installedRelease &&
|
|
402
|
-
options.installedRelease !== options.supportPolicy.currentReleaseVersion) {
|
|
403
|
-
options.findings.push({
|
|
404
|
-
...releaseFinding({
|
|
405
|
-
ruleId: "brandlab-release-upgrade-available",
|
|
406
|
-
severity: "info",
|
|
407
|
-
expected: "No migration is active until a target release is approved.",
|
|
408
|
-
actual: `${options.supportPolicy.currentReleaseVersion} is current; ${options.installedRelease} remains installed.`,
|
|
409
|
-
remediation: "Keep monitoring the tolerated exact pin or select a separately approved target release.",
|
|
410
|
-
}),
|
|
411
|
-
classification: "advisory",
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
if (!options.targetManifest) {
|
|
417
|
-
options.findings.push({
|
|
418
|
-
...releaseFinding({
|
|
419
|
-
ruleId: "brandlab-migration-target-unsupported",
|
|
420
|
-
severity: "blocker",
|
|
421
|
-
expected: "A target release with an embedded runtime contract",
|
|
422
|
-
actual: `${options.targetRelease} has no runtime release contract.`,
|
|
423
|
-
remediation: "Select a manifest-backed runtime contract before preparing migration work.",
|
|
424
|
-
}),
|
|
425
|
-
classification: "migration",
|
|
426
|
-
});
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
const targetLifecycle = options.supportPolicy.releases[options.targetRelease]?.lifecycle;
|
|
430
|
-
if (!targetLifecycle) {
|
|
431
|
-
options.findings.push({
|
|
432
|
-
...releaseFinding({
|
|
433
|
-
ruleId: "brandlab-migration-target-not-approved",
|
|
434
|
-
severity: "blocker",
|
|
435
|
-
expected: "A target release explicitly approved in mutable support policy",
|
|
436
|
-
actual: `${options.targetRelease} has a runtime contract but no approved support-policy lifecycle.`,
|
|
437
|
-
remediation: "Keep the installed release unchanged until the candidate release and project migration are separately approved.",
|
|
438
|
-
}),
|
|
439
|
-
classification: "migration",
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
if (targetLifecycle === "security-blocked" || targetLifecycle === "retired") {
|
|
443
|
-
options.findings.push({
|
|
444
|
-
...releaseFinding({
|
|
445
|
-
ruleId: `brandlab-migration-target-${targetLifecycle}`,
|
|
446
|
-
severity: "blocker",
|
|
447
|
-
expected: "A current or tolerated target release",
|
|
448
|
-
actual: `${options.targetRelease} is ${targetLifecycle}.`,
|
|
449
|
-
remediation: "Select a current or tolerated target release.",
|
|
450
|
-
}),
|
|
451
|
-
classification: "migration",
|
|
452
|
-
});
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (!options.installedRelease || options.installedRelease === options.targetRelease)
|
|
456
|
-
return;
|
|
457
|
-
const comparison = compareBrandlabSemver(options.targetRelease, options.installedRelease);
|
|
458
|
-
options.findings.push({
|
|
459
|
-
...releaseFinding({
|
|
460
|
-
ruleId: comparison < 0
|
|
461
|
-
? "brandlab-migration-downgrade-review"
|
|
462
|
-
: "brandlab-migration-target-selected",
|
|
463
|
-
severity: comparison < 0 ? "blocker" : "info",
|
|
464
|
-
expected: comparison < 0
|
|
465
|
-
? "A separately documented rollback decision"
|
|
466
|
-
: "Target-release migration evidence",
|
|
467
|
-
actual: `${options.installedRelease} -> ${options.targetRelease}`,
|
|
468
|
-
remediation: comparison < 0
|
|
469
|
-
? "Use the immutable rollback manifest and record explicit owner approval."
|
|
470
|
-
: "Address migration findings and preserve the installed release until target proof passes.",
|
|
471
|
-
}),
|
|
472
|
-
classification: "migration",
|
|
197
|
+
installedRelease: options.installedRelease,
|
|
198
|
+
attestation: options.attestation,
|
|
473
199
|
});
|
|
474
|
-
|
|
200
|
+
inspectRuntime({
|
|
475
201
|
findings: options.findings,
|
|
476
202
|
packageJson: options.packageJson,
|
|
477
203
|
packageLock: options.packageLock,
|
|
478
|
-
|
|
204
|
+
manifest: options.manifest,
|
|
479
205
|
});
|
|
480
206
|
}
|
|
481
|
-
function
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const detail = packageLock.state === "missing"
|
|
485
|
-
? "package-lock.json is missing."
|
|
486
|
-
: packageLock.state === "unsupported"
|
|
487
|
-
? `package-lock.json uses unsupported lockfileVersion ${String(packageLock.lockfileVersion)}.`
|
|
488
|
-
: "package-lock.json is invalid or has no npm root package entry.";
|
|
489
|
-
options.findings.push(releaseFinding({
|
|
490
|
-
ruleId: `brandlab-release-package-lock-${packageLock.state}`,
|
|
491
|
-
severity: "blocker",
|
|
492
|
-
expected: "A valid npm package-lock.json (lockfile v2 or v3) with root and resolved package evidence",
|
|
493
|
-
actual: detail,
|
|
494
|
-
remediation: "Regenerate the npm lockfile from the exact supported package declarations and review the resulting diff.",
|
|
495
|
-
path: "package-lock.json",
|
|
496
|
-
}));
|
|
207
|
+
function inspectBrandlabPackages(options) {
|
|
208
|
+
if (options.packageLock.state !== "valid") {
|
|
209
|
+
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"));
|
|
497
210
|
return;
|
|
498
211
|
}
|
|
499
212
|
for (const packageName of BRANDLAB_SHARED_PACKAGE_NAMES) {
|
|
500
213
|
const declared = declaredPackageSpec(options.packageJson, packageName);
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
options.packageJson.devDependencies?.[packageName] !== undefined
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
severity: "blocker",
|
|
508
|
-
expected: `${packageName} declared exactly once`,
|
|
509
|
-
actual: `${packageName} appears in both dependencies and devDependencies.`,
|
|
510
|
-
remediation: "Keep each Brandlab package in one appropriate dependency section.",
|
|
511
|
-
path: "package.json",
|
|
512
|
-
}));
|
|
513
|
-
}
|
|
514
|
-
if (packageLock.rootDependencies[packageName] !== undefined &&
|
|
515
|
-
packageLock.rootDevDependencies[packageName] !== undefined) {
|
|
516
|
-
options.findings.push(releaseFinding({
|
|
517
|
-
ruleId: "brandlab-release-lock-duplicate-declaration",
|
|
518
|
-
severity: "blocker",
|
|
519
|
-
expected: `${packageName} recorded once in the lockfile root`,
|
|
520
|
-
actual: `${packageName} appears in both root dependency sections.`,
|
|
521
|
-
remediation: "Regenerate the lockfile from the single package.json declaration.",
|
|
522
|
-
path: "package-lock.json",
|
|
523
|
-
}));
|
|
524
|
-
}
|
|
525
|
-
if (lockedSpec !== options.installedRelease || lockedSpec !== declared) {
|
|
526
|
-
options.findings.push(releaseFinding({
|
|
527
|
-
ruleId: "brandlab-release-lock-root-spec-mismatch",
|
|
528
|
-
severity: "blocker",
|
|
529
|
-
expected: `${packageName}@${options.installedRelease} in package.json and the lockfile root`,
|
|
530
|
-
actual: `package.json=${declared ?? "missing"}; lockfile=${lockedSpec ?? "missing"}.`,
|
|
531
|
-
remediation: "Reinstall from the exact lockstep package declarations so root lock specifications match package.json.",
|
|
532
|
-
path: "package-lock.json",
|
|
533
|
-
}));
|
|
534
|
-
}
|
|
535
|
-
if (!resolvedPackage ||
|
|
536
|
-
resolvedPackage.version !== options.installedRelease ||
|
|
537
|
-
resolvedPackage.linked ||
|
|
538
|
-
!resolvedPackage.resolved ||
|
|
539
|
-
!resolvedPackage.integrity) {
|
|
540
|
-
options.findings.push(releaseFinding({
|
|
541
|
-
ruleId: "brandlab-release-lock-resolution-mismatch",
|
|
542
|
-
severity: "blocker",
|
|
543
|
-
expected: `${packageName} resolved to immutable ${options.installedRelease}`,
|
|
544
|
-
actual: !resolvedPackage
|
|
545
|
-
? "No resolved node_modules package entry exists."
|
|
546
|
-
: `version=${resolvedPackage.version ?? "missing"}; linked=${String(resolvedPackage.linked)}; resolved=${resolvedPackage.resolved ? "present" : "missing"}; integrity=${resolvedPackage.integrity ? "present" : "missing"}.`,
|
|
547
|
-
remediation: "Produce a clean lockfile with a non-workspace resolved package, source and integrity at the declared exact release.",
|
|
548
|
-
path: "package-lock.json",
|
|
549
|
-
}));
|
|
214
|
+
const locked = lockedRootSpec(options.packageLock, packageName);
|
|
215
|
+
const resolved = options.packageLock.resolvedPackages.get(packageName);
|
|
216
|
+
const duplicate = options.packageJson.dependencies?.[packageName] !== undefined &&
|
|
217
|
+
options.packageJson.devDependencies?.[packageName] !== undefined;
|
|
218
|
+
if (duplicate || declared !== options.installedRelease || locked !== declared) {
|
|
219
|
+
options.findings.push(releaseFinding("brandlab-release-package-pin-mismatch", `${packageName}@${options.installedRelease} declared once and locked exactly`, `package.json=${declared ?? "missing"}; lockfile=${locked ?? "missing"}; duplicate=${String(duplicate)}.`, "Restore exact lockstep package declarations and regenerate the lockfile.", "package.json"));
|
|
550
220
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
remediation: "Do not adopt this release until publication evidence is finalized and independently verified.",
|
|
561
|
-
}));
|
|
221
|
+
if (!resolved ||
|
|
222
|
+
resolved.version !== options.installedRelease ||
|
|
223
|
+
resolved.linked ||
|
|
224
|
+
!resolved.integrity ||
|
|
225
|
+
!resolved.resolved ||
|
|
226
|
+
!isApprovedRegistryResolution(resolved.resolved, packageName, options.installedRelease)) {
|
|
227
|
+
options.findings.push(releaseFinding("brandlab-release-package-resolution-mismatch", `${packageName}@${options.installedRelease} from the canonical npm registry`, !resolved
|
|
228
|
+
? "The resolved lockfile package is missing."
|
|
229
|
+
: `version=${resolved.version ?? "missing"}; linked=${String(resolved.linked)}; integrity=${resolved.integrity ? "present" : "missing"}.`, "Run a clean npm install from the exact published release.", "package-lock.json"));
|
|
562
230
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
severity: "blocker",
|
|
567
|
-
expected: `${packageName} lock integrity matching the immutable release manifest`,
|
|
568
|
-
actual: resolvedPackage?.integrity
|
|
569
|
-
? "The lock integrity differs from verified registry evidence."
|
|
570
|
-
: "The lock integrity is missing.",
|
|
571
|
-
remediation: "Recreate the lock from the verified registry release and investigate unexpected package content.",
|
|
572
|
-
path: "package-lock.json",
|
|
573
|
-
}));
|
|
231
|
+
const expectedIntegrity = options.attestation?.packages[packageName].registry.integrity ?? null;
|
|
232
|
+
if (expectedIntegrity && resolved?.integrity !== expectedIntegrity) {
|
|
233
|
+
options.findings.push(releaseFinding("brandlab-release-package-integrity-mismatch", `${packageName} integrity matching the immutable manifest`, "The lockfile integrity differs from the release attestation.", "Regenerate the lockfile from the verified registry package.", "package-lock.json"));
|
|
574
234
|
}
|
|
575
|
-
if (options.installedAttestation &&
|
|
576
|
-
(!resolvedPackage?.resolved ||
|
|
577
|
-
!isApprovedBrandlabRegistryResolution(resolvedPackage.resolved, packageName, options.installedRelease))) {
|
|
578
|
-
options.findings.push(releaseFinding({
|
|
579
|
-
ruleId: "brandlab-release-lock-registry-source-mismatch",
|
|
580
|
-
severity: "blocker",
|
|
581
|
-
expected: `${packageName}@${options.installedRelease} from the canonical HTTPS npm registry artifact`,
|
|
582
|
-
actual: resolvedPackage?.resolved
|
|
583
|
-
? "The lockfile resolution does not use the approved registry.npmjs.org package artifact."
|
|
584
|
-
: "The lockfile resolution source is missing.",
|
|
585
|
-
remediation: "Regenerate the lockfile with the canonical npm registry and the immutable manifest integrity; do not accept file, Git or alternate-registry resolutions.",
|
|
586
|
-
path: "package-lock.json",
|
|
587
|
-
}));
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
function isApprovedBrandlabRegistryResolution(resolved, packageName, version) {
|
|
592
|
-
try {
|
|
593
|
-
const url = new URL(resolved);
|
|
594
|
-
const artifactName = packageName.split("/").at(-1);
|
|
595
|
-
if (!artifactName)
|
|
596
|
-
return false;
|
|
597
|
-
return url.protocol === "https:" &&
|
|
598
|
-
url.hostname === "registry.npmjs.org" &&
|
|
599
|
-
url.port === "" &&
|
|
600
|
-
url.username === "" &&
|
|
601
|
-
url.password === "" &&
|
|
602
|
-
url.search === "" &&
|
|
603
|
-
url.hash === "" &&
|
|
604
|
-
url.pathname === `/${packageName}/-/${artifactName}-${version}.tgz`;
|
|
605
|
-
}
|
|
606
|
-
catch {
|
|
607
|
-
return false;
|
|
608
235
|
}
|
|
609
236
|
}
|
|
610
|
-
function
|
|
237
|
+
function inspectRuntime(options) {
|
|
611
238
|
for (const [packageName, runtimeField] of RUNTIME_PACKAGE_FIELDS) {
|
|
612
239
|
const expected = options.manifest.runtime[runtimeField];
|
|
613
240
|
const declared = declaredPackageSpec(options.packageJson, packageName);
|
|
614
|
-
|
|
615
|
-
options.findings.push(runtimeFinding({
|
|
616
|
-
ruleId: `brandlab-runtime-${slugify(packageName)}-declaration`,
|
|
617
|
-
expected: `${packageName}@${expected} declared exactly`,
|
|
618
|
-
actual: `${packageName} is ${declared ?? "missing"}.`,
|
|
619
|
-
remediation: `Use the ${options.manifest.releaseVersion} embedded runtime contract.`,
|
|
620
|
-
path: "package.json",
|
|
621
|
-
}));
|
|
622
|
-
}
|
|
623
|
-
if (options.packageLock.state !== "valid")
|
|
624
|
-
continue;
|
|
625
|
-
const lockedSpec = lockedRootSpec(options.packageLock, packageName);
|
|
241
|
+
const locked = lockedRootSpec(options.packageLock, packageName);
|
|
626
242
|
const resolved = options.packageLock.resolvedPackages.get(packageName);
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
remediation: "Regenerate package-lock.json from the exact runtime declaration.",
|
|
633
|
-
path: "package-lock.json",
|
|
634
|
-
}));
|
|
635
|
-
}
|
|
636
|
-
if (!resolved || resolved.version !== expected || resolved.linked) {
|
|
637
|
-
options.findings.push(runtimeFinding({
|
|
638
|
-
ruleId: `brandlab-runtime-${slugify(packageName)}-resolution`,
|
|
639
|
-
expected: `${packageName} resolved to ${expected}`,
|
|
640
|
-
actual: !resolved
|
|
641
|
-
? "No resolved node_modules package entry exists."
|
|
642
|
-
: `version=${resolved.version ?? "missing"}; linked=${String(resolved.linked)}.`,
|
|
643
|
-
remediation: "Reinstall the immutable runtime baseline and preserve the reviewed lockfile.",
|
|
644
|
-
path: "package-lock.json",
|
|
645
|
-
}));
|
|
243
|
+
if (!runtimeVersionSatisfiesRelease(packageName, declared, expected) ||
|
|
244
|
+
locked !== declared ||
|
|
245
|
+
resolved?.version !== declared ||
|
|
246
|
+
resolved.linked) {
|
|
247
|
+
options.findings.push(runtimeFinding(`brandlab-runtime-${slugify(packageName)}`, runtimeExpectation(packageName, expected), `package.json=${declared ?? "missing"}; lockfile=${locked ?? "missing"}; resolved=${resolved?.version ?? "missing"}.`, `Use the supported ${options.manifest.releaseVersion} runtime or a newer exact security patch in the same framework line.`, "package.json"));
|
|
646
248
|
}
|
|
647
249
|
}
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
expected: `engines.node=${options.manifest.runtime.node}`,
|
|
653
|
-
actual: `engines.node=${nodeEngine ?? "missing"}.`,
|
|
654
|
-
remediation: "Restore the installed release's Node engine declaration.",
|
|
655
|
-
path: "package.json",
|
|
656
|
-
}));
|
|
657
|
-
}
|
|
658
|
-
if (options.packageLock.state === "valid" &&
|
|
659
|
-
options.packageLock.rootEngines.node !== options.manifest.runtime.node) {
|
|
660
|
-
options.findings.push(runtimeFinding({
|
|
661
|
-
ruleId: "brandlab-runtime-node-lock-spec",
|
|
662
|
-
expected: `lockfile root engines.node=${options.manifest.runtime.node}`,
|
|
663
|
-
actual: `lockfile root engines.node=${options.packageLock.rootEngines.node ?? "missing"}.`,
|
|
664
|
-
remediation: "Regenerate package-lock.json after restoring the supported Node engine.",
|
|
665
|
-
path: "package-lock.json",
|
|
666
|
-
}));
|
|
250
|
+
const node = options.packageJson.engines?.node ?? null;
|
|
251
|
+
const lockNode = options.packageLock.rootEngines.node ?? null;
|
|
252
|
+
if (node !== options.manifest.runtime.node || lockNode !== node) {
|
|
253
|
+
options.findings.push(runtimeFinding("brandlab-runtime-node", `engines.node=${options.manifest.runtime.node}`, `package.json=${node ?? "missing"}; lockfile=${lockNode ?? "missing"}.`, "Restore the supported Node engine and regenerate the lockfile.", "package.json"));
|
|
667
254
|
}
|
|
668
255
|
if (options.packageJson.packageManager !== options.manifest.runtime.packageManager) {
|
|
669
|
-
options.findings.push(runtimeFinding(
|
|
670
|
-
ruleId: "brandlab-runtime-package-manager",
|
|
671
|
-
expected: `packageManager=${options.manifest.runtime.packageManager}`,
|
|
672
|
-
actual: `packageManager=${options.packageJson.packageManager ?? "missing"}.`,
|
|
673
|
-
remediation: "Restore the installed release's exact npm package-manager declaration.",
|
|
674
|
-
path: "package.json",
|
|
675
|
-
}));
|
|
256
|
+
options.findings.push(runtimeFinding("brandlab-runtime-package-manager", options.manifest.runtime.packageManager, options.packageJson.packageManager ?? "missing", "Use the package manager declared by the current release.", "package.json"));
|
|
676
257
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const locked = lockedRootSpec(options.packageLock, packageName);
|
|
685
|
-
const resolved = options.packageLock.resolvedPackages.get(packageName)?.version ?? null;
|
|
686
|
-
if (declared !== options.targetManifest.releaseVersion ||
|
|
687
|
-
locked !== options.targetManifest.releaseVersion ||
|
|
688
|
-
resolved !== options.targetManifest.releaseVersion) {
|
|
689
|
-
packageMismatches.push(`${packageName}(declared=${declared ?? "missing"},locked=${locked ?? "missing"},resolved=${resolved ?? "missing"})`);
|
|
258
|
+
const expectedSharp = options.manifest.runtime.sharp;
|
|
259
|
+
if (expectedSharp) {
|
|
260
|
+
const override = options.packageJson.overrides?.sharp ?? null;
|
|
261
|
+
if (override !== expectedSharp ||
|
|
262
|
+
options.packageLock.sharpResolutions.length !== 1 ||
|
|
263
|
+
options.packageLock.sharpResolutions[0]?.version !== expectedSharp) {
|
|
264
|
+
options.findings.push(runtimeFinding("brandlab-runtime-sharp", `one sharp@${expectedSharp} resolution`, `override=${override ?? "missing"}; resolutions=${options.packageLock.sharpResolutions.map((entry) => `${entry.path}=${entry.version}`).join(", ") || "none"}.`, "Restore the release-owned Sharp override and regenerate the lockfile.", "package-lock.json"));
|
|
690
265
|
}
|
|
691
266
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
267
|
+
}
|
|
268
|
+
function inspectProjectConfig(root, findings) {
|
|
269
|
+
const configPath = join(root, "brandlab.config.ts");
|
|
270
|
+
const source = readOptionalText(configPath);
|
|
271
|
+
if (!source) {
|
|
272
|
+
findings.push(staticFinding({
|
|
273
|
+
ruleId: "brandlab-config-missing",
|
|
274
|
+
surface: "routes",
|
|
275
|
+
path: "brandlab.config.ts",
|
|
276
|
+
expected: "One small typed Brandlab project configuration",
|
|
277
|
+
actual: "brandlab.config.ts is missing.",
|
|
278
|
+
remediation: "Add project identity, route families and enabled capabilities.",
|
|
698
279
|
}));
|
|
280
|
+
return;
|
|
699
281
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
if (options.packageJson.engines?.node !== options.targetManifest.runtime.node) {
|
|
711
|
-
runtimeMismatches.push(`node(expected=${options.targetManifest.runtime.node},actual=${options.packageJson.engines?.node ?? "missing"})`);
|
|
712
|
-
}
|
|
713
|
-
if (options.packageJson.packageManager !== options.targetManifest.runtime.packageManager) {
|
|
714
|
-
runtimeMismatches.push(`packageManager(expected=${options.targetManifest.runtime.packageManager},actual=${options.packageJson.packageManager ?? "missing"})`);
|
|
715
|
-
}
|
|
716
|
-
if (runtimeMismatches.length > 0) {
|
|
717
|
-
options.findings.push(migrationRuntimeFinding({
|
|
718
|
-
ruleId: "brandlab-migration-runtime-baseline",
|
|
719
|
-
expected: `The ${options.targetManifest.releaseVersion} immutable runtime baseline`,
|
|
720
|
-
actual: runtimeMismatches.join("; "),
|
|
721
|
-
remediation: "Apply and lock the target runtime only inside the separately approved migration.",
|
|
282
|
+
if (!/\bdefineBrandlabProject\s*\(\s*\{/.test(source) ||
|
|
283
|
+
!/\bprojectId\s*:\s*['"][a-z0-9][a-z0-9-]*['"]/.test(source) ||
|
|
284
|
+
!/\bprofile\s*:\s*['"]next-(?:local-service|root-level)['"]/.test(source)) {
|
|
285
|
+
findings.push(staticFinding({
|
|
286
|
+
ruleId: "brandlab-config-invalid",
|
|
287
|
+
surface: "routes",
|
|
288
|
+
path: "brandlab.config.ts",
|
|
289
|
+
expected: "A literal defineBrandlabProject configuration with projectId and profile",
|
|
290
|
+
actual: "The minimum project identity contract could not be verified.",
|
|
291
|
+
remediation: "Keep the configuration literal and limited to project variation.",
|
|
722
292
|
}));
|
|
723
293
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
surface: "runtime",
|
|
743
|
-
classification: "migration",
|
|
744
|
-
severity: "blocker",
|
|
745
|
-
contract: { name: "runtime", version: "target-runtime-contract" },
|
|
746
|
-
evidenceKind: "static",
|
|
747
|
-
path: "package-lock.json",
|
|
748
|
-
expected: options.expected,
|
|
749
|
-
actual: options.actual,
|
|
750
|
-
remediation: options.remediation,
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
function evaluateApplicableArchitectureContracts(options) {
|
|
754
|
-
const installedConfiguration = options.installedManifest?.contracts.configuration ?? null;
|
|
755
|
-
const targetConfiguration = options.targetManifest?.contracts.configuration ?? null;
|
|
756
|
-
const hasDistinctTarget = Boolean(options.targetRelease &&
|
|
757
|
-
options.targetRelease !== options.installedManifest?.releaseVersion);
|
|
758
|
-
let configV2Inspection = null;
|
|
759
|
-
const inspectConfigV2 = () => {
|
|
760
|
-
configV2Inspection ??= inspectImmutableBrandlabConfigV2(options.root);
|
|
761
|
-
return configV2Inspection;
|
|
762
|
-
};
|
|
763
|
-
let configV3Inspection = null;
|
|
764
|
-
const inspectConfigV3 = () => {
|
|
765
|
-
configV3Inspection ??= inspectBrandlabConfigV3(options.root);
|
|
766
|
-
return configV3Inspection;
|
|
767
|
-
};
|
|
768
|
-
const legacyInspections = new Map();
|
|
769
|
-
let legacyArchitectureInput;
|
|
770
|
-
let legacyArchitectureInputResolved = false;
|
|
771
|
-
const inspectLegacy = (contract) => {
|
|
772
|
-
const cached = legacyInspections.get(contract);
|
|
773
|
-
if (cached)
|
|
774
|
-
return cached;
|
|
775
|
-
if (!legacyArchitectureInputResolved) {
|
|
776
|
-
legacyArchitectureInput = options.architecture === undefined
|
|
777
|
-
? readBrandlabArchitectureConfig(options.root)
|
|
778
|
-
: options.architecture;
|
|
779
|
-
legacyArchitectureInputResolved = true;
|
|
780
|
-
}
|
|
781
|
-
const inspection = inspectLegacyArchitectureContract({
|
|
782
|
-
root: options.root,
|
|
783
|
-
architecture: legacyArchitectureInput,
|
|
784
|
-
contract,
|
|
785
|
-
});
|
|
786
|
-
legacyInspections.set(contract, inspection);
|
|
787
|
-
return inspection;
|
|
788
|
-
};
|
|
789
|
-
if (installedConfiguration === CONFIGURATION_V2_CONTRACT) {
|
|
790
|
-
const inspection = inspectConfigV2();
|
|
791
|
-
for (const issue of inspection.issues) {
|
|
792
|
-
options.findings.push(mapConfigIssue(issue, "maintenance", options.installedManifest?.contracts));
|
|
294
|
+
const envSource = readOptionalText(join(root, ".env.example")) ?? "";
|
|
295
|
+
if (/\bcontact\s*:\s*\{/.test(source)) {
|
|
296
|
+
requireEnvKey(findings, envSource, "RESEND_API_KEY", "contact");
|
|
297
|
+
requireEnvKey(findings, envSource, "CONTACT_EMAIL", "contact");
|
|
298
|
+
}
|
|
299
|
+
if (/\breviews\s*:\s*\{[\s\S]*?\bprovider\s*:\s*['"]google-places['"]/.test(source)) {
|
|
300
|
+
requireEnvKey(findings, envSource, "GOOGLE_MAPS_PLATFORM_API_KEY", "reviews");
|
|
301
|
+
}
|
|
302
|
+
for (const retired of ["CONTACT_CONTEXT_SECRET", "KV_REST_API_URL", "KV_REST_API_TOKEN"]) {
|
|
303
|
+
if (new RegExp(`^${retired}=`, "m").test(envSource)) {
|
|
304
|
+
findings.push(staticFinding({
|
|
305
|
+
ruleId: "brandlab-environment-retired-contact-state",
|
|
306
|
+
surface: "environment",
|
|
307
|
+
path: ".env.example",
|
|
308
|
+
expected: "Only current contact environment variables",
|
|
309
|
+
actual: `${retired} is still documented.`,
|
|
310
|
+
remediation: `Remove ${retired}; it is not part of the simplified contact standard.`,
|
|
311
|
+
}));
|
|
793
312
|
}
|
|
794
313
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
314
|
+
}
|
|
315
|
+
function requireEnvKey(findings, envSource, key, capability) {
|
|
316
|
+
if (new RegExp(`^${key}=`, "m").test(envSource))
|
|
317
|
+
return;
|
|
318
|
+
findings.push(staticFinding({
|
|
319
|
+
ruleId: `brandlab-environment-${capability}-${slugify(key)}`,
|
|
320
|
+
surface: "environment",
|
|
321
|
+
path: ".env.example",
|
|
322
|
+
expected: `${key} documented for the enabled ${capability} capability`,
|
|
323
|
+
actual: `${key} is missing from .env.example.`,
|
|
324
|
+
remediation: `Document the non-secret ${key} variable name without adding a credential value.`,
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
function inspectProjectSource(root, findings) {
|
|
328
|
+
const sourceRoot = join(root, "src");
|
|
329
|
+
if (!existsSync(sourceRoot))
|
|
330
|
+
return;
|
|
331
|
+
for (const file of sourceFiles(sourceRoot)) {
|
|
332
|
+
const source = readFileSync(file, "utf8");
|
|
333
|
+
const executableSource = stripCommentsPreservingLayout(source);
|
|
334
|
+
for (const rule of SOURCE_RULES) {
|
|
335
|
+
const match = executableSource.match(rule.pattern);
|
|
336
|
+
if (!match || match.index === undefined)
|
|
337
|
+
continue;
|
|
338
|
+
findings.push(staticFinding({
|
|
339
|
+
ruleId: rule.ruleId,
|
|
340
|
+
surface: rule.surface,
|
|
341
|
+
path: normalizePath(relative(root, file)),
|
|
342
|
+
line: lineAt(source, match.index),
|
|
343
|
+
expected: rule.expected,
|
|
344
|
+
actual: "A project-owned implementation was detected.",
|
|
345
|
+
remediation: rule.remediation,
|
|
346
|
+
}));
|
|
799
347
|
}
|
|
800
348
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
349
|
+
}
|
|
350
|
+
function stripCommentsPreservingLayout(source) {
|
|
351
|
+
const output = [...source];
|
|
352
|
+
let state = "code";
|
|
353
|
+
let escaped = false;
|
|
354
|
+
for (let index = 0; index < output.length; index += 1) {
|
|
355
|
+
const current = output[index];
|
|
356
|
+
const next = output[index + 1];
|
|
357
|
+
if (state === "line") {
|
|
358
|
+
if (current === "\n" || current === "\r") {
|
|
359
|
+
state = "code";
|
|
809
360
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
options.targetManifest?.contracts.architecture === "architecture-v2")) {
|
|
813
|
-
const v2Inspection = inspectLegacy("architecture-v2");
|
|
814
|
-
for (const issue of targetOnlyLegacyArchitectureIssues(installedInspection, v2Inspection)) {
|
|
815
|
-
options.findings.push(mapArchitectureIssue(issue, "advisory", {
|
|
816
|
-
contractVersion: "architecture-v2",
|
|
817
|
-
}));
|
|
818
|
-
}
|
|
361
|
+
else {
|
|
362
|
+
output[index] = " ";
|
|
819
363
|
}
|
|
364
|
+
continue;
|
|
820
365
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
options.findings.push(mapConfigIssue(issue, "advisory", {
|
|
831
|
-
configuration: CONFIGURATION_V3_CONTRACT,
|
|
832
|
-
}));
|
|
366
|
+
if (state === "block") {
|
|
367
|
+
if (current === "*" && next === "/") {
|
|
368
|
+
output[index] = " ";
|
|
369
|
+
output[index + 1] = " ";
|
|
370
|
+
index += 1;
|
|
371
|
+
state = "code";
|
|
372
|
+
}
|
|
373
|
+
else if (current !== "\n" && current !== "\r") {
|
|
374
|
+
output[index] = " ";
|
|
833
375
|
}
|
|
376
|
+
continue;
|
|
834
377
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
378
|
+
if (state !== "code") {
|
|
379
|
+
if (escaped) {
|
|
380
|
+
escaped = false;
|
|
381
|
+
}
|
|
382
|
+
else if (current === "\\") {
|
|
383
|
+
escaped = true;
|
|
840
384
|
}
|
|
385
|
+
else if ((state === "single" && current === "'") ||
|
|
386
|
+
(state === "double" && current === '"') ||
|
|
387
|
+
(state === "template" && current === "`")) {
|
|
388
|
+
state = "code";
|
|
389
|
+
}
|
|
390
|
+
continue;
|
|
841
391
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
for (const issue of inspectConfigV2().issues) {
|
|
845
|
-
options.findings.push(mapConfigIssue(issue, "migration", options.targetManifest?.contracts));
|
|
392
|
+
if (current === "'") {
|
|
393
|
+
state = "single";
|
|
846
394
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
for (const issue of inspectConfigV3().issues) {
|
|
850
|
-
options.findings.push(mapConfigIssue(issue, "migration", options.targetManifest?.contracts));
|
|
395
|
+
else if (current === '"') {
|
|
396
|
+
state = "double";
|
|
851
397
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
if (
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
}
|
|
398
|
+
else if (current === "`") {
|
|
399
|
+
state = "template";
|
|
400
|
+
}
|
|
401
|
+
else if (current === "/" && next === "/") {
|
|
402
|
+
output[index] = " ";
|
|
403
|
+
output[index + 1] = " ";
|
|
404
|
+
index += 1;
|
|
405
|
+
state = "line";
|
|
406
|
+
}
|
|
407
|
+
else if (current === "/" && next === "*") {
|
|
408
|
+
output[index] = " ";
|
|
409
|
+
output[index + 1] = " ";
|
|
410
|
+
index += 1;
|
|
411
|
+
state = "block";
|
|
867
412
|
}
|
|
868
413
|
}
|
|
869
|
-
|
|
870
|
-
const inspectedConfigV3 = configV3Inspection;
|
|
871
|
-
return {
|
|
872
|
-
evaluator: BRANDLAB_ARCHITECTURE_DISPATCH_RULESET_ID,
|
|
873
|
-
installedContract: options.installedManifest?.contracts.architecture ?? null,
|
|
874
|
-
targetContract: options.targetManifest?.contracts.architecture ?? null,
|
|
875
|
-
legacy: Object.fromEntries([...legacyInspections.entries()]
|
|
876
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
877
|
-
.map(([contract, inspection]) => [
|
|
878
|
-
contract,
|
|
879
|
-
{
|
|
880
|
-
rulesetId: inspection.rulesetId,
|
|
881
|
-
profile: inspection.report.profile,
|
|
882
|
-
status: inspection.report.status,
|
|
883
|
-
pages: inspection.report.pageFileCount,
|
|
884
|
-
applicableRuleIds: inspection.issues.map((issue) => issue.code).sort(),
|
|
885
|
-
},
|
|
886
|
-
])),
|
|
887
|
-
configV2: inspectedConfigV2
|
|
888
|
-
? {
|
|
889
|
-
projectId: inspectedConfigV2.config?.projectId ?? null,
|
|
890
|
-
profile: inspectedConfigV2.config?.profile ?? null,
|
|
891
|
-
imports: [...inspectedConfigV2.imports].sort(),
|
|
892
|
-
executableImports: [...inspectedConfigV2.executableImports].sort(),
|
|
893
|
-
}
|
|
894
|
-
: null,
|
|
895
|
-
configV3: inspectedConfigV3
|
|
896
|
-
? {
|
|
897
|
-
projectId: inspectedConfigV3.config?.projectId ?? null,
|
|
898
|
-
profile: inspectedConfigV3.config?.profile ?? null,
|
|
899
|
-
imports: [...inspectedConfigV3.imports].sort(),
|
|
900
|
-
executableImports: [...inspectedConfigV3.executableImports].sort(),
|
|
901
|
-
outputOwnedImports: [...inspectedConfigV3.outputOwnedImports].sort(),
|
|
902
|
-
executableExports: Object.fromEntries([...inspectedConfigV3.executableExports.entries()]
|
|
903
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
904
|
-
.map(([moduleName, exports]) => [moduleName, [...exports].sort()])),
|
|
905
|
-
outputOwnedExports: Object.fromEntries([...inspectedConfigV3.outputOwnedExports.entries()]
|
|
906
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
907
|
-
.map(([moduleName, exports]) => [moduleName, [...exports].sort()])),
|
|
908
|
-
}
|
|
909
|
-
: null,
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
function mapArchitectureIssue(issue, classification, options) {
|
|
913
|
-
const rawSeverity = issue.severity === "error"
|
|
914
|
-
? "blocker"
|
|
915
|
-
: issue.severity === "warning"
|
|
916
|
-
? "warning"
|
|
917
|
-
: "info";
|
|
918
|
-
const severity = classification === "advisory" && rawSeverity === "blocker"
|
|
919
|
-
? "warning"
|
|
920
|
-
: rawSeverity;
|
|
921
|
-
return {
|
|
922
|
-
ruleId: issue.code,
|
|
923
|
-
surface: surfaceForRule(issue.code),
|
|
924
|
-
classification,
|
|
925
|
-
severity,
|
|
926
|
-
contract: {
|
|
927
|
-
name: "architecture",
|
|
928
|
-
version: options.contractVersion ?? LEGACY_ARCHITECTURE_EVALUATOR_CONTRACT,
|
|
929
|
-
},
|
|
930
|
-
evidenceKind: "static",
|
|
931
|
-
path: issue.path,
|
|
932
|
-
expected: issue.fix ?? "The project matches its applicable Brandlab architecture contract.",
|
|
933
|
-
actual: issue.detail,
|
|
934
|
-
remediation: issue.fix ?? "Apply the supported shared-platform implementation for this rule.",
|
|
935
|
-
};
|
|
414
|
+
return output.join("");
|
|
936
415
|
}
|
|
937
|
-
function
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
name: contractName,
|
|
952
|
-
version: contractVersion,
|
|
953
|
-
},
|
|
954
|
-
evidenceKind: issue.evidenceKind,
|
|
955
|
-
path: issue.path,
|
|
956
|
-
line: issue.line,
|
|
957
|
-
expected: issue.expected,
|
|
958
|
-
actual: issue.actual,
|
|
959
|
-
remediation: issue.remediation,
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
function contractNameForConfigSurface(surface) {
|
|
963
|
-
if (/contact|email|delivery|attachment/.test(surface))
|
|
964
|
-
return "contact";
|
|
965
|
-
if (/analytics/.test(surface))
|
|
966
|
-
return "analytics";
|
|
967
|
-
if (/metadata|schema|breadcrumb|sitemap|robots|location|review|google-places/.test(surface)) {
|
|
968
|
-
return "seo";
|
|
969
|
-
}
|
|
970
|
-
if (/platform-contract|business|routes|headers|accessibility|blog|architecture/.test(surface)) {
|
|
971
|
-
return "architecture";
|
|
416
|
+
function sourceFiles(root) {
|
|
417
|
+
const files = [];
|
|
418
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
419
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
420
|
+
continue;
|
|
421
|
+
const path = join(root, entry.name);
|
|
422
|
+
if (entry.isDirectory()) {
|
|
423
|
+
files.push(...sourceFiles(path));
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (SOURCE_EXTENSIONS.has(extension(entry.name)) &&
|
|
427
|
+
!/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) {
|
|
428
|
+
files.push(path);
|
|
429
|
+
}
|
|
972
430
|
}
|
|
973
|
-
return
|
|
974
|
-
}
|
|
975
|
-
function releaseFinding(options) {
|
|
976
|
-
return {
|
|
977
|
-
ruleId: options.ruleId,
|
|
978
|
-
surface: "release",
|
|
979
|
-
classification: "maintenance",
|
|
980
|
-
severity: options.severity,
|
|
981
|
-
contract: { name: "release-authority", version: "v3" },
|
|
982
|
-
evidenceKind: "static",
|
|
983
|
-
path: options.path,
|
|
984
|
-
expected: options.expected,
|
|
985
|
-
actual: options.actual,
|
|
986
|
-
remediation: options.remediation,
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
function summarize(findings, classification) {
|
|
990
|
-
const relevant = findings.filter((finding) => finding.classification === classification);
|
|
991
|
-
const blockers = relevant.filter((finding) => finding.severity === "blocker").length;
|
|
992
|
-
const warnings = relevant.filter((finding) => finding.severity === "warning").length;
|
|
993
|
-
const info = relevant.filter((finding) => finding.severity === "info").length;
|
|
994
|
-
return {
|
|
995
|
-
status: blockers > 0 ? "blocked" : warnings > 0 ? "warning" : "pass",
|
|
996
|
-
total: relevant.length,
|
|
997
|
-
blockers,
|
|
998
|
-
warnings,
|
|
999
|
-
info,
|
|
1000
|
-
};
|
|
431
|
+
return files.sort();
|
|
1001
432
|
}
|
|
1002
433
|
function readPackageJson(root) {
|
|
1003
434
|
const source = readOptionalText(join(root, "package.json"));
|
|
@@ -1011,48 +442,38 @@ function readPackageJson(root) {
|
|
|
1011
442
|
}
|
|
1012
443
|
}
|
|
1013
444
|
function readPackageLock(root) {
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
return emptyPackageLockInspection("missing");
|
|
1018
|
-
}
|
|
445
|
+
const source = readOptionalText(join(root, "package-lock.json"));
|
|
446
|
+
if (!source)
|
|
447
|
+
return emptyPackageLock("missing");
|
|
1019
448
|
let parsed;
|
|
1020
449
|
try {
|
|
1021
450
|
parsed = JSON.parse(source);
|
|
1022
451
|
}
|
|
1023
452
|
catch {
|
|
1024
|
-
return
|
|
1025
|
-
digest: digestBrandlabAuthority(source),
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
if (!isObjectRecord(parsed)) {
|
|
1029
|
-
return emptyPackageLockInspection("invalid", {
|
|
1030
|
-
digest: digestBrandlabAuthority(parsed),
|
|
1031
|
-
});
|
|
453
|
+
return emptyPackageLock("invalid", { digest: digestBrandlabAuthority(source) });
|
|
1032
454
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
455
|
+
if (!isRecord(parsed))
|
|
456
|
+
return emptyPackageLock("invalid");
|
|
457
|
+
const lockfileVersion = typeof parsed.lockfileVersion === "number" ? parsed.lockfileVersion : null;
|
|
1036
458
|
if (lockfileVersion !== 2 && lockfileVersion !== 3) {
|
|
1037
|
-
return
|
|
1038
|
-
lockfileVersion,
|
|
1039
|
-
digest: digestBrandlabAuthority(parsed),
|
|
1040
|
-
});
|
|
459
|
+
return emptyPackageLock("unsupported", { lockfileVersion });
|
|
1041
460
|
}
|
|
1042
|
-
if (!
|
|
1043
|
-
return
|
|
1044
|
-
lockfileVersion,
|
|
1045
|
-
digest: digestBrandlabAuthority(parsed),
|
|
1046
|
-
});
|
|
461
|
+
if (!isRecord(parsed.packages) || !isRecord(parsed.packages[""])) {
|
|
462
|
+
return emptyPackageLock("invalid", { lockfileVersion });
|
|
1047
463
|
}
|
|
1048
464
|
const rootPackage = parsed.packages[""];
|
|
1049
465
|
const rootDependencies = stringRecord(rootPackage.dependencies);
|
|
1050
466
|
const rootDevDependencies = stringRecord(rootPackage.devDependencies);
|
|
1051
467
|
const rootEngines = stringRecord(rootPackage.engines);
|
|
1052
468
|
const resolvedPackages = new Map();
|
|
1053
|
-
|
|
469
|
+
const packageNames = [
|
|
470
|
+
...BRANDLAB_SHARED_PACKAGE_NAMES,
|
|
471
|
+
...RUNTIME_PACKAGE_FIELDS.map(([name]) => name),
|
|
472
|
+
"sharp",
|
|
473
|
+
];
|
|
474
|
+
for (const packageName of packageNames) {
|
|
1054
475
|
const entry = parsed.packages[`node_modules/${packageName}`];
|
|
1055
|
-
if (!
|
|
476
|
+
if (!isRecord(entry))
|
|
1056
477
|
continue;
|
|
1057
478
|
resolvedPackages.set(packageName, {
|
|
1058
479
|
version: typeof entry.version === "string" ? entry.version : null,
|
|
@@ -1061,6 +482,13 @@ function readPackageLock(root) {
|
|
|
1061
482
|
linked: entry.link === true,
|
|
1062
483
|
});
|
|
1063
484
|
}
|
|
485
|
+
const sharpResolutions = Object.entries(parsed.packages)
|
|
486
|
+
.filter(([path]) => path === "node_modules/sharp" || path.endsWith("/node_modules/sharp"))
|
|
487
|
+
.map(([path, entry]) => ({
|
|
488
|
+
path,
|
|
489
|
+
version: isRecord(entry) && typeof entry.version === "string" ? entry.version : null,
|
|
490
|
+
}))
|
|
491
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
1064
492
|
return {
|
|
1065
493
|
state: "valid",
|
|
1066
494
|
lockfileVersion,
|
|
@@ -1068,170 +496,135 @@ function readPackageLock(root) {
|
|
|
1068
496
|
rootDevDependencies,
|
|
1069
497
|
rootEngines,
|
|
1070
498
|
resolvedPackages,
|
|
499
|
+
sharpResolutions,
|
|
1071
500
|
fingerprint: {
|
|
1072
|
-
state: "valid",
|
|
1073
501
|
lockfileVersion,
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
rootDevDependencies,
|
|
1081
|
-
rootEngines,
|
|
1082
|
-
resolvedPackages,
|
|
1083
|
-
fingerprint: {},
|
|
1084
|
-
}, packageName)])),
|
|
1085
|
-
node: rootEngines.node ?? null,
|
|
1086
|
-
},
|
|
1087
|
-
resolved: Object.fromEntries([...resolvedPackages.entries()].map(([packageName, entry]) => [
|
|
1088
|
-
packageName,
|
|
1089
|
-
{ version: entry.version, linked: entry.linked },
|
|
502
|
+
rootDependencies,
|
|
503
|
+
rootDevDependencies,
|
|
504
|
+
rootEngines,
|
|
505
|
+
resolved: Object.fromEntries([...resolvedPackages].map(([name, value]) => [
|
|
506
|
+
name,
|
|
507
|
+
{ version: value.version, linked: value.linked },
|
|
1090
508
|
])),
|
|
509
|
+
sharpResolutions,
|
|
1091
510
|
},
|
|
1092
511
|
};
|
|
1093
512
|
}
|
|
1094
|
-
function
|
|
513
|
+
function emptyPackageLock(state, fingerprint = {}) {
|
|
1095
514
|
return {
|
|
1096
515
|
state,
|
|
1097
|
-
lockfileVersion: typeof
|
|
516
|
+
lockfileVersion: typeof fingerprint.lockfileVersion === "number"
|
|
517
|
+
? fingerprint.lockfileVersion
|
|
518
|
+
: null,
|
|
1098
519
|
rootDependencies: {},
|
|
1099
520
|
rootDevDependencies: {},
|
|
1100
521
|
rootEngines: {},
|
|
1101
522
|
resolvedPackages: new Map(),
|
|
1102
|
-
|
|
523
|
+
sharpResolutions: [],
|
|
524
|
+
fingerprint: { state, ...fingerprint },
|
|
1103
525
|
};
|
|
1104
526
|
}
|
|
527
|
+
function runtimeVersionSatisfiesRelease(packageName, actual, expected) {
|
|
528
|
+
if (!actual || !isBrandlabSemver(actual))
|
|
529
|
+
return false;
|
|
530
|
+
if (!["next", "react", "react-dom"].includes(packageName))
|
|
531
|
+
return actual === expected;
|
|
532
|
+
const actualParts = actual.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
533
|
+
const expectedParts = expected.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
534
|
+
return Boolean(actualParts &&
|
|
535
|
+
expectedParts &&
|
|
536
|
+
actualParts[1] === expectedParts[1] &&
|
|
537
|
+
actualParts[2] === expectedParts[2] &&
|
|
538
|
+
compareBrandlabSemver(actual, expected) >= 0);
|
|
539
|
+
}
|
|
540
|
+
function runtimeExpectation(packageName, expected) {
|
|
541
|
+
return ["next", "react", "react-dom"].includes(packageName)
|
|
542
|
+
? `${packageName}@${expected} or a newer exact patch in the same major/minor line`
|
|
543
|
+
: `${packageName}@${expected}`;
|
|
544
|
+
}
|
|
545
|
+
function isApprovedRegistryResolution(resolved, packageName, version) {
|
|
546
|
+
try {
|
|
547
|
+
const url = new URL(resolved);
|
|
548
|
+
const artifact = packageName.split("/").at(-1);
|
|
549
|
+
return Boolean(artifact &&
|
|
550
|
+
url.protocol === "https:" &&
|
|
551
|
+
url.hostname === "registry.npmjs.org" &&
|
|
552
|
+
url.pathname === `/${packageName}/-/${artifact}-${version}.tgz`);
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
1105
558
|
function declaredPackageSpec(packageJson, packageName) {
|
|
1106
|
-
return packageJson.dependencies?.[packageName] ??
|
|
559
|
+
return (packageJson.dependencies?.[packageName] ??
|
|
1107
560
|
packageJson.devDependencies?.[packageName] ??
|
|
1108
|
-
null;
|
|
1109
|
-
}
|
|
1110
|
-
function lockedRootSpec(packageLock, packageName) {
|
|
1111
|
-
return packageLock.rootDependencies[packageName] ??
|
|
1112
|
-
packageLock.rootDevDependencies[packageName] ??
|
|
1113
|
-
null;
|
|
1114
|
-
}
|
|
1115
|
-
function stringRecord(value) {
|
|
1116
|
-
if (!isObjectRecord(value))
|
|
1117
|
-
return {};
|
|
1118
|
-
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
1119
|
-
}
|
|
1120
|
-
function isObjectRecord(value) {
|
|
1121
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
561
|
+
null);
|
|
1122
562
|
}
|
|
1123
|
-
function
|
|
1124
|
-
return
|
|
563
|
+
function lockedRootSpec(lock, packageName) {
|
|
564
|
+
return lock.rootDependencies[packageName] ?? lock.rootDevDependencies[packageName] ?? null;
|
|
1125
565
|
}
|
|
1126
|
-
function
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
566
|
+
function releaseFinding(ruleId, expected, actual, remediation, path) {
|
|
567
|
+
return {
|
|
568
|
+
ruleId,
|
|
569
|
+
surface: "release",
|
|
570
|
+
severity: "blocker",
|
|
571
|
+
contract: { name: "release", version: "current-release" },
|
|
572
|
+
evidenceKind: "static",
|
|
573
|
+
...(path ? { path } : {}),
|
|
574
|
+
expected,
|
|
575
|
+
actual,
|
|
576
|
+
remediation,
|
|
577
|
+
};
|
|
1134
578
|
}
|
|
1135
|
-
function
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
quote = null;
|
|
1148
|
-
continue;
|
|
1149
|
-
}
|
|
1150
|
-
if (char === '"' || char === "'" || char === "`")
|
|
1151
|
-
quote = char;
|
|
1152
|
-
else if (char === openChar)
|
|
1153
|
-
depth += 1;
|
|
1154
|
-
else if (char === closeChar) {
|
|
1155
|
-
depth -= 1;
|
|
1156
|
-
if (depth === 0)
|
|
1157
|
-
return source.slice(start + 1, index);
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
return null;
|
|
579
|
+
function runtimeFinding(ruleId, expected, actual, remediation, path) {
|
|
580
|
+
return {
|
|
581
|
+
ruleId,
|
|
582
|
+
surface: "runtime",
|
|
583
|
+
severity: "blocker",
|
|
584
|
+
contract: { name: "runtime", version: "installed-release" },
|
|
585
|
+
evidenceKind: "static",
|
|
586
|
+
path,
|
|
587
|
+
expected,
|
|
588
|
+
actual,
|
|
589
|
+
remediation,
|
|
590
|
+
};
|
|
1161
591
|
}
|
|
1162
|
-
function
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
quote = null;
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
1179
|
-
quote = char;
|
|
1180
|
-
continue;
|
|
1181
|
-
}
|
|
1182
|
-
if (char === "[")
|
|
1183
|
-
arrayDepth += 1;
|
|
1184
|
-
else if (char === "]")
|
|
1185
|
-
arrayDepth -= 1;
|
|
1186
|
-
else if (char === "{" && arrayDepth === 0) {
|
|
1187
|
-
const body = extractDelimitedBody(source, index, "{", "}");
|
|
1188
|
-
if (body !== null) {
|
|
1189
|
-
bodies.push(body);
|
|
1190
|
-
index += body.length + 1;
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
return bodies;
|
|
592
|
+
function staticFinding(options) {
|
|
593
|
+
return {
|
|
594
|
+
ruleId: options.ruleId,
|
|
595
|
+
surface: options.surface,
|
|
596
|
+
severity: "blocker",
|
|
597
|
+
contract: { name: "project", version: "current-only-v2" },
|
|
598
|
+
evidenceKind: "static",
|
|
599
|
+
...(options.path ? { path: options.path } : {}),
|
|
600
|
+
...(options.line ? { line: options.line } : {}),
|
|
601
|
+
expected: options.expected,
|
|
602
|
+
actual: options.actual,
|
|
603
|
+
remediation: options.remediation,
|
|
604
|
+
};
|
|
1195
605
|
}
|
|
1196
|
-
function
|
|
1197
|
-
return
|
|
606
|
+
function compareFindings(left, right) {
|
|
607
|
+
return (left.ruleId.localeCompare(right.ruleId) ||
|
|
608
|
+
(left.path ?? "").localeCompare(right.path ?? "") ||
|
|
609
|
+
(left.line ?? 0) - (right.line ?? 0));
|
|
1198
610
|
}
|
|
1199
|
-
function
|
|
1200
|
-
|
|
1201
|
-
return raw ? Number(raw) : null;
|
|
611
|
+
function readOptionalText(path) {
|
|
612
|
+
return existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
1202
613
|
}
|
|
1203
|
-
function
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
return [...arraySource.matchAll(/['\"]([^'\"]+)['\"]/g)].map((match) => match[1]);
|
|
614
|
+
function stringRecord(value) {
|
|
615
|
+
if (!isRecord(value))
|
|
616
|
+
return {};
|
|
617
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
1208
618
|
}
|
|
1209
|
-
function
|
|
1210
|
-
|
|
1211
|
-
target[key] = value;
|
|
619
|
+
function isRecord(value) {
|
|
620
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1212
621
|
}
|
|
1213
|
-
function
|
|
1214
|
-
|
|
1215
|
-
|
|
622
|
+
function extension(name) {
|
|
623
|
+
const index = name.lastIndexOf(".");
|
|
624
|
+
return index === -1 ? "" : name.slice(index);
|
|
1216
625
|
}
|
|
1217
|
-
function
|
|
1218
|
-
|
|
1219
|
-
"contact",
|
|
1220
|
-
"metadata",
|
|
1221
|
-
"schema",
|
|
1222
|
-
"sitemap",
|
|
1223
|
-
"breadcrumb",
|
|
1224
|
-
"analytics",
|
|
1225
|
-
"accessibility",
|
|
1226
|
-
"runtime",
|
|
1227
|
-
"release",
|
|
1228
|
-
]) {
|
|
1229
|
-
if (ruleId.toLowerCase().includes(surface))
|
|
1230
|
-
return surface;
|
|
1231
|
-
}
|
|
1232
|
-
if (/api key|secret|credential|recaptcha/i.test(ruleId))
|
|
1233
|
-
return "security";
|
|
1234
|
-
return "architecture";
|
|
626
|
+
function lineAt(source, index) {
|
|
627
|
+
return source.slice(0, index).split("\n").length;
|
|
1235
628
|
}
|
|
1236
629
|
function slugify(value) {
|
|
1237
630
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -1239,23 +632,4 @@ function slugify(value) {
|
|
|
1239
632
|
function normalizePath(value) {
|
|
1240
633
|
return value.split(/[/\\]/).join("/");
|
|
1241
634
|
}
|
|
1242
|
-
function escapeRegExp(value) {
|
|
1243
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1244
|
-
}
|
|
1245
|
-
function compareFindings(first, second) {
|
|
1246
|
-
const classificationOrder = {
|
|
1247
|
-
maintenance: 0,
|
|
1248
|
-
migration: 1,
|
|
1249
|
-
advisory: 2,
|
|
1250
|
-
};
|
|
1251
|
-
const severityOrder = {
|
|
1252
|
-
blocker: 0,
|
|
1253
|
-
warning: 1,
|
|
1254
|
-
info: 2,
|
|
1255
|
-
};
|
|
1256
|
-
return (classificationOrder[first.classification] - classificationOrder[second.classification] ||
|
|
1257
|
-
severityOrder[first.severity] - severityOrder[second.severity] ||
|
|
1258
|
-
first.ruleId.localeCompare(second.ruleId) ||
|
|
1259
|
-
(first.path ?? "").localeCompare(second.path ?? ""));
|
|
1260
|
-
}
|
|
1261
635
|
//# sourceMappingURL=project.js.map
|