fix(linux): accept extracted AppImage runtimes with APPDIR only

This commit is contained in:
Neil
2026-08-31 05:48:29 -07:00
parent daff0b9301
commit f821ca0cca
8 changed files with 73 additions and 16 deletions
+6 -6
View File
@@ -11,7 +11,7 @@ import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import {
hasAppImageRuntimeEnvironment,
hasAppImagePathEnvironment,
resolveAppImageRuntimeIdentity
} from './appimage-runtime-identity'
@@ -231,10 +231,10 @@ describe.skipIf(process.platform === 'win32')('resolveAppImageRuntimeIdentity',
})
})
describe('hasAppImageRuntimeEnvironment', () => {
it('detects either AppImage runtime variable', () => {
expect(hasAppImageRuntimeEnvironment({ APPIMAGE: '/tmp/Orca.AppImage' })).toBe(true)
expect(hasAppImageRuntimeEnvironment({ APPDIR: '/tmp/.mount_Orca123' })).toBe(true)
expect(hasAppImageRuntimeEnvironment({ APPIMAGE: '', APPDIR: '' })).toBe(false)
describe('hasAppImagePathEnvironment', () => {
it('requires the AppImage file path before treating the runtime as verifiable', () => {
expect(hasAppImagePathEnvironment({ APPIMAGE: '/tmp/Orca.AppImage' })).toBe(true)
expect(hasAppImagePathEnvironment({ APPDIR: '/tmp/.mount_Orca123' })).toBe(false)
expect(hasAppImagePathEnvironment({ APPIMAGE: '', APPDIR: '/tmp/.mount_Orca123' })).toBe(false)
})
})
+3 -4
View File
@@ -143,10 +143,9 @@ function hasAppImagePackageEvidence(runtimeRoot: string, resourcesPath: string):
}
}
export function hasAppImageRuntimeEnvironment(
environment: NodeJS.ProcessEnv = process.env
): boolean {
return Boolean(environment.APPIMAGE || environment.APPDIR)
/** Returns whether the process inherited an AppImage file path to validate. */
export function hasAppImagePathEnvironment(environment: NodeJS.ProcessEnv = process.env): boolean {
return Boolean(environment.APPIMAGE)
}
export function resolveAppImageRuntimeIdentity(
+2 -2
View File
@@ -4,7 +4,7 @@ import { basename, dirname, join } from 'node:path'
import { getAppEnvironment } from '../../shared/app-environment'
import type { CliInstallStatus } from '../../shared/cli-install-types'
import {
hasAppImageRuntimeEnvironment,
hasAppImagePathEnvironment,
resolveAppImageRuntimeIdentity
} from '../appimage-runtime-identity'
import {
@@ -106,7 +106,7 @@ export abstract class CliInstallLocation {
this.platform === 'linux' &&
this.isPackaged &&
!hasExplicitAppImagePath &&
hasAppImageRuntimeEnvironment() &&
hasAppImagePathEnvironment() &&
!runtimeAppImageIdentity
this.appImagePath =
this.platform === 'linux' && this.isPackaged
@@ -70,6 +70,38 @@ function installerOptions(fixture: Fixture) {
}
describe.skipIf(process.platform === 'win32')('AppImage CLI ownership', () => {
it('uses the mounted bundled launcher when only APPDIR is inherited', async () => {
const fixture = await makeFixture()
const resourcesPath = join(fixture.root, 'mounted', 'resources')
const launcherPath = join(resourcesPath, 'bin', 'orca-ide')
await mkdir(dirname(launcherPath), { recursive: true })
await writeFile(launcherPath, '#!/usr/bin/env bash\n', { mode: 0o755 })
vi.stubEnv('APPIMAGE', '')
vi.stubEnv('APPDIR', dirname(resourcesPath))
const installer = new CliInstaller({
platform: 'linux',
isPackaged: true,
userDataPath: join(fixture.root, 'user-data'),
resourcesPath,
execPath: join(dirname(resourcesPath), 'orca-ide'),
appPath: join(resourcesPath, 'app.asar'),
homePath: join(fixture.root, 'home'),
processPathEnv: fixture.commandDirectory,
commandPathOverride: fixture.commandPath
})
await expect(installer.getStatus()).resolves.toMatchObject({
state: 'not_installed',
launcherPath
})
await expect(installer.install()).resolves.toMatchObject({
state: 'installed',
launcherPath
})
await expect(readlink(fixture.commandPath)).resolves.toBe(launcherPath)
})
it('ignores inherited APPIMAGE without the matching runtime identity', async () => {
const fixture = await makeFixture()
const resourcesPath = join(fixture.root, 'installed', 'resources')
@@ -72,6 +72,7 @@ async function makeFixture(): Promise<{ homePath: string; resourcesPath: string
}
afterEach(async () => {
vi.unstubAllEnvs()
filePublicationFailures.link = 0
filePublicationFailures.replaceBeforeRename = ''
registrationLock.completed = null
@@ -81,6 +82,17 @@ afterEach(async () => {
})
describe('installLinuxBareOrcaDispatcher', () => {
it('uses the mounted bundled launcher when only APPDIR is inherited', async () => {
const { homePath, resourcesPath } = await makeFixture()
vi.stubEnv('APPIMAGE', '')
vi.stubEnv('APPDIR', resourcesPath)
const result = await installLinuxBareOrcaDispatcher({ resourcesPath, homePath })
expect(result.state).toBe('installed')
expect(result.target).toBe(join(resourcesPath, 'bin', 'orca-ide'))
})
it('writes an executable bare-orca dispatcher that execs the bundled orca-ide launcher', async () => {
const { homePath, resourcesPath } = await makeFixture()
+2 -2
View File
@@ -4,7 +4,7 @@ import { copyFile, link, lstat, mkdir, readFile, rename, unlink, writeFile } fro
import { homedir } from 'node:os'
import { dirname, join } from 'node:path'
import {
hasAppImageRuntimeEnvironment,
hasAppImagePathEnvironment,
resolveAppImageRuntimeIdentity
} from '../appimage-runtime-identity'
import {
@@ -89,7 +89,7 @@ async function resolveStableLauncherPath(
): Promise<string | null> {
const hasExplicitAppImagePath = Object.hasOwn(options, 'appImagePath')
const runtimeIdentity = resolveAppImageRuntimeIdentity({ resourcesPath: options.resourcesPath })
if (!hasExplicitAppImagePath && hasAppImageRuntimeEnvironment() && !runtimeIdentity) {
if (!hasExplicitAppImagePath && hasAppImagePathEnvironment() && !runtimeIdentity) {
return null
}
const appImagePath = hasExplicitAppImagePath
@@ -26,10 +26,24 @@ async function makeFixture(): Promise<{ userDataPath: string; resourcesPath: str
}
afterEach(async () => {
vi.unstubAllEnvs()
await Promise.all(created.splice(0).map((dir) => rm(dir, { recursive: true, force: true })))
})
describe('ensureLinuxTerminalOrcaCliShimDir', () => {
it('uses the mounted bundled launcher when only APPDIR is inherited', async () => {
const { userDataPath, resourcesPath } = await makeFixture()
vi.stubEnv('APPIMAGE', '')
vi.stubEnv('APPDIR', resourcesPath)
const shimDir = ensureLinuxTerminalOrcaCliShimDir({ userDataPath, resourcesPath })
expect(shimDir).toBe(join(userDataPath, 'linux-orca-cli-shim'))
expect(readFileSync(join(shimDir!, 'orca'), 'utf8')).toContain(
`exec '${join(resourcesPath, 'bin', 'orca-ide')}' "$@"`
)
})
it('writes an executable bare-orca shim that execs the bundled orca-ide launcher', async () => {
const { userDataPath, resourcesPath } = await makeFixture()
+2 -2
View File
@@ -9,7 +9,7 @@ import {
} from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import {
hasAppImageRuntimeEnvironment,
hasAppImagePathEnvironment,
resolveAppImageRuntimeIdentity
} from '../appimage-runtime-identity'
import {
@@ -50,7 +50,7 @@ export function ensureLinuxTerminalOrcaCliShimDir(
options.resourcesPath === undefined ? process.resourcesPath : options.resourcesPath
const hasExplicitAppImagePath = Object.hasOwn(options, 'appImagePath')
const runtimeIdentity = resolveAppImageRuntimeIdentity({ resourcesPath })
if (!hasExplicitAppImagePath && hasAppImageRuntimeEnvironment() && !runtimeIdentity) {
if (!hasExplicitAppImagePath && hasAppImagePathEnvironment() && !runtimeIdentity) {
return null
}
const appImagePath = hasExplicitAppImagePath