Files
2026-07-13 13:05:33 +08:00

136 lines
4.6 KiB
JavaScript

// Tier definitions as DATA, so trimming the copied file set is a config change
// rather than a code change. A tier resolves (given a discovered inventory) to a
// flat list of copy operations { sourcePath, destRel, kind }.
import { dirname, join, relative, sep } from 'node:path'
// GPU/render DLLs that a run-as-node Electron host plausibly never loads. The
// spike measures whether dropping them still yields a working ConPTY host.
// ffmpeg.dll is deliberately NOT here — it is kept in the no-gpu tier.
export const GPU_DLLS = new Set([
'libegl.dll',
'libglesv2.dll',
'vk_swiftshader.dll',
'vulkan-1.dll',
'd3dcompiler_47.dll'
])
// Each tier declares which top-level DLLs to include. The exe, runtime data
// blobs, daemon bundle, and node-pty are in every tier — they are the
// irreducible core (Orca.exe needs icu + snapshots even as node; the daemon
// needs its bundle; node-pty needs its native + conpty runtime).
export const TIER_DEFINITIONS = {
full: { label: 'TIER_FULL_RUNTIME', dlls: 'all' },
'no-gpu': { label: 'TIER_NO_GPU_DLLS', dlls: 'non-gpu' },
minimal: { label: 'TIER_MINIMAL', dlls: 'none' }
}
function isGpuDll(name) {
return GPU_DLLS.has(name.toLowerCase())
}
/**
* Which top-level DLL entries a tier keeps. Pure over the inventory's DLL list
* so selftest can exercise it without a real build.
*/
export function selectTierDlls(topLevelDlls, tier) {
const def = TIER_DEFINITIONS[tier]
if (!def || def.dlls === 'none') {
return []
}
if (def.dlls === 'all') {
return topLevelDlls
}
return topLevelDlls.filter((e) => !isGpuDll(e.name))
}
/**
* A source file/dir's path relative to the win-unpacked root, normalized to '/'.
*
* Why relative to the APP DIR (not the app.asar.unpacked root): node-pty is
* packaged at resources/node_modules/node-pty (see
* config/packaged-runtime-node-modules.cjs), a SIBLING of app.asar.unpacked.
* The daemon resolves `require('node-pty')` by walking parent dirs up from
* resources/app.asar.unpacked/out/main/daemon-entry.js, which passes through
* resources/ and finds resources/node_modules/node-pty. Mirroring the full
* win-unpacked layout verbatim is the only copy that preserves that walk.
*/
export function toPosixRelative(appDir, absPath) {
return relative(appDir, absPath).split(sep).join('/')
}
/**
* Build the ordered copy plan for a tier. Returns { ops, warnings } where each
* op is { sourcePath, destRel, kind: 'file' | 'dir' }. Every destRel mirrors the
* source's win-unpacked-relative path. Missing required inputs are surfaced as
* warnings rather than thrown, so the report stays complete.
*/
export function resolveTierFileSet(inv, tier) {
const ops = []
const warnings = []
const appDir = inv.appDir
const addFile = (entry, requiredLabel, optional = false) => {
if (!entry || !entry.exists) {
if (requiredLabel) {
warnings.push(`missing required input: ${requiredLabel}`)
}
return
}
ops.push({
sourcePath: entry.path,
destRel: toPosixRelative(appDir, entry.path),
kind: 'file',
optional
})
}
// Core: exe + runtime data blobs live next to Orca.exe in win-unpacked.
addFile(inv.hostExe, 'Orca.exe')
for (const entry of inv.runtimeData) {
addFile(entry, entry.name)
}
// Top-level DLLs per tier.
for (const dll of selectTierDlls(inv.topLevelDlls, tier)) {
addFile(dll)
}
// Daemon bundle: the entry, its sibling chunks/, and the unpacked
// out/package.json (CJS/ESM loader resolution). Mirror the layout verbatim.
if (inv.daemonEntry.exists && inv.unpackedRoot) {
addFile(inv.daemonEntry, 'daemon-entry.js')
const entryDir = dirname(inv.daemonEntry.path)
const chunksDir = join(entryDir, 'chunks')
ops.push({
sourcePath: chunksDir,
destRel: toPosixRelative(appDir, chunksDir),
kind: 'dir',
optional: true
})
const pkgJson = join(inv.unpackedRoot, 'out', 'package.json')
ops.push({
sourcePath: pkgJson,
destRel: toPosixRelative(appDir, pkgJson),
kind: 'file',
optional: true
})
} else {
warnings.push('missing required input: daemon-entry.js (+ chunks)')
}
// node-pty package tree (native binding + conpty runtime), mirrored at its
// real win-unpacked path (resources/node_modules/node-pty).
if (inv.nodePty.exists && inv.nodePty.conptyNode) {
ops.push({
sourcePath: inv.nodePty.packageDir,
destRel: toPosixRelative(appDir, inv.nodePty.packageDir),
kind: 'dir'
})
} else {
warnings.push('missing required input: node-pty (with conpty.node)')
}
return { ops, warnings, label: TIER_DEFINITIONS[tier].label }
}