mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 08:04:05 +00:00
* refactor oversized Electron facilities * fix interactive process timeout and shortcut repeat guard * chore(child-process): drop stale cli-installer allowlist entry cli-installer.ts now routes privileged spawns through runProcess via cli-privileged-processes.ts, so the shrink-only ratchet flags it as stale. * refactor(child-process): extract the bounded output sink runProcess's timeoutMs opt-out (required to preserve the unbounded osascript admin prompt) pushed run-process.ts past the 300-line cap. Move createOutputSink to its own module rather than add a max-lines bypass, which AGENTS.md forbids. Moved verbatim; no behavior change.
279 lines
9.0 KiB
TypeScript
279 lines
9.0 KiB
TypeScript
import { lstat, readFile, readlink } from 'node:fs/promises'
|
|
import { basename, dirname, resolve } from 'node:path'
|
|
import type { CliInstallMethod, CliInstallStatus } from '../../shared/cli-install-types'
|
|
import { buildAppImageCliWrapper } from './appimage-cli-wrapper'
|
|
import { DEV_COMMAND_NAME, DEV_LAUNCHER_DIR } from './cli-install-constants'
|
|
import { buildWindowsForwarder, extractManagedUnixLauncherTarget } from './cli-dev-launcher'
|
|
import { isMissingError } from './cli-install-errors'
|
|
import { CliInstallLocation } from './cli-install-location'
|
|
import { isPathInsideOrEqual, samePathEntry } from './cli-install-path-format'
|
|
|
|
export class CliCommandInspection extends CliInstallLocation {
|
|
protected async inspectAppImageWrapper(
|
|
commandPath: string,
|
|
appImagePath: string
|
|
): Promise<CliInstallStatus> {
|
|
try {
|
|
const stats = await lstat(commandPath)
|
|
if (!stats.isFile()) {
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath: appImagePath,
|
|
installMethod: 'wrapper',
|
|
supported: true,
|
|
state: 'conflict',
|
|
currentTarget: null,
|
|
detail: `${commandPath} exists but is not an Orca launcher script.`
|
|
})
|
|
}
|
|
|
|
const currentContent = await readFile(commandPath, 'utf8')
|
|
const expectedContent = buildAppImageCliWrapper(appImagePath)
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath: appImagePath,
|
|
installMethod: 'wrapper',
|
|
supported: true,
|
|
state: currentContent === expectedContent ? 'installed' : 'stale',
|
|
currentTarget: appImagePath,
|
|
detail:
|
|
currentContent === expectedContent
|
|
? `Registered at ${commandPath}.`
|
|
: `${commandPath} points to a different launcher.`
|
|
})
|
|
} catch (error) {
|
|
if (isMissingError(error)) {
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath: appImagePath,
|
|
installMethod: 'wrapper',
|
|
supported: true,
|
|
state: 'not_installed',
|
|
currentTarget: null,
|
|
detail: `Register ${commandPath} to use Orca from the terminal.`
|
|
})
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
protected async inspectSymlink(
|
|
commandPath: string,
|
|
launcherPath: string
|
|
): Promise<CliInstallStatus> {
|
|
try {
|
|
const stats = await lstat(commandPath)
|
|
if (!stats.isSymbolicLink()) {
|
|
if (stats.isFile()) {
|
|
const currentContent = await readFile(commandPath, 'utf8')
|
|
const managedTarget = extractManagedUnixLauncherTarget(currentContent)
|
|
if (managedTarget) {
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath,
|
|
installMethod: 'symlink',
|
|
supported: true,
|
|
state: 'stale',
|
|
currentTarget: managedTarget,
|
|
detail: `${commandPath} contains an older Orca launcher.`
|
|
})
|
|
}
|
|
}
|
|
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath,
|
|
installMethod: 'symlink',
|
|
supported: true,
|
|
state: 'conflict',
|
|
currentTarget: null,
|
|
detail: `${commandPath} exists but is not an Orca symlink.`
|
|
})
|
|
}
|
|
|
|
const currentTarget = await readlink(commandPath)
|
|
const resolvedCurrentTarget = resolve(dirname(commandPath), currentTarget)
|
|
const resolvedLauncher = resolve(launcherPath)
|
|
const isInstalled = resolvedCurrentTarget === resolvedLauncher
|
|
const isManagedStaleTarget =
|
|
!isInstalled && this.isManagedSymlinkTarget(resolvedCurrentTarget, launcherPath)
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath,
|
|
installMethod: 'symlink',
|
|
supported: true,
|
|
state: isInstalled ? 'installed' : isManagedStaleTarget ? 'stale' : 'conflict',
|
|
currentTarget: resolvedCurrentTarget,
|
|
detail: isInstalled
|
|
? `Registered at ${commandPath}.`
|
|
: isManagedStaleTarget
|
|
? `${commandPath} points to an older Orca launcher.`
|
|
: `${commandPath} points to a non-Orca launcher.`
|
|
})
|
|
} catch (error) {
|
|
if (isMissingError(error)) {
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath,
|
|
installMethod: 'symlink',
|
|
supported: true,
|
|
state: 'not_installed',
|
|
currentTarget: null,
|
|
detail: `Register ${commandPath} to use Orca from the terminal.`
|
|
})
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
protected isManagedSymlinkTarget(resolvedTarget: string, launcherPath: string): boolean {
|
|
const expectedName = basename(launcherPath)
|
|
if (this.isPackaged && this.isSiblingDevLauncherTarget(resolvedTarget, expectedName)) {
|
|
return true
|
|
}
|
|
|
|
if (basename(resolvedTarget) !== expectedName) {
|
|
return false
|
|
}
|
|
|
|
const devLauncherDir = resolve(this.userDataPath, ...DEV_LAUNCHER_DIR)
|
|
if (isPathInsideOrEqual(devLauncherDir, resolvedTarget)) {
|
|
return true
|
|
}
|
|
|
|
if (this.platform === 'darwin') {
|
|
// Why: reclaim symlinks to an older Orca.app launcher, but never replace arbitrary user-owned symlinks.
|
|
return /(?:^|[/\\])[^/\\]+\.app[/\\]Contents[/\\]Resources[/\\]bin[/\\][^/\\]+$/.test(
|
|
resolvedTarget
|
|
)
|
|
}
|
|
|
|
if (this.platform === 'linux') {
|
|
return /(?:^|[/\\])resources[/\\]bin[/\\][^/\\]+$/.test(resolvedTarget)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
protected isSiblingDevLauncherTarget(
|
|
resolvedTarget: string,
|
|
packagedLauncherName: string
|
|
): boolean {
|
|
if (![packagedLauncherName, DEV_COMMAND_NAME].includes(basename(resolvedTarget))) {
|
|
return false
|
|
}
|
|
|
|
const packagedUserDataPath = resolve(this.userDataPath)
|
|
const siblingDevUserDataPath = `${packagedUserDataPath}-dev`
|
|
const siblingDevLauncherDir = resolve(siblingDevUserDataPath, ...DEV_LAUNCHER_DIR)
|
|
|
|
// Why: dev builds generate launchers under the sibling `*-dev` profile; packaged Orca must reclaim that command.
|
|
return (
|
|
basename(siblingDevUserDataPath) === `${basename(packagedUserDataPath)}-dev` &&
|
|
isPathInsideOrEqual(siblingDevLauncherDir, resolvedTarget)
|
|
)
|
|
}
|
|
|
|
protected isLinuxAppImage(): boolean {
|
|
return this.platform === 'linux' && Boolean(this.appImagePath)
|
|
}
|
|
|
|
protected isWindowsPackagedBundledCommand(
|
|
commandPath: string | null,
|
|
launcherPath: string | null
|
|
): boolean {
|
|
return (
|
|
this.platform === 'win32' &&
|
|
this.isPackaged &&
|
|
commandPath !== null &&
|
|
launcherPath !== null &&
|
|
samePathEntry('win32', commandPath, launcherPath)
|
|
)
|
|
}
|
|
|
|
protected async inspectWindowsWrapper(
|
|
commandPath: string,
|
|
launcherPath: string
|
|
): Promise<CliInstallStatus> {
|
|
try {
|
|
const stats = await lstat(commandPath)
|
|
if (!stats.isFile()) {
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath,
|
|
installMethod: 'wrapper',
|
|
supported: true,
|
|
state: 'conflict',
|
|
currentTarget: null,
|
|
detail: `${commandPath} exists but is not an Orca launcher script.`
|
|
})
|
|
}
|
|
|
|
if (this.isWindowsPackagedBundledCommand(commandPath, launcherPath)) {
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath,
|
|
installMethod: 'wrapper',
|
|
supported: true,
|
|
state: 'installed',
|
|
currentTarget: launcherPath,
|
|
detail: `Registered at ${commandPath}.`
|
|
})
|
|
}
|
|
|
|
const currentContent = await readFile(commandPath, 'utf8')
|
|
const expectedContent = buildWindowsForwarder(launcherPath)
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath,
|
|
installMethod: 'wrapper',
|
|
supported: true,
|
|
state: currentContent === expectedContent ? 'installed' : 'stale',
|
|
currentTarget: launcherPath,
|
|
detail:
|
|
currentContent === expectedContent
|
|
? `Registered at ${commandPath}.`
|
|
: `${commandPath} points to a different launcher.`
|
|
})
|
|
} catch (error) {
|
|
if (isMissingError(error)) {
|
|
return this.buildStatus({
|
|
commandPath,
|
|
launcherPath,
|
|
installMethod: 'wrapper',
|
|
supported: true,
|
|
state: 'not_installed',
|
|
currentTarget: null,
|
|
detail: `Register ${commandPath} to use Orca from Command Prompt or PowerShell.`
|
|
})
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
protected buildStatus(args: {
|
|
commandPath: string
|
|
launcherPath: string
|
|
installMethod: CliInstallMethod
|
|
supported: boolean
|
|
state: CliInstallStatus['state']
|
|
currentTarget: string | null
|
|
detail: string | null
|
|
}): CliInstallStatus {
|
|
return {
|
|
platform: this.platform,
|
|
commandName: this.commandName,
|
|
commandPath: args.commandPath,
|
|
pathDirectory: dirname(args.commandPath),
|
|
pathConfigured: false,
|
|
launcherPath: args.launcherPath,
|
|
installMethod: args.installMethod,
|
|
supported: args.supported,
|
|
state: args.state,
|
|
currentTarget: args.currentTarget,
|
|
unsupportedReason: null,
|
|
detail: args.detail
|
|
}
|
|
}
|
|
}
|