Files
orca/config/scripts/build-windows-security-addon.mjs
T
Neil f152101010 ci: reuse prepared Windows security addon in PR packages (#29352)
* ci: reuse prepared Windows security addon in PR packages

* ci: prepare bounded Windows security reuse qualification

* ci: ignore owned Windows benchmark receipts during source checks

* test: inject Windows security qualification dependencies

* test(mobile): register chat view mocks in test roots
2026-10-15 21:05:34 -07:00

179 lines
6.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Compile native/windows-security for an orcad Windows slot.
*
* orcad runs under its own pinned Node with no node_modules, and its secure-file writes and
* data-root privacy check need this addon: without it the privacy check fails closed and orcad
* refuses to start. N-API, so one build serves every Node. Arm64 cross-compiles from x64.
*
* Builds in a copy of the sources so the desktop's own build/Release (the host arch) survives.
*
* node config/scripts/build-windows-security-addon.mjs --arch=arm64
*/
import { copyFileSync, cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
import { createRequire } from 'node:module'
import { isAbsolute, join, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { runProcessSync } from '@orca/process-host'
import { NODE_RUNTIME_ASSETS } from '../../src/shared/node-runtime-pin.ts'
import { sha256Of } from './orcad-prebuild-slot-contents.mjs'
import { ORCAD_WINDOWS_SECURITY_FILENAME } from '../../src/shared/orcad-artifacts.ts'
const { PE_MACHINE, describePeMachine, readPeMachine } = createRequire(import.meta.url)(
'./windows-pe-machine.cjs'
)
const ROOT = resolve(import.meta.dirname, '..', '..')
const SUPPORTED_ARCHES = ['x64', 'arm64']
export function windowsSecurityAddonOutput(root, arch) {
return join(root, '.build', 'windows-security', arch, ORCAD_WINDOWS_SECURITY_FILENAME)
}
export function probeWindowsSecurityAddon(
runtimePath,
candidate,
arch,
run = runProcessSync,
cwd = ROOT
) {
return run({
program: runtimePath,
args: [
'-e',
`if (process.platform !== 'win32' || process.arch !== ${JSON.stringify(arch)} || process.versions.electron) process.exit(1);` +
`const sid = require(${JSON.stringify(candidate)}).currentUserSid();` +
`if (typeof sid !== 'string' || !/^S-1-\\d+(?:-\\d+)+$/.test(sid)) process.exit(2);` +
`process.stdout.write('windows-security-ready');`
],
cwd,
timeoutMs: 30_000
})
}
export function reusePreparedWindowsSecurityAddon(
arch,
output,
{
enabled = false,
runtimePath,
root = ROOT,
platform = process.platform,
hostArch = process.arch,
runtimeAssets = NODE_RUNTIME_ASSETS,
run = runProcessSync
} = {}
) {
if (!enabled || platform !== 'win32' || arch !== hostArch || !runtimePath) {
return false
}
const candidate = resolve(
root,
'native/windows-security/build/Release/orca_windows_security.node'
)
try {
if (
readPeMachine(candidate) !== PE_MACHINE[arch] ||
!isAbsolute(runtimePath) ||
sha256Of(runtimePath) !== runtimeAssets[`win32-${arch}`]?.executableSha256
) {
return false
}
const prepared = run({
program: process.execPath,
args: [
join(root, 'config/scripts/ensure-native-runtime.mjs'),
'--runtime=node',
'--check-only'
],
cwd: root,
timeoutMs: 30_000
})
if (prepared.code !== 0 || prepared.timedOut || prepared.outputTruncated) {
return false
}
const probe = probeWindowsSecurityAddon(runtimePath, candidate, arch, run, root)
if (
probe.code !== 0 ||
probe.timedOut ||
probe.outputTruncated ||
probe.stdout !== 'windows-security-ready'
) {
return false
}
} catch {
return false
}
mkdirSync(resolve(output, '..'), { recursive: true })
copyFileSync(candidate, output)
console.log(`[windows-security] reused prepared ${arch} -> ${output}`)
return true
}
export function buildWindowsSecurityAddon(
arch,
output = windowsSecurityAddonOutput(ROOT, arch),
{ root = ROOT, run = runProcessSync, ...options } = {}
) {
if (!SUPPORTED_ARCHES.includes(arch)) {
throw new Error(`--arch must be one of ${SUPPORTED_ARCHES.join(', ')}; got ${arch}`)
}
if (process.platform !== 'win32') {
throw new Error(
`The Windows security addon only builds on Windows; running on ${process.platform}.`
)
}
if (reusePreparedWindowsSecurityAddon(arch, output, { ...options, root, run })) {
return output
}
// Inside the repo so binding.gyp's `require('node-addon-api')` resolves from root node_modules.
const workDir = join(root, '.build', 'windows-security', arch, 'gyp')
rmSync(workDir, { recursive: true, force: true })
mkdirSync(workDir, { recursive: true })
copyFileSync(join(root, 'native/windows-security/binding.gyp'), join(workDir, 'binding.gyp'))
cpSync(join(root, 'native/windows-security/src'), join(workDir, 'src'), { recursive: true })
const result = run({
program: process.execPath,
args: [
join(root, 'node_modules', 'node-gyp', 'bin', 'node-gyp.js'),
'rebuild',
`--arch=${arch}`
],
cwd: workDir,
stdio: 'inherit',
timeoutMs: null
})
if (result.code !== 0) {
throw new Error(`node-gyp failed for ${arch} with exit ${result.code}`)
}
const built = join(workDir, 'build', 'Release', 'orca_windows_security.node')
if (!existsSync(built)) {
throw new Error(`node-gyp reported success but ${built} is missing.`)
}
// Why check the artifact: node-gyp can ignore --arch, and a host could not load the result.
const machine = readPeMachine(built)
if (machine !== PE_MACHINE[arch]) {
throw new Error(`Built binary is ${describePeMachine(machine)}, expected ${arch}.`)
}
mkdirSync(resolve(output, '..'), { recursive: true })
copyFileSync(built, output)
console.log(`[windows-security] ${arch} -> ${output}`)
return output
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
try {
const arch =
process.argv.find((arg) => arg.startsWith('--arch='))?.slice('--arch='.length) ?? process.arch
buildWindowsSecurityAddon(arch, undefined, {
enabled: process.argv.includes('--reuse-prepared-runtime'),
runtimePath: process.argv
.find((arg) => arg.startsWith('--node-runtime='))
?.slice('--node-runtime='.length)
})
} catch (error) {
console.error(`[windows-security] ${error instanceof Error ? error.message : String(error)}`)
process.exit(1)
}
}