Files
orca/config/scripts/verify-packaged-plugin-resources.test.mjs
T
Merge Sim 9cf6ba9cd7 fix(repo): check every file out with LF so shipped scripts still run on Windows
Git for Windows defaults to core.autocrlf=true, so a stock Windows clone
rewrote the working tree on checkout. That is not cosmetic: Orca ships
executable shell scripts straight out of this repo, and a \r on the shebang
line stops them running — `resources/linux/bin/orca-ide` dies with
`env: bash\r: No such file or directory` (exit 127) instead of executing.
The same reaches `resources/darwin/bin/orca`, the deb/rpm maintainer scripts
and .husky/pre-commit. 122 of the 130 tracked shebang files had no protection.

Replace the nine ad-hoc eol=lf pins with one repo-wide `* text=auto eol=lf`.
`text=auto` leaves the binary decision to Git and the index was already
LF-only, so this changes zero index blobs: `git add --renormalize .` across
all 20,050 tracked files stages nothing.

Add config/scripts/check-line-ending-policy.mjs to `pnpm lint`. For every
tracked file the OS has to exec (leading `#!` or the executable bit) it
asserts the committed blob has no CRLF and that `git check-attr eol` resolves
to `lf` — two independent failure modes, neither implying the other. The
population is derived from content rather than hand-listed, because a curated
list would have had to name 122 files today and would silently miss the 123rd.

Whether resources/win32/bin/orca.cmd should be pinned to CRLF is left
undecided; it changes a shipped byte. .gitattributes carries the one-line
follow-up as a comment.

Refs STA-4307
2026-08-29 19:38:55 -07:00

94 lines
3.7 KiB
JavaScript

import { spawnSync } from 'node:child_process'
import { cp, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
const require = createRequire(import.meta.url)
const { verifyPackagedPluginResources } = require('./verify-packaged-plugin-resources.cjs')
describe('verify packaged plugin resources', () => {
it('accepts exact launch bytes copied into a packaged resources directory', async () => {
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-packaged-plugins-'))
try {
await cp(
join(process.cwd(), 'resources', 'plugins', 'launch'),
join(resourcesDir, 'plugins', 'launch'),
{ recursive: true }
)
expect(() => verifyPackagedPluginResources(resourcesDir)).not.toThrow()
} finally {
await rm(resourcesDir, { recursive: true, force: true })
}
})
it('rejects mutated bytes in the packaged output', async () => {
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-packaged-plugins-'))
try {
const launchRoot = join(resourcesDir, 'plugins', 'launch')
await cp(join(process.cwd(), 'resources', 'plugins', 'launch'), launchRoot, {
recursive: true
})
await writeFile(
join(launchRoot, 'stablyai.orca-navigation-shortcuts', 'extra.json'),
'{"mutated":true}\n'
)
expect(() => verifyPackagedPluginResources(resourcesDir)).toThrow(
'packaged bytes do not match stablyai.orca-navigation-shortcuts'
)
} finally {
await rm(resourcesDir, { recursive: true, force: true })
}
})
// The tree is hashed by raw bytes, so a CRLF checkout on Windows breaks the
// pinned hash. These two guard the `.gitattributes` eol=lf pin that prevents it.
// Asserted through check-attr, not against the text of `.gitattributes`: what matters
// is the attribute Git resolves, whichever rule supplies it. `text` is asserted as
// well as `eol` — under `-text` Git copies the blob verbatim, so an LF checkout would
// depend on the blob staying LF rather than on the pin.
it('pins the launch tree to LF so Windows checkouts hash identically', () => {
const resolved = spawnSync(
'git',
['check-attr', 'text', 'eol', '--', 'resources/plugins/launch/bundled-plugins.json'],
{ cwd: process.cwd(), encoding: 'utf8' }
)
expect(resolved.status).toBe(0)
const [text, eol] = resolved.stdout
.trim()
.split('\n')
.map((line) => line.split(': ').at(-1))
// `auto` or `set`: either means Git converts on checkout. `unset` (`-text`) does not.
expect(['auto', 'set']).toContain(text)
expect(eol).toBe('lf')
})
it('rejects a CRLF checkout of the launch tree', async () => {
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-packaged-plugins-'))
try {
const launchRoot = join(resourcesDir, 'plugins', 'launch')
await cp(join(process.cwd(), 'resources', 'plugins', 'launch'), launchRoot, {
recursive: true
})
for (const entry of await readdir(launchRoot, { recursive: true })) {
const path = join(launchRoot, entry)
if (!(await stat(path)).isFile()) {
continue
}
await writeFile(path, (await readFile(path, 'utf8')).replace(/\r?\n/g, '\r\n'))
}
// Every file is rewritten, so the first mismatch is whichever plugin sorts
// first — don't pin a name a later branch can reorder.
expect(() => verifyPackagedPluginResources(resourcesDir)).toThrow(
/packaged bytes do not match stablyai\./
)
} finally {
await rm(resourcesDir, { recursive: true, force: true })
}
})
})