mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 09:37:33 +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.
130 lines
4.7 KiB
TypeScript
130 lines
4.7 KiB
TypeScript
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { tmpdir } from 'node:os'
|
|
import { dirname, join, resolve } from 'node:path'
|
|
import { runProcessSync } from '@orca/process-host'
|
|
import ts from 'typescript-api'
|
|
import type { Plugin } from 'rolldown'
|
|
import { createWorkspaceSourceResolver } from '../scripts/workspace-source-exports.mjs'
|
|
|
|
function configError(diagnostic: ts.Diagnostic): Error {
|
|
return new Error(ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'))
|
|
}
|
|
|
|
function processHostInputs(root: string): {
|
|
directory: string
|
|
files: string[]
|
|
directories: ts.MapLike<ts.WatchDirectoryFlags>
|
|
} {
|
|
const source = createWorkspaceSourceResolver(root).resolve('@orca/process-host')
|
|
if (!source) {
|
|
throw new Error('The process-host workspace source export is missing')
|
|
}
|
|
const manifest = resolve(root, source.manifest)
|
|
const directory = dirname(manifest)
|
|
const config = join(directory, 'tsconfig.json')
|
|
const parsed = ts.getParsedCommandLineOfConfigFile(config, undefined, {
|
|
...ts.sys,
|
|
onUnRecoverableConfigFileDiagnostic(diagnostic) {
|
|
throw configError(diagnostic)
|
|
}
|
|
})
|
|
if (!parsed || parsed.errors.length > 0) {
|
|
throw parsed?.errors[0] ? configError(parsed.errors[0]) : new Error(`Cannot read ${config}`)
|
|
}
|
|
const configSource = parsed.options.configFile
|
|
const inheritedConfigs =
|
|
configSource && typeof configSource === 'object' && 'extendedSourceFiles' in configSource
|
|
? configSource.extendedSourceFiles
|
|
: []
|
|
return {
|
|
directory,
|
|
files: [manifest, config, ...parsed.fileNames, ...(inheritedConfigs ?? [])],
|
|
directories: parsed.wildcardDirectories ?? {}
|
|
}
|
|
}
|
|
|
|
export function createProcessHostDevRebuildPlugin(root = process.cwd()): Plugin {
|
|
const directoryWatchers = new Map<string, { recursive: boolean; watcher: ts.FileWatcher }>()
|
|
let signalDirectory: string | undefined
|
|
let signalFile: string | undefined
|
|
let signalVersion = 0
|
|
return {
|
|
name: 'orca-process-host-dev-rebuild',
|
|
buildStart() {
|
|
if (!this.meta.watchMode) {
|
|
return
|
|
}
|
|
const inputs = processHostInputs(root)
|
|
for (const file of inputs.files) {
|
|
this.addWatchFile(file)
|
|
}
|
|
// The installed bundler invalidates exact files; source creation needs a private watch signal.
|
|
if (!signalDirectory) {
|
|
signalDirectory = mkdtempSync(join(tmpdir(), 'orca-process-host-watch-'))
|
|
signalFile = join(signalDirectory, 'source-directory-change')
|
|
writeFileSync(signalFile, String(signalVersion))
|
|
}
|
|
if (signalFile) {
|
|
this.addWatchFile(signalFile)
|
|
}
|
|
for (const [directory, watched] of directoryWatchers) {
|
|
if (inputs.directories[directory] === undefined) {
|
|
watched.watcher.close()
|
|
directoryWatchers.delete(directory)
|
|
}
|
|
}
|
|
for (const [directory, flags] of Object.entries(inputs.directories)) {
|
|
const recursive = flags === ts.WatchDirectoryFlags.Recursive
|
|
const previous = directoryWatchers.get(directory)
|
|
if (previous?.recursive === recursive) {
|
|
continue
|
|
}
|
|
previous?.watcher.close()
|
|
if (!ts.sys.watchDirectory) {
|
|
throw new Error('The process-host source directory watcher is unavailable')
|
|
}
|
|
const watcher = ts.sys.watchDirectory(
|
|
directory,
|
|
() => {
|
|
if (signalFile) {
|
|
writeFileSync(signalFile, String(++signalVersion))
|
|
}
|
|
},
|
|
recursive,
|
|
{ watchDirectory: ts.WatchDirectoryKind.UseFsEvents }
|
|
)
|
|
directoryWatchers.set(directory, { recursive, watcher })
|
|
}
|
|
// The external package must finish emitting before electron-vite restarts the app. The
|
|
// package's own build keeps its compiler and per-file atomic dist sync identical to build:packages.
|
|
const result = runProcessSync({
|
|
program: process.execPath,
|
|
args: [
|
|
createRequire(join(inputs.directory, 'package.json')).resolve('tsx/cli'),
|
|
'--conditions=orca-source',
|
|
join(inputs.directory, 'scripts', 'build-dist.mjs')
|
|
],
|
|
cwd: inputs.directory,
|
|
timeoutMs: 60_000
|
|
})
|
|
if (result.code !== 0) {
|
|
throw new Error(
|
|
`[process-host-dev-rebuild] Package compilation failed: ${result.stderr || result.stdout || result.signal || result.code}`
|
|
)
|
|
}
|
|
},
|
|
closeWatcher() {
|
|
signalFile = undefined
|
|
for (const { watcher } of directoryWatchers.values()) {
|
|
watcher.close()
|
|
}
|
|
directoryWatchers.clear()
|
|
if (signalDirectory) {
|
|
rmSync(signalDirectory, { recursive: true, force: true })
|
|
signalDirectory = undefined
|
|
}
|
|
}
|
|
}
|
|
}
|