From bdc0921718bb794b42968f97d98756b27ed462f9 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 11 Oct 2026 05:15:31 -0700 Subject: [PATCH] test(e2e): one posixShellQuote; docker-ssh-host fixture names (Phase 3.99 0.11b) (#27449) * test(e2e): one posixShellQuote; docker-ssh-host fixture names * test(e2e): leave the main-failing stall-recovery spec untouched --------- Co-authored-by: m4air --- config/scripts/orcad-e2e-routing.test.mjs | 2 +- config/scripts/pr-e2e-source-routing.mjs | 8 +- .../scripts/ssh-browser-e2e-routing.test.mjs | 6 +- .../Dockerfile | 0 .../global-setup-cli-artifact.unit.test.ts | 2 +- tests/e2e/global-setup.ts | 4 +- ...red-remote-terminal-stall-recovery.spec.ts | 7 +- ...elay-image.ts => docker-ssh-host-image.ts} | 14 +- ...ay-target.ts => docker-ssh-host-target.ts} | 130 +++++------------- .../host-created-terminal-retention-oracle.ts | 7 +- tests/e2e/helpers/orcad-convert-host.ts | 26 ++-- .../paired-terminal-parking-fixture.ts | 7 +- .../remote-session-bulk-open-fixture.ts | 7 +- .../ssh-remote-only-browser-fixture.ts | 49 +++---- .../helpers/startup-exec-readiness-oracle.ts | 13 +- .../helpers/terminal-workspace-root-link.ts | 4 +- .../host-parked-pane-remote-viewer.spec.ts | 7 +- ...e-terminal-client-restart-survival.spec.ts | 7 +- ...terminal-host-quit-reconnect-input.spec.ts | 7 +- ...minal-host-restart-background-sync.spec.ts | 7 +- ...rminal-initial-snapshot-scrollback.spec.ts | 3 +- ...te-terminal-lossy-initial-snapshot.spec.ts | 7 +- ...terminal-materialization-reconnect.spec.ts | 7 +- ...rminal-parked-reveal-interactivity.spec.ts | 7 +- ...terminal-parked-scrollback-restart.spec.ts | 3 +- ...erminal-parked-scrollback-survives.spec.ts | 7 +- ...remote-terminal-probe-gap-recovery.spec.ts | 7 +- ...erminal-truncated-tail-first-paint.spec.ts | 7 +- ...mote-agent-session-focus-authority.spec.ts | 7 +- ...etwork-execution-route.docker.unit.test.ts | 31 ++--- .../e2e/ssh-orcad-browser-drop-owner.spec.ts | 6 +- tests/e2e/ssh-orcad-browser-routing.spec.ts | 14 +- .../ssh-orcad-browser-service-status.spec.ts | 14 +- tests/e2e/ssh-orcad-idle-exit.spec.ts | 56 ++++---- tests/e2e/ssh-orcad-open-in-owner.spec.ts | 6 +- .../e2e/ssh-orcad-terminal-root-owner.spec.ts | 10 +- .../e2e/ssh-orcad-vm-provisioned-root.spec.ts | 62 ++++----- tests/e2e/ssh-orcad-workspace-tools.spec.ts | 10 +- tests/e2e/terminal-node-command.ts | 7 +- 39 files changed, 229 insertions(+), 356 deletions(-) rename tests/e2e/fixtures/{docker-ssh-relay => docker-ssh-host}/Dockerfile (100%) rename tests/e2e/helpers/{docker-ssh-relay-image.ts => docker-ssh-host-image.ts} (85%) rename tests/e2e/helpers/{docker-ssh-relay-target.ts => docker-ssh-host-target.ts} (58%) diff --git a/config/scripts/orcad-e2e-routing.test.mjs b/config/scripts/orcad-e2e-routing.test.mjs index 018eda14a75..63fe127ed68 100644 --- a/config/scripts/orcad-e2e-routing.test.mjs +++ b/config/scripts/orcad-e2e-routing.test.mjs @@ -125,7 +125,7 @@ it('routes managed target-owner guards and menus to the real launch regression', 'tests/e2e/helpers/orcad-convert-flow.ts', 'tests/e2e/helpers/orcad-convert-host.ts', 'tests/e2e/helpers/orcad-upgrade-profile.ts', - 'tests/e2e/helpers/docker-ssh-relay-target.ts' + 'tests/e2e/helpers/docker-ssh-host-target.ts' ], ORCAD_OPEN_IN_OWNER_E2E_SPEC ) diff --git a/config/scripts/pr-e2e-source-routing.mjs b/config/scripts/pr-e2e-source-routing.mjs index 6addbe03624..806c0341bc1 100644 --- a/config/scripts/pr-e2e-source-routing.mjs +++ b/config/scripts/pr-e2e-source-routing.mjs @@ -144,7 +144,7 @@ export const PR_E2E_SOURCE_ROUTES = [ id: 'ssh.orcad-browser-drop-owner', specs: ['tests/e2e/ssh-orcad-browser-drop-owner.spec.ts'], matches: (file) => - /^tests\/e2e\/helpers\/(?:orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-relay-target|browser-split-(?:guest-probes|page-server))\.ts$/.test( + /^tests\/e2e\/helpers\/(?:orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-host-target|browser-split-(?:guest-probes|page-server))\.ts$/.test( file ) || (isProductSource(file) && @@ -156,7 +156,7 @@ export const PR_E2E_SOURCE_ROUTES = [ id: 'ssh.orcad-open-in-owner', specs: ['tests/e2e/ssh-orcad-open-in-owner.spec.ts'], matches: (file) => - /^tests\/e2e\/helpers\/(?:orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-relay-target)\.ts$/.test( + /^tests\/e2e\/helpers\/(?:orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-host-target)\.ts$/.test( file ) || (isProductSource(file) && @@ -168,7 +168,7 @@ export const PR_E2E_SOURCE_ROUTES = [ id: 'ssh.orcad-terminal-root-owner', specs: ['tests/e2e/ssh-orcad-terminal-root-owner.spec.ts'], matches: (file) => - /^tests\/e2e\/helpers\/(?:terminal-workspace-root-link|orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-relay-target)\.ts$/.test( + /^tests\/e2e\/helpers\/(?:terminal-workspace-root-link|orcad-convert-(?:flow|host)|orcad-upgrade-profile|docker-ssh-host-target)\.ts$/.test( file ) || (isProductSource(file) && @@ -208,7 +208,7 @@ export const PR_E2E_SOURCE_ROUTES = [ specs: ['tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts'], matches: (file) => file === 'tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts' || - /^tests\/e2e\/helpers\/docker-ssh-relay-(?:image|target)\.ts$/.test(file) || + /^tests\/e2e\/helpers\/docker-ssh-host-(?:image|target)\.ts$/.test(file) || (isProductSource(file) && /^src\/main\/(?:browser\/(?:ssh-browser-network-execution-route|browser-network-deferred-socket|browser-network-execution-route|system-ssh-socks-client-socket)|ssh\/system-ssh-dynamic-forward-process)\.ts$/.test( file diff --git a/config/scripts/ssh-browser-e2e-routing.test.mjs b/config/scripts/ssh-browser-e2e-routing.test.mjs index e0ffd2f8ad9..10d18e76631 100644 --- a/config/scripts/ssh-browser-e2e-routing.test.mjs +++ b/config/scripts/ssh-browser-e2e-routing.test.mjs @@ -32,13 +32,13 @@ it('executes both Docker network routes in a Node job with their opt-in enabled' 'src/main/browser/browser-network-execution-route.ts', 'src/main/browser/system-ssh-socks-client-socket.ts', 'src/main/ssh/system-ssh-dynamic-forward-process.ts', - 'tests/e2e/helpers/docker-ssh-relay-target.ts', - 'tests/e2e/helpers/docker-ssh-relay-image.ts' + 'tests/e2e/helpers/docker-ssh-host-target.ts', + 'tests/e2e/helpers/docker-ssh-host-image.ts' ]) { expect(selectPrE2eSpecs([changed])).toContain(spec) } expect(selectPrE2eSpecs(['src/renderer/src/components/Unrelated.tsx'])).not.toContain(spec) - expect(selectPrE2eSpecs(['tests/e2e/helpers/docker-ssh-relay-terminal-tabs.ts'])).not.toContain( + expect(selectPrE2eSpecs(['tests/e2e/helpers/docker-ssh-host-terminal-tabs.ts'])).not.toContain( spec ) }) diff --git a/tests/e2e/fixtures/docker-ssh-relay/Dockerfile b/tests/e2e/fixtures/docker-ssh-host/Dockerfile similarity index 100% rename from tests/e2e/fixtures/docker-ssh-relay/Dockerfile rename to tests/e2e/fixtures/docker-ssh-host/Dockerfile diff --git a/tests/e2e/global-setup-cli-artifact.unit.test.ts b/tests/e2e/global-setup-cli-artifact.unit.test.ts index 555b6a9c7d1..d9adcdf0a4c 100644 --- a/tests/e2e/global-setup-cli-artifact.unit.test.ts +++ b/tests/e2e/global-setup-cli-artifact.unit.test.ts @@ -15,7 +15,7 @@ vi.mock('node:fs', () => ({ realpathSync: (value: string) => value, writeFileSync: fixture.write })) -vi.mock('./helpers/docker-ssh-relay-image', () => ({ prepareDockerSshRelayImage: vi.fn() })) +vi.mock('./helpers/docker-ssh-host-image', () => ({ prepareDockerSshHostImage: vi.fn() })) import globalSetup, { workerTestRepositoryPathFile } from './global-setup' diff --git a/tests/e2e/global-setup.ts b/tests/e2e/global-setup.ts index a0070b25186..77b24614743 100644 --- a/tests/e2e/global-setup.ts +++ b/tests/e2e/global-setup.ts @@ -15,7 +15,7 @@ import { randomUUID } from 'node:crypto' import { existsSync, mkdirSync, mkdtempSync, realpathSync, writeFileSync } from 'node:fs' import path from 'node:path' import os from 'node:os' -import { prepareDockerSshRelayImage } from './helpers/docker-ssh-relay-image' +import { prepareDockerSshHostImage } from './helpers/docker-ssh-host-image' export const E2E_TEST_REPO_PATH_FILE_ENV = 'ORCA_E2E_TEST_REPO_PATH_FILE' const RUN_TEST_REPO_PATH_FILE = @@ -117,7 +117,7 @@ export default function globalSetup(): void { } if (process.env.ORCA_E2E_SSH_DOCKER === '1') { console.error('[e2e] Preparing Docker OpenSSH fixture image...') - prepareDockerSshRelayImage(root) + prepareDockerSshHostImage(root) } // ── 2. Create a seeded test git repo ─────────────────────────────── diff --git a/tests/e2e/headless-paired-remote-terminal-stall-recovery.spec.ts b/tests/e2e/headless-paired-remote-terminal-stall-recovery.spec.ts index 4aa41b7ee17..a892c16f2d7 100644 --- a/tests/e2e/headless-paired-remote-terminal-stall-recovery.spec.ts +++ b/tests/e2e/headless-paired-remote-terminal-stall-recovery.spec.ts @@ -8,6 +8,7 @@ import { expect, test } from './helpers/orca-app' import { launchHeadlessPairedRuntimeHost } from './helpers/headless-paired-runtime-host' import { launchPairedWebClient } from './helpers/paired-electron-client' import { getTerminalContent, waitForActivePanePtyId } from './helpers/terminal' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const MIN_EXHAUSTED_ACK_BYTES = 400 * 1024 const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-headless-stalled-stream-')) @@ -40,15 +41,11 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(): string { const command = [process.execPath, fixturePath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } async function callRuntime(page: Page, method: string, params: unknown): Promise { diff --git a/tests/e2e/helpers/docker-ssh-relay-image.ts b/tests/e2e/helpers/docker-ssh-host-image.ts similarity index 85% rename from tests/e2e/helpers/docker-ssh-relay-image.ts rename to tests/e2e/helpers/docker-ssh-host-image.ts index 9b7515bc546..773ed941837 100644 --- a/tests/e2e/helpers/docker-ssh-relay-image.ts +++ b/tests/e2e/helpers/docker-ssh-host-image.ts @@ -30,20 +30,20 @@ function hashDockerFixtureDirectory(fixtureDir: string): string { } function fixtureImage(root: string): string { - const fixtureDir = path.join(root, 'tests', 'e2e', 'fixtures', 'docker-ssh-relay') + const fixtureDir = path.join(root, 'tests', 'e2e', 'fixtures', 'docker-ssh-host') const digest = hashDockerFixtureDirectory(fixtureDir) - return `orca-e2e-ssh-relay:${digest}` + return `orca-e2e-ssh-host:${digest}` } -export function getDockerSshRelayImage(): string { +export function getDockerSshHostImage(): string { return process.env.ORCA_E2E_SSH_DOCKER_IMAGE ?? fixtureImage(process.cwd()) } -export function prepareDockerSshRelayImage(root: string): void { +export function prepareDockerSshHostImage(root: string): void { if (process.env.ORCA_E2E_SSH_DOCKER_IMAGE) { return } - const fixtureDir = path.join(root, 'tests', 'e2e', 'fixtures', 'docker-ssh-relay') + const fixtureDir = path.join(root, 'tests', 'e2e', 'fixtures', 'docker-ssh-host') const image = fixtureImage(root) execFileSync( 'docker', @@ -52,7 +52,7 @@ export function prepareDockerSshRelayImage(root: string): void { ) } -export function ensureDockerSshRelayImage(root: string): void { +export function ensureDockerSshHostImage(root: string): void { if (process.env.ORCA_E2E_SSH_DOCKER_IMAGE) { return } @@ -60,5 +60,5 @@ export function ensureDockerSshRelayImage(root: string): void { if (spawnSync('docker', ['image', 'inspect', image], { stdio: 'ignore' }).status === 0) { return } - prepareDockerSshRelayImage(root) + prepareDockerSshHostImage(root) } diff --git a/tests/e2e/helpers/docker-ssh-relay-target.ts b/tests/e2e/helpers/docker-ssh-host-target.ts similarity index 58% rename from tests/e2e/helpers/docker-ssh-relay-target.ts rename to tests/e2e/helpers/docker-ssh-host-target.ts index 9d96859968e..abb6c763ccf 100644 --- a/tests/e2e/helpers/docker-ssh-relay-target.ts +++ b/tests/e2e/helpers/docker-ssh-host-target.ts @@ -3,15 +3,14 @@ import { randomUUID } from 'node:crypto' import { mkdtempSync, readFileSync, rmSync } from 'node:fs' import os from 'node:os' import path from 'node:path' -import { getDockerSshRelayImage } from './docker-ssh-relay-image' +import { getDockerSshHostImage } from './docker-ssh-host-image' +import { quotePosixShell } from '../../../src/shared/wsl-login-shell-command' import type { TestInfo } from '@stablyai/playwright-test' -export const DOCKER_SSH_RELAY_REMOTE_REPO_PATH = '/tmp/orca-docker-relay-perf-repo' -export const DOCKER_SSH_PROXY_JUMP_REMOTE_REPO_PATH = '/tmp/orca-docker-proxy-jump-repo' -export const DOCKER_SSH_SECOND_HUB_REMOTE_REPO_PATH = '/tmp/orca-docker-second-hub-repo' +export const DOCKER_SSH_HOST_REMOTE_REPO_PATH = '/tmp/orca-docker-relay-perf-repo' -export type DockerSshRelayTarget = { +export type DockerSshHostTarget = { containerName: string containerIp: string host: string @@ -32,12 +31,8 @@ function tryRun(command: string, args: string[], opts: { timeoutMs?: number } = spawnSync(command, args, { stdio: 'ignore', timeout: opts.timeoutMs ?? 10_000 }) } -export function shellQuote(value: string): string { - return `'${value.replaceAll("'", "'\\''")}'` -} - -export function execDockerSshRelayTargetCommand( - target: DockerSshRelayTarget, +export function execDockerSshHostTargetCommand( + target: DockerSshHostTarget, command: string ): string { return run('docker', ['exec', target.containerName, 'bash', '-lc', command], { @@ -45,8 +40,8 @@ export function execDockerSshRelayTargetCommand( }) } -export function execDockerSshRelayTargetControlCommand( - target: DockerSshRelayTarget, +export function execDockerSshHostTargetControlCommand( + target: DockerSshHostTarget, command: string ): string { return run('docker', [ @@ -60,7 +55,7 @@ export function execDockerSshRelayTargetControlCommand( ]) } -function sshArgs(target: DockerSshRelayTarget, command: string): string[] { +function sshArgs(target: DockerSshHostTarget, command: string): string[] { return [ '-i', target.identityFile, @@ -79,7 +74,7 @@ function sshArgs(target: DockerSshRelayTarget, command: string): string[] { ] } -function waitForSsh(target: DockerSshRelayTarget): void { +function waitForSsh(target: DockerSshHostTarget): void { const deadline = Date.now() + 90_000 let lastError = '' while (Date.now() < deadline) { @@ -103,42 +98,25 @@ function waitForSsh(target: DockerSshRelayTarget): void { ) } -export function dockerSshRelayRepoSentinel(target: DockerSshRelayTarget, repoPath: string): string { - return `${target.containerName}:${repoPath}` -} - -function seedRemoteRepo(target: DockerSshRelayTarget, repoPath: string): void { - const sentinel = dockerSshRelayRepoSentinel(target, repoPath) - execDockerSshRelayTargetCommand( +function seedRemoteRepo(target: DockerSshHostTarget, repoPath: string): void { + const sentinel = `${target.containerName}:${repoPath}` + execDockerSshHostTargetCommand( target, [ - `rm -rf ${shellQuote(repoPath)}`, - `mkdir -p ${shellQuote(repoPath)}`, - `cd ${shellQuote(repoPath)}`, + `rm -rf ${quotePosixShell(repoPath)}`, + `mkdir -p ${quotePosixShell(repoPath)}`, + `cd ${quotePosixShell(repoPath)}`, 'git init', 'git config user.email e2e@test.local', 'git config user.name "Orca Docker SSH E2E"', - `printf '%s\\n' ${shellQuote(sentinel)} > .orca-e2e-destination-id`, - `printf '%s\\n' ${shellQuote(`remote relay ${sentinel}`)} > README.md`, + `printf '%s\\n' ${quotePosixShell(sentinel)} > .orca-e2e-destination-id`, + `printf '%s\\n' ${quotePosixShell(`remote relay ${sentinel}`)} > README.md`, 'git add README.md .orca-e2e-destination-id', 'git commit -m initial' ].join(' && ') ) } -/** - * The fixture image ships Debian's `/etc/bash.bashrc` with the xterm title block commented out and - * an all-comments `/root/.bashrc`, so its shell never emits OSC 0. Orca derives a tab title from - * that sequence, so without this every SSH tab keeps its `Terminal N` placeholder no matter how - * healthy the shell is. Opt in from specs that assert on titles; a real user's shell sets one. - */ -export function enableDockerSshRelayTargetShellTitle(target: DockerSshRelayTarget): void { - execDockerSshRelayTargetControlCommand( - target, - `printf '%s\\n' ${shellQuote(String.raw`PS1="\[\e]0;\u@\h: \w\a\]$PS1"`)} >> /root/.bashrc` - ) -} - /** * Attempts a `direct-tcpip` channel to a closed loopback port using the host's own ssh client. * @@ -146,7 +124,7 @@ export function enableDockerSshRelayTargetShellTitle(target: DockerSshRelayTarge * reports a connect failure (port 9 is closed on any sane host), while one running * `AllowTcpForwarding no` refuses the channel itself with "administratively prohibited". */ -function probeDockerSshRelayTargetForwarding(target: DockerSshRelayTarget): string { +function probeDockerSshHostTargetForwarding(target: DockerSshHostTarget): string { const result = spawnSync( 'ssh', [ @@ -181,15 +159,15 @@ function probeDockerSshRelayTargetForwarding(target: DockerSshRelayTarget): stri * this before the app connects. Returns once a real ssh client has confirmed the refusal, so a * config that silently failed to apply surfaces here rather than as a confusing assertion later. */ -export function blockDockerSshRelayTargetTcpForwarding(target: DockerSshRelayTarget): void { - execDockerSshRelayTargetControlCommand( +export function blockDockerSshHostTargetTcpForwarding(target: DockerSshHostTarget): void { + execDockerSshHostTargetControlCommand( target, "printf '%s\\n' 'AllowTcpForwarding no' >> /etc/ssh/sshd_config; kill -HUP 1" ) const deadline = Date.now() + 60_000 let lastProbe = '' while (Date.now() < deadline) { - lastProbe = probeDockerSshRelayTargetForwarding(target) + lastProbe = probeDockerSshHostTargetForwarding(target) if (/administratively prohibited/i.test(lastProbe)) { return } @@ -200,58 +178,20 @@ export function blockDockerSshRelayTargetTcpForwarding(target: DockerSshRelayTar ) } -/** - * Makes the target unreachable over SSH while the container, its fixtures and `docker exec` stay - * up: sshd re-binds to the container's loopback, so the published port stops reaching it. Only new - * connections are refused; an established session lives until its client closes it. Returns once - * a real ssh client fails to connect, so a config that silently failed to apply surfaces here. - */ -export function refuseDockerSshRelayTargetConnections(target: DockerSshRelayTarget): void { - // Why HUP, not a restart: see blockDockerSshRelayTargetTcpForwarding — PID 1 is sshd. - execDockerSshRelayTargetControlCommand( - target, - "printf '%s\\n' 'ListenAddress 127.0.0.1' >> /etc/ssh/sshd_config; kill -HUP 1" - ) - const deadline = Date.now() + 60_000 - let lastProbe = '' - while (Date.now() < deadline) { - const result = spawnSync('ssh', sshArgs(target, 'true'), { - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'pipe'], - timeout: 15_000 - }) - lastProbe = result.stderr || result.stdout || `exit ${result.status}` - // Why the auth check: a refusal must look like a dead host, never a credential prompt. - if (result.status !== 0 && !/permission denied/i.test(lastProbe)) { - return - } - Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 500) - } - throw new Error(`sshd kept accepting connections after ListenAddress 127.0.0.1: ${lastProbe}`) -} - -export function writeDockerSshRelayTargetFile( - target: DockerSshRelayTarget, +export function writeDockerSshHostTargetFile( + target: DockerSshHostTarget, filePath: string, contents: string ): void { - execDockerSshRelayTargetCommand( + execDockerSshHostTargetCommand( target, - `printf '%s' ${shellQuote(contents)} > ${shellQuote(filePath)}` + `printf '%s' ${quotePosixShell(contents)} > ${quotePosixShell(filePath)}` ) } -/** Why not `writeDockerSshRelayTargetFile`: that one passes the contents as a shell argument, so a - * payload the size of a real repository's path list exceeds ARG_MAX before it reaches the shell. */ -export function copyFileIntoDockerSshRelayTarget( - target: DockerSshRelayTarget, - localPath: string, - remotePath: string -): void { - run('docker', ['cp', localPath, `${target.containerName}:${remotePath}`], { timeoutMs: 120_000 }) -} - -export function startDockerSshRelayTarget(testInfo: TestInfo): DockerSshRelayTarget { +export function startDockerSshHostTarget( + testInfo: Pick +): DockerSshHostTarget { const host = process.env.ORCA_E2E_SSH_TARGET_HOST?.trim() || '127.0.0.1' if (host === 'localhost' || host === '::1' || host.startsWith('127.')) { if (process.env.ORCA_E2E_SSH_TARGET_HOST) { @@ -264,7 +204,7 @@ export function startDockerSshRelayTarget(testInfo: TestInfo): DockerSshRelayTar run('ssh-keygen', ['-t', 'ed25519', '-N', '', '-f', identityFile, '-q']) const publicKey = readFileSync(`${identityFile}.pub`, 'utf8').trim() const containerName = `orca-ssh-e2e-${testInfo.workerIndex}-${Date.now()}-${randomUUID().slice(0, 8)}` - let target: DockerSshRelayTarget | null = null + let target: DockerSshHostTarget | null = null try { tryRun('docker', ['rm', '-f', containerName]) @@ -279,7 +219,7 @@ export function startDockerSshRelayTarget(testInfo: TestInfo): DockerSshRelayTar `${bindHost}::22`, '-e', `AUTHORIZED_KEY=${publicKey}`, - getDockerSshRelayImage(), + getDockerSshHostImage(), 'bash', '-lc', [ @@ -308,19 +248,17 @@ export function startDockerSshRelayTarget(testInfo: TestInfo): DockerSshRelayTar } target = { containerName, containerIp, host, identityFile, port, tempDir } waitForSsh(target) - seedRemoteRepo(target, DOCKER_SSH_RELAY_REMOTE_REPO_PATH) - seedRemoteRepo(target, DOCKER_SSH_PROXY_JUMP_REMOTE_REPO_PATH) - seedRemoteRepo(target, DOCKER_SSH_SECOND_HUB_REMOTE_REPO_PATH) + seedRemoteRepo(target, DOCKER_SSH_HOST_REMOTE_REPO_PATH) return target } catch (error) { - cleanupDockerSshRelayTarget( + cleanupDockerSshHostTarget( target ?? { containerName, containerIp: '', host, identityFile, port: 0, tempDir } ) throw error } } -export function cleanupDockerSshRelayTarget(target: DockerSshRelayTarget | null): void { +export function cleanupDockerSshHostTarget(target: DockerSshHostTarget | null): void { if (!target) { return } diff --git a/tests/e2e/helpers/host-created-terminal-retention-oracle.ts b/tests/e2e/helpers/host-created-terminal-retention-oracle.ts index 6e068575ecf..9017954168b 100644 --- a/tests/e2e/helpers/host-created-terminal-retention-oracle.ts +++ b/tests/e2e/helpers/host-created-terminal-retention-oracle.ts @@ -15,6 +15,7 @@ import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs' import os from 'node:os' import path from 'node:path' import { expect, type Page } from '@stablyai/playwright-test' +import { quotePosixShell } from '../../../src/shared/wsl-login-shell-command' export type RuntimeRpcCall = (method: string, params: unknown) => Promise @@ -90,16 +91,12 @@ export function writeRetentionFixture(directory: string): string { return fixturePath } -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - export function retentionFixtureCommand(fixturePath: string, sinkPath: string): string { const command = [process.execPath, fixturePath, sinkPath] return process.platform === 'win32' ? // PowerShell needs the call operator when the executable is quoted; cmd.exe also accepts it. `& ${command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ')}` - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } export function readSink(sinkPath: string): string { diff --git a/tests/e2e/helpers/orcad-convert-host.ts b/tests/e2e/helpers/orcad-convert-host.ts index 0d8c4eeea6d..800043b4108 100644 --- a/tests/e2e/helpers/orcad-convert-host.ts +++ b/tests/e2e/helpers/orcad-convert-host.ts @@ -8,13 +8,13 @@ import type { TestInfo } from '@stablyai/playwright-test' import type { SshTargetCreateInput } from '../../../src/shared/ssh-types' import { parseWindowsHostCellDescriptor } from '../../../src/main/ssh/ssh-windows-host-cells' import { - blockDockerSshRelayTargetTcpForwarding, - cleanupDockerSshRelayTarget, - DOCKER_SSH_RELAY_REMOTE_REPO_PATH, - execDockerSshRelayTargetCommand, - execDockerSshRelayTargetControlCommand, - startDockerSshRelayTarget -} from './docker-ssh-relay-target' + blockDockerSshHostTargetTcpForwarding, + cleanupDockerSshHostTarget, + DOCKER_SSH_HOST_REMOTE_REPO_PATH, + execDockerSshHostTargetCommand, + execDockerSshHostTargetControlCommand, + startDockerSshHostTarget +} from './docker-ssh-host-target' export const ORCAD_CONVERT_HOST_ENV = 'ORCA_E2E_ORCAD_CONVERT_HOST' @@ -50,7 +50,7 @@ echo "$killed" export function startOrcadConvertHost(source: string, testInfo: TestInfo): OrcadConvertHost { const label = `orcad convert E2E ${Date.now()}` if (source === 'docker') { - const target = startDockerSshRelayTarget(testInfo) + const target = startDockerSshHostTarget(testInfo) return { input: { label, @@ -61,14 +61,14 @@ export function startOrcadConvertHost(source: string, testInfo: TestInfo): Orcad identitiesOnly: true, relayGracePeriodSeconds: 1 }, - remoteRepoPath: DOCKER_SSH_RELAY_REMOTE_REPO_PATH, + remoteRepoPath: DOCKER_SSH_HOST_REMOTE_REPO_PATH, remoteFolderPath: '/tmp', - cleanup: () => cleanupDockerSshRelayTarget(target), - blockTcpForwarding: () => blockDockerSshRelayTargetTcpForwarding(target), - exec: (command) => execDockerSshRelayTargetCommand(target, command), + cleanup: () => cleanupDockerSshHostTarget(target), + blockTcpForwarding: () => blockDockerSshHostTargetTcpForwarding(target), + exec: (command) => execDockerSshHostTargetCommand(target, command), dropSshConnections: () => Number( - execDockerSshRelayTargetControlCommand(target, DROP_SSH_CONNECTIONS).split('\n').at(-1) + execDockerSshHostTargetControlCommand(target, DROP_SSH_CONNECTIONS).split('\n').at(-1) ), setFrozen: (frozen) => execFileSync('docker', [frozen ? 'pause' : 'unpause', target.containerName], { diff --git a/tests/e2e/helpers/paired-terminal-parking-fixture.ts b/tests/e2e/helpers/paired-terminal-parking-fixture.ts index f7632869d3e..2a0e792e435 100644 --- a/tests/e2e/helpers/paired-terminal-parking-fixture.ts +++ b/tests/e2e/helpers/paired-terminal-parking-fixture.ts @@ -1,6 +1,7 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' import os from 'node:os' import path from 'node:path' +import { quotePosixShell } from '../../../src/shared/wsl-login-shell-command' const FILL_ROWS = 6_000 const FLOOD_ROWS = 4_000 @@ -8,15 +9,11 @@ const TITLE_FRAME_INTERVAL_MS = 70 const TITLE_FRAME_COUNT = 30 const TITLE_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(fixturePath: string, marker: string): string { const command = [process.execPath, fixturePath, marker] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } export function createPairedTerminalParkingFixture(): { diff --git a/tests/e2e/helpers/remote-session-bulk-open-fixture.ts b/tests/e2e/helpers/remote-session-bulk-open-fixture.ts index 359add80b4e..8109d1de497 100644 --- a/tests/e2e/helpers/remote-session-bulk-open-fixture.ts +++ b/tests/e2e/helpers/remote-session-bulk-open-fixture.ts @@ -1,16 +1,13 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' import os from 'node:os' import path from 'node:path' - -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} +import { quotePosixShell } from '../../../src/shared/wsl-login-shell-command' function fixtureCommand(fixturePath: string, marker: string): string { const command = [process.execPath, fixturePath, marker] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } /** diff --git a/tests/e2e/helpers/ssh-remote-only-browser-fixture.ts b/tests/e2e/helpers/ssh-remote-only-browser-fixture.ts index d6c7ad440aa..e03746440f8 100644 --- a/tests/e2e/helpers/ssh-remote-only-browser-fixture.ts +++ b/tests/e2e/helpers/ssh-remote-only-browser-fixture.ts @@ -1,10 +1,10 @@ import { - execDockerSshRelayTargetCommand, - execDockerSshRelayTargetControlCommand, - shellQuote, - writeDockerSshRelayTargetFile, - type DockerSshRelayTarget -} from './docker-ssh-relay-target' + execDockerSshHostTargetCommand, + execDockerSshHostTargetControlCommand, + writeDockerSshHostTargetFile, + type DockerSshHostTarget +} from './docker-ssh-host-target' +import { quotePosixShell } from '../../../src/shared/wsl-login-shell-command' /** * An HTTP fixture that exists only inside the SSH container, on a hostname only the @@ -59,17 +59,17 @@ const SERVER_SOURCE = [ ].join('\n') /** Publishes the remote-only origin inside the container and waits for it to accept sockets. */ -export function startSshRemoteOnlyBrowserFixture(target: DockerSshRelayTarget): void { - execDockerSshRelayTargetCommand( +export function startSshRemoteOnlyBrowserFixture(target: DockerSshHostTarget): void { + execDockerSshHostTargetCommand( target, - `grep -q ${shellQuote(SSH_REMOTE_ONLY_HOST)} /etc/hosts || printf '127.0.0.1 %s\\n' ${shellQuote( + `grep -q ${quotePosixShell(SSH_REMOTE_ONLY_HOST)} /etc/hosts || printf '127.0.0.1 %s\\n' ${quotePosixShell( SSH_REMOTE_ONLY_HOST )} >> /etc/hosts` ) - writeDockerSshRelayTargetFile(target, SERVER_PATH, SERVER_SOURCE) - execDockerSshRelayTargetCommand( + writeDockerSshHostTargetFile(target, SERVER_PATH, SERVER_SOURCE) + execDockerSshHostTargetCommand( target, - `rm -f ${shellQuote(REQUEST_LOG_PATH)}; nohup node ${shellQuote( + `rm -f ${quotePosixShell(REQUEST_LOG_PATH)}; nohup node ${quotePosixShell( SERVER_PATH )} >/tmp/sta-4150-ssh-browser-fixture.log 2>&1 /dev/null || true` + `cat ${quotePosixShell(REQUEST_LOG_PATH)} 2>/dev/null || true` ) return contents .split('\n') .filter((line) => line.trim().length > 0) .map((line) => JSON.parse(line) as SshRemoteOnlyRequest) } - -/** - * Kills the container's established SSH sessions without touching the listener, so the - * transport really dies and the same target can be reconnected. - * - * `pgrep -f '^sshd: '` matches only accepted-connection processes: the daemon's own command - * line is `/usr/sbin/sshd -D -e`, and the shell running this command starts with `bash`. - */ -export function killSshRelayTargetTransport(target: DockerSshRelayTarget): number { - const killed = execDockerSshRelayTargetControlCommand( - target, - "pids=$(pgrep -f '^sshd: ' || true); for pid in $pids; do kill -9 $pid || true; done; printf '%s' \"$(printf '%s\\n' $pids | grep -c . || true)\"" - ) - return Number(killed.trim() || '0') -} diff --git a/tests/e2e/helpers/startup-exec-readiness-oracle.ts b/tests/e2e/helpers/startup-exec-readiness-oracle.ts index 7aa56e38863..3bb43862825 100644 --- a/tests/e2e/helpers/startup-exec-readiness-oracle.ts +++ b/tests/e2e/helpers/startup-exec-readiness-oracle.ts @@ -10,6 +10,7 @@ import { toWebTerminalSurfaceTabId } from '../../../src/shared/terminal-surface- import { expect } from './orca-app' import { getTerminalContent, waitForActivePanePtyId } from './terminal' import { readFreshTerminalInventory } from './terminal-inventory-observation' +import { quotePosixShell } from '../../../src/shared/wsl-login-shell-command' const RECOVERY_DEADLINE_MS = 8_000 @@ -39,8 +40,8 @@ function execProfileContents( const guard = `ORCA_STA4067_EXEC_${runId.replaceAll(/[^A-Za-z0-9_]/g, '_')}` const barrierScript = barrier ? [ - `: > ${shellQuote(barrier.startedPath)}`, - `while [[ ! -e ${shellQuote(barrier.releasePath)} ]]; do sleep 0.02; done` + `: > ${quotePosixShell(barrier.startedPath)}`, + `while [[ ! -e ${quotePosixShell(barrier.releasePath)} ]]; do sleep 0.02; done` ].join('\n ') : '' return `if [[ -z "\${${guard}:-}" ]]; then @@ -59,10 +60,6 @@ export function zshExecProfileContents(runId: string, barrier?: BashExecProfileB return execProfileContents(runId, ZSH_EXEC_LINE, barrier) } -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function splitMarker(marker: string): [string, string] { const midpoint = Math.floor(marker.length / 2) return [marker.slice(0, midpoint), marker.slice(midpoint)] @@ -70,7 +67,7 @@ function splitMarker(marker: string): [string, string] { function markerCommand(marker: string): string { const [left, right] = splitMarker(marker) - return `printf '%s%s\\n' ${shellQuote(left)} ${shellQuote(right)}` + return `printf '%s%s\\n' ${quotePosixShell(left)} ${quotePosixShell(right)}` } function count(text: string, marker: string): number { @@ -150,7 +147,7 @@ export async function createStartupExecTerminal( ): Promise { const startupMarker = `STA4067_STARTUP_READY_${runId}` const command = [ - `printf '%s|%s\\n' "$$" "$(tty)" > ${shellQuote(ledgerPath)}`, + `printf '%s|%s\\n' "$$" "$(tty)" > ${quotePosixShell(ledgerPath)}`, markerCommand(startupMarker) ].join('; ') const created = await callStartupExecRuntime<{ diff --git a/tests/e2e/helpers/terminal-workspace-root-link.ts b/tests/e2e/helpers/terminal-workspace-root-link.ts index 2798cd458fa..c59478f0e5b 100644 --- a/tests/e2e/helpers/terminal-workspace-root-link.ts +++ b/tests/e2e/helpers/terminal-workspace-root-link.ts @@ -1,7 +1,7 @@ import type { Page, TestInfo } from '@stablyai/playwright-test' import { expect } from './orca-app' -import { shellQuote } from './docker-ssh-relay-target' import { focusActiveTerminalInput, getTerminalContent } from './terminal' +import { quotePosixShell } from '../../../src/shared/wsl-login-shell-command' export async function openTerminalWorkspaceRootLink( page: Page, @@ -13,7 +13,7 @@ export async function openTerminalWorkspaceRootLink( await focusActiveTerminalInput(page) const readyMarker = `ROOT_LINK_PRINTED_${Date.now()}` await page.keyboard.insertText( - String.raw`printf '\n%s\n%s\n' ${shellQuote(rootPath)} ${shellQuote(readyMarker)}` + String.raw`printf '\n%s\n%s\n' ${quotePosixShell(rootPath)} ${quotePosixShell(readyMarker)}` ) await page.keyboard.press('Enter') await expect diff --git a/tests/e2e/host-parked-pane-remote-viewer.spec.ts b/tests/e2e/host-parked-pane-remote-viewer.spec.ts index f2f8dd7fb39..de722116b12 100644 --- a/tests/e2e/host-parked-pane-remote-viewer.spec.ts +++ b/tests/e2e/host-parked-pane-remote-viewer.spec.ts @@ -46,6 +46,7 @@ import { } from './helpers/paired-electron-client' import { focusActiveTerminalInput } from './helpers/terminal' import { parkHiddenTabBehindDecoy } from './helpers/terminal-hidden-parking' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const PARK_DELAY_MS = 30_000 const PAINT_BUDGET_MS = 20_000 @@ -81,15 +82,11 @@ test.afterAll(() => { // Why: the HOST must park quickly too, so its launch env carries the override. test.use({ orcaAppExtraEnv: { ORCA_E2E_TERMINAL_PARKING_DELAY_MS: String(PARK_DELAY_MS) } }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(sinkPath: string): string { const command = [process.execPath, fixturePath, sinkPath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } function readSink(sinkPath: string): string { diff --git a/tests/e2e/paired-remote-terminal-client-restart-survival.spec.ts b/tests/e2e/paired-remote-terminal-client-restart-survival.spec.ts index 0374e7b8316..707c9c9946f 100644 --- a/tests/e2e/paired-remote-terminal-client-restart-survival.spec.ts +++ b/tests/e2e/paired-remote-terminal-client-restart-survival.spec.ts @@ -39,6 +39,7 @@ import { type PairedElectronClient } from './helpers/paired-electron-client' import { waitForActiveWorktree, waitForSessionReady } from './helpers/store' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' /** What a user would accept for "my terminal is back" after reopening the app. */ const RESTORE_BUDGET_MS = 60_000 @@ -72,15 +73,11 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(sinkPath: string): string { const command = [process.execPath, fixturePath, sinkPath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } function readSinkLines(sinkPath: string): string[] { diff --git a/tests/e2e/paired-remote-terminal-host-quit-reconnect-input.spec.ts b/tests/e2e/paired-remote-terminal-host-quit-reconnect-input.spec.ts index 5bd8d490682..cb02e03d678 100644 --- a/tests/e2e/paired-remote-terminal-host-quit-reconnect-input.spec.ts +++ b/tests/e2e/paired-remote-terminal-host-quit-reconnect-input.spec.ts @@ -30,6 +30,7 @@ import { holdWindowGraphPublication, releaseWindowGraphPublication } from './helpers/hold-window-graph-publication' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-paired-host-quit-input-')) const fixturePath = path.join(scratch, 'paired-host-quit-terminal.mjs') @@ -59,16 +60,12 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(sinkPath: string): string { const command = [process.execPath, fixturePath, sinkPath] return process.platform === 'win32' ? // Why: the isolated Windows profile starts PowerShell, which parses a leading quoted path as a string, not a command. `& ${command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ')}` - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } function seededRepoPathOrSkip(): string { diff --git a/tests/e2e/paired-remote-terminal-host-restart-background-sync.spec.ts b/tests/e2e/paired-remote-terminal-host-restart-background-sync.spec.ts index b895170529f..9e1c1de712c 100644 --- a/tests/e2e/paired-remote-terminal-host-restart-background-sync.spec.ts +++ b/tests/e2e/paired-remote-terminal-host-restart-background-sync.spec.ts @@ -16,6 +16,7 @@ import { } from './helpers/paired-electron-client' import { attachRepoAndOpenTerminal, createRestartSession } from './helpers/orca-restart' import { waitForTabParked } from './helpers/terminal-hidden-parking' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const PARK_DELAY_MS = 2_000 const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-paired-host-restart-background-')) @@ -47,15 +48,11 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(sinkPath: string): string { const command = [process.execPath, fixturePath, sinkPath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } function seededRepoPathOrSkip(): string { diff --git a/tests/e2e/paired-remote-terminal-initial-snapshot-scrollback.spec.ts b/tests/e2e/paired-remote-terminal-initial-snapshot-scrollback.spec.ts index e868149db8b..86126bc054b 100644 --- a/tests/e2e/paired-remote-terminal-initial-snapshot-scrollback.spec.ts +++ b/tests/e2e/paired-remote-terminal-initial-snapshot-scrollback.spec.ts @@ -25,6 +25,7 @@ import { readPairedPaneContent, waitForPairedPaneMarker } from './helpers/paired-host-terminal' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const LINE_COUNT = 300 const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-initial-scrollback-')) @@ -48,7 +49,7 @@ function fixtureCommand(): string { const command = [process.execPath, fixturePath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map((value) => `'${value.replaceAll("'", `'\\''`)}'`).join(' ') + : command.map(quotePosixShell).join(' ') } test('a desktop client opening a remote terminal receives its scrollback, not just the screen', async ({ diff --git a/tests/e2e/paired-remote-terminal-lossy-initial-snapshot.spec.ts b/tests/e2e/paired-remote-terminal-lossy-initial-snapshot.spec.ts index 0747e3e6bea..48cb5c18d2e 100644 --- a/tests/e2e/paired-remote-terminal-lossy-initial-snapshot.spec.ts +++ b/tests/e2e/paired-remote-terminal-lossy-initial-snapshot.spec.ts @@ -9,6 +9,7 @@ import { createRuntimeDesktopPairingOffer, launchPairedElectronClient } from './helpers/paired-electron-client' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-paired-lossy-snapshot-')) const fixturePath = path.join(scratch, 'lossy-snapshot-terminal.mjs') @@ -29,15 +30,11 @@ test.use({ orcaAppExtraEnv: { ORCA_E2E_FORCE_REMOTE_TERMINAL_INITIAL_SNAPSHOT_TRUNCATED: '1' } }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(marker: string): string { const command = [process.execPath, fixturePath, marker] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } async function callRuntime( diff --git a/tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts b/tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts index fda18fc74c6..1cb35438189 100644 --- a/tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts +++ b/tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts @@ -17,6 +17,7 @@ import { } from './helpers/paired-electron-client' import { getTerminalContent, waitForActivePanePtyId } from './helpers/terminal' import { readFreshTerminalInventory } from './helpers/terminal-inventory-observation' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-paired-materialize-')) const fixturePath = path.join(scratch, 'materialize-terminal.mjs') @@ -49,15 +50,11 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(): string { const command = [process.execPath, fixturePath, processedInputPath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } async function callRuntime( diff --git a/tests/e2e/paired-remote-terminal-parked-reveal-interactivity.spec.ts b/tests/e2e/paired-remote-terminal-parked-reveal-interactivity.spec.ts index 7c413c06126..1b9e87d50b2 100644 --- a/tests/e2e/paired-remote-terminal-parked-reveal-interactivity.spec.ts +++ b/tests/e2e/paired-remote-terminal-parked-reveal-interactivity.spec.ts @@ -37,6 +37,7 @@ import { } from './helpers/paired-electron-client' import { focusActiveTerminalInput } from './helpers/terminal' import { waitForTabParked } from './helpers/terminal-hidden-parking' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const PARK_DELAY_MS = 2_000 const LIVE_PAINT_BUDGET_MS = 12_000 @@ -75,15 +76,11 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(sinkPath: string): string { const command = [process.execPath, fixturePath, sinkPath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } function readSink(sinkPath: string): string { diff --git a/tests/e2e/paired-remote-terminal-parked-scrollback-restart.spec.ts b/tests/e2e/paired-remote-terminal-parked-scrollback-restart.spec.ts index 608c31111af..b91818bff36 100644 --- a/tests/e2e/paired-remote-terminal-parked-scrollback-restart.spec.ts +++ b/tests/e2e/paired-remote-terminal-parked-scrollback-restart.spec.ts @@ -55,6 +55,7 @@ import { import { focusActiveTerminalInput } from './helpers/terminal' import { waitForTabParked } from './helpers/terminal-hidden-parking' import { readPersistedProfileState } from './helpers/persisted-profile-state' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const PARK_DELAY_MS = 2_000 const PAINT_BUDGET_MS = 30_000 @@ -88,7 +89,7 @@ function fixtureCommand(): string { const command = [process.execPath, fixturePath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map((value) => `'${value.replaceAll("'", `'\\''`)}'`).join(' ') + : command.map(quotePosixShell).join(' ') } type OnDiskPartitionReading = { diff --git a/tests/e2e/paired-remote-terminal-parked-scrollback-survives.spec.ts b/tests/e2e/paired-remote-terminal-parked-scrollback-survives.spec.ts index e7ca5fc566a..cc8e62de335 100644 --- a/tests/e2e/paired-remote-terminal-parked-scrollback-survives.spec.ts +++ b/tests/e2e/paired-remote-terminal-parked-scrollback-survives.spec.ts @@ -49,6 +49,7 @@ import { } from './helpers/paired-host-terminal' import { focusActiveTerminalInput } from './helpers/terminal' import { waitForTabParked } from './helpers/terminal-hidden-parking' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const PARK_DELAY_MS = 2_000 const PAINT_BUDGET_MS = 30_000 @@ -81,15 +82,11 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(): string { const command = [process.execPath, fixturePath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } type ParkKind = 'ordinary' | 'force' diff --git a/tests/e2e/paired-remote-terminal-probe-gap-recovery.spec.ts b/tests/e2e/paired-remote-terminal-probe-gap-recovery.spec.ts index 9cd76f0d3e5..81335f1b991 100644 --- a/tests/e2e/paired-remote-terminal-probe-gap-recovery.spec.ts +++ b/tests/e2e/paired-remote-terminal-probe-gap-recovery.spec.ts @@ -14,6 +14,7 @@ import { launchPairedWebClient } from './helpers/paired-electron-client' import { getTerminalContent, waitForActivePanePtyId } from './helpers/terminal' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-paired-probe-gap-')) const fixturePath = path.join(scratch, 'probe-gap-terminal.mjs') @@ -44,15 +45,11 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(): string { const command = [process.execPath, fixturePath, processedInputPath] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } async function callRuntime(page: Page, method: string, params: unknown): Promise { diff --git a/tests/e2e/paired-remote-terminal-truncated-tail-first-paint.spec.ts b/tests/e2e/paired-remote-terminal-truncated-tail-first-paint.spec.ts index 79c7145cd22..aeed931b7e8 100644 --- a/tests/e2e/paired-remote-terminal-truncated-tail-first-paint.spec.ts +++ b/tests/e2e/paired-remote-terminal-truncated-tail-first-paint.spec.ts @@ -11,6 +11,7 @@ import { launchPairedWebClient } from './helpers/paired-electron-client' import { getTerminalContent, waitForActivePanePtyId } from './helpers/terminal' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const RETENTION_PARK_DELAY_MS = 100 const scratch = mkdtempSync(path.join(os.tmpdir(), 'orca-paired-truncated-tail-')) @@ -46,15 +47,11 @@ test.use({ } }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(marker: string): string { const command = [process.execPath, fixturePath, marker] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } async function callRuntime(page: Page, method: string, params: unknown): Promise { diff --git a/tests/e2e/remote-agent-session-focus-authority.spec.ts b/tests/e2e/remote-agent-session-focus-authority.spec.ts index f8f9facd446..a1594f34fa3 100644 --- a/tests/e2e/remote-agent-session-focus-authority.spec.ts +++ b/tests/e2e/remote-agent-session-focus-authority.spec.ts @@ -9,6 +9,7 @@ import { } from './helpers/paired-electron-client' import { toWebTerminalSurfaceTabId } from '../../src/shared/terminal-surface-id' import type { RuntimeTerminalSummary } from '../../src/shared/runtime-types' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' type ClientMirror = { activeTabId: string | null @@ -45,15 +46,11 @@ test.afterAll(() => { rmSync(scratch, { recursive: true, force: true }) }) -function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'` -} - function fixtureCommand(scriptPath: string, ...args: string[]): string { const command = [process.execPath, scriptPath, ...args] return process.platform === 'win32' ? command.map((value) => `"${value.replaceAll('"', '""')}"`).join(' ') - : command.map(shellQuote).join(' ') + : command.map(quotePosixShell).join(' ') } function countAgentSpawns(): number { diff --git a/tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts b/tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts index b0e782689e8..a2099fee423 100644 --- a/tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts +++ b/tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts @@ -3,18 +3,17 @@ import { readFileSync, writeFileSync } from 'node:fs' import path from 'node:path' import { Client } from 'ssh2' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' -import type { TestInfo } from '@stablyai/playwright-test' import type { SshConnection } from '../../src/main/ssh/ssh-connection' import type { SshProviderEpoch } from '../../src/shared/ssh-types' -import { ensureDockerSshRelayImage } from './helpers/docker-ssh-relay-image' +import { ensureDockerSshHostImage } from './helpers/docker-ssh-host-image' import { - cleanupDockerSshRelayTarget, - execDockerSshRelayTargetCommand, - shellQuote, - startDockerSshRelayTarget, - type DockerSshRelayTarget -} from './helpers/docker-ssh-relay-target' + cleanupDockerSshHostTarget, + execDockerSshHostTargetCommand, + startDockerSshHostTarget, + type DockerSshHostTarget +} from './helpers/docker-ssh-host-target' import { resolveSshBrowserNetworkExecutionRoute } from '../../src/main/browser/ssh-browser-network-execution-route' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const runDocker = process.env.ORCA_RUN_DOCKER_SSH_BROWSER_E2E === '1' const executionHost = { @@ -25,13 +24,13 @@ const executionHost = { } describe.runIf(runDocker)('SSH browser network execution route Docker journey', () => { - let target: DockerSshRelayTarget | null = null + let target: DockerSshHostTarget | null = null let client: Client | null = null beforeAll(async () => { - ensureDockerSshRelayImage(process.cwd()) - target = startDockerSshRelayTarget({ workerIndex: 0 } as TestInfo) - execDockerSshRelayTargetCommand( + ensureDockerSshHostImage(process.cwd()) + target = startDockerSshHostTarget({ workerIndex: 0 }) + execDockerSshHostTargetCommand( target, "grep -q 'remote-only.internal' /etc/hosts || printf '127.0.0.1 remote-only.internal\\n' >> /etc/hosts" ) @@ -39,9 +38,9 @@ describe.runIf(runDocker)('SSH browser network execution route Docker journey', "const http=require('http')", "http.createServer((_request,response)=>response.end('sta-4150-remote-only')).listen(18080,'127.0.0.1')" ].join(';') - execDockerSshRelayTargetCommand( + execDockerSshHostTargetCommand( target, - `nohup node -e ${shellQuote(server)} >/tmp/sta-4150-http.log 2>&1 /tmp/sta-4150-http.log 2>&1 {socket.destroy();if(Date.now()>=deadline)process.exit(1);setTimeout(probe,25)})}", 'probe()' ].join(';') - execDockerSshRelayTargetCommand(target, `node -e ${shellQuote(waitForServer)}`) + execDockerSshHostTargetCommand(target, `node -e ${quotePosixShell(waitForServer)}`) client = new Client() await new Promise((resolve, reject) => { client!.once('ready', resolve) @@ -70,7 +69,7 @@ describe.runIf(runDocker)('SSH browser network execution route Docker journey', if (client) { client.end() } - cleanupDockerSshRelayTarget(target) + cleanupDockerSshHostTarget(target) }) it('resolves and connects the exact remote-only domain on the SSH host', async () => { diff --git a/tests/e2e/ssh-orcad-browser-drop-owner.spec.ts b/tests/e2e/ssh-orcad-browser-drop-owner.spec.ts index 471646d634d..465e7c4b980 100644 --- a/tests/e2e/ssh-orcad-browser-drop-owner.spec.ts +++ b/tests/e2e/ssh-orcad-browser-drop-owner.spec.ts @@ -9,7 +9,6 @@ import { convertAndRetain, serverCall } from './helpers/orcad-convert-flow' import { waitForSessionReady } from './helpers/store' import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' import { openFileExplorer } from './helpers/file-explorer' -import { shellQuote } from './helpers/docker-ssh-relay-target' import { startBrowserSplitPageServer } from './helpers/browser-split-page-server' import { navigateGuest, @@ -18,6 +17,7 @@ import { } from './helpers/browser-split-guest-probes' import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' import { getWorktreeHostIdentity } from '../../src/shared/worktree/host-qualified-identity' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs owned Docker host') @@ -87,10 +87,10 @@ test('managed Files drag cannot load a desktop same-path HTML file', async ({ throw new Error('Missing owned host control') } host.exec( - `mkdir -p ${shellQuote(path.dirname(testRepoPath))} && git clone --quiet ${shellQuote(host.remoteRepoPath)} ${shellQuote(testRepoPath)} && printf '%s' 'REMOTE_BROWSER_DROP_OWNER' > ${shellQuote(filePath)}` + `mkdir -p ${quotePosixShell(path.dirname(testRepoPath))} && git clone --quiet ${quotePosixShell(host.remoteRepoPath)} ${quotePosixShell(testRepoPath)} && printf '%s' 'REMOTE_BROWSER_DROP_OWNER' > ${quotePosixShell(filePath)}` ) host.exec( - `printf '%s' REMOTE_DROP_READY > ${shellQuote(path.join(testRepoPath, 'REMOTE_DROP_READY.txt'))}` + `printf '%s' REMOTE_DROP_READY > ${quotePosixShell(path.join(testRepoPath, 'REMOTE_DROP_READY.txt'))}` ) const first = await session.launch() app = first.app diff --git a/tests/e2e/ssh-orcad-browser-routing.spec.ts b/tests/e2e/ssh-orcad-browser-routing.spec.ts index a3af5dc0fec..8eceb449795 100644 --- a/tests/e2e/ssh-orcad-browser-routing.spec.ts +++ b/tests/e2e/ssh-orcad-browser-routing.spec.ts @@ -5,10 +5,10 @@ import { expect, test } from './helpers/orca-app' import { createRestartSession } from './helpers/orca-restart' import { waitForSessionReady, waitForStartupWorktreeRefresh } from './helpers/store' import { - cleanupDockerSshRelayTarget, - DOCKER_SSH_RELAY_REMOTE_REPO_PATH, - startDockerSshRelayTarget -} from './helpers/docker-ssh-relay-target' + cleanupDockerSshHostTarget, + DOCKER_SSH_HOST_REMOTE_REPO_PATH, + startDockerSshHostTarget +} from './helpers/docker-ssh-host-target' import { seedRelayEraProfile } from './helpers/orcad-upgrade-profile' import { startSshRemoteOnlyBrowserFixture, @@ -58,7 +58,7 @@ async function guestMarker(page: Page, tabId: string): Promise { test('retained and new browser tabs keep SSH routing and login cookies after managed conversion', async (// oxlint-disable-next-line no-empty-pattern -- Owns app launch. {}, testInfo) => { test.setTimeout(5 * 60_000) - const target = startDockerSshRelayTarget(testInfo) + const target = startDockerSshHostTarget(testInfo) const scratch = createRetentionFixtureDirectory() const template = path.join(scratch, 'template') const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: template }) @@ -82,7 +82,7 @@ test('retained and new browser tabs keep SSH routing and login cookies after man identitiesOnly: true, relayGracePeriodSeconds: 1 }, - { repoPath: DOCKER_SSH_RELAY_REMOTE_REPO_PATH, folderPath: '/tmp' } + { repoPath: DOCKER_SSH_HOST_REMOTE_REPO_PATH, folderPath: '/tmp' } ) const launched = await session.launch() app = launched.app @@ -366,7 +366,7 @@ test('retained and new browser tabs keep SSH routing and login cookies after man await session.close(app) } await session.dispose() - cleanupDockerSshRelayTarget(target) + cleanupDockerSshHostTarget(target) rmSync(scratch, { recursive: true, force: true }) } }) diff --git a/tests/e2e/ssh-orcad-browser-service-status.spec.ts b/tests/e2e/ssh-orcad-browser-service-status.spec.ts index cd11f50de70..414f427655d 100644 --- a/tests/e2e/ssh-orcad-browser-service-status.spec.ts +++ b/tests/e2e/ssh-orcad-browser-service-status.spec.ts @@ -5,10 +5,10 @@ import { expect, test } from './helpers/orca-app' import { createRestartSession } from './helpers/orca-restart' import { waitForSessionReady } from './helpers/store' import { - cleanupDockerSshRelayTarget, - DOCKER_SSH_RELAY_REMOTE_REPO_PATH, - startDockerSshRelayTarget -} from './helpers/docker-ssh-relay-target' + cleanupDockerSshHostTarget, + DOCKER_SSH_HOST_REMOTE_REPO_PATH, + startDockerSshHostTarget +} from './helpers/docker-ssh-host-target' import { seedRelayEraProfile } from './helpers/orcad-upgrade-profile' import { startSshRemoteOnlyBrowserFixture, @@ -23,7 +23,7 @@ test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs Docker an test('an unavailable browser on a responding managed host does not report a server outage', async (// oxlint-disable-next-line no-empty-pattern -- Owns app launch. {}, testInfo) => { test.setTimeout(5 * 60_000) - const target = startDockerSshRelayTarget(testInfo) + const target = startDockerSshHostTarget(testInfo) const scratch = createRetentionFixtureDirectory() const template = path.join(scratch, 'template') const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: template }) @@ -47,7 +47,7 @@ test('an unavailable browser on a responding managed host does not report a serv identitiesOnly: true, relayGracePeriodSeconds: 1 }, - { repoPath: DOCKER_SSH_RELAY_REMOTE_REPO_PATH, folderPath: '/tmp' } + { repoPath: DOCKER_SSH_HOST_REMOTE_REPO_PATH, folderPath: '/tmp' } ) const launched = await session.launch() app = launched.app @@ -168,7 +168,7 @@ test('an unavailable browser on a responding managed host does not report a serv await session.close(app) } await session.dispose() - cleanupDockerSshRelayTarget(target) + cleanupDockerSshHostTarget(target) rmSync(scratch, { recursive: true, force: true }) } }) diff --git a/tests/e2e/ssh-orcad-idle-exit.spec.ts b/tests/e2e/ssh-orcad-idle-exit.spec.ts index cdfb78e060a..12aa5424cfd 100644 --- a/tests/e2e/ssh-orcad-idle-exit.spec.ts +++ b/tests/e2e/ssh-orcad-idle-exit.spec.ts @@ -16,12 +16,12 @@ import { createRestartSession } from './helpers/orca-restart' import { reconnect } from './helpers/orcad-convert-flow' import { ORCAD_CONVERT_HOST_ENV } from './helpers/orcad-convert-host' import { - cleanupDockerSshRelayTarget, - DOCKER_SSH_RELAY_REMOTE_REPO_PATH, - execDockerSshRelayTargetCommand, - startDockerSshRelayTarget, - type DockerSshRelayTarget -} from './helpers/docker-ssh-relay-target' + cleanupDockerSshHostTarget, + DOCKER_SSH_HOST_REMOTE_REPO_PATH, + execDockerSshHostTargetCommand, + startDockerSshHostTarget, + type DockerSshHostTarget +} from './helpers/docker-ssh-host-target' import { ORCAD_E2E_IDLE_TIMEOUT_ENV } from '../../src/shared/orcad-idle-exit' const HOST = process.env[ORCAD_CONVERT_HOST_ENV] @@ -31,8 +31,8 @@ const IDLE_TIMEOUT_MS = 15_000 const RECORD = '/root/.orca/orcad-idle-stop.json' /** PIDs of running orcad slots; empty once every slot has exited. */ -function runningOrcadPids(target: DockerSshRelayTarget): string[] { - return execDockerSshRelayTargetCommand( +function runningOrcadPids(target: DockerSshHostTarget): string[] { + return execDockerSshHostTargetCommand( target, 'for f in /root/.orca-remote/orcad-*/.orcad-pid; do pid=$(cat "$f" 2>/dev/null) && ' + 'kill -0 "$pid" 2>/dev/null && echo "$pid"; done; true' @@ -42,8 +42,8 @@ function runningOrcadPids(target: DockerSshRelayTarget): string[] { .filter(Boolean) } -function readIdleStopRecord(target: DockerSshRelayTarget): unknown { - const raw = execDockerSshRelayTargetCommand(target, `cat ${RECORD} 2>/dev/null || true`).trim() +function readIdleStopRecord(target: DockerSshHostTarget): unknown { + const raw = execDockerSshHostTargetCommand(target, `cat ${RECORD} 2>/dev/null || true`).trim() return raw ? JSON.parse(raw) : null } @@ -54,7 +54,7 @@ test('a managed orcad stops after idling and starts again on the next connect', `Set ${ORCAD_CONVERT_HOST_ENV}=docker and ORCA_E2E_ORCAD_CONVERT_TEMPLATE` ) test.setTimeout(15 * 60_000) - const target = startDockerSshRelayTarget(testInfo) + const target = startDockerSshHostTarget(testInfo) const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE_SOURCE!, [ORCAD_E2E_IDLE_TIMEOUT_ENV]: String(IDLE_TIMEOUT_MS) @@ -80,7 +80,7 @@ test('a managed orcad stops after idling and starts again on the next connect', .catch((error: unknown) => { // The server logs what kept it up; without it a timeout explains nothing. console.error( - execDockerSshRelayTargetCommand(target, 'tail -n 40 /root/.orca-remote/orcad-*/orcad.log') + execDockerSshHostTargetCommand(target, 'tail -n 40 /root/.orca-remote/orcad-*/orcad.log') ) throw error }) @@ -111,13 +111,13 @@ test('a managed orcad stops after idling and starts again on the next connect', await session.close(app) } await session.dispose() - cleanupDockerSshRelayTarget(target) + cleanupDockerSshHostTarget(target) } }) async function connectManagedHost( page: Page, - target: DockerSshRelayTarget + target: DockerSshHostTarget ): Promise<{ targetId: string; environmentId: string }> { const connected = await page.evaluate( async (input) => { @@ -157,8 +157,8 @@ async function callEnvironment(page: Page, environmentId: string, method: string } /** The bracket keeps pgrep from matching the shell that runs it. */ -function processAlive(target: DockerSshRelayTarget, marker: string): boolean { - const found = execDockerSshRelayTargetCommand( +function processAlive(target: DockerSshHostTarget, marker: string): boolean { + const found = execDockerSshHostTargetCommand( target, `pgrep -f '[s]leep ${marker}' >/dev/null && echo yes || true` ) @@ -172,7 +172,7 @@ test('a live managed process that does not answer remains unverifiable', async ( 'Requires the owned Docker SSH host and template' ) test.setTimeout(5 * 60_000) - const target = startDockerSshRelayTarget(testInfo) + const target = startDockerSshHostTarget(testInfo) const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE_SOURCE! }) let app: ElectronApplication | null = null let pausedPid: string | null = null @@ -187,7 +187,7 @@ test('a live managed process that does not answer remains unverifiable', async ( expect(pids).toHaveLength(1) const pid = pids[0] expect(pid).toMatch(/^\d+$/) - execDockerSshRelayTargetCommand(target, `kill -STOP ${pid}`) + execDockerSshHostTargetCommand(target, `kill -STOP ${pid}`) pausedPid = pid const held = await page.evaluate((targetId) => window.api.ssh.connect({ targetId }), targetId) console.log( @@ -200,7 +200,7 @@ test('a live managed process that does not answer remains unverifiable', async ( environmentId, serving: { state: 'unverifiable' } }) - execDockerSshRelayTargetCommand(target, `kill -CONT ${pid}`) + execDockerSshHostTargetCommand(target, `kill -CONT ${pid}`) pausedPid = null const recovered = JSON.parse(await reconnect(page, targetId)) console.log( @@ -215,13 +215,13 @@ test('a live managed process that does not answer remains unverifiable', async ( expect(runningOrcadPids(target)).toEqual([pid]) } finally { if (pausedPid) { - execDockerSshRelayTargetCommand(target, `kill -CONT ${pausedPid}`) + execDockerSshHostTargetCommand(target, `kill -CONT ${pausedPid}`) } if (app) { await session.close(app) } await session.dispose() - cleanupDockerSshRelayTarget(target) + cleanupDockerSshHostTarget(target) } }) @@ -232,7 +232,7 @@ test('a killed managed orcad comes back with its terminal, and starts fresh afte `Set ${ORCAD_CONVERT_HOST_ENV}=docker and ORCA_E2E_ORCAD_CONVERT_TEMPLATE` ) test.setTimeout(15 * 60_000) - const target = startDockerSshRelayTarget(testInfo) + const target = startDockerSshHostTarget(testInfo) const session = createRestartSession(testInfo, { ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE_SOURCE! }) let app: ElectronApplication | null = null try { @@ -245,19 +245,19 @@ test('a killed managed orcad comes back with its terminal, and starts fresh afte const marker = String(800_000 + Math.floor(Math.random() * 100_000)) expect( await callEnvironment(page, environmentId, 'repo.add', { - path: DOCKER_SSH_RELAY_REMOTE_REPO_PATH + path: DOCKER_SSH_HOST_REMOTE_REPO_PATH }) ).toMatchObject({ ok: true }) expect( await callEnvironment(page, environmentId, 'terminal.create', { - worktree: `path:${DOCKER_SSH_RELAY_REMOTE_REPO_PATH}`, + worktree: `path:${DOCKER_SSH_HOST_REMOTE_REPO_PATH}`, command: `exec sleep ${marker}` }) ).toMatchObject({ ok: true }) await expect.poll(() => processAlive(target, marker), { timeout: 60_000 }).toBe(true) // Killed: the daemon keeps the terminal, and the next call starts orcad, which adopts it. - execDockerSshRelayTargetCommand( + execDockerSshHostTargetCommand( target, 'kill -TERM $(cat /root/.orca-remote/orcad-*/.orcad-pid)' ) @@ -272,8 +272,8 @@ test('a killed managed orcad comes back with its terminal, and starts fresh afte expect(processAlive(target, marker)).toBe(true) // A reboot takes orcad, the daemon and its terminal; the next connect starts both fresh. - execDockerSshRelayTargetCommand(target, "pkill -KILL -f '/root/[.]orca-remote/' || true") - execDockerSshRelayTargetCommand(target, `pkill -KILL -f '[s]leep ${marker}' || true`) + execDockerSshHostTargetCommand(target, "pkill -KILL -f '/root/[.]orca-remote/' || true") + execDockerSshHostTargetCommand(target, `pkill -KILL -f '[s]leep ${marker}' || true`) await expect.poll(() => runningOrcadPids(target), { timeout: 60_000 }).toEqual([]) const connected = JSON.parse(await reconnect(page, targetId)) expect(connected).toMatchObject({ kind: 'managed', environmentId }) @@ -288,6 +288,6 @@ test('a killed managed orcad comes back with its terminal, and starts fresh afte await session.close(app) } await session.dispose() - cleanupDockerSshRelayTarget(target) + cleanupDockerSshHostTarget(target) } }) diff --git a/tests/e2e/ssh-orcad-open-in-owner.spec.ts b/tests/e2e/ssh-orcad-open-in-owner.spec.ts index c4a5695255e..6433b462c1b 100644 --- a/tests/e2e/ssh-orcad-open-in-owner.spec.ts +++ b/tests/e2e/ssh-orcad-open-in-owner.spec.ts @@ -17,9 +17,9 @@ import { seedRelayEraProfile } from './helpers/orcad-upgrade-profile' import { convertAndRetain, serverCall } from './helpers/orcad-convert-flow' import { waitForSessionReady } from './helpers/store' import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' -import { shellQuote } from './helpers/docker-ssh-relay-target' import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' import { folderWorkspaceKey } from '../../src/shared/workspace-scope' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE @@ -52,7 +52,7 @@ for (const surface of [ // This recorder writes argv/content and exits; it never opens or focuses a window. writeFileSync( recorder, - `#!/bin/sh\nprintf 'DESKTOP_PID=%s\\nARG=%s\\n' "$$" "$1" > ${shellQuote(receiptPath)}\nif [ -d "$1" ]; then cat "$1/README.md"; else cat "$1"; fi >> ${shellQuote(receiptPath)}\n` + `#!/bin/sh\nprintf 'DESKTOP_PID=%s\\nARG=%s\\n' "$$" "$1" > ${quotePosixShell(receiptPath)}\nif [ -d "$1" ]; then cat "$1/README.md"; else cat "$1"; fi >> ${quotePosixShell(receiptPath)}\n` ) chmodSync(recorder, 0o755) const receipt = (): string => (existsSync(receiptPath) ? readFileSync(receiptPath, 'utf8') : '') @@ -65,7 +65,7 @@ for (const surface of [ } const execute = host.exec execute( - `mkdir -p ${shellQuote(owned)} && git clone --quiet ${shellQuote(host.remoteRepoPath)} ${shellQuote(repoPath)} && printf '%s\\n' ${shellQuote(`# ${remoteMarker}`)} > ${shellQuote(filePath)} && mkdir -p ${shellQuote(folderPath)} && printf '%s\\n' ${shellQuote(`# ${remoteMarker}`)} > ${shellQuote(path.join(folderPath, 'README.md'))}` + `mkdir -p ${quotePosixShell(owned)} && git clone --quiet ${quotePosixShell(host.remoteRepoPath)} ${quotePosixShell(repoPath)} && printf '%s\\n' ${quotePosixShell(`# ${remoteMarker}`)} > ${quotePosixShell(filePath)} && mkdir -p ${quotePosixShell(folderPath)} && printf '%s\\n' ${quotePosixShell(`# ${remoteMarker}`)} > ${quotePosixShell(path.join(folderPath, 'README.md'))}` ) const first = await session.launch() app = first.app diff --git a/tests/e2e/ssh-orcad-terminal-root-owner.spec.ts b/tests/e2e/ssh-orcad-terminal-root-owner.spec.ts index 61c1c3b8ecd..03f83530b59 100644 --- a/tests/e2e/ssh-orcad-terminal-root-owner.spec.ts +++ b/tests/e2e/ssh-orcad-terminal-root-owner.spec.ts @@ -10,7 +10,6 @@ import { convertAndRetain, serverCall } from './helpers/orcad-convert-flow' import { waitForSessionReady } from './helpers/store' import { dismissTransientAnnouncement } from './helpers/ssh-config-host-picker' import { openFileExplorer } from './helpers/file-explorer' -import { shellQuote } from './helpers/docker-ssh-relay-target' import { openTerminalWorkspaceRootLink } from './helpers/terminal-workspace-root-link' import { toRuntimeExecutionHostId } from '../../src/shared/execution-host' import { getWorktreeHostIdentity } from '../../src/shared/worktree/host-qualified-identity' @@ -20,6 +19,7 @@ import { getTerminalContent, waitForActivePanePtyId } from './helpers/terminal' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE test.skip(!TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', 'Needs owned Docker host') @@ -45,7 +45,7 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s } const execute = host.exec execute( - `mkdir -p ${shellQuote(path.dirname(testRepoPath))} && git clone --quiet ${shellQuote(host.remoteRepoPath)} ${shellQuote(testRepoPath)} && printf 'REMOTE_COMMAND_FILE_OWNER\n' > ${shellQuote(filePath)}` + `mkdir -p ${quotePosixShell(path.dirname(testRepoPath))} && git clone --quiet ${quotePosixShell(host.remoteRepoPath)} ${quotePosixShell(testRepoPath)} && printf 'REMOTE_COMMAND_FILE_OWNER\n' > ${quotePosixShell(filePath)}` ) const first = await session.launch() app = first.app @@ -88,7 +88,7 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s 'DESKTOP_TERMINAL_DIRECTORY_OWNER\n' ) execute( - `mkdir -p ${shellQuote(markerDirectory)} && printf 'REMOTE_TERMINAL_DIRECTORY_OWNER\n' > ${shellQuote(path.join(markerDirectory, 'TERMINAL_OWNER.txt'))}` + `mkdir -p ${quotePosixShell(markerDirectory)} && printf 'REMOTE_TERMINAL_DIRECTORY_OWNER\n' > ${quotePosixShell(path.join(markerDirectory, 'TERMINAL_OWNER.txt'))}` ) await dismissTransientAnnouncement(page) const localIdentity = getWorktreeHostIdentity({ id: ids[0], hostId: 'local' }) @@ -173,11 +173,11 @@ test('a managed terminal path link cannot switch to a desktop workspace at the s .toContain('REMOTE_TERMINAL_DIRECTORY_OWNER') console.log('[actual-managed-terminal]', await getTerminalContent(page)) execute( - `mkdir -p ${shellQuote(intermediate)} && printf 'REMOTE_LINK_DIRECTORY_OWNER\n' > ${shellQuote(path.join(intermediate, 'LINK_DIRECTORY_OWNER.txt'))}` + `mkdir -p ${quotePosixShell(intermediate)} && printf 'REMOTE_LINK_DIRECTORY_OWNER\n' > ${quotePosixShell(path.join(intermediate, 'LINK_DIRECTORY_OWNER.txt'))}` ) console.log( '[actual-host-link-directory-marker]', - execute(`cat ${shellQuote(path.join(intermediate, 'LINK_DIRECTORY_OWNER.txt'))}`) + execute(`cat ${quotePosixShell(path.join(intermediate, 'LINK_DIRECTORY_OWNER.txt'))}`) ) const foreignTarget = await openTerminalWorkspaceRootLink(page, testInfo, intermediate) const popover = page.locator('[data-terminal-link-action-popover]') diff --git a/tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts b/tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts index af04ff2453b..84c66ae6915 100644 --- a/tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts +++ b/tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts @@ -4,17 +4,17 @@ import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { homedir, tmpdir } from 'node:os' import path from 'node:path' import { expect, test } from './helpers/orca-app' -import { ensureDockerSshRelayImage } from './helpers/docker-ssh-relay-image' +import { ensureDockerSshHostImage } from './helpers/docker-ssh-host-image' import { - cleanupDockerSshRelayTarget, - execDockerSshRelayTargetCommand, - shellQuote, - startDockerSshRelayTarget, - DOCKER_SSH_RELAY_REMOTE_REPO_PATH, - type DockerSshRelayTarget -} from './helpers/docker-ssh-relay-target' + cleanupDockerSshHostTarget, + execDockerSshHostTargetCommand, + startDockerSshHostTarget, + DOCKER_SSH_HOST_REMOTE_REPO_PATH, + type DockerSshHostTarget +} from './helpers/docker-ssh-host-target' import { ensureTerminalVisible, waitForSessionReady } from './helpers/store' import { ORCAD_TEMPLATE } from './helpers/orcad-managed-workspace' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' /** A recipe VM's SSH root, served by a managed orcad the first connect deploys. */ test.skip( @@ -32,11 +32,11 @@ test('adopts a recipe-provisioned SSH root without creating a linked worktree', }, testInfo) => { // The first connect deploys the VM's managed server before the root is adopted. test.setTimeout(420_000) - let target: DockerSshRelayTarget | null = null + let target: DockerSshHostTarget | null = null const sourceRepo = mkdtempSync(path.join(tmpdir(), 'orca-provisioned-root-source-')) try { - ensureDockerSshRelayImage(process.cwd()) - target = startDockerSshRelayTarget(testInfo) + ensureDockerSshHostImage(process.cwd()) + target = startDockerSshHostTarget(testInfo) const expectedRefHead = seedRecipeRepo(sourceRepo, target) await waitForSessionReady(orcaPage) const sourceRepoId = await addRecipeRepo(orcaPage, sourceRepo) @@ -80,28 +80,28 @@ test('adopts a recipe-provisioned SSH root without creating a linked worktree', const adopted = await findAdopted() await ensureTerminalVisible(orcaPage) expect(adopted).toMatchObject({ - path: DOCKER_SSH_RELAY_REMOTE_REPO_PATH, + path: DOCKER_SSH_HOST_REMOTE_REPO_PATH, isMainWorktree: true, ephemeralVmCheckoutMode: 'provisioned-root' }) // The VM's managed server holds the workspace; no relay serves it. expect(adopted?.hostId).toMatch(/^runtime:/) expect( - execDockerSshRelayTargetCommand( + execDockerSshHostTargetCommand( target, - `git -C ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)} worktree list --porcelain | grep -c '^worktree '` + `git -C ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)} worktree list --porcelain | grep -c '^worktree '` ) ).toBe('1') expect( - execDockerSshRelayTargetCommand( + execDockerSshHostTargetCommand( target, - `git -C ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)} branch --show-current` + `git -C ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)} branch --show-current` ) ).toBe(workspaceName) expect( - execDockerSshRelayTargetCommand( + execDockerSshHostTargetCommand( target, - `git -C ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)} rev-parse HEAD` + `git -C ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)} rev-parse HEAD` ) ).toBe(expectedRefHead) @@ -130,9 +130,9 @@ test('adopts a recipe-provisioned SSH root without creating a linked worktree', ) .toBe(false) const destroyed = target - expect(() => execDockerSshRelayTargetCommand(destroyed, 'true')).toThrow() + expect(() => execDockerSshHostTargetCommand(destroyed, 'true')).toThrow() } finally { - cleanupDockerSshRelayTarget(target) + cleanupDockerSshHostTarget(target) rmSync(sourceRepo, { recursive: true, force: true }) } }) @@ -151,11 +151,11 @@ async function addRecipeRepo(page: Parameters[0], re }, repoPath) } -function seedRecipeRepo(repoPath: string, target: DockerSshRelayTarget): string { +function seedRecipeRepo(repoPath: string, target: DockerSshHostTarget): string { const createScript = path.join(repoPath, 'create.sh') const destroyScript = path.join(repoPath, 'destroy.sh') // The recipe's isolated HOME must still address the engine that owns the fixture container. - const docker = `docker --config ${shellQuote(process.env.DOCKER_CONFIG ?? path.join(homedir(), '.docker'))}` + const docker = `docker --config ${quotePosixShell(process.env.DOCKER_CONFIG ?? path.join(homedir(), '.docker'))}` writeFileSync( createScript, `#!/usr/bin/env bash @@ -165,9 +165,9 @@ set -euo pipefail [ -n "\${ORCA_REPO_REF:-}" ] [ -n "\${ORCA_REPO_REF_HEAD:-}" ] [ -n "\${ORCA_REPO_BRANCH:-}" ] -${docker} exec ${shellQuote(target.containerName)} git -C ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)} cat-file -e "$ORCA_REPO_REF_HEAD^{commit}" -${docker} exec ${shellQuote(target.containerName)} git -C ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)} checkout -B "$ORCA_REPO_BRANCH" "$ORCA_REPO_REF_HEAD" >&2 -node -e 'console.log(JSON.stringify({schemaVersion:2,checkoutMode:"provisioned-root",connection:{type:"ssh",projectRoot:process.argv[1],target:{label:"Docker provisioned root",host:process.argv[2],port:Number(process.argv[3]),username:"root",identityFile:process.argv[4],identitiesOnly:true}}}))' ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)} ${shellQuote(target.host)} ${target.port} ${shellQuote(target.identityFile)} +${docker} exec ${quotePosixShell(target.containerName)} git -C ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)} cat-file -e "$ORCA_REPO_REF_HEAD^{commit}" +${docker} exec ${quotePosixShell(target.containerName)} git -C ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)} checkout -B "$ORCA_REPO_BRANCH" "$ORCA_REPO_REF_HEAD" >&2 +node -e 'console.log(JSON.stringify({schemaVersion:2,checkoutMode:"provisioned-root",connection:{type:"ssh",projectRoot:process.argv[1],target:{label:"Docker provisioned root",host:process.argv[2],port:Number(process.argv[3]),username:"root",identityFile:process.argv[4],identitiesOnly:true}}}))' ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)} ${quotePosixShell(target.host)} ${target.port} ${quotePosixShell(target.identityFile)} ` ) writeFileSync( @@ -175,7 +175,7 @@ node -e 'console.log(JSON.stringify({schemaVersion:2,checkoutMode:"provisioned-r `#!/usr/bin/env bash set -euo pipefail cat >/dev/null -${docker} rm -f ${shellQuote(target.containerName)} >/dev/null +${docker} rm -f ${quotePosixShell(target.containerName)} >/dev/null ` ) chmodSync(createScript, 0o755) @@ -202,18 +202,18 @@ ${docker} rm -f ${shellQuote(target.containerName)} >/dev/null cwd: repoPath, encoding: 'utf8' }).trim() - execDockerSshRelayTargetCommand( + execDockerSshHostTargetCommand( target, - `rm -rf ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)} && mkdir -p ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)}` + `rm -rf ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)} && mkdir -p ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)}` ) execFileSync('docker', [ 'cp', `${repoPath}${path.sep}.`, - `${target.containerName}:${DOCKER_SSH_RELAY_REMOTE_REPO_PATH}` + `${target.containerName}:${DOCKER_SSH_HOST_REMOTE_REPO_PATH}` ]) - execDockerSshRelayTargetCommand( + execDockerSshHostTargetCommand( target, - `chown -R root:root ${shellQuote(DOCKER_SSH_RELAY_REMOTE_REPO_PATH)}` + `chown -R root:root ${quotePosixShell(DOCKER_SSH_HOST_REMOTE_REPO_PATH)}` ) return expectedRefHead } diff --git a/tests/e2e/ssh-orcad-workspace-tools.spec.ts b/tests/e2e/ssh-orcad-workspace-tools.spec.ts index 7725e1fead8..deebcb46ab0 100644 --- a/tests/e2e/ssh-orcad-workspace-tools.spec.ts +++ b/tests/e2e/ssh-orcad-workspace-tools.spec.ts @@ -5,7 +5,6 @@ */ import { expect, test } from './helpers/orca-app' import { reconnect } from './helpers/orcad-convert-flow' -import { shellQuote } from './helpers/docker-ssh-relay-target' import { callEnvironment, createPairedHostTerminal } from './helpers/paired-host-terminal' import { reserveLocalPort } from './helpers/reserve-local-port' import { openTerminalWorkspaceRootLink } from './helpers/terminal-workspace-root-link' @@ -18,6 +17,7 @@ import { typeInTerminal, withManagedHost } from './helpers/orcad-managed-workspace' +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' test.skip( !ORCAD_TEMPLATE || process.env.ORCA_E2E_SSH_DOCKER !== '1', @@ -29,7 +29,7 @@ test('Quick Open finds a file in a monorepo-sized managed workspace', async (// {}, testInfo) => { test.setTimeout(10 * 60_000) await withManagedHost(testInfo, async (fixture) => { - const repo = shellQuote(fixture.host.remoteRepoPath) + const repo = quotePosixShell(fixture.host.remoteRepoPath) // ~22.6k tracked paths: past the size whose single reply once overflowed the transport (#12547). fixture.host.exec!( [ @@ -70,7 +70,7 @@ for (const acrossReconnect of [false, true]) { const script = `require('node:http').createServer((_q, r) => r.end(${JSON.stringify(marker)})).listen(${remotePort}, '127.0.0.1')` // Started from the workspace so the scan attributes it to this workspace. fixture.host.exec!( - `cd ${shellQuote(fixture.host.remoteRepoPath)} && nohup node -e ${shellQuote(script)} >/tmp/orca-http.log 2>&1 < /dev/null &` + `cd ${quotePosixShell(fixture.host.remoteRepoPath)} && nohup node -e ${quotePosixShell(script)} >/tmp/orca-http.log 2>&1 < /dev/null &` ) // The scan the Ports panel shows for a workspace on this host. await expect @@ -120,7 +120,7 @@ test('a terminal link opens an image outside the managed workspace', async (// o 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVR4AWN8z8DwnwEJMDGgAcICAO2mBAXmO4drAAAAAElFTkSuQmCC' await withManagedHost(testInfo, async (fixture) => { fixture.host.exec!( - `printf '%s' ${shellQuote(imageBase64)} | base64 -d > ${shellQuote(imagePath)}` + `printf '%s' ${quotePosixShell(imageBase64)} | base64 -d > ${quotePosixShell(imagePath)}` ) // Why /root: the default folder workspace is /tmp, which would make the image a workspace file. const { page, seeded } = await launchManagedWorkspace(fixture, { folderPath: '/root' }) @@ -154,7 +154,7 @@ test('a managed terminal reports agent status from hooks and from Pi-style title '--data-urlencode "paneKey=${ORCA_PANE_KEY}" --data-urlencode "tabId=${ORCA_TAB_ID}"', '--data-urlencode "worktreeId=${ORCA_WORKTREE_ID}" --data-urlencode "env=${ORCA_AGENT_HOOK_ENV}"', '--data-urlencode "version=${ORCA_AGENT_HOOK_VERSION}"', - `--data-urlencode ${shellQuote(`payload=${payload}`)} >/tmp/orca-hook.log 2>&1 ) &` + `--data-urlencode ${quotePosixShell(`payload=${payload}`)} >/tmp/orca-hook.log 2>&1 ) &` ].join(' ') ) await expect diff --git a/tests/e2e/terminal-node-command.ts b/tests/e2e/terminal-node-command.ts index c85dd813698..26dfd5c9688 100644 --- a/tests/e2e/terminal-node-command.ts +++ b/tests/e2e/terminal-node-command.ts @@ -1,11 +1,8 @@ import { quotePowerShellLiteral } from '../../src/shared/powershell-native-argument' - -function quotePosixShellArg(value: string): string { - return `'${value.replaceAll("'", "'\\''")}'` -} +import { quotePosixShell } from '../../src/shared/wsl-login-shell-command' function quoteTerminalArg(value: string): string { - return process.platform === 'win32' ? quotePowerShellLiteral(value) : quotePosixShellArg(value) + return process.platform === 'win32' ? quotePowerShellLiteral(value) : quotePosixShell(value) } export function nodeTerminalCommand(args: readonly string[]): string {