Files
orca/config/scripts/check-runtime-electron-ratchet.test.mjs
T
Brennan Benson 29e55047b5 build(cli): typecheck the CLI by following its imports, not a hand list (#30632)
tsconfig.cli.json listed 284 src/main files because composite made any
unlisted import fail the parallel typecheck with TS6307. Nothing references
it as a project, so drop composite and keep only the CLI's roots plus the
backup worker entry, which is loaded by path. The Electron import ratchet now
also walks the CLI entry, replacing the fence the list implied.
2026-10-20 17:16:00 -07:00

246 lines
9.5 KiB
JavaScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import process from 'node:process'
import {
collectElectronImporters,
collectStructuredChatEntryPoints,
defaultEntryPoints,
main
} from './check-runtime-electron-ratchet.mjs'
describe('structured chat coverage', () => {
const roots = []
afterEach(() => {
for (const root of roots.splice(0)) {
rmSync(root, { recursive: true, force: true })
}
})
function fixture(files) {
const root = mkdtempSync(path.join(tmpdir(), 'orca-electron-ratchet-'))
roots.push(root)
for (const [file, source] of Object.entries(files)) {
const absolute = path.join(root, file)
mkdirSync(path.dirname(absolute), { recursive: true })
writeFileSync(absolute, source)
}
return root
}
// Every lane that must exist.
const requiredLanes = {
'src/main/native-chat/reader.ts': 'export {}',
'src/main/claude/claude-session.ts': 'export {}',
'src/main/codex/codex-session.ts': 'export {}',
'src/main/runtime/structured-agent-session-host.ts': 'export {}',
'src/main/provider-process/provider-process-teardown.ts': 'export {}',
'src/main/acp/acp-structured-session-adapter.ts': 'export {}',
'src/main/jsonl-rpc/peer.ts': 'export {}',
'src/main/pi/rpc-launch.ts': 'export {}',
'src/shared/agent-session-record.ts': 'export {}',
'src/packages/process-host/src/run-process.ts': 'export {}'
}
it('covers whole lane directories, structured runtime files at any depth, and no test code', () => {
const sources = [
...Object.keys(requiredLanes),
'src/main/native-chat/nested/reader.ts',
'src/main/native-chat/worker.mjs',
'src/main/claude/other.ts',
'src/main/codex/codex-provider-timeline-identity.ts',
'src/shared/nested/agent-session-account-home.ts',
'src/shared/relay-runtime-self-test-report.ts',
'src/packages/process-host/src/nested/process-state.ts',
'src/main/runtime/agent-session-record.ts',
'src/main/runtime/structured-agent-runtime-registrations.ts',
'src/main/runtime/rpc/methods/structured-agent-session-agents.ts',
'src/main/acp/adapter.ts',
'src/main/provider-process/worker.ts',
'src/main/jsonl-rpc/nested/dialect.ts'
]
const excluded = [
'src/main/native-chat/reader.test.ts',
'src/main/native-chat/reader.spec.ts',
'src/main/native-chat/reader-test-support.ts',
'src/main/native-chat/reader.test-support.ts',
'src/main/native-chat/structured-agent-session-rest-test-rig.ts',
'src/main/native-chat/structured-agent-session-host-test-data.ts',
'src/main/native-chat/reader.test-fixture.ts',
'src/main/native-chat/reader-fixtures.ts',
'src/main/codex/codex-turn-lifecycle-fake.ts',
'src/main/codex/codex-session-backfill-fs-mocks.ts',
'src/main/native-chat/__fixtures__/reader.ts',
'src/main/native-chat/test-support/reader.ts',
'src/main/runtime/orca-runtime-tests/structured-agent-session-host.ts',
'src/shared/types.d.ts',
'src/packages/process-host/src/run-process.test.ts',
'src/packages/process-host/src/__fixtures__/spawned-child.ts',
'src/main/runtime/other.ts',
'src/main/runtime/rpc/methods/browser.ts',
'src/main/jsonl-rpc/peer.test.ts',
'src/main/pi/rpc-launch.test.ts',
'src/main/pi/titlebar-extension-service.ts'
]
const root = fixture(
Object.fromEntries([...sources, ...excluded].map((file) => [file, 'export {}']))
)
expect(collectStructuredChatEntryPoints(root)).toEqual(
sources.map((file) => path.join(root, ...file.split('/'))).sort()
)
})
it.each(Object.keys(requiredLanes).map((file) => path.posix.dirname(file)))(
'fails loudly when %s goes missing, so a rename cannot empty it',
(lane) => {
const without = Object.fromEntries(
Object.entries(requiredLanes).filter(([file]) => !file.startsWith(`${lane}/`))
)
expect(() => collectStructuredChatEntryPoints(fixture(without))).toThrow(`${lane} is missing`)
expect(collectStructuredChatEntryPoints(fixture(requiredLanes))).toHaveLength(
Object.keys(requiredLanes).length
)
}
)
it.each([true, false])(
'finds Electron through an unwired lane package (sideEffects=%s)',
async (sideEffects) => {
const root = fixture({
...requiredLanes,
'src/main/acp/adapter.ts': "import 'acp-desktop-package'",
'src/main/provider-process/worker.ts': "import 'provider-desktop-package'",
'src/main/jsonl-rpc/peer.ts': "import 'rpc-desktop-package'",
'src/main/pi/rpc-launch.ts': "import 'pi-desktop-package'",
'node_modules/acp-desktop-package/package.json': JSON.stringify({
main: 'index.js',
sideEffects
}),
'node_modules/acp-desktop-package/index.js': "require('electron')",
'node_modules/provider-desktop-package/package.json': JSON.stringify({
main: 'index.js',
sideEffects
}),
'node_modules/provider-desktop-package/index.js': "require('electron')",
'node_modules/rpc-desktop-package/package.json': JSON.stringify({
main: 'index.js',
sideEffects
}),
'node_modules/rpc-desktop-package/index.js': "require('electron')",
'node_modules/pi-desktop-package/package.json': JSON.stringify({
main: 'index.js',
sideEffects
}),
'node_modules/pi-desktop-package/index.js': "require('electron')"
})
const current = await collectElectronImporters(collectStructuredChatEntryPoints(root))
expect(current.map((file) => file.split('/node_modules/').pop())).toEqual([
'acp-desktop-package/index.js',
'pi-desktop-package/index.js',
'provider-desktop-package/index.js',
'rpc-desktop-package/index.js'
])
}
)
it('checks public workspace imports against source without requiring a built package', async () => {
const root = fixture({ 'entry.ts': "import '@orca/process-host'" })
const target = path.join(process.cwd(), 'src/packages/process-host/src/run-process.ts')
const current = await collectElectronImporters([path.join(root, 'entry.ts')], {
plugins: [
{
name: 'inject-process-host-electron-import',
setup(pluginBuild) {
pluginBuild.onLoad({ filter: /run-process\.ts$/ }, (args) =>
args.path === target ? { contents: "import 'electron'", loader: 'ts' } : undefined
)
}
}
]
})
expect(current).toEqual(['src/packages/process-host/src/run-process.ts'])
})
it('bounds shared dependency output without dropping any entry point', async () => {
const entries = Array.from({ length: 40 }, (_, index) => `entry-${index}.ts`)
const root = fixture({
'shared.ts': `export const payload = ${JSON.stringify('x'.repeat(64 * 1024))}`,
...Object.fromEntries(
entries.map((file) => [file, "import 'electron'; export { payload } from './shared'"])
)
})
let emittedBytes = 0
const current = await collectElectronImporters(
entries.map((file) => path.join(root, file)),
{
plugins: [
{
name: 'measure-audit-output',
setup(builder) {
builder.onEnd((result) => {
emittedBytes = result.outputFiles.reduce(
(bytes, file) => bytes + file.contents.byteLength,
0
)
})
}
}
]
}
)
expect(current.map((file) => path.basename(file)).sort()).toEqual(entries.sort())
expect(emittedBytes).toBeLessThan(128 * 1024)
})
})
describe('the default entry points', () => {
const lanes = ['native-chat', 'claude', 'codex', 'runtime'].map((lane) => `src/main/${lane}/`)
it('are the runtime and CLI entries plus a file from every lane that exists', () => {
const entries = defaultEntryPoints().map((file) =>
path.relative(process.cwd(), file).split(path.sep).join('/')
)
expect(entries.slice(0, 4)).toEqual([
'src/main/runtime/orca-runtime.ts',
'src/main/runtime/runtime-rpc.ts',
'src/main/orcad/main.ts',
'src/cli/index.ts'
])
for (const lane of [...lanes, 'src/shared/', 'src/packages/process-host/src/']) {
expect(entries.some((file) => file.startsWith(lane))).toBe(true)
}
})
})
// Why `main`: it is what `pnpm lint` and CI run, so these fail if its entry list drops the lanes.
describe('the command-line check', () => {
afterEach(() => {
vi.restoreAllMocks()
})
// Why a file the runtime doesn't load: only the structured-chat lanes can catch it.
it('fails on Electron in structured-chat code the runtime graph does not reach', async () => {
const target = path.join(
process.cwd(),
'src',
'main',
'native-chat',
'transcript-read-cache.ts'
)
const addElectron = {
name: 'add-electron-import',
setup(pluginBuild) {
pluginBuild.onLoad({ filter: /transcript-read-cache\.ts$/ }, (args) =>
args.path === target
? { contents: `import 'electron'\n${readFileSync(target, 'utf8')}`, loader: 'ts' }
: undefined
)
}
}
const error = vi.spyOn(console, 'error').mockImplementation(() => {})
expect(await main({ plugins: [addElectron] })).toBe(1)
expect(error.mock.calls.join('\n')).toContain('+ src/main/native-chat/transcript-read-cache.ts')
}, 120_000)
})