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

147 lines
5.7 KiB
JavaScript

// Locates the daemon-host inputs inside a packaged win-unpacked build.
//
// Layout the packager produces (see config/electron-builder.config.cjs and
// config/packaged-runtime-node-modules.cjs):
// <app-dir>/Orca.exe electron binary (run as node)
// <app-dir>/icudtl.dat ICU data (needed even as node)
// <app-dir>/snapshot_blob.bin V8 snapshot
// <app-dir>/v8_context_snapshot.bin V8 context snapshot
// <app-dir>/*.dll electron/GPU runtime DLLs
// <app-dir>/resources/app.asar.unpacked/out/main/daemon-entry.js (+ chunks/)
// <app-dir>/resources/app.asar.unpacked/node_modules/node-pty/** native + conpty
import { existsSync, readdirSync, statSync } from 'node:fs'
import { basename, join } from 'node:path'
export const RUNTIME_DATA_FILES = ['icudtl.dat', 'snapshot_blob.bin', 'v8_context_snapshot.bin']
export const HOST_EXE = 'Orca.exe'
// Windows arch dir names node-pty prebuilds ship under; build/Release is the
// packaged rebuild location and takes precedence.
const NODE_PTY_NATIVE_CANDIDATES = ['build/Release', 'prebuilds/win32-x64', 'prebuilds/win32-arm64']
function fileEntry(dir, name) {
const path = join(dir, name)
if (!existsSync(path)) {
return { name, path, exists: false, size: 0 }
}
return { name, path, exists: true, size: statSync(path).size }
}
function listTopLevelDlls(appDir) {
const dlls = []
for (const name of readdirSync(appDir)) {
if (name.toLowerCase().endsWith('.dll')) {
dlls.push(fileEntry(appDir, name))
}
}
return dlls.sort((a, b) => a.name.localeCompare(b.name))
}
// Resolve the app.asar.unpacked root: everything the forked daemon-entry
// require-closure resolves (chunks, node-pty) lives under it, so the copied
// host must mirror it verbatim.
function resolveUnpackedRoot(appDir) {
const candidate = join(appDir, 'resources', 'app.asar.unpacked')
return existsSync(candidate) ? candidate : null
}
// getDaemonEntryPath() in daemon-init.ts probes daemon-entry.js at the unpacked
// root first, then out/main — mirror that resolution order here.
function resolveDaemonEntry(unpackedRoot) {
if (!unpackedRoot) {
return { name: 'daemon-entry.js', path: '', exists: false, size: 0, relFromUnpacked: '' }
}
const direct = join(unpackedRoot, 'daemon-entry.js')
if (existsSync(direct)) {
return { ...fileEntry(unpackedRoot, 'daemon-entry.js'), relFromUnpacked: 'daemon-entry.js' }
}
const nested = join('out', 'main', 'daemon-entry.js')
const nestedPath = join(unpackedRoot, nested)
return {
name: 'daemon-entry.js',
path: nestedPath,
exists: existsSync(nestedPath),
size: existsSync(nestedPath) ? statSync(nestedPath).size : 0,
relFromUnpacked: nested.split('\\').join('/')
}
}
function resolveNodePty(unpackedRoot, appDir) {
// Prefer the unpacked-root copy (what the daemon require-closure resolves);
// fall back to a resources-level copy some builds also stage.
const roots = []
if (unpackedRoot) {
roots.push(join(unpackedRoot, 'node_modules', 'node-pty'))
}
roots.push(join(appDir, 'resources', 'node_modules', 'node-pty'))
for (const dir of roots) {
if (!existsSync(dir)) {
continue
}
for (const rel of NODE_PTY_NATIVE_CANDIDATES) {
const nativeDir = join(dir, ...rel.split('/'))
if (existsSync(join(nativeDir, 'conpty.node'))) {
return {
exists: true,
packageDir: dir,
nativeDir,
nativeRel: rel,
conptyNode: fileEntry(nativeDir, 'conpty.node'),
// node-pty's Windows addon loads conpty.dll from <native>/conpty/.
conptyDll: fileEntry(join(nativeDir, 'conpty'), 'conpty.dll'),
openConsole: fileEntry(join(nativeDir, 'conpty'), 'OpenConsole.exe')
}
}
}
// node-pty present but no ConPTY native found under known dirs.
return { exists: true, packageDir: dir, nativeDir: '', nativeRel: '', conptyNode: null }
}
return { exists: false, packageDir: '', nativeDir: '', nativeRel: '', conptyNode: null }
}
/**
* Discover every daemon-host input in `appDir`. Never throws for missing files;
* each entry carries an `exists` flag so the caller can print a full report and
* decide whether the chosen tier is buildable.
*/
export function inventoryAppDir(appDir) {
const unpackedRoot = resolveUnpackedRoot(appDir)
return {
appDir,
unpackedRoot,
hostExe: fileEntry(appDir, HOST_EXE),
runtimeData: RUNTIME_DATA_FILES.map((name) => fileEntry(appDir, name)),
topLevelDlls: listTopLevelDlls(appDir),
daemonEntry: resolveDaemonEntry(unpackedRoot),
nodePty: resolveNodePty(unpackedRoot, appDir)
}
}
/** Human-readable inventory dump for the run report. */
export function formatInventory(inv) {
const lines = []
const kib = (n) => `${(n / 1024).toFixed(1)} KiB`
const mark = (e) => (e.exists ? 'OK ' : 'MISS')
lines.push(`app-dir: ${inv.appDir}`)
lines.push(` ${mark(inv.hostExe)} ${inv.hostExe.name} (${kib(inv.hostExe.size)})`)
for (const e of inv.runtimeData) {
lines.push(` ${mark(e)} ${e.name} (${kib(e.size)})`)
}
lines.push(` top-level DLLs: ${inv.topLevelDlls.length}`)
for (const e of inv.topLevelDlls) {
lines.push(` - ${basename(e.name)} (${kib(e.size)})`)
}
lines.push(
` ${mark(inv.daemonEntry)} daemon-entry: ${inv.daemonEntry.relFromUnpacked || '(none)'}`
)
const np = inv.nodePty
lines.push(` node-pty: ${np.exists ? np.packageDir : '(none)'}`)
if (np.conptyNode) {
lines.push(` native: ${np.nativeRel} conpty.node (${kib(np.conptyNode.size)})`)
lines.push(` conpty.dll: ${mark(np.conptyDll)} OpenConsole.exe: ${mark(np.openConsole)}`)
}
return lines.join('\n')
}