/* eslint-disable max-lines -- Why: launch-home materialization needs path safety, link/copy fallback, reconciliation, and cleanup in one place so auth-only account isolation cannot drift across platforms. */ import { cpSync, existsSync, lstatSync, linkSync, mkdirSync, readFileSync, readlinkSync, readdirSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs' import { execFileSync } from 'node:child_process' import { createHash } from 'node:crypto' import { dirname, isAbsolute, join, relative, resolve } from 'node:path' import { parseWslUncPath } from '../../shared/wsl-paths' import { getOrcaManagedCodexHomePath } from './codex-home-paths' const LAUNCH_HOME_MARKER = '.orca-managed-launch-home' const LAUNCH_HOME_LINK_MARKERS_DIR = '.orca-launch-home-links' const LAUNCH_HOME_MARKER_VERSION = 1 const SHARED_LAUNCH_ENTRY_NAMES = new Set([ 'config.toml', 'hooks.json', 'history.jsonl', 'sessions', 'skills', 'plugins', 'plugin-state', 'profile-v2', 'themes', 'prompts' ]) const MUTABLE_SHARED_FILE_ENTRIES = new Set([ 'config.toml', 'hooks.json', 'history.jsonl', 'profile-v2' ]) const MUTABLE_SHARED_DIRECTORY_ENTRIES = new Set(['sessions', 'plugin-state', 'profile-v2']) type LaunchEntryMarker = { version: number sourcePath: string mode: 'link' | 'copy' targetDigest: string | null sourceDigest: string | null } export function getOrcaCodexLaunchHomePath(accountId: string | null): string { return getScopedCodexLaunchHomePath(getOrcaCodexLaunchHostRootPath(), accountId) } export function ensureOrcaCodexLaunchHome(accountId: string | null): string { return ensureScopedCodexLaunchHome(getOrcaCodexLaunchHostRootPath(), accountId) } export function materializeOrcaCodexLaunchHome(accountId: string | null): string { return materializeScopedCodexLaunchHome( getOrcaManagedCodexHomePath(), getOrcaCodexLaunchHostRootPath(), accountId ) } export function removeOrcaCodexLaunchHome(accountId: string): void { removeScopedCodexLaunchHome( getOrcaCodexLaunchHostRootPathWithOptions({ create: false }), accountId ) } export function materializeOrcaCodexActiveHome(launchHomePath: string): string { return pointActiveCodexHomeAtLaunchHome(getOrcaCodexActiveHostHomePath(), launchHomePath) } export function pointActiveCodexHomeAtLaunchHome( activeHomePath: string, launchHomePath: string ): string { if (pointWslActiveCodexHomeAtLaunchHome(activeHomePath, launchHomePath)) { return activeHomePath } mkdirSync(dirname(activeHomePath), { recursive: true }) if (activeHomeAlreadyPointsToLaunchHome(activeHomePath, launchHomePath)) { return activeHomePath } const nextLinkPath = `${activeHomePath}.next-${process.pid}-${Date.now()}` removeActiveHomeLinkIfOwned(nextLinkPath) try { createSharedEntryLink(launchHomePath, nextLinkPath) replaceActiveHomeLink(activeHomePath, nextLinkPath) return activeHomePath } catch (error) { removeActiveHomeLinkIfOwned(nextLinkPath) console.warn('[codex-home] Failed to point active Codex home at launch home:', error) return launchHomePath } } function pointWslActiveCodexHomeAtLaunchHome( activeHomePath: string, launchHomePath: string ): boolean { const paths = getSameDistroWslPaths(launchHomePath, activeHomePath) if (!paths) { return false } const nextLinuxPath = `${paths.targetLinuxPath}.next-${process.pid}-${Date.now()}` try { execFileSync( 'wsl.exe', [ '-d', paths.distro, '--', 'bash', '-lc', 'mkdir -p "$(dirname "$2")" && rm -f -- "$3" && ln -s -- "$1" "$3" && mv -Tf -- "$3" "$2"', 'sh', paths.sourceLinuxPath, paths.targetLinuxPath, nextLinuxPath ], { stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000 } ) return true } catch { return false } } export function getScopedCodexLaunchHomePath( launchRootPath: string, accountId: string | null ): string { const launchHomePath = resolveCodexLaunchHomePath(launchRootPath, accountId) mkdirSync(launchHomePath, { recursive: true }) return launchHomePath } export function ensureScopedCodexLaunchHome( launchRootPath: string, accountId: string | null ): string { const launchHomePath = getScopedCodexLaunchHomePath(launchRootPath, accountId) writeLaunchHomeMarker(launchHomePath, accountId) return launchHomePath } export function materializeScopedCodexLaunchHome( sharedHomePath: string, launchRootPath: string, accountId: string | null ): string { reconcileMutableLaunchHomeFilesIntoSharedHome(sharedHomePath, launchRootPath) const launchHomePath = getScopedCodexLaunchHomePath(launchRootPath, accountId) writeLaunchHomeMarker(launchHomePath, accountId) const sharedEntries = new Set() for (const entryName of listSharedLaunchEntryNames(sharedHomePath)) { sharedEntries.add(entryName) linkSharedEntryIntoLaunchHome(sharedHomePath, launchHomePath, entryName) } removeStaleLaunchHomeEntries(launchHomePath, sharedHomePath, sharedEntries) return launchHomePath } export function removeScopedCodexLaunchHome(launchRootPath: string, accountId: string): void { const launchHomePath = resolveCodexLaunchHomePath(launchRootPath, accountId) if (!existsSync(launchHomePath)) { return } const launchHomeStat = lstatSync(launchHomePath) if (!launchHomeStat.isDirectory() || launchHomeStat.isSymbolicLink()) { console.warn('[codex-home] Refusing to remove unexpected launch-home root:', launchHomePath) return } if (!isMarkedLaunchHomeForAccount(launchHomePath, accountId)) { // Why: older builds could write auth before the launch-home marker existed. // Remove only the deterministic credential file, not an unmarked directory. rmSync(join(launchHomePath, 'auth.json'), { force: true }) return } if (!isContainedPath(launchRootPath, launchHomePath)) { console.warn('[codex-home] Refusing to remove launch home outside host root:', launchHomePath) return } if (removeWslPathIfPossible(launchHomePath)) { return } rmSync(launchHomePath, { recursive: true, force: true }) } function getOrcaCodexLaunchHostRootPath(): string { return getOrcaCodexLaunchHostRootPathWithOptions({ create: true }) } function getOrcaCodexLaunchHostRootPathWithOptions(options: { create: boolean }): string { const rootPath = join(dirname(getOrcaManagedCodexHomePath()), 'launch', 'host') if (options.create) { mkdirSync(rootPath, { recursive: true }) } return rootPath } function getOrcaCodexActiveHostHomePath(): string { return join(dirname(getOrcaManagedCodexHomePath()), 'active', 'host', 'home') } function resolveCodexLaunchHomePath(launchRootPath: string, accountId: string | null): string { return join(launchRootPath, getLaunchSelectionSegment(accountId), 'home') } function getLaunchSelectionSegment(accountId: string | null): string { if (accountId === null) { return 'system' } return `account-${createHash('sha256').update(accountId).digest('hex').slice(0, 32)}` } function listSharedLaunchEntryNames(sharedHomePath: string): string[] { try { const sharedEntries = new Set() for (const entryName of readdirSync(sharedHomePath)) { if (!isSharedLaunchEntryName(entryName)) { continue } sharedEntries.add(entryName) if (entryName.endsWith('.sqlite')) { sharedEntries.add(`${entryName}-wal`) sharedEntries.add(`${entryName}-shm`) } } return [...sharedEntries].sort() } catch { return [] } } function isSharedLaunchEntryName(entryName: string): boolean { return SHARED_LAUNCH_ENTRY_NAMES.has(entryName) || isCodexSqliteEntryName(entryName) } function isCodexSqliteEntryName(entryName: string): boolean { return ( entryName.endsWith('.sqlite') || entryName.endsWith('.sqlite-wal') || entryName.endsWith('.sqlite-shm') ) } function isCodexSqliteSidecarEntryName(entryName: string): boolean { return entryName.endsWith('.sqlite-wal') || entryName.endsWith('.sqlite-shm') } function getCodexSqliteMainEntryName(entryName: string): string | null { if (entryName.endsWith('.sqlite-wal')) { return entryName.slice(0, -'-wal'.length) } if (entryName.endsWith('.sqlite-shm')) { return entryName.slice(0, -'-shm'.length) } return null } function linkSharedEntryIntoLaunchHome( sharedHomePath: string, launchHomePath: string, entryName: string ): void { const sourcePath = join(sharedHomePath, entryName) const targetPath = join(launchHomePath, entryName) const existingMarker = readLaunchEntryMarker(launchHomePath, entryName) reconcileMutableLaunchEntryIfNeeded(sourcePath, targetPath, existingMarker) if (!existsSync(sourcePath) && !canLinkMissingSharedEntry(sharedHomePath, entryName)) { removeLaunchEntryIfOwned(targetPath, launchHomePath, entryName, sourcePath) return } materializeMissingSharedEntryIfNeeded(sourcePath, entryName) if (targetAlreadyPointsToSource(targetPath, sourcePath)) { markLaunchEntry(launchHomePath, entryName, sourcePath, 'link') return } const ownedTarget = existingMarker?.sourcePath === sourcePath && targetExistsForLaunchRemoval(targetPath) if (targetExistsForLaunchRemoval(targetPath) && !ownedTarget) { if (!replaceUnownedLaunchEntryAllowed(launchHomePath, entryName)) { return } removeLaunchEntry(targetPath) } if (ownedTarget) { removeLaunchEntry(targetPath) } try { createSharedEntryLink(sourcePath, targetPath) markLaunchEntry(launchHomePath, entryName, sourcePath, 'link') } catch (error) { if (!copyFallbackAllowed(sourcePath, entryName)) { console.warn('[codex-home] Failed to link shared Codex launch entry:', entryName, error) return } try { removeLaunchEntry(targetPath) cpSync(sourcePath, targetPath, { recursive: true, force: false, errorOnExist: true, dereference: true }) markLaunchEntry(launchHomePath, entryName, sourcePath, 'copy') } catch { console.warn('[codex-home] Failed to copy shared Codex launch entry:', entryName, error) } } } function materializeMissingSharedEntryIfNeeded(sourcePath: string, entryName: string): void { if ( process.platform !== 'win32' || !isCodexSqliteSidecarEntryName(entryName) || existsSync(sourcePath) ) { return } try { writeFileSync(sourcePath, '', { flag: 'wx', mode: 0o600 }) } catch (error) { if (existsSync(sourcePath)) { return } throw error } } function createSharedEntryLink(sourcePath: string, targetPath: string): void { if (createWslSymlinkIfPossible(sourcePath, targetPath)) { return } const sourceStat = existsSync(sourcePath) ? lstatSync(sourcePath) : null if ( sourceStat?.isFile() && process.platform === 'win32' && createHardLinkIfPossible(sourcePath, targetPath) ) { return } if (targetAlreadyPointsToSource(targetPath, sourcePath)) { return } try { symlinkSync( sourcePath, targetPath, sourceStat?.isDirectory() && process.platform === 'win32' ? 'junction' : undefined ) } catch (error) { if (targetAlreadyPointsToSource(targetPath, sourcePath)) { return } if ( sourceStat?.isFile() && process.platform === 'win32' && createHardLinkIfPossible(sourcePath, targetPath) ) { return } if (targetAlreadyPointsToSource(targetPath, sourcePath)) { return } throw error } } function createHardLinkIfPossible(sourcePath: string, targetPath: string): boolean { try { linkSync(sourcePath, targetPath) return true } catch { return false } } function createWslSymlinkIfPossible(sourcePath: string, targetPath: string): boolean { const paths = getSameDistroWslPaths(sourcePath, targetPath) if (!paths) { return false } execFileSync( 'wsl.exe', ['-d', paths.distro, '--', 'ln', '-s', paths.sourceLinuxPath, paths.targetLinuxPath], { stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000 } ) return true } function getSameDistroWslPaths( sourcePath: string, targetPath: string ): { distro: string; sourceLinuxPath: string; targetLinuxPath: string } | null { if (process.platform !== 'win32') { return null } const sourceWsl = parseWslUncPath(sourcePath) const targetWsl = parseWslUncPath(targetPath) if (!sourceWsl || !targetWsl || sourceWsl.distro !== targetWsl.distro) { return null } return { distro: sourceWsl.distro, sourceLinuxPath: sourceWsl.linuxPath, targetLinuxPath: targetWsl.linuxPath } } function runWslPathCommand(distro: string, args: string[]): boolean { try { execFileSync('wsl.exe', ['-d', distro, '--', ...args], { stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000 }) return true } catch { return false } } function wslPathExists(targetPath: string): boolean | null { if (process.platform !== 'win32') { return null } const targetWsl = parseWslUncPath(targetPath) if (!targetWsl) { return null } return runWslPathCommand(targetWsl.distro, [ 'sh', '-c', 'test -e "$1" || test -L "$1"', 'sh', targetWsl.linuxPath ]) } function removeWslPathIfPossible(targetPath: string): boolean { if (process.platform !== 'win32') { return false } const targetWsl = parseWslUncPath(targetPath) if (!targetWsl) { return false } return runWslPathCommand(targetWsl.distro, ['rm', '-rf', '--', targetWsl.linuxPath]) } function activeHomeAlreadyPointsToLaunchHome( activeHomePath: string, launchHomePath: string ): boolean { if (targetAlreadyPointsToSource(activeHomePath, launchHomePath)) { return true } return false } function replaceActiveHomeLink(activeHomePath: string, nextLinkPath: string): void { try { renameSync(nextLinkPath, activeHomePath) } catch (error) { if (!activeHomeLinkIsReplaceable(activeHomePath)) { throw error } removeActiveHomeLinkIfOwned(activeHomePath) renameSync(nextLinkPath, activeHomePath) } } function activeHomeLinkIsReplaceable(activeHomePath: string): boolean { try { const stat = lstatSync(activeHomePath) return stat.isSymbolicLink() || isWindowsReadableLink(activeHomePath) } catch { return true } } function removeActiveHomeLinkIfOwned(activeHomePath: string): void { try { const stat = lstatSync(activeHomePath) if (stat.isSymbolicLink()) { unlinkSync(activeHomePath) } else if (isWindowsReadableLink(activeHomePath)) { rmdirSync(activeHomePath) } } catch { // Missing or inaccessible temporary links are handled by the caller. } } function isWindowsReadableLink(targetPath: string): boolean { if (process.platform !== 'win32') { return false } try { readlinkSync(targetPath) return true } catch { return false } } function readWslSymlinkTarget(targetPath: string): string | null { if (process.platform !== 'win32') { return null } const targetWsl = parseWslUncPath(targetPath) if (!targetWsl) { return null } try { return execFileSync( 'wsl.exe', ['-d', targetWsl.distro, '--', 'readlink', targetWsl.linuxPath], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000 } ).trim() } catch { return null } } function copyFallbackAllowed(sourcePath: string, entryName: string): boolean { if (entryName === 'hooks.json') { return false } if (isCodexSqliteEntryName(entryName)) { // Why: copying SQLite/WAL/SHM files forks Codex state and breaks lock coherence. return false } const sourceStat = lstatSync(sourcePath) return !sourceStat.isDirectory() || !MUTABLE_SHARED_DIRECTORY_ENTRIES.has(entryName) } function canLinkMissingSharedEntry(sharedHomePath: string, entryName: string): boolean { const mainEntryName = getCodexSqliteMainEntryName(entryName) return ( isCodexSqliteSidecarEntryName(entryName) && mainEntryName !== null && existsSync(join(sharedHomePath, mainEntryName)) ) } function replaceUnownedLaunchEntryAllowed(launchHomePath: string, entryName: string): boolean { // Why: older launch-home builds let Codex create local DB forks before the // shared SQLite link policy existed; those forks must be replaced in place. return isCodexSqliteEntryName(entryName) && existsSync(join(launchHomePath, LAUNCH_HOME_MARKER)) } function isContainedPath(rootPath: string, candidatePath: string): boolean { const relativePath = relative(resolve(rootPath), resolve(candidatePath)) return ( Boolean(relativePath) && relativePath !== '..' && !isAbsolute(relativePath) && !relativePath.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`) ) } function reconcileMutableLaunchHomeFilesIntoSharedHome( sharedHomePath: string, launchRootPath: string ): void { let selectionEntries: string[] try { selectionEntries = readdirSync(launchRootPath) } catch { return } for (const selectionEntry of selectionEntries.sort()) { const launchHomePath = join(launchRootPath, selectionEntry, 'home') if (!existsSync(join(launchHomePath, LAUNCH_HOME_MARKER))) { continue } reconcileMarkedMutableFiles(sharedHomePath, launchHomePath) } } function reconcileMarkedMutableFiles(sharedHomePath: string, launchHomePath: string): void { const markerDir = join(launchHomePath, LAUNCH_HOME_LINK_MARKERS_DIR) let markerFiles: string[] try { markerFiles = readdirSync(markerDir) } catch { return } for (const markerFile of markerFiles.sort()) { const entryName = markerFile.replace(/\.json$/, '') const marker = readLaunchEntryMarker(launchHomePath, entryName) if (!marker || !MUTABLE_SHARED_FILE_ENTRIES.has(entryName)) { continue } if (marker.sourcePath !== join(sharedHomePath, entryName)) { continue } reconcileMutableLaunchEntryIfNeeded(marker.sourcePath, join(launchHomePath, entryName), marker) } } function reconcileMutableLaunchEntryIfNeeded( sourcePath: string, targetPath: string, marker: LaunchEntryMarker | null ): void { if (!marker || !MUTABLE_SHARED_FILE_ENTRIES.has(targetPath.split(/[\\/]/).at(-1) ?? '')) { return } if (!targetExistsForLaunchRemoval(targetPath)) { return } if (targetAlreadyPointsToSource(targetPath, sourcePath)) { return } try { if (lstatSync(targetPath).isSymbolicLink() || !statSync(targetPath).isFile()) { return } const targetDigest = digestFile(targetPath) if (targetDigest === marker.targetDigest) { return } const sourceDigest = existsSync(sourcePath) ? digestFile(sourcePath) : null if ( sourceDigest !== null && marker.sourceDigest !== null && sourceDigest !== marker.sourceDigest && statSync(sourcePath).mtimeMs > statSync(targetPath).mtimeMs ) { return } mkdirSync(dirname(sourcePath), { recursive: true }) cpSync(targetPath, sourcePath, { force: true }) } catch (error) { console.warn('[codex-home] Failed to reconcile launch-home Codex entry:', targetPath, error) } } function removeStaleLaunchHomeEntries( launchHomePath: string, sharedHomePath: string, sharedEntries: Set ): void { const markerDir = join(launchHomePath, LAUNCH_HOME_LINK_MARKERS_DIR) let markerFiles: string[] try { markerFiles = readdirSync(markerDir) } catch { return } for (const markerFile of markerFiles) { const entryName = markerFile.replace(/\.json$/, '') if (!sharedEntries.has(entryName)) { removeLaunchEntryIfOwned( join(launchHomePath, entryName), launchHomePath, entryName, join(sharedHomePath, entryName) ) } } } function removeLaunchEntryIfOwned( targetPath: string, launchHomePath: string, entryName: string, sourcePath: string ): void { const marker = readLaunchEntryMarker(launchHomePath, entryName) if (marker?.sourcePath !== sourcePath) { return } removeLaunchEntry(targetPath) rmSync(getLaunchEntryMarkerPath(launchHomePath, entryName), { force: true }) } function removeLaunchEntry(targetPath: string): void { if (!targetExistsForLaunchRemoval(targetPath)) { return } try { if (removeWslPathIfPossible(targetPath)) { return } const stat = lstatSync(targetPath) if (stat.isSymbolicLink()) { try { unlinkSync(targetPath) } catch (error) { if (process.platform !== 'win32') { throw error } rmdirSync(targetPath) } return } rmSync(targetPath, { recursive: stat.isDirectory(), force: true }) } catch (error) { console.warn('[codex-home] Failed to remove owned launch-home entry:', targetPath, error) } } function targetExistsForLaunchRemoval(targetPath: string): boolean { const wslExists = wslPathExists(targetPath) if (wslExists !== null) { return wslExists } try { lstatSync(targetPath) return true } catch { return false } } function writeLaunchHomeMarker(launchHomePath: string, accountId: string | null): void { writeFileSync( join(launchHomePath, LAUNCH_HOME_MARKER), `${JSON.stringify({ version: LAUNCH_HOME_MARKER_VERSION, accountId }, null, 2)}\n`, { encoding: 'utf-8', mode: 0o600 } ) } function isMarkedLaunchHomeForAccount(launchHomePath: string, accountId: string): boolean { try { const parsed: unknown = JSON.parse( readFileSync(join(launchHomePath, LAUNCH_HOME_MARKER), 'utf-8') ) if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { return false } const marker = parsed as { version?: unknown; accountId?: unknown } return marker.version === LAUNCH_HOME_MARKER_VERSION && marker.accountId === accountId } catch { return false } } function markLaunchEntry( launchHomePath: string, entryName: string, sourcePath: string, mode: 'link' | 'copy' ): void { const markerPath = getLaunchEntryMarkerPath(launchHomePath, entryName) mkdirSync(dirname(markerPath), { recursive: true }) const shouldTrackDigests = MUTABLE_SHARED_FILE_ENTRIES.has(entryName) writeFileSync( markerPath, `${JSON.stringify( { version: LAUNCH_HOME_MARKER_VERSION, sourcePath, mode, // Why: digests are only used to reconcile mutable config-style files; // hashing SQLite databases on every PTY launch can read gigabytes. sourceDigest: shouldTrackDigests ? digestPathIfFile(sourcePath) : null, targetDigest: shouldTrackDigests ? digestPathIfFile(join(launchHomePath, entryName)) : null } satisfies LaunchEntryMarker, null, 2 )}\n`, { encoding: 'utf-8', mode: 0o600 } ) } function readLaunchEntryMarker( launchHomePath: string, entryName: string ): LaunchEntryMarker | null { try { const parsed: unknown = JSON.parse( readFileSync(getLaunchEntryMarkerPath(launchHomePath, entryName), 'utf-8') ) if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { return null } const marker = parsed as Partial if ( marker.version !== LAUNCH_HOME_MARKER_VERSION || typeof marker.sourcePath !== 'string' || (marker.mode !== 'link' && marker.mode !== 'copy') ) { return null } return { version: marker.version, sourcePath: marker.sourcePath, mode: marker.mode, sourceDigest: typeof marker.sourceDigest === 'string' ? marker.sourceDigest : null, targetDigest: typeof marker.targetDigest === 'string' ? marker.targetDigest : null } } catch { return null } } function targetAlreadyPointsToSource(targetPath: string, sourcePath: string): boolean { const paths = getSameDistroWslPaths(sourcePath, targetPath) if (paths) { return readWslSymlinkTarget(targetPath) === paths.sourceLinuxPath } try { const targetStat = lstatSync(targetPath) if (targetStat.isSymbolicLink()) { return linkTargetsMatch(readlinkSync(targetPath), sourcePath) } if (process.platform === 'win32') { try { if (linkTargetsMatch(readlinkSync(targetPath), sourcePath)) { return true } } catch { // Non-link files fall through to the hard-link identity check below. } } if (!targetStat.isFile() || !existsSync(sourcePath)) { return false } const sourceStat = statSync(sourcePath) return ( sourceStat.isFile() && targetStat.dev === sourceStat.dev && targetStat.ino === sourceStat.ino && targetStat.nlink > 1 ) } catch { return false } } function linkTargetsMatch(actualTarget: string, expectedTarget: string): boolean { const expectedWsl = parseWslUncPath(expectedTarget) if (expectedWsl && actualTarget === expectedWsl.linuxPath) { return true } if (process.platform !== 'win32') { return actualTarget === expectedTarget } return normalizeWindowsLinkTarget(actualTarget) === normalizeWindowsLinkTarget(expectedTarget) } function normalizeWindowsLinkTarget(linkTarget: string): string { return linkTarget.replace(/^\\\\\?\\/, '').toLowerCase() } function getLaunchEntryMarkerPath(launchHomePath: string, entryName: string): string { return join(launchHomePath, LAUNCH_HOME_LINK_MARKERS_DIR, `${entryName}.json`) } function digestPathIfFile(targetPath: string): string | null { try { if (!statSync(targetPath).isFile()) { return null } return digestFile(targetPath) } catch { return null } } function digestFile(targetPath: string): string { return createHash('sha256').update(readFileSync(targetPath)).digest('hex') }