diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..d3c8713c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Normalize line endings to LF for every tracked text file, regardless of a +# contributor's local core.autocrlf setting. Several guards and tests compare +# file content or hash it (e.g. guards/markdown-links.mjs, patches applied via +# pnpm, CI-workflow regex assertions in scripts/release*.test.mjs) and break or +# silently diverge when a checkout introduces CRLF bytes that were not in the +# reviewed source. +* text=auto eol=lf + +# Explicitly binary — never subject to line-ending normalization or diffing. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.woff binary +*.woff2 binary +*.ttf binary +*.otf binary diff --git a/guards/markdown-links.mjs b/guards/markdown-links.mjs index d55f7fdb..fe477ec5 100644 --- a/guards/markdown-links.mjs +++ b/guards/markdown-links.mjs @@ -138,9 +138,11 @@ export default { name: "markdown-links", description: "tracked Markdown files do not link to missing local targets", run() { - const root = execFileSync("git", ["rev-parse", "--show-toplevel"], { - encoding: "utf8", - }).trim(); + const root = resolve( + execFileSync("git", ["rev-parse", "--show-toplevel"], { + encoding: "utf8", + }).trim(), + ); const violations = []; for (const file of trackedMarkdownFiles(root)) { diff --git a/scripts/dependencies-security.test.mjs b/scripts/dependencies-security.test.mjs index 76f850ab..bea95055 100644 --- a/scripts/dependencies-security.test.mjs +++ b/scripts/dependencies-security.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; -import { readFile } from "node:fs/promises"; +import { readdir, readFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import test from "node:test"; import { fileURLToPath, pathToFileURL } from "node:url"; @@ -11,16 +11,16 @@ async function patchedImageSizeModule() { const lockfile = await readFile(join(root, "pnpm-lock.yaml"), "utf8"); const match = lockfile.match(/^ image-size@2\.0\.2: ([a-f0-9]+)$/m); assert.ok(match, "image-size 2.0.2 must remain pinned to the reviewed local patch"); - return join( - root, - "node_modules", - ".pnpm", - `image-size@2.0.2_patch_hash=${match[1]}`, - "node_modules", - "image-size", - "dist", - "index.mjs", + + const storeDir = join(root, "node_modules", ".pnpm"); + const entries = await readdir(storeDir); + const patchedEntry = entries.find((entry) => /^image-size@2\.0\.2_patch_hash[=_][a-f0-9]+$/.test(entry)); + assert.ok( + patchedEntry, + "expected a patched image-size@2.0.2 entry under node_modules/.pnpm — run pnpm install", ); + + return join(storeDir, patchedEntry, "node_modules", "image-size", "dist", "index.mjs"); } function writeBox(buffer, offset, size, name) { diff --git a/scripts/release.integration.test.mjs b/scripts/release.integration.test.mjs index cb75d7e6..796e7969 100644 --- a/scripts/release.integration.test.mjs +++ b/scripts/release.integration.test.mjs @@ -32,7 +32,7 @@ async function fixture(t) { join(packageDir, "lifecycle.mjs"), "import { writeFileSync } from 'node:fs';\nwriteFileSync(process.env.RELEASE_LIFECYCLE_MARKER, 'ran');\n", ); - execFileSync("tar", ["-czf", tarball, "-C", root, "package"]); + execFileSync("tar", ["--force-local", "-czf", tarball, "-C", root, "package"]); t.after(async () => { await rm(root, { recursive: true, force: true }); diff --git a/scripts/release.mjs b/scripts/release.mjs index 53c06501..b4bca626 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -129,7 +129,7 @@ export function inspectTarball(tarball, { exec = execFileSync } = {}) { let manifest; try { manifest = JSON.parse( - exec("tar", ["-xOf", absolute, "package/package.json"], { encoding: "utf8" }), + exec("tar", ["--force-local", "-xOf", absolute, "package/package.json"], { encoding: "utf8" }), ); } catch (error) { throw new Error(`cannot read package/package.json from ${absolute}: ${error.message}`);