mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
* Reduce native dependency installs to the host platform * Remove install policy documentation * Guard cross-arch packaging and scope release installs to the runner electron-builder only logs a warning for a missing extraResources source, so a host-only install silently shipped a foreign-arch slice without its natives — `pnpm build:mac` on Apple Silicon produced an x64 DMG with no sherpa-onnx-darwin-x64 and no @parcel/watcher-darwin-x64. The previous beforePack hook covered only win32. - Add assertPackagedNativeVariantsInstalled, an arch-aware check over the target's sherpa-onnx, @parcel/watcher, and (on Windows) node-gyp addons. beforePack now runs it for every platform, with remedies split: another architecture comes from install:release, the os:win32 addons need a Windows host. - Drop --os from the release installs. Every packaging job already runs on a runner whose OS matches its target, so only the macOS lanes need extra breadth, and only on CPU for their x64+arm64 config. Windows and Linux packaging return to a plain host-only install. - Add --frozen-lockfile to install:release so a bare run cannot rewrite the lockfile. - Restore the install policy reference doc and the CONTRIBUTING note, plus the rationale comments dropped from the runtime contract test. - Gate the packaging-closure assertions on whether the Windows addons are installed rather than on the host OS, so a cross-arch install exercises them off Windows too. - Make the workflow contract test read `run:` steps as well as retry-action commands, and enforce host-only scoping on the non-macOS packaging lanes. - Remove the unreferenced install measurement script; its numbers live in the policy doc. * Track the install policy doc and index it from AGENTS.md docs/** is ignored behind a per-file allow-list, so the new reference doc was only committed via git add -f and future edits would be skipped. Add it to the allow-list and give it an AGENTS.md entry like every other tracked reference doc, so the host-only install rule is discoverable before someone packages a second architecture. * Route Windows-lane removals through the retrying helper Adding these four specs to the PR Windows lane pulled them into the windows-lane-tree-removal-boundary ratchet, which failed on 20 raw recursive removals. On Windows a bare rmSync races a handle the OS has not released, throwing EPERM after the assertions already passed and reporting a green test as a lane failure. * Adapt the packaging guard to the vendored Windows registry addon main vendored windows-native-registry as the workspace package @orca/windows-registry (#20438). A workspace link resolves on every host, so including it in the installed-Windows-addons checks proved nothing. @vscode/windows-process-tree is the only os: win32 npm addon left, so it alone decides whether the win32 resource plan resolves.
151 lines
5.8 KiB
JavaScript
151 lines
5.8 KiB
JavaScript
import { execFileSync } from 'node:child_process'
|
|
import {
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
realpathSync,
|
|
writeFileSync
|
|
} from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, resolve } from 'node:path'
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
import { removeTreeSync } from '../../src/shared/windows-transient-lock-removal.ts'
|
|
import {
|
|
assertWindowsProcessTreeCreationTimePatch,
|
|
assertWindowsProcessTreeRuntimeCreationTime,
|
|
inspectWindowsProcessTreeAddon,
|
|
nodeGypRebuildInvocation,
|
|
stageWindowsProcessTreeNodeAddonApiHeaders,
|
|
WINDOWS_PROCESS_TREE_NODE_ADDON_API_HEADERS,
|
|
WINDOWS_PROCESS_TREE_PACKAGE_DIR
|
|
} from './windows-process-tree-gyp-rebuild.mjs'
|
|
import { writeFakeWindowsProcessTreeWithNodeAddonApi } from './rebuild-native-deps-test-fixtures.mjs'
|
|
|
|
describe('windows-process-tree node-gyp rebuild', () => {
|
|
// The installed Windows dependency is exercised by the Windows CI lane.
|
|
it.runIf(process.platform === 'win32')(
|
|
"resolves node-addon-api's gyp target from the rebuild cwd",
|
|
() => {
|
|
// gyp probes node-addon-api with the package's physical directory as cwd,
|
|
// so the emitted target is store-relative; gyp then resolves that hop
|
|
// against the rebuild cwd. Rebuilding from pnpm's node_modules link sends
|
|
// the hop outside the store and configure fails (run 32999886072).
|
|
const { cwd } = nodeGypRebuildInvocation('x64')
|
|
const targets = execFileSync(process.execPath, ['-p', "require('node-addon-api').targets"], {
|
|
cwd: realpathSync(WINDOWS_PROCESS_TREE_PACKAGE_DIR),
|
|
encoding: 'utf8'
|
|
}).trim()
|
|
expect(existsSync(resolve(cwd, targets))).toBe(true)
|
|
}
|
|
)
|
|
|
|
it('forwards the requested arch to node-gyp', () => {
|
|
const { args } = nodeGypRebuildInvocation('arm64', import.meta.dirname)
|
|
expect(args).toContain('rebuild')
|
|
expect(args).toContain('--arch=arm64')
|
|
})
|
|
|
|
it('copies node-addon-api headers into the patched include dir', () => {
|
|
const packageDir = mkdtempSync(join(tmpdir(), 'orca-windows-process-tree-headers-'))
|
|
try {
|
|
const nodeAddonApiDir = join(packageDir, 'node_modules', 'node-addon-api')
|
|
mkdirSync(nodeAddonApiDir, { recursive: true })
|
|
writeFileSync(join(packageDir, 'package.json'), '{"dependencies":{"node-addon-api":"*"}}\n')
|
|
writeFileSync(join(nodeAddonApiDir, 'package.json'), '{"name":"node-addon-api"}\n')
|
|
for (const header of WINDOWS_PROCESS_TREE_NODE_ADDON_API_HEADERS) {
|
|
writeFileSync(join(nodeAddonApiDir, header), `// ${header}\n`)
|
|
}
|
|
|
|
const stagedDir = stageWindowsProcessTreeNodeAddonApiHeaders(packageDir)
|
|
expect(stagedDir).toBe(join(packageDir, 'deps', 'node-addon-api'))
|
|
for (const header of WINDOWS_PROCESS_TREE_NODE_ADDON_API_HEADERS) {
|
|
expect(readFileSync(join(stagedDir, header), 'utf8')).toBe(`// ${header}\n`)
|
|
}
|
|
} finally {
|
|
removeTreeSync(packageDir)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('inspecting a compiled windows-process-tree addon', () => {
|
|
let dir
|
|
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(join(tmpdir(), 'orca-windows-process-tree-addon-'))
|
|
})
|
|
afterEach(() => {
|
|
removeTreeSync(dir)
|
|
})
|
|
|
|
it('reports a binary that still imports ReadProcessMemory as unpatched', () => {
|
|
const addonPath = join(dir, 'windows_process_tree.node')
|
|
writeFileSync(addonPath, Buffer.from('MZ\0\0KERNEL32.dll\0ReadProcessMemory\0', 'binary'))
|
|
expect(inspectWindowsProcessTreeAddon(addonPath)).toBe('unpatched')
|
|
})
|
|
|
|
it('reports a binary without the import as clean', () => {
|
|
const addonPath = join(dir, 'windows_process_tree.node')
|
|
writeFileSync(addonPath, Buffer.from('MZ\0\0ntdll.dll\0NtQueryInformationProcess\0', 'binary'))
|
|
expect(inspectWindowsProcessTreeAddon(addonPath)).toBe('clean')
|
|
})
|
|
|
|
// The whole point of the tri-state: absence is not evidence of safety, and a
|
|
// boolean made "there is no binary" indistinguishable from "checked, clean".
|
|
it('reports an absent binary as missing rather than clean', () => {
|
|
expect(inspectWindowsProcessTreeAddon(join(dir, 'windows_process_tree.node'))).toBe('missing')
|
|
})
|
|
|
|
it('inspects whatever path it is handed, including a relay-staged addon', () => {
|
|
// The relay loads `./windows-process-tree.node` beside its bundle, which is
|
|
// nowhere near a node_modules package directory.
|
|
const staged = join(dir, 'windows-process-tree.node')
|
|
writeFileSync(staged, Buffer.from('MZ\0\0ReadProcessMemory\0', 'binary'))
|
|
expect(inspectWindowsProcessTreeAddon(staged)).toBe('unpatched')
|
|
})
|
|
})
|
|
|
|
describe('windows-process-tree CreationTime patch assertion', () => {
|
|
let dir
|
|
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(join(tmpdir(), 'orca-windows-process-tree-creation-time-'))
|
|
})
|
|
afterEach(() => {
|
|
removeTreeSync(dir)
|
|
})
|
|
|
|
it('accepts a package whose source and JS surfaces expose process creation time', () => {
|
|
writeFakeWindowsProcessTreeWithNodeAddonApi(dir)
|
|
|
|
expect(() =>
|
|
assertWindowsProcessTreeCreationTimePatch(
|
|
join(dir, 'node_modules', '@vscode', 'windows-process-tree')
|
|
)
|
|
).not.toThrow()
|
|
})
|
|
|
|
it('rejects a package missing the process creation-time patch', () => {
|
|
writeFakeWindowsProcessTreeWithNodeAddonApi(dir, { creationTimePatchApplied: false })
|
|
|
|
expect(() =>
|
|
assertWindowsProcessTreeCreationTimePatch(
|
|
join(dir, 'node_modules', '@vscode', 'windows-process-tree')
|
|
)
|
|
).toThrow('process creation-time patch')
|
|
})
|
|
|
|
it('requires the runtime ProcessDataFlag.CreationTime enum', () => {
|
|
expect(() =>
|
|
assertWindowsProcessTreeRuntimeCreationTime({
|
|
ProcessDataFlag: { None: 0, Memory: 1, CommandLine: 2, CreationTime: 4 }
|
|
})
|
|
).not.toThrow()
|
|
expect(() =>
|
|
assertWindowsProcessTreeRuntimeCreationTime({
|
|
ProcessDataFlag: { None: 0, Memory: 1, CommandLine: 2 }
|
|
})
|
|
).toThrow('ProcessDataFlag.CreationTime')
|
|
})
|
|
})
|