Files
orca/config/scripts/skill-recipe-shell.test.mjs
T
Neilandoutof-place 7b31e739d2 Fix Vercel recipe failures on stock macOS Bash (#28257)
Guard empty argument-array expansion so the bundled setup recipe runs under Bash 3.2 with unset-variable checks. Preserve exact arguments and cleanup exit behavior; normalize only test fixture line endings.

Co-authored-by: outof-place <contact@outofplace.space>
2026-10-13 18:18:50 -07:00

206 lines
7.4 KiB
JavaScript

import { execFile } from 'node:child_process'
import { existsSync } from 'node:fs'
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { delimiter, join, resolve } from 'node:path'
import { promisify } from 'node:util'
import { describe, expect, it } from 'vitest'
const run = promisify(execFile)
const referenceRoot = resolve(
import.meta.dirname,
'../../skill-guides/orca-per-workspace-env/references'
)
const vercel = await readFile(resolve(referenceRoot, 'provider-vercel.md'), 'utf8')
const ssh = await readFile(resolve(referenceRoot, 'ssh-host.md'), 'utf8')
const cleanup = vercel.match(/```bash\n(cleanup_snapshot\(\) \{[\s\S]*?\n\})\n```/u)?.[1]
async function executeShell(shell, script, env = {}) {
try {
const output = await run(shell, ['-c', script], {
env: { ...process.env, ORCA_BACKGROUND_LAUNCH: '1', ...env }
})
return { ...output, code: 0 }
} catch (error) {
return { stdout: error.stdout, stderr: error.stderr, code: error.code }
}
}
// Stock macOS Bash must run even when PATH starts with a newer Bash.
const shells = existsSync('/bin/bash') ? ['/bin/bash', 'bash'] : ['bash']
describe.skipIf(process.platform === 'win32').each(shells)(
'recipe shell examples (%s)',
(shell) => {
const runShell = (script, env) => executeShell(shell, script, env)
it.each(['base', 'auth'])('cleans the %s sandbox on failure and success', async (phase) => {
expect(cleanup).toBeDefined()
const trap = vercel.match(new RegExp(`trap 'cleanup_snapshot "\\$${phase}"' EXIT`, 'u'))?.[0]
expect(trap).toBeDefined()
expect(vercel.indexOf(trap)).toBeLessThan(
vercel.indexOf(`vercel sandbox create --name "$${phase}"`)
)
for (const exitCode of [0, 7]) {
const result = await runShell(`set -euo pipefail
${cleanup}
vercel_args=(--scope test-scope)
${phase}=unique-test-sandbox
vercel() { printf '%s\\n' "$@"; }
${trap}
exit ${exitCode}`)
expect(result.code).toBe(exitCode)
expect(result.stderr).toBe('sandbox\nremove\nunique-test-sandbox\n--scope\ntest-scope\n')
}
})
it('reports failed cleanup even after an otherwise successful snapshot', async () => {
const result = await runShell(`set -euo pipefail
${cleanup}
vercel_args=()
vercel() { return 9; }
trap 'cleanup_snapshot unique-test-sandbox' EXIT
exit 0`)
expect(result.code).toBe(1)
expect(result.stderr).toContain('Sandbox cleanup failed for unique-test-sandbox')
})
it.each([
{ scope: '', project: '' },
{ scope: 'scope with spaces *', project: '' },
{ scope: '', project: 'project with spaces ?' },
{ scope: 'scope with spaces *', project: 'project with spaces ?' }
])(
'runs the full create recipe with scope "$scope" and project "$project"',
async ({ scope, project }) => {
const result = await runCreateRecipe(shell, {
VERCEL_SCOPE: scope,
VERCEL_PROJECT: project
})
expect(result.code).toBe(0)
const output = JSON.parse(result.stdout)
expect(output.userData).toMatchObject({
provider: 'vercel-sandbox',
resourceId: 'orca-fixture-123',
snapshotId: 'snap_fixture'
})
expect(result.calls.map((args) => args.slice(0, 2))).toEqual([
['sandbox', 'create'],
['sandbox', 'exec'],
['sandbox', 'exec']
])
const expectedFlags = [
...(scope ? ['--scope', scope] : []),
...(project ? ['--project', project] : [])
]
for (const args of result.calls) {
const start = args.findIndex((arg) => arg === '--scope' || arg === '--project')
if (expectedFlags.length) {
expect(args.slice(start, start + expectedFlags.length)).toEqual(expectedFlags)
} else {
expect(start).toBe(-1)
}
expect(args).not.toContain('')
}
}
)
it('removes a failed create with no optional flags', async () => {
const result = await runCreateRecipe(shell, { VERCEL_CREATE_EXIT: '7' })
expect(result.code).toBe(7)
expect(result.calls.map((args) => args.slice(0, 2))).toEqual([
['sandbox', 'create'],
['sandbox', 'remove']
])
expect(result.calls[1]).toEqual(['sandbox', 'remove', 'orca-fixture-123'])
expect(result.stderr).not.toContain('unbound variable')
})
it('disables Git prompts when the Vercel token is absent', async () => {
const prefix = vercel.match(
/-- bash -lc 'set -euo pipefail; cd "\$ORCA_PROJECT_ROOT"; \\\n([\s\S]*?) git fetch/u
)?.[1]
expect(prefix).toBeDefined()
const result = await runShell(
`set -euo pipefail\nunset GH_TOKEN\n${prefix}\nprintf '%s' "$GIT_TERMINAL_PROMPT"`
)
expect(result.code).toBe(0)
expect(result.stdout).toBe('0')
})
it('uses host credentials and refuses unverified SSH hosts without forwarding tokens', async () => {
const script = ssh.match(/```bash\n(#!\/usr\/bin\/env bash[\s\S]*?)\n```/u)?.[1]
expect(script).toBeDefined()
const sync = script.slice(0, script.indexOf('# 2. print'))
const result = await runShell(
`ssh() { printf '%s\\n' "$@"; }
ssh_username=worker
host=example.test
ssh_port=2222
project_root='/remote/path with spaces'
repo_url=https://example.test/org/repo.git
repo_ref=main
${sync}`,
{ GH_TOKEN: 'test-token-must-not-be-forwarded' }
)
expect(result.code).toBe(0)
expect(result.stderr).toContain('StrictHostKeyChecking=yes')
expect(result.stderr).toContain('BatchMode=yes')
expect(result.stderr).not.toContain('test-token-must-not-be-forwarded')
expect(result.stderr).not.toContain('GH_TOKEN=')
expect(script).toContain('export GIT_TERMINAL_PROMPT=0')
})
}
)
async function runCreateRecipe(shell, overrides) {
const script = vercel.match(/```bash\n(#!\/usr\/bin\/env bash[\s\S]*?)\n```/u)?.[1]
expect(script).toBeDefined()
const directory = await mkdtemp(join(tmpdir(), 'orca-vercel-recipe-'))
const bin = join(directory, 'bin')
const log = join(directory, 'calls.jsonl')
try {
await mkdir(bin)
const command = join(bin, 'vercel')
await writeFile(
command,
`#!/usr/bin/env node
const { appendFileSync } = require('node:fs')
const args = process.argv.slice(2)
appendFileSync(process.env.VERCEL_FIXTURE_LOG, JSON.stringify(args) + '\\n')
if (args[1] === 'create') {
if (process.env.VERCEL_CREATE_EXIT) process.exit(Number(process.env.VERCEL_CREATE_EXIT))
console.log('https://fixture.vercel.run')
} else if (args[1] === 'exec' && args.includes('60s')) {
console.log(JSON.stringify({ userData: {} }))
}
`
)
await chmod(command, 0o755)
const result = await executeShell(
shell,
`scope="$VERCEL_SCOPE"; project="$VERCEL_PROJECT"
snapshot_id=snap_fixture; port=6768; repo_url=https://example.test/repo.git; repo_ref=main; project_root='/fixture repo'
${script}`,
{
PATH: `${bin}${delimiter}${process.env.PATH}`,
VERCEL_FIXTURE_LOG: log,
VERCEL_SCOPE: '',
VERCEL_PROJECT: '',
VERCEL_CREATE_EXIT: '',
GH_TOKEN: 'fixture-token',
ORCA_RECIPE_ID: 'fixture',
ORCA_VM_INSTANCE_ID: '123',
...overrides
}
)
const calls = (await readFile(log, 'utf8'))
.trim()
.split('\n')
.map((line) => JSON.parse(line))
return { ...result, calls }
} finally {
await rm(directory, { recursive: true, force: true })
}
}