Skip to content

Commit 1e4d28f

Browse files
authored
Add CI check that parses every password-rules.json entry (#1094)
Adds a Node-based lint step that loads tools/PasswordRulesParser.js and calls parsePasswordRules() on every "password-rules" string in quirks/password-rules.json. Any parse error (such as the comma-instead-of- semicolon hetzner.com regression referenced in #907) is reported with the offending domain and causes the lint job to fail. Fixes #907
1 parent 49a48f5 commit 1e4d28f

2 files changed

Lines changed: 59 additions & 0 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2026 Apple Inc. Licensed under MIT License.
2+
//
3+
// Validates every "password-rules" string in quirks/password-rules.json by
4+
// loading tools/PasswordRulesParser.js and calling parsePasswordRules() on it.
5+
// Any parse error (e.g. ",required" instead of "; required", as in #907) causes
6+
// CI to fail with a clear message identifying the offending domain.
7+
8+
"use strict";
9+
10+
const fs = require("fs");
11+
const path = require("path");
12+
const vm = require("vm");
13+
14+
const repoRoot = path.resolve(__dirname, "..", "..", "..");
15+
const parserPath = path.join(repoRoot, "tools", "PasswordRulesParser.js");
16+
const rulesPath = path.join(repoRoot, "quirks", "password-rules.json");
17+
18+
const parserSource = fs.readFileSync(parserPath, "utf8");
19+
const rules = JSON.parse(fs.readFileSync(rulesPath, "utf8"));
20+
21+
const sandbox = {
22+
capturedErrors: [],
23+
console: {
24+
assert() {},
25+
warn() {},
26+
error(message) { sandbox.capturedErrors.push(String(message)); },
27+
},
28+
};
29+
vm.createContext(sandbox);
30+
vm.runInContext(parserSource, sandbox, { filename: parserPath });
31+
32+
let failedDomains = 0;
33+
for (const domain of Object.keys(rules)) {
34+
const ruleString = rules[domain]["password-rules"];
35+
sandbox.capturedErrors.length = 0;
36+
sandbox.__ruleString = ruleString;
37+
try {
38+
vm.runInContext("parsePasswordRules(__ruleString)", sandbox);
39+
} catch (e) {
40+
sandbox.capturedErrors.push(`threw ${e.message}`);
41+
}
42+
if (sandbox.capturedErrors.length > 0) {
43+
failedDomains++;
44+
console.error(`${domain}: ${ruleString}`);
45+
for (const message of sandbox.capturedErrors) {
46+
console.error(` ${message}`);
47+
}
48+
}
49+
}
50+
51+
if (failedDomains > 0) {
52+
console.error(`\n${failedDomains} domain(s) in password-rules.json failed to parse.`);
53+
process.exit(1);
54+
}
55+
56+
console.log(`OK: ${Object.keys(rules).length} password-rules entries parsed cleanly.`);

.github/workflows/lint.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ jobs:
3030
- uses: actions/checkout@v6
3131
- name: Lint Sort Order
3232
run: cat quirks/password-rules.json | grep "^ \"" | sort -c
33+
- uses: actions/setup-node@v6
34+
- name: Lint Rule Syntax
35+
run: node .github/workflows/lint-scripts/password-rules-parse.js
3336

3437
websites-shared-credentials:
3538
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)