diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f9c77dd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Check out everything with LF, even on Windows (core.autocrlf=true would +# otherwise write CRLF, which breaks the front-matter regexes in the +# editor-tokens/editor-icons/site-tokens gen scripts). +* text=auto eol=lf diff --git a/apps/cli/scripts/vendor-assets.mjs b/apps/cli/scripts/vendor-assets.mjs index 924db13..66ee13d 100644 --- a/apps/cli/scripts/vendor-assets.mjs +++ b/apps/cli/scripts/vendor-assets.mjs @@ -17,7 +17,7 @@ import { copyFileSync, existsSync, mkdirSync } from "node:fs"; import { createRequire } from "node:module"; -import { dirname, join } from "node:path"; +import { basename, join } from "node:path"; import { fileURLToPath } from "node:url"; const require = createRequire(import.meta.url); @@ -50,7 +50,10 @@ function resolve(specifier, what) { // serves `${bundle}.map` derived from whatever path it resolved, so the map has // to land beside its bundle here too. function copyWithMap(from, toDir) { - const name = from.split("/").pop(); + // basename, not split("/"): require.resolve returns a native path, so on + // Windows there is no "/" to split on and `name` would come back as the whole + // absolute path — which join() then appends to the destination wholesale. + const name = basename(from); copyFileSync(from, join(toDir, name)); if (existsSync(`${from}.map`)) { copyFileSync(`${from}.map`, join(toDir, `${name}.map`)); @@ -87,5 +90,5 @@ for (const name of expected) { } console.log( - `vendor-assets: ${expected.length} bundles + ${FONTS.length} fonts -> ${dirname(vendorDir)}/vendor` + `vendor-assets: ${expected.length} bundles + ${FONTS.length} fonts -> ${vendorDir}` ); diff --git a/packages/editor-icons/scripts/gen.mjs b/packages/editor-icons/scripts/gen.mjs index 9499373..5e01b42 100644 --- a/packages/editor-icons/scripts/gen.mjs +++ b/packages/editor-icons/scripts/gen.mjs @@ -39,7 +39,9 @@ if (!existsSync(specPath)) { } const raw = readFileSync(specPath, "utf8"); -const match = raw.match(/^---\n([\s\S]*?)\n---/); +// \r? because a Windows checkout with core.autocrlf=true hands us CRLF, and a +// front-matter block that opens `---\r\n` would otherwise read as absent. +const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!match) { throw new Error("gen: ICONS.md is missing its YAML front-matter block"); } diff --git a/packages/editor-tokens/scripts/gen.mjs b/packages/editor-tokens/scripts/gen.mjs index 2313715..ad2a8b7 100644 --- a/packages/editor-tokens/scripts/gen.mjs +++ b/packages/editor-tokens/scripts/gen.mjs @@ -16,7 +16,9 @@ if (!existsSync(specPath)) { } const raw = readFileSync(specPath, "utf8"); -const match = raw.match(/^---\n([\s\S]*?)\n---/); +// \r? because a Windows checkout with core.autocrlf=true hands us CRLF, and a +// front-matter block that opens `---\r\n` would otherwise read as absent. +const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!match) { throw new Error("gen: EDITOR.md is missing its YAML front-matter block"); } diff --git a/packages/overlay/scripts/check-css.mjs b/packages/overlay/scripts/check-css.mjs index d03950c..a06acd4 100644 --- a/packages/overlay/scripts/check-css.mjs +++ b/packages/overlay/scripts/check-css.mjs @@ -16,8 +16,14 @@ */ import { readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; +import { fileURLToPath } from "node:url"; -const DIR = new URL("../src/styles/", import.meta.url).pathname; +// fileURLToPath, not `.pathname`. `.pathname` is a URL component, not a path: +// on Windows it yields `/C:/…`, which readdirSync resolves against the current +// drive as `C:\C:\…`, and on every platform it stays percent-encoded, so a +// checkout under a directory with a space in it fails too. `join(DIR, file)` +// below needs a string, so this cannot stay a URL. +const DIR = fileURLToPath(new URL("../src/styles/", import.meta.url)); const START = /export const css\s*=\s*`/; const problems = []; diff --git a/packages/site-tokens/scripts/gen.mjs b/packages/site-tokens/scripts/gen.mjs index aa173dc..3e28a2c 100644 --- a/packages/site-tokens/scripts/gen.mjs +++ b/packages/site-tokens/scripts/gen.mjs @@ -16,7 +16,9 @@ if (!existsSync(designPath)) { } const raw = readFileSync(designPath, "utf8"); -const match = raw.match(/^---\n([\s\S]*?)\n---/); +// \r? because a Windows checkout with core.autocrlf=true hands us CRLF, and a +// front-matter block that opens `---\r\n` would otherwise read as absent. +const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!match) { throw new Error("gen: DESIGN.md is missing its YAML front-matter block"); } diff --git a/scripts/sync-readme.mjs b/scripts/sync-readme.mjs index 0bf56b5..3b28b23 100644 --- a/scripts/sync-readme.mjs +++ b/scripts/sync-readme.mjs @@ -33,6 +33,10 @@ const LINK = /(!?)\[([^\]]*)\]\(([^)\s]+)\)/g; /** ``` or ~~~ opening or closing a fenced block, which is never rewritten. */ const FENCE = /^\s*(?:```|~~~)/; /** A scheme, a protocol-relative host, or an anchor — already resolvable. */ +// Matches CRLF as readily as LF: a Windows working tree would otherwise leave a +// trailing \r on every line, and the rejoin below emits LF, so the --check +// comparison could never match the file on disk. +const LINE_BREAK = /\r?\n/; const ABSOLUTE = /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i; const LEADING_DOT_SLASH = /^\.\//; const REPO_URL = /github\.com[/:]([^/]+)\/([^/.]+)/; @@ -94,7 +98,7 @@ function render(source, slug) { return `${bang}[${label}](${absolutize(target, bang === "!", slug)})`; }); - for (const line of source.split("\n")) { + for (const line of source.split(LINE_BREAK)) { if (FENCE.test(line)) { inFence = !inFence; out.push(line); @@ -115,7 +119,11 @@ if (process.argv.includes("--check")) { } catch { die("apps/cli/README.md is missing — run `node scripts/sync-readme.mjs`"); } - if (current !== text) { + // Compare on content, not line endings. .gitattributes pins a fresh checkout + // to LF, but a file that arrived some other way — an existing clone, a zip, a + // patch, an editor configured for CRLF — would otherwise report as eternally + // stale with no way for the contributor to make it pass. + if (current.replace(/\r\n/g, "\n") !== text) { die( "apps/cli/README.md is stale — run `node scripts/sync-readme.mjs` and commit the result" );