mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +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.
147 lines
5.2 KiB
JavaScript
147 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
import { createRequire } from 'node:module'
|
|
import {
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
readdirSync,
|
|
realpathSync,
|
|
renameSync,
|
|
rmSync,
|
|
statSync
|
|
} from 'node:fs'
|
|
import { join, resolve } from 'node:path'
|
|
import { copyPrivateTree } from './space-sharing-copy.mjs'
|
|
import { spawnProcess } from '@orca/process-host'
|
|
import { preparePrAppImageTools } from './package-linux-formats-appimage.mjs'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const formats = ['AppImage', 'deb', 'rpm']
|
|
|
|
export function linuxFormatArguments({ format, appDirectory, outputDirectory }) {
|
|
if (!formats.includes(format)) {
|
|
throw new Error(`Unsupported Linux package format: ${format}`)
|
|
}
|
|
return [
|
|
'--config',
|
|
'config/electron-builder-pr-linux.config.cjs',
|
|
'--linux',
|
|
format,
|
|
'--x64',
|
|
'--publish',
|
|
'never',
|
|
'--prepackaged',
|
|
appDirectory,
|
|
'--config.directories.output',
|
|
outputDirectory,
|
|
'--config.deb.compression=gz',
|
|
'--config.rpm.compression=gzip'
|
|
]
|
|
}
|
|
|
|
function runElectronBuilder(args, environment) {
|
|
return new Promise((resolveBuild, reject) => {
|
|
const child = spawnProcess({
|
|
program: process.execPath,
|
|
args: [require.resolve('electron-builder/cli.js'), ...args],
|
|
env: { ...process.env, ORCA_BACKGROUND_LAUNCH: '1', ...environment },
|
|
stdio: 'inherit'
|
|
})
|
|
child.once('error', reject)
|
|
child.once('close', (code, signal) => {
|
|
if (code === 0 && signal === null) {
|
|
resolveBuild()
|
|
} else {
|
|
reject(new Error(`electron-builder failed (code=${code}, signal=${signal})`))
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
export async function packageLinuxFormats({
|
|
preparedDirectory = resolve('dist/linux-unpacked'),
|
|
outputDirectory = resolve('dist'),
|
|
prepareAppImageTools = preparePrAppImageTools,
|
|
runBuilder = runElectronBuilder
|
|
} = {}) {
|
|
const marker = join(preparedDirectory, 'resources/package-type')
|
|
if (readFileSync(marker, 'utf8') !== 'AppImage') {
|
|
throw new Error('Expected a fresh Linux directory build with its AppImage package marker')
|
|
}
|
|
const startedAt = performance.now()
|
|
mkdirSync(outputDirectory, { recursive: true })
|
|
const staging = mkdtempSync(join(outputDirectory, '.linux-package-formats-'))
|
|
try {
|
|
const results = await Promise.allSettled(
|
|
formats.map(async (format) => {
|
|
const startedFormatAt = performance.now()
|
|
const appDirectory = join(staging, format, 'app')
|
|
const formatOutput = join(staging, format, 'artifacts')
|
|
mkdirSync(join(staging, format))
|
|
// Preserve packaged modes; only the format metadata may be rewritten by electron-builder.
|
|
copyPrivateTree(preparedDirectory, appDirectory, { unprotect: () => {} })
|
|
console.log(
|
|
`[linux-package] ${format} copied in ${Math.round(performance.now() - startedFormatAt)}ms`
|
|
)
|
|
const environment =
|
|
format === 'AppImage'
|
|
? await prepareAppImageTools({ directory: join(staging, format, 'tools') })
|
|
: {}
|
|
await runBuilder(
|
|
linuxFormatArguments({ format, appDirectory, outputDirectory: formatOutput }),
|
|
environment
|
|
)
|
|
const artifacts = readdirSync(formatOutput).filter((name) => name.endsWith(`.${format}`))
|
|
const artifactStats =
|
|
artifacts.length === 1 ? statSync(join(formatOutput, artifacts[0])) : null
|
|
if (!artifactStats?.isFile() || artifactStats.size === 0) {
|
|
throw new Error(`${format} did not produce exactly one nonempty package`)
|
|
}
|
|
console.log(
|
|
`[linux-package] ${format} finished in ${Math.round(performance.now() - startedFormatAt)}ms`
|
|
)
|
|
return { format, formatOutput, artifact: artifacts[0] }
|
|
})
|
|
)
|
|
const failures = results.flatMap((result, index) =>
|
|
result.status === 'rejected'
|
|
? [new Error(`${formats[index]} packaging failed`, { cause: result.reason })]
|
|
: []
|
|
)
|
|
if (failures.length > 0) {
|
|
throw new AggregateError(failures, 'Linux package formats failed')
|
|
}
|
|
const completed = results.flatMap((result) =>
|
|
result.status === 'fulfilled' ? [result.value] : []
|
|
)
|
|
const metadataDirectory = join(outputDirectory, 'linux-package-formats')
|
|
for (const destination of [
|
|
metadataDirectory,
|
|
...completed.map(({ artifact }) => join(outputDirectory, artifact))
|
|
]) {
|
|
if (existsSync(destination)) {
|
|
throw new Error(`Refusing to replace existing package output: ${destination}`)
|
|
}
|
|
}
|
|
mkdirSync(metadataDirectory)
|
|
for (const { format, formatOutput, artifact } of completed) {
|
|
renameSync(join(formatOutput, artifact), join(outputDirectory, artifact))
|
|
// Update manifests and builder diagnostics have overlapping names across formats.
|
|
renameSync(formatOutput, join(metadataDirectory, format))
|
|
}
|
|
console.log(
|
|
`[linux-package] all formats finished in ${Math.round(performance.now() - startedAt)}ms`
|
|
)
|
|
} finally {
|
|
rmSync(staging, { recursive: true, force: true })
|
|
}
|
|
}
|
|
|
|
if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(import.meta.filename)) {
|
|
if (process.platform !== 'linux' || process.arch !== 'x64') {
|
|
throw new Error('PR Linux packaging requires a Linux x64 host')
|
|
}
|
|
await packageLinuxFormats()
|
|
}
|