diff --git a/src/main/appimage-runtime-identity.test.ts b/src/main/appimage-runtime-identity.test.ts index 903d67827f9..f9a7319ee1c 100644 --- a/src/main/appimage-runtime-identity.test.ts +++ b/src/main/appimage-runtime-identity.test.ts @@ -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) }) }) diff --git a/src/main/appimage-runtime-identity.ts b/src/main/appimage-runtime-identity.ts index 860d6c83a63..08b2a92dd65 100644 --- a/src/main/appimage-runtime-identity.ts +++ b/src/main/appimage-runtime-identity.ts @@ -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( diff --git a/src/main/cli/cli-install-location.ts b/src/main/cli/cli-install-location.ts index e65c17cc1cd..228e574addc 100644 --- a/src/main/cli/cli-install-location.ts +++ b/src/main/cli/cli-install-location.ts @@ -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 diff --git a/src/main/cli/cli-installer-appimage-ownership.test.ts b/src/main/cli/cli-installer-appimage-ownership.test.ts index 92fb7325ec8..81147e09886 100644 --- a/src/main/cli/cli-installer-appimage-ownership.test.ts +++ b/src/main/cli/cli-installer-appimage-ownership.test.ts @@ -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') diff --git a/src/main/cli/linux-bare-orca-dispatcher.test.ts b/src/main/cli/linux-bare-orca-dispatcher.test.ts index 18a16d53f60..b8031535134 100644 --- a/src/main/cli/linux-bare-orca-dispatcher.test.ts +++ b/src/main/cli/linux-bare-orca-dispatcher.test.ts @@ -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() diff --git a/src/main/cli/linux-bare-orca-dispatcher.ts b/src/main/cli/linux-bare-orca-dispatcher.ts index 780dca8b1f6..3bf9fae35ad 100644 --- a/src/main/cli/linux-bare-orca-dispatcher.ts +++ b/src/main/cli/linux-bare-orca-dispatcher.ts @@ -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 { 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 diff --git a/src/main/cli/linux-terminal-orca-cli-shim.test.ts b/src/main/cli/linux-terminal-orca-cli-shim.test.ts index 164271bd7f2..905d4807368 100644 --- a/src/main/cli/linux-terminal-orca-cli-shim.test.ts +++ b/src/main/cli/linux-terminal-orca-cli-shim.test.ts @@ -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() diff --git a/src/main/cli/linux-terminal-orca-cli-shim.ts b/src/main/cli/linux-terminal-orca-cli-shim.ts index 1a0ebb40ba4..7697bac01ac 100644 --- a/src/main/cli/linux-terminal-orca-cli-shim.ts +++ b/src/main/cli/linux-terminal-orca-cli-shim.ts @@ -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