mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
* Reuse the headless detector compiler without full dependency setup * Keep optional compiler-cache saves from failing cache warming
161 lines
5.1 KiB
JavaScript
161 lines
5.1 KiB
JavaScript
import { createHash } from 'node:crypto'
|
|
import {
|
|
appendFileSync,
|
|
cpSync,
|
|
existsSync,
|
|
lstatSync,
|
|
mkdirSync,
|
|
readFileSync,
|
|
readdirSync,
|
|
rmSync,
|
|
symlinkSync,
|
|
writeFileSync
|
|
} from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { dirname, join, resolve } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
|
|
const ROOT = resolve(import.meta.dirname, '../..')
|
|
|
|
function inventory(directory, prefix = '') {
|
|
if (lstatSync(directory).isSymbolicLink()) {
|
|
throw new Error('Compiler cache directory is a symlink')
|
|
}
|
|
return readdirSync(directory)
|
|
.flatMap((name) => {
|
|
const path = join(directory, name)
|
|
const file = `${prefix}${name}`
|
|
const stat = lstatSync(path)
|
|
if (stat.isSymbolicLink()) {
|
|
throw new Error('Compiler cache contains a symlink')
|
|
}
|
|
if (stat.isDirectory()) {
|
|
return inventory(path, `${file}/`)
|
|
}
|
|
if (!stat.isFile()) {
|
|
throw new Error('Compiler cache contains a special file')
|
|
}
|
|
return [{ file, sha256: createHash('sha256').update(readFileSync(path)).digest('hex') }]
|
|
})
|
|
.sort((a, b) => a.file.localeCompare(b.file))
|
|
}
|
|
|
|
export function compilerCacheIdentity({
|
|
policyHash = process.env.COMPILER_POLICY_HASH,
|
|
cacheRoot = process.env.RUNNER_TEMP,
|
|
platform = process.platform,
|
|
arch = process.arch,
|
|
node = process.version
|
|
} = {}) {
|
|
if (!policyHash || !cacheRoot) {
|
|
throw new Error('Compiler cache requires policy hash and cache root')
|
|
}
|
|
return {
|
|
key: `headless-compiler-v1-${platform}-${arch}-${node}-${policyHash}`,
|
|
path: join(cacheRoot, 'headless-detector-compiler')
|
|
}
|
|
}
|
|
|
|
export function packCompilerCache({ root = ROOT, identity = compilerCacheIdentity() } = {}) {
|
|
const require = createRequire(join(root, 'package.json'))
|
|
const esbuildDir = dirname(require.resolve('esbuild/package.json'))
|
|
const nativeName = `@esbuild/${process.platform}-${process.arch}`
|
|
const nativeDir = dirname(require.resolve(`${nativeName}/package.json`, { paths: [esbuildDir] }))
|
|
rmSync(identity.path, { recursive: true, force: true })
|
|
mkdirSync(join(identity.path, 'node_modules', '@esbuild'), { recursive: true })
|
|
cpSync(esbuildDir, join(identity.path, 'node_modules', 'esbuild'), {
|
|
recursive: true,
|
|
dereference: true
|
|
})
|
|
cpSync(nativeDir, join(identity.path, 'node_modules', nativeName), {
|
|
recursive: true,
|
|
dereference: true
|
|
})
|
|
writeFileSync(
|
|
join(identity.path, 'manifest.json'),
|
|
JSON.stringify({
|
|
key: identity.key,
|
|
node: process.version,
|
|
version: require('esbuild').version,
|
|
files: inventory(identity.path)
|
|
})
|
|
)
|
|
}
|
|
|
|
export async function activateCompilerCache({
|
|
root = ROOT,
|
|
identity = compilerCacheIdentity()
|
|
} = {}) {
|
|
const dependencies = join(root, 'node_modules')
|
|
let created = false
|
|
try {
|
|
if (existsSync(dependencies)) {
|
|
throw new Error('Compiler activation requires an empty dependency tree')
|
|
}
|
|
const files = inventory(identity.path).filter((row) => row.file !== 'manifest.json')
|
|
const manifest = JSON.parse(readFileSync(join(identity.path, 'manifest.json'), 'utf8'))
|
|
if (
|
|
manifest.key !== identity.key ||
|
|
manifest.node !== process.version ||
|
|
JSON.stringify(files) !== JSON.stringify(manifest.files)
|
|
) {
|
|
throw new Error('Compiler cache identity or contents differ')
|
|
}
|
|
mkdirSync(join(dependencies, '@esbuild'), { recursive: true })
|
|
created = true
|
|
for (const name of ['esbuild', `@esbuild/${process.platform}-${process.arch}`]) {
|
|
symlinkSync(join(identity.path, 'node_modules', name), join(dependencies, name), 'dir')
|
|
}
|
|
const require = createRequire(join(root, 'package.json'))
|
|
const esbuild = require('esbuild')
|
|
if (esbuild.version !== manifest.version) {
|
|
throw new Error('Compiler API version differs')
|
|
}
|
|
await esbuild.build({
|
|
stdin: { contents: 'export const value = 1' },
|
|
write: false,
|
|
logLevel: 'silent'
|
|
})
|
|
return { available: true }
|
|
} catch (error) {
|
|
if (created) {
|
|
rmSync(dependencies, { recursive: true, force: true })
|
|
}
|
|
return { available: false, reason: String(error) }
|
|
}
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
const phase = process.argv[2]
|
|
const identity =
|
|
phase === 'identity'
|
|
? compilerCacheIdentity()
|
|
: {
|
|
key: process.env.COMPILER_CACHE_KEY,
|
|
path: process.env.COMPILER_CACHE_PATH
|
|
}
|
|
if (!identity.key || !identity.path) {
|
|
throw new Error('Compiler cache identity required')
|
|
}
|
|
const output = (values) => {
|
|
for (const [name, value] of Object.entries(values)) {
|
|
appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`)
|
|
}
|
|
}
|
|
if (phase === 'identity') {
|
|
output(identity)
|
|
} else if (phase === 'pack') {
|
|
packCompilerCache({ identity })
|
|
} else if (phase === 'activate') {
|
|
const result = await activateCompilerCache({ identity })
|
|
output({ available: result.available })
|
|
console.log(
|
|
result.available
|
|
? 'Validated headless compiler cache'
|
|
: `Use normal dependency install: ${result.reason}`
|
|
)
|
|
} else {
|
|
throw new Error('Expected identity, pack, or activate')
|
|
}
|
|
}
|