Files
orca/config/scripts/node-server-change-scope.mjs
T
Neil 8ff6296bc7 Speed up serializer checks and keep native caches stable (#24476)
* Reuse serializer oracle cells and isolate native cache policy

* Preserve native cache post-save paths and record hosted oracle gain

* Record native cache reuse and separate cancel-test startup budget
2026-10-01 21:43:56 -07:00

144 lines
4.8 KiB
JavaScript

import { build } from 'esbuild'
import { appendFileSync, globSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import {
externalNativeAddons,
ORCAD_CHILD_ENTRY_POINTS,
ORCAD_ENTRY_POINT
} from './orcad-entry-build.mjs'
import { nodeServerTestPaths } from './node-server-test-paths.mjs'
import { nodeServerQualification } from './node-server-qualification.mjs'
const ROOT = resolve(import.meta.dirname, '../..')
const BUILD_SCRIPTS = [
'config/scripts/build-orcad-node.mjs',
'config/scripts/server-build-target.mjs',
'config/scripts/pinned-node-downloads.mjs',
'config/scripts/build-orcad.mjs',
'config/scripts/build-orcad-prebuilds.mjs',
'config/scripts/orcad-prebuild-smoke-child.cjs',
'config/scripts/build-windows-process-tree-relay-addon.mjs',
'config/scripts/run-node-server-tests.mjs',
'config/vitest.config.ts',
'config/scripts/happy-dom-offscreen-canvas.ts',
'config/scripts/happy-dom-mutation-observer-retention.ts',
'config/scripts/vitest-host-ports-setup.ts',
'config/scripts/vitest-real-agent-home-write-guard.ts'
]
const ALWAYS_FILES = new Set([
'package.json',
'pnpm-lock.yaml',
'pnpm-workspace.yaml',
'.npmrc',
'.pnpmfile.cjs',
'tsconfig.json',
'.github/workflows/node-server-tests.yml',
'config/scripts/node-server-change-scope.mjs',
'config/scripts/node-server-change-scope.test.mjs',
'config/scripts/node-server-qualification.mjs',
'config/scripts/node-server-qualification.test.mjs'
])
const ALWAYS_PREFIXES = [
'.github/actions/install-node-dependencies/',
'.github/actions/prepare-native-runtime/',
// These areas also contain worker paths and fixtures opened without an import.
'src/main/persistence/',
'src/main/sqlite/',
'src/main/orcad/',
'src/main/daemon/pty-subprocess/',
'src/main/providers/',
'config/patches/',
'config/tsconfig',
'native/',
'resources/licenses/ripgrep/'
]
export function discoverNodeServerTests(root = ROOT) {
const selectors = nodeServerTestPaths({ artifact: true, crossRuntime: true })
return globSync(
['src/**/*.test.{ts,tsx}', 'config/scripts/**/*.test.{ts,mjs}', 'tests/e2e/**/*.unit.test.ts'],
{ cwd: root }
)
.map((file) => file.replaceAll('\\', '/'))
.filter((file) => selectors.some((selector) => file.includes(selector)))
.sort()
}
export async function collectNodeServerInputs({ root = ROOT, entryPoints } = {}) {
const entries = entryPoints ?? [
ORCAD_ENTRY_POINT,
...Object.values(ORCAD_CHILD_ENTRY_POINTS),
...BUILD_SCRIPTS,
...discoverNodeServerTests(root)
]
const result = await build({
absWorkingDir: root,
entryPoints: entries,
bundle: true,
write: false,
outdir: resolve(root, '.node-server-scope'),
platform: 'node',
format: 'esm',
splitting: true,
packages: 'external',
loader: { '.svg': 'empty', '.png': 'empty', '.webp': 'empty', '.css': 'empty' },
plugins: [externalNativeAddons],
metafile: true,
logLevel: 'silent'
})
if (result.warnings.length > 0) {
throw new Error(result.warnings.map((warning) => warning.text).join('\n'))
}
return new Set(
Object.keys(result.metafile.inputs).map((file) =>
file.replaceAll('\\', '/').replace(/\?.*$/, '')
)
)
}
export async function classifyNodeServerChanges(changedFiles, collect = collectNodeServerInputs) {
if (changedFiles.length === 0) {
return { shouldRun: true, reason: 'No complete changed-file evidence' }
}
const selectors = nodeServerTestPaths({ artifact: true, crossRuntime: true })
const forced = changedFiles.find(
(file) =>
ALWAYS_FILES.has(file) ||
ALWAYS_PREFIXES.some((prefix) => file.startsWith(prefix)) ||
selectors.some((selector) => file.includes(selector))
)
if (forced) {
return { shouldRun: true, reason: `Build or CI input changed: ${forced}` }
}
try {
const inputs = await collect()
const matched = changedFiles.find((file) => inputs.has(file))
return {
shouldRun: Boolean(matched),
reason: matched
? `Runtime or test dependency changed: ${matched}`
: 'No headless-server inputs changed'
}
} catch (error) {
return {
shouldRun: true,
graphUnavailable: true,
reason: `Dependency graph unavailable: ${String(error)}`
}
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const changedFiles = readFileSync(process.argv[2], 'utf8').split('\0').filter(Boolean)
const result = await classifyNodeServerChanges(changedFiles)
console.log(result.reason)
const policy = nodeServerQualification(changedFiles, result)
const output = `should_run=${result.shouldRun}\nqualification=${policy.qualification}\nrunners=${JSON.stringify(policy.runners)}\n`
if (process.env.GITHUB_OUTPUT) {
appendFileSync(process.env.GITHUB_OUTPUT, output)
} else {
process.stdout.write(output)
}
}