mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
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.
246 lines
9.5 KiB
JavaScript
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)
|
|
})
|