mirror of
https://github.com/stablyai/orca.git
synced 2026-11-05 19:47:27 +00:00
Extract subprocess execution and process contracts into @orca/process-host with explicit public entry points, independent checks, and one Node 18 compatible CommonJS runtime. Migrate application consumers and integrate package builds, dependency selection, import enforcement, and runtime resource staging. Preserve daemon startup ownership and profile isolation, and qualify development rebuild recovery and concurrent output publication. Remove unnecessary aliases, test forwarding exports, and a duplicate output scan. Validation: full typechecks, changed-code quality, package and artifact checks, native Windows builds/types plus 306 runtime tests, and independent native reviews. Matched full E2E controls on original main and unchanged reviewed head both pass all 17 applicable tests (nine skips). Earlier intermittent E2E failures are documented in the PR; no assertions were weakened. The final combined-tree audit preserves both branches with no import-boundary violations.
269 lines
10 KiB
TypeScript
269 lines
10 KiB
TypeScript
import * as nodeFs from 'node:fs'
|
|
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
import * as nodePath from 'node:path'
|
|
import { join } from 'node:path'
|
|
import { tmpdir } from 'node:os'
|
|
import { EventEmitter } from 'node:events'
|
|
import { runInNewContext } from 'node:vm'
|
|
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
DEV_BUNDLE_ID,
|
|
DEV_HELPER_BUNDLE_ID,
|
|
getDevHelperPlistPatches
|
|
} from './dev-electron-bundle-identity.mjs'
|
|
import {
|
|
BOOTSTRAP_FATAL_LOG_ENV_VAR,
|
|
BOOTSTRAP_FATAL_LOG_FILE_NAME,
|
|
createBootstrapFatalExitBanner
|
|
} from '../build-plugins/bootstrap-fatal-exit-banner'
|
|
import { createRequire } from 'node:module'
|
|
import { electronViteConfig } from '../../electron.vite.config'
|
|
import { BOOTSTRAP_FATAL_EXIT_GUARD_KEY } from '../../src/main/startup/bootstrap-fatal-exit-guard'
|
|
|
|
type BootstrapProcessMock = EventEmitter & {
|
|
env: Record<string, string>
|
|
pid: number
|
|
exit: (code: number) => void
|
|
exitCode?: number
|
|
}
|
|
|
|
/** Runs the banner in a bare context and raises the bootstrap fault it guards against. */
|
|
function failBootstrapWithBanner(options: {
|
|
env: Record<string, string>
|
|
tmpdir?: string
|
|
stderrWrites?: string[]
|
|
}): BootstrapProcessMock {
|
|
const processMock = new EventEmitter() as BootstrapProcessMock
|
|
processMock.env = options.env
|
|
processMock.pid = 4242
|
|
processMock.exit = () => {}
|
|
const fsShim = {
|
|
...nodeFs,
|
|
writeSync: (descriptor: number, data: string) => {
|
|
if (descriptor === 2) {
|
|
options.stderrWrites?.push(data)
|
|
return data.length
|
|
}
|
|
return nodeFs.writeSync(descriptor, data)
|
|
}
|
|
}
|
|
const context = {
|
|
process: processMock,
|
|
setImmediate: () => {},
|
|
require: (specifier: string) => {
|
|
if (specifier === 'node:fs') {
|
|
return fsShim
|
|
}
|
|
if (specifier === 'node:path') {
|
|
return nodePath
|
|
}
|
|
if (specifier === 'node:os' && options.tmpdir !== undefined) {
|
|
return { tmpdir: () => options.tmpdir }
|
|
}
|
|
// Electron's own module is unreachable from a bootstrap fault this early.
|
|
throw new Error(`unexpected require: ${specifier}`)
|
|
}
|
|
}
|
|
|
|
runInNewContext(createBootstrapFatalExitBanner(), context)
|
|
processMock.emit('uncaughtException', new Error("Cannot find module 'ws'"))
|
|
return processMock
|
|
}
|
|
|
|
const electronBuilderConfig = createRequire(import.meta.url)('../electron-builder.config.cjs')
|
|
|
|
describe('Electron Vite output contract', () => {
|
|
it("minifies main and renderer with rolldown's in-process minifier", () => {
|
|
// Why: 'esbuild' routes every chunk through a second, undeclared transpiler.
|
|
expect(electronViteConfig.main?.build?.minify).toBe('oxc')
|
|
expect(electronViteConfig.renderer?.build?.minify).toBe('oxc')
|
|
expect(electronViteConfig.main?.esbuild).toBeUndefined()
|
|
expect(electronViteConfig.renderer?.esbuild).toBeUndefined()
|
|
})
|
|
|
|
it('emits hidden main source maps that packaging strips from app.asar', () => {
|
|
// Hidden maps decode minified crash traces without the bundle referencing
|
|
// files that the packaged app never ships.
|
|
expect(electronViteConfig.main?.build?.sourcemap).toBe('hidden')
|
|
expect(electronBuilderConfig.files).toContain('!out/**/*.map')
|
|
})
|
|
|
|
it('keeps main-process and plain-Node entries at stable CommonJS paths', () => {
|
|
const output = electronViteConfig.main?.build?.rollupOptions?.output
|
|
if (!output || Array.isArray(output)) {
|
|
throw new Error('Expected one main-process output')
|
|
}
|
|
|
|
expect(output.format).toBe('cjs')
|
|
expect(output.entryFileNames).toBe('[name].js')
|
|
expect(output.chunkFileNames).toBe('chunks/[name]-[hash].js')
|
|
})
|
|
|
|
it('keeps CLI main imports unpacked at stable paths', () => {
|
|
const input = electronViteConfig.main?.build?.rollupOptions?.input
|
|
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
throw new Error('Expected named main-process inputs')
|
|
}
|
|
|
|
for (const name of [
|
|
'gitlab/project-ref-parser',
|
|
'orca-profiles/profile-index-store',
|
|
'persistence/profile-state/profile-state-access',
|
|
'persistence/profile-state/profile-state-active-location',
|
|
'persistence/profile-state/profile-state-backup-path',
|
|
'persistence/profile-state/profile-state-database-recovery',
|
|
'persistence/profile-state/profile-state-domain-reader',
|
|
'persistence/profile-state/legacy-json/profile-state-export-path',
|
|
'persistence/profile-state/profile-state-offline-settings',
|
|
'persistence/profile-state/legacy-json/profile-state-recovery',
|
|
'persistence/profile-state/profile-state-recovery-command',
|
|
'persistence/profile-state/profile-state-storage-classification',
|
|
'startup/http1-compatibility-marker'
|
|
]) {
|
|
expect(input).toHaveProperty(name)
|
|
}
|
|
expect(electronBuilderConfig.asarUnpack).toContain('out/main/gitlab/project-ref-parser.js')
|
|
expect(electronBuilderConfig.asarUnpack).toContain('out/main/persistence/profile-state/**')
|
|
expect(electronBuilderConfig.asarUnpack).toContain(
|
|
'out/main/orca-profiles/profile-index-store.js'
|
|
)
|
|
expect(electronBuilderConfig.asarUnpack).toContain(
|
|
'out/main/startup/http1-compatibility-marker.js'
|
|
)
|
|
})
|
|
|
|
it('externalizes packaged dependencies but bundles self-contained main dependencies', () => {
|
|
const external = electronViteConfig.main?.build?.rollupOptions?.external
|
|
if (typeof external !== 'function') {
|
|
throw new Error('Expected main-process external predicate')
|
|
}
|
|
|
|
expect(external('node-pty', undefined, false)).toBe(true)
|
|
expect(external('@parcel/watcher', undefined, false)).toBe(true)
|
|
expect(external('electron', undefined, false)).toBe(true)
|
|
expect(external('node:fs', undefined, false)).toBe(true)
|
|
expect(external('@orca/process-host', undefined, false)).toBe(true)
|
|
expect(external('@orca/process-host/spawn-observer', undefined, false)).toBe(true)
|
|
expect(external('@xterm/headless', undefined, false)).toBe(false)
|
|
expect(external('@xterm/addon-serialize', undefined, false)).toBe(false)
|
|
expect(external('tldts', undefined, false)).toBe(false)
|
|
expect(external('zod', undefined, false)).toBe(false)
|
|
expect(external('smol-toml', undefined, false)).toBe(false)
|
|
expect(external('smol-toml/package.json', undefined, false)).toBe(false)
|
|
expect(electronViteConfig.main?.build?.externalizeDeps?.exclude).toContain('tldts')
|
|
expect(electronViteConfig.main?.build?.externalizeDeps?.exclude).toContain('zod')
|
|
expect(electronViteConfig.main?.build?.externalizeDeps?.exclude).toContain('smol-toml')
|
|
})
|
|
|
|
it('bundles validation dependencies used by the sandboxed preload', () => {
|
|
expect(electronViteConfig.preload?.build?.externalizeDeps?.exclude).toContain('zod')
|
|
})
|
|
|
|
it('exits when a static import fails before source error guards load', () => {
|
|
const processMock = new EventEmitter() as EventEmitter & {
|
|
exit: (code: number) => void
|
|
exitCode?: number
|
|
stderr: { write: (chunk: string) => boolean }
|
|
}
|
|
let scheduledExit: (() => void) | null = null
|
|
let exitedWith: number | null = null
|
|
const stderrWrites: string[] = []
|
|
processMock.exit = (code) => {
|
|
exitedWith = code
|
|
}
|
|
processMock.stderr = {
|
|
write: (chunk) => {
|
|
stderrWrites.push(chunk)
|
|
return true
|
|
}
|
|
}
|
|
const context = {
|
|
process: processMock,
|
|
setImmediate: (callback: () => void) => {
|
|
scheduledExit = callback
|
|
}
|
|
}
|
|
|
|
runInNewContext(createBootstrapFatalExitBanner(), context)
|
|
processMock.emit('uncaughtException', new Error("Cannot find module 'zod'"))
|
|
|
|
expect(processMock.exitCode).toBe(1)
|
|
expect(scheduledExit).not.toBeNull()
|
|
scheduledExit?.()
|
|
expect(exitedWith).toBe(1)
|
|
expect(context).toHaveProperty(BOOTSTRAP_FATAL_EXIT_GUARD_KEY)
|
|
expect(stderrWrites.join('')).toContain("Cannot find module 'zod'")
|
|
})
|
|
|
|
it('records the bootstrap failure it exits on, since the guard hides Electron dialog', () => {
|
|
const logDirectory = mkdtempSync(join(tmpdir(), 'orca-bootstrap-fatal-'))
|
|
const logPath = join(logDirectory, 'fatal.log')
|
|
const stderrWrites: string[] = []
|
|
|
|
try {
|
|
const processMock = failBootstrapWithBanner({
|
|
env: { [BOOTSTRAP_FATAL_LOG_ENV_VAR]: logPath },
|
|
stderrWrites
|
|
})
|
|
|
|
expect(stderrWrites.join('')).toContain("Cannot find module 'ws'")
|
|
const recorded = readFileSync(logPath, 'utf8')
|
|
expect(recorded).toContain("Cannot find module 'ws'")
|
|
expect(recorded).toContain('pid=4242')
|
|
expect(processMock.exitCode).toBe(1)
|
|
} finally {
|
|
rmSync(logDirectory, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('creates the parent directory an overridden log path names but does not have', () => {
|
|
const logDirectory = mkdtempSync(join(tmpdir(), 'orca-bootstrap-fatal-'))
|
|
const logPath = join(logDirectory, 'nested', 'diagnostics', 'fatal.log')
|
|
|
|
try {
|
|
const processMock = failBootstrapWithBanner({
|
|
env: { [BOOTSTRAP_FATAL_LOG_ENV_VAR]: logPath }
|
|
})
|
|
|
|
expect(readFileSync(logPath, 'utf8')).toContain("Cannot find module 'ws'")
|
|
expect(processMock.exitCode).toBe(1)
|
|
} finally {
|
|
rmSync(logDirectory, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('falls back to the default location when the overridden log path is unwritable', () => {
|
|
const logDirectory = mkdtempSync(join(tmpdir(), 'orca-bootstrap-fatal-'))
|
|
const fallbackDirectory = join(logDirectory, 'fallback')
|
|
|
|
try {
|
|
const processMock = failBootstrapWithBanner({
|
|
// A directory can never be opened as the log file, so the override must yield.
|
|
env: { [BOOTSTRAP_FATAL_LOG_ENV_VAR]: logDirectory },
|
|
tmpdir: fallbackDirectory
|
|
})
|
|
|
|
const recorded = readFileSync(join(fallbackDirectory, BOOTSTRAP_FATAL_LOG_FILE_NAME), 'utf8')
|
|
expect(recorded).toContain("Cannot find module 'ws'")
|
|
expect(processMock.exitCode).toBe(1)
|
|
} finally {
|
|
rmSync(logDirectory, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('isolates renderer entry side effects behind strict facades', () => {
|
|
expect(electronViteConfig.renderer?.build?.rollupOptions?.preserveEntrySignatures).toBe(
|
|
'strict'
|
|
)
|
|
})
|
|
|
|
it('gives the dev terminal daemon helper the TCC identity watched by Orca', () => {
|
|
// Asserted on the values rather than the source text: the ids moved into
|
|
// dev-electron-bundle-identity.mjs so every dev bundle signs to one cdhash.
|
|
expect(DEV_HELPER_BUNDLE_ID).toBe(`${DEV_BUNDLE_ID}.helper`)
|
|
expect(getDevHelperPlistPatches()).toEqual([
|
|
{ key: 'CFBundleIdentifier', value: DEV_HELPER_BUNDLE_ID }
|
|
])
|
|
})
|
|
})
|