From 67dc092f18159202e9aa8687f883caa3bcbd340b Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Wed, 7 Oct 2026 00:09:40 -0700 Subject: [PATCH] fix(windows): keep generated skills and snapshots LF (#25972) Pin generated skill JSON and Vitest snapshots to LF so Windows autocrlf checkouts pass byte-for-byte verification. Cover the checkout behavior with a real Git regression test. Co-authored-by: Shuhei Konno --- .gitattributes | 4 ++ ...enerated-artifact-line-ending-pin.test.mjs | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 config/scripts/generated-artifact-line-ending-pin.test.mjs diff --git a/.gitattributes b/.gitattributes index be42f7cfd68..8157af1df2c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -97,3 +97,7 @@ # Generated ACP schemas are checked byte-for-byte against formatted output. /src/main/acp/generated/*.generated.ts linguist-generated=true text eol=lf +# Generated skill bundle artifacts are verified byte-for-byte against fresh output. +/resources/skills/*.json linguist-generated=true text eol=lf +# Vitest writes snapshots with LF, so checkout bytes must match on every host. +**/__snapshots__/*.snap text eol=lf diff --git a/config/scripts/generated-artifact-line-ending-pin.test.mjs b/config/scripts/generated-artifact-line-ending-pin.test.mjs new file mode 100644 index 00000000000..8b628099d35 --- /dev/null +++ b/config/scripts/generated-artifact-line-ending-pin.test.mjs @@ -0,0 +1,49 @@ +import { execFileSync } from 'node:child_process' +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { expect, it } from 'vitest' + +const projectDir = resolve(import.meta.dirname, '../..') + +it('keeps byte-compared artifacts and source LF in an autocrlf checkout', () => { + const root = mkdtempSync(join(tmpdir(), 'orca-line-ending-checkout-')) + const lf = 'first\nsecond\n' + const crlf = 'first\r\nsecond\r\n' + const paths = new Map([ + ['resources/skills/current-manifest.json', lf], + ['resources/skills/snapshot-registry.json', lf], + ['resources/skills/release-mapping.json', lf], + ['src/main/__snapshots__/example.test.ts.snap', lf], + ['src/renderer/src/example.tsx', lf], + ['src/main/example.ts', lf], + ['config/scripts/example.mjs', lf], + ...['cmd', 'bat', 'ps1', 'nsh'].map((extension) => [`config/example.${extension}`, crlf]), + ['resources/skills-extra/example.json', crlf], + ['vendor/resources/skills/example.json', crlf], + ['scripts/example.mjs', crlf], + ['src/main/runtime/__fixtures__/example.txt', crlf], + ['src/main/daemon/__fixtures__/pty-transcripts/example.txt', crlf] + ]) + const git = (args) => execFileSync('git', args, { cwd: root, stdio: 'pipe' }) + + try { + git(['init', '--quiet']) + writeFileSync(join(root, '.gitattributes'), readFileSync(join(projectDir, '.gitattributes'))) + for (const path of paths.keys()) { + mkdirSync(dirname(join(root, path)), { recursive: true }) + writeFileSync(join(root, path), path.includes('__fixtures__') ? crlf : lf) + } + git(['-c', 'core.autocrlf=input', 'add', '--', '.']) + for (const path of paths.keys()) { + rmSync(join(root, path)) + } + git(['-c', 'core.autocrlf=true', 'checkout-index', '--all', '--force']) + + for (const [path, expected] of paths) { + expect(readFileSync(join(root, path), 'utf8'), path).toBe(expected) + } + } finally { + rmSync(root, { recursive: true, force: true }) + } +})