Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Without this, a Windows checkout with `core.autocrlf=true` (the Windows
# default) writes CRLF into the working tree while the committed content is LF.
# The scripts that parse these files are written against LF, so the pre-push
# audit fails on files that are perfectly valid, and the push is refused.
# Pinning the checkout to LF keeps the working tree byte-identical to what is
# committed, on every platform.

* text=auto eol=lf

*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary
*.woff binary
*.woff2 binary
22 changes: 15 additions & 7 deletions scripts/check-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,26 @@
// presence of arguments: a push that changes a doc but no rule passes
// `--rules` with nothing after it, and must not silently become a full audit.

import { existsSync, readFileSync, readdirSync } from "node:fs";
import { existsSync, readFileSync as readRaw, readdirSync } from "node:fs";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

// Every pattern below is written against LF. A Windows checkout with
// `core.autocrlf=true` puts CRLF in the working tree, so `/^---\n/` stops
// matching and every rule reads as having no frontmatter, failing the whole
// audit on files that are perfectly valid. `.gitattributes` keeps markdown at
// LF on checkout; stripping CR here keeps the audit correct on clones made
// before that landed, and on any file that reaches us with CRLF anyway.
const readFileSync = (path) => readRaw(path, "utf8").replace(/\r\n/g, "\n");

const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
const DOCS_DIR = join(REPO_ROOT, "docs");
const RULES_DIR = join(DOCS_DIR, "rules");
const PROBLEM_MAX = 250; // DEV-050

// The only per-repo setting: the rule id prefix (e.g. DEV, HR). Everything else
// is the fixed org standard, identical in every repo.
const P = readFileSync(join(REPO_ROOT, "rules.config.yml"), "utf8")
const P = readFileSync(join(REPO_ROOT, "rules.config.yml"))
.match(/^idPrefix:\s*"?([A-Za-z]+)"?\s*$/m)[1];

const errors = [];
Expand All @@ -63,7 +71,7 @@ for (const file of files) {
// Report every rule failure against its repo-relative path, so the message
// names the offending rule and doubles as a clickable link to it.
const ref = `docs/rules/${file}`;
const text = readFileSync(join(RULES_DIR, file), "utf8");
const text = readFileSync(join(RULES_DIR, file));
const fmMatch = text.match(/^---\n([\s\S]*?)\n---/);
if (!fmMatch) {
if (inScope(file)) fail(ref, "missing frontmatter block");
Expand Down Expand Up @@ -129,14 +137,14 @@ for (const id of deps.keys()) {

// DEV-040: every prose rule link resolves.
for (const file of files) {
const text = readFileSync(join(RULES_DIR, file), "utf8");
const text = readFileSync(join(RULES_DIR, file));
for (const m of text.matchAll(new RegExp(`\\]\\(\\.\\/(${P}-\\d+)\\.md\\)`, "g"))) {
if (!ids.has(m[1])) fail(`docs/rules/${file}`, `links ${m[1]} which does not exist`);
}
}

// Index: README lists every rule, and links no rule that does not exist.
const readme = readFileSync(join(RULES_DIR, "README.md"), "utf8");
const readme = readFileSync(join(RULES_DIR, "README.md"));
for (const id of ids.keys()) {
if (!readme.includes(`[${id}]`)) fail("docs/rules/README.md", `does not list ${id}`);
}
Expand Down Expand Up @@ -176,7 +184,7 @@ while (queue.length) {
reachable.add(file);
if (!existsSync(file)) continue;
const dir = dirname(file);
const body = readFileSync(file, "utf8");
const body = readFileSync(file);
// Follow both inline links `](target)` and reference-link definitions
// `[label]: target`, so a repo may use either link style and still index its
// tree through the same spine.
Expand Down Expand Up @@ -205,7 +213,7 @@ for (const md of walkMarkdown(DOCS_DIR)) {
if (!existsSync(rootReadme)) {
fail("README.md", "repository has no root README");
} else {
const readme = readFileSync(rootReadme, "utf8");
const readme = readFileSync(rootReadme);
const h1 = readme.match(/^#\s+\S.*$/m);
if (!h1) fail("README.md", "has no H1 title");
else {
Expand Down
Loading