mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix(build): pin config/relay-assets LF so one release is one relay hash (#19024)
* fix(build): pin config/relay-assets LF so one release is one relay hash * test(build): correct why the negative fixtures exist Review measured it: the first assertion checks the eol attribute via check-attr, not file content, so it fails first without the pin. The fixtures add over-broadness coverage, they do not carry the test. * test(build): key the relay line-ending pin off the manifest, not a directory A path glob proves the directory is non-empty, not that it is still the directory build-relay reads from. Relocating an asset into config/scripts (where only **/*.mjs is pinned) reintroduced the CRLF bug with the suite fully green. RELAY_ARTIFACTS is the right anchor: build-relay refuses to emit an artifact absent from it, so a relocated or new asset cannot slip past. Bundles have no tracked source and drop out with zero hits. --------- Co-authored-by: Orca Worker <orca-worker@localhost>
This commit is contained in:
@@ -8,6 +8,11 @@
|
||||
/src/cli/bundled-skill-guides.ts text eol=lf
|
||||
# Bundled plugin trees are byte-hashed; CRLF checkout would break the pinned hash.
|
||||
/resources/plugins/** text eol=lf
|
||||
# Relay assets are copied verbatim into the bundle and hashed byte-for-byte into
|
||||
# .version, which names the immutable remote install dir. A CRLF checkout makes a
|
||||
# Windows-built client disagree with a mac/Linux-built one on the same release,
|
||||
# so one host ends up with two relay trees (#17886 review).
|
||||
/config/relay-assets/** text eol=lf
|
||||
# Pin the bytes so a patch reads and diffs identically on every host. It is NOT
|
||||
# what makes the hash right: pnpm hashes a patch LF-normalized, so a CRLF checkout
|
||||
# cannot change it. Believing otherwise put a hand-computed raw digest in the
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { resolve } from 'node:path'
|
||||
import { RELAY_ARTIFACTS } from '../../src/shared/relay-artifacts.ts'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
/**
|
||||
* Guard the `.gitattributes` pin that keeps `config/relay-assets` on LF.
|
||||
*
|
||||
* `core.autocrlf=true` ships in the Git-for-Windows system config, so without a
|
||||
* pin a Windows runner checks these out as CRLF. build-relay.mjs copies them
|
||||
* verbatim into the bundle and hashes them byte-for-byte into `.version`, which
|
||||
* names the immutable remote relay directory -- so a Windows-built client and a
|
||||
* mac/Linux-built one disagree on the same release, and one SSH host ends up with
|
||||
* two relay trees, each paying its own remote native-dep compile.
|
||||
*
|
||||
* Measured on v1.4.197: master-cloexec-patch.cjs shipped at 11229 bytes from the
|
||||
* mac runner and 11547 (= 11229 + 318 lines) from the Windows one.
|
||||
*/
|
||||
const projectDir = resolve(import.meta.dirname, '../..')
|
||||
|
||||
function git(args) {
|
||||
return execFileSync('git', args, { cwd: projectDir, encoding: 'utf8' })
|
||||
}
|
||||
|
||||
/** `git check-attr -z` emits NUL-separated path/attr/value triples. */
|
||||
function eolAttributes(paths) {
|
||||
const fields = git(['check-attr', '-z', 'eol', '--', ...paths]).split('\0')
|
||||
const found = new Map()
|
||||
for (let index = 0; index + 2 < fields.length; index += 3) {
|
||||
found.set(fields[index], fields[index + 2])
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyed off the manifest, not a directory: build-relay refuses to emit an
|
||||
* artifact absent from RELAY_ARTIFACTS, so relocating an asset cannot slip
|
||||
* past this the way a path glob would. esbuild bundles have no tracked
|
||||
* source and contribute no hits, so they need no classifying.
|
||||
*/
|
||||
function trackedManifestSources() {
|
||||
const paths = new Set()
|
||||
for (const { filename } of RELAY_ARTIFACTS) {
|
||||
const hits = git(['ls-files', '-z', '--', `*/${filename}`]).split('\0').filter(Boolean)
|
||||
for (const path of hits) {
|
||||
paths.add(path)
|
||||
}
|
||||
}
|
||||
return [...paths]
|
||||
}
|
||||
|
||||
describe('config/relay-assets line-ending pin', () => {
|
||||
it('pins every tracked relay artifact source to LF', () => {
|
||||
const assets = trackedManifestSources()
|
||||
expect(assets.length).toBeGreaterThan(0)
|
||||
|
||||
const attributes = eolAttributes(assets)
|
||||
const unpinned = assets.filter((path) => attributes.get(path) !== 'lf')
|
||||
|
||||
expect(
|
||||
unpinned,
|
||||
'A relay asset left on the platform default gets CRLF on a Windows runner, ' +
|
||||
'which changes the .version hash and splits one release across two remote ' +
|
||||
'relay directories. Pin it in .gitattributes.'
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
// Why: the assertion above only sees files that exist today. These fix the
|
||||
// pattern itself -- broad enough to cover a file added tomorrow, narrow enough
|
||||
// not to claim neighbours.
|
||||
it.each([
|
||||
['config/relay-assets/example.cjs', 'lf'],
|
||||
['config/relay-assets/nested/deeper/example.cjs', 'lf'],
|
||||
['config/relay-assets/example.txt', 'lf'],
|
||||
['config/relay-assets-extra/example.cjs', 'unspecified'],
|
||||
['vendor/config/relay-assets/example.cjs', 'unspecified']
|
||||
])('resolves %s to eol=%s', (path, expected) => {
|
||||
expect(eolAttributes([path]).get(path)).toBe(expected)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user