mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
perf(cli): use packaged Node for ordinary Mac commands
This commit is contained in:
@@ -20,7 +20,6 @@ fi
|
||||
|
||||
CONTENTS="$APP_PATH/Contents"
|
||||
ELECTRON="$CONTENTS/MacOS/Orca"
|
||||
# The unpacked entrypoint runs directly in Electron's Node mode.
|
||||
CLI="$CONTENTS/Resources/app.asar.unpacked/out/cli/index.js"
|
||||
|
||||
export ORCA_NODE_OPTIONS="${NODE_OPTIONS-}"
|
||||
@@ -28,4 +27,49 @@ export ORCA_NODE_REPL_EXTERNAL_MODULE="${NODE_REPL_EXTERNAL_MODULE-}"
|
||||
unset NODE_OPTIONS
|
||||
unset NODE_REPL_EXTERNAL_MODULE
|
||||
|
||||
function plist_version() {
|
||||
local file=$1 key=$2 line found=0 remaining=256
|
||||
REPLY=
|
||||
[ -r "$file" ] || return 1
|
||||
while IFS= read -r line; do
|
||||
[ "$remaining" -gt 0 ] || return 1
|
||||
remaining=$((remaining - 1))
|
||||
if [[ $line == *"<key>$key</key>"* ]]; then
|
||||
found=1
|
||||
line=${line#*</key>}
|
||||
fi
|
||||
if [ "$found" -eq 1 ]; then
|
||||
if [[ $line =~ ^[[:space:]]*\<string\>([0-9]{1,3}\.[0-9]{1,3}(\.[0-9]{1,3})?)\</string\>[[:space:]]*$ ]]; then
|
||||
REPLY=${BASH_REMATCH[1]}
|
||||
return 0
|
||||
fi
|
||||
[[ $line =~ ^[[:space:]]*$ ]] || return 1
|
||||
fi
|
||||
done < "$file"
|
||||
return 1
|
||||
}
|
||||
|
||||
function supports_packaged_node() {
|
||||
local minimum current index required actual
|
||||
plist_version "$NODE_CONTENTS/Info.plist" LSMinimumSystemVersion || return 1
|
||||
IFS=. read -r -a minimum <<< "$REPLY"
|
||||
plist_version /System/Library/CoreServices/SystemVersion.plist ProductVersion || return 1
|
||||
IFS=. read -r -a current <<< "$REPLY"
|
||||
for index in 0 1 2; do
|
||||
required=$((10#${minimum[$index]:-0}))
|
||||
actual=$((10#${current[$index]:-0}))
|
||||
[ "$actual" -gt "$required" ] && return 0
|
||||
[ "$actual" -lt "$required" ] && return 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
NODE_CONTENTS="$CONTENTS/Helpers/Orca Agents.app/Contents"
|
||||
NODE="$NODE_CONTENTS/MacOS/Orca Agents"
|
||||
if [ -x "$NODE" ] && supports_packaged_node; then
|
||||
export ORCA_CLI_ELECTRON_EXECUTABLE="$ELECTRON"
|
||||
unset ELECTRON_RUN_AS_NODE
|
||||
exec "$NODE" "$CLI" "$@"
|
||||
fi
|
||||
|
||||
ELECTRON_RUN_AS_NODE=1 exec "$ELECTRON" "$CLI" "$@"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
import './runtime/packaged-cli-interpreter'
|
||||
import {
|
||||
findCommandSpec,
|
||||
isCommandGroup,
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { ProcessResult, ProcessSpec } from '@orca/process-host/process-spec'
|
||||
import { selectPluginTestNode } from './plugin-test-node'
|
||||
|
||||
const { runDefault, packaged } = vi.hoisted(() => {
|
||||
const packaged: { path: string | null } = { path: null }
|
||||
return { runDefault: vi.fn(), packaged }
|
||||
})
|
||||
vi.mock('@orca/process-host', () => ({ runProcess: runDefault }))
|
||||
vi.mock('../../runtime/packaged-cli-interpreter', () => ({
|
||||
getPackagedElectronExecutable: () => packaged.path
|
||||
}))
|
||||
afterEach(() => {
|
||||
packaged.path = null
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
const ORCA = { execPath: '/Applications/Orca.app/Contents/MacOS/Orca', version: 'v22.9.0' }
|
||||
|
||||
function answering(stdout: string | Error, code = 0) {
|
||||
@@ -17,9 +30,19 @@ function answering(stdout: string | Error, code = 0) {
|
||||
}
|
||||
|
||||
describe('selectPluginTestNode', () => {
|
||||
it('uses root Electron for the fallback child while the packaged CLI runs on Node', async () => {
|
||||
packaged.path = '/Applications/Orca.app/Contents/MacOS/Orca'
|
||||
runDefault.mockRejectedValue(new Error('spawn node ENOENT'))
|
||||
expect(await selectPluginTestNode()).toMatchObject({
|
||||
program: '/Applications/Orca.app/Contents/MacOS/Orca',
|
||||
env: { ELECTRON_RUN_AS_NODE: '1' },
|
||||
source: 'orca'
|
||||
})
|
||||
})
|
||||
|
||||
it('uses a new enough system node by the absolute path it reports, probed outside the plugin', async () => {
|
||||
const probe = answering('v20.6.0 /usr/local/bin/node\n')
|
||||
const node = await selectPluginTestNode({ ...ORCA, electron: '38.0.0', run: probe.run })
|
||||
const node = await selectPluginTestNode({ ...ORCA, electron: true, run: probe.run })
|
||||
expect(node).toEqual({
|
||||
program: '/usr/local/bin/node',
|
||||
env: {},
|
||||
@@ -32,7 +55,7 @@ describe('selectPluginTestNode', () => {
|
||||
|
||||
it('keeps a Windows path with spaces whole', async () => {
|
||||
const probe = answering('v22.1.0 C:\\Program Files\\nodejs\\node.exe\r\n')
|
||||
const node = await selectPluginTestNode({ ...ORCA, electron: '38.0.0', run: probe.run })
|
||||
const node = await selectPluginTestNode({ ...ORCA, electron: true, run: probe.run })
|
||||
expect(node.program).toBe('C:\\Program Files\\nodejs\\node.exe')
|
||||
})
|
||||
|
||||
@@ -42,7 +65,7 @@ describe('selectPluginTestNode', () => {
|
||||
['failing', answering('', 1)],
|
||||
['answering garbage', answering('nope')]
|
||||
])('falls back to Orca as Node when the system node is %s', async (_reason, probe) => {
|
||||
const node = await selectPluginTestNode({ ...ORCA, electron: '38.0.0', run: probe.run })
|
||||
const node = await selectPluginTestNode({ ...ORCA, electron: true, run: probe.run })
|
||||
expect(node).toEqual({
|
||||
program: ORCA.execPath,
|
||||
env: { ELECTRON_RUN_AS_NODE: '1' },
|
||||
@@ -53,7 +76,7 @@ describe('selectPluginTestNode', () => {
|
||||
|
||||
it('needs no Electron switch when the CLI itself runs on plain Node', async () => {
|
||||
const probe = answering(new Error('spawn node ENOENT'))
|
||||
const node = await selectPluginTestNode({ ...ORCA, electron: undefined, run: probe.run })
|
||||
const node = await selectPluginTestNode({ ...ORCA, electron: false, run: probe.run })
|
||||
expect(node.env).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPackagedElectronExecutable } from '../../runtime/packaged-cli-interpreter'
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@@ -21,15 +22,18 @@ type Dependencies = {
|
||||
/** The runtime this CLI runs on: Electron as Node in a packaged app, or Node in development. */
|
||||
execPath: string
|
||||
version: string
|
||||
electron: string | undefined
|
||||
electron: boolean
|
||||
}
|
||||
|
||||
const defaults = (): Dependencies => ({
|
||||
run: runProcess,
|
||||
execPath: process.execPath,
|
||||
version: process.version,
|
||||
electron: process.versions.electron
|
||||
})
|
||||
const defaults = (): Dependencies => {
|
||||
const packagedElectron = getPackagedElectronExecutable()
|
||||
return {
|
||||
run: runProcess,
|
||||
execPath: packagedElectron ?? process.execPath,
|
||||
version: process.version,
|
||||
electron: Boolean(packagedElectron || process.versions.electron)
|
||||
}
|
||||
}
|
||||
|
||||
function supports(version: string): boolean {
|
||||
const [major = 0, minor = 0] = version.replace(/^v/, '').split('.').map(Number)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { reenterPackagedElectron } from '../runtime/packaged-cli-interpreter'
|
||||
import { z } from 'zod'
|
||||
import type { CommandHandler } from '../dispatch/dispatch'
|
||||
import { printResult } from '../output/command-result-output'
|
||||
@@ -119,6 +120,7 @@ export const PROFILE_STATE_HANDLERS: Record<string, CommandHandler> = {
|
||||
rejectProfileStateRemoteSelection(flags)
|
||||
const selector = parseSelector(flags)
|
||||
const profileId = parseProfileId(flags)
|
||||
reenterPackagedElectron()
|
||||
const userDataPath = getDefaultUserDataPath()
|
||||
let result: ProfileStateRollbackResult
|
||||
if (canLaunchProfileStateRecovery()) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { reenterPackagedElectron } from './packaged-cli-interpreter'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { resolve } from 'node:path'
|
||||
import type { CommandHandler } from '../dispatch/dispatch'
|
||||
@@ -138,6 +139,7 @@ export const CORE_HANDLERS: Record<string, CommandHandler> = {
|
||||
}
|
||||
const port = getOptionalServePort(flags)
|
||||
const pairingAddressValue = flags.get('pairing-address')
|
||||
reenterPackagedElectron()
|
||||
const exitCode = await serveOrcaApp({
|
||||
json,
|
||||
port,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { reenterPackagedElectron } from './packaged-cli-interpreter'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import type { CliStatusResult, RuntimeStatus } from '../../shared/runtime-types'
|
||||
import { runtimeHostConnectionState } from '../../shared/remote-runtime/host-status/runtime-host-connection-state'
|
||||
@@ -307,6 +308,7 @@ export class RuntimeClient {
|
||||
if (this.remote) {
|
||||
return this.getCliStatus()
|
||||
}
|
||||
reenterPackagedElectron()
|
||||
return openLocalOrca(
|
||||
() => this.getCliStatus(),
|
||||
(options) => this.call<{ requested: boolean }>('desktop.openWindowInBackground', {}, options),
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import { accessSync } from 'node:fs'
|
||||
import type * as Fs from 'node:fs'
|
||||
|
||||
vi.mock('node:fs', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof Fs>()),
|
||||
accessSync: vi.fn()
|
||||
}))
|
||||
|
||||
const platform = process.platform
|
||||
const argv = process.argv
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process, 'platform', { value: platform })
|
||||
process.argv = argv
|
||||
vi.unstubAllEnvs()
|
||||
vi.restoreAllMocks()
|
||||
vi.mocked(accessSync).mockReset()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('consumes the shim marker before replacing a lifecycle command with root Electron', async () => {
|
||||
Object.defineProperty(process, 'platform', { value: 'darwin' })
|
||||
process.argv = ['/helper/node', '/app/cli.js', 'open', '--json']
|
||||
vi.stubEnv('ORCA_CLI_ELECTRON_EXECUTABLE', '/Applications/Orca.app/Contents/MacOS/Orca')
|
||||
vi.stubEnv('ORCA_NODE_OPTIONS', '--max-old-space-size=2048')
|
||||
const replaced = new Error('Process replaced')
|
||||
let invocation:
|
||||
| { file: string; args: readonly string[] | undefined; env: NodeJS.ProcessEnv | undefined }
|
||||
| undefined
|
||||
vi.spyOn(process, 'execve').mockImplementation((file, args, env) => {
|
||||
invocation = { file, args, env }
|
||||
throw replaced
|
||||
})
|
||||
vi.resetModules()
|
||||
const { reenterPackagedElectron } = await import('./packaged-cli-interpreter')
|
||||
expect(process.env.ORCA_CLI_ELECTRON_EXECUTABLE).toBeUndefined()
|
||||
expect(reenterPackagedElectron).toThrow(replaced)
|
||||
expect(invocation).toMatchObject({
|
||||
file: '/Applications/Orca.app/Contents/MacOS/Orca',
|
||||
args: ['/Applications/Orca.app/Contents/MacOS/Orca', '/app/cli.js', 'open', '--json'],
|
||||
env: { ELECTRON_RUN_AS_NODE: '1', ORCA_NODE_OPTIONS: '--max-old-space-size=2048' }
|
||||
})
|
||||
expect(invocation?.env).not.toHaveProperty('ORCA_CLI_ELECTRON_EXECUTABLE')
|
||||
})
|
||||
|
||||
it('reports an unavailable app before attempting native process replacement', async () => {
|
||||
Object.defineProperty(process, 'platform', { value: 'darwin' })
|
||||
process.argv = ['/helper/node', '/app/cli.js', 'open']
|
||||
vi.stubEnv('ORCA_CLI_ELECTRON_EXECUTABLE', '/missing/Orca')
|
||||
vi.mocked(accessSync).mockImplementation(() => {
|
||||
throw new Error('ENOENT')
|
||||
})
|
||||
vi.spyOn(process, 'execve').mockImplementation(() => {
|
||||
throw new Error('Native replacement attempted')
|
||||
})
|
||||
vi.resetModules()
|
||||
const { reenterPackagedElectron } = await import('./packaged-cli-interpreter')
|
||||
expect(reenterPackagedElectron).toThrow('The installed Orca app is unavailable')
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
import { isAbsolute } from 'node:path'
|
||||
import { accessSync, constants } from 'node:fs'
|
||||
import { RuntimeClientError } from './types'
|
||||
|
||||
const electron = process.env.ORCA_CLI_ELECTRON_EXECUTABLE
|
||||
delete process.env.ORCA_CLI_ELECTRON_EXECUTABLE
|
||||
const packagedElectron =
|
||||
process.platform === 'darwin' && electron && isAbsolute(electron) ? electron : null
|
||||
|
||||
export function getPackagedElectronExecutable(): string | null {
|
||||
return packagedElectron
|
||||
}
|
||||
|
||||
/** App-owned operations retain the root app's update and process identity. */
|
||||
export function reenterPackagedElectron(): void {
|
||||
if (!packagedElectron) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
accessSync(packagedElectron, constants.X_OK)
|
||||
accessSync(process.argv[1], constants.R_OK)
|
||||
} catch {
|
||||
throw new RuntimeClientError(
|
||||
'app_not_found',
|
||||
'The installed Orca app is unavailable. Reinstall Orca and try again.'
|
||||
)
|
||||
}
|
||||
process.execve(packagedElectron, [packagedElectron, ...process.argv.slice(1)], {
|
||||
...process.env,
|
||||
ELECTRON_RUN_AS_NODE: '1'
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { reenterPackagedElectron } from '../runtime/packaged-cli-interpreter'
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { homedir } from 'node:os'
|
||||
import { dirname } from 'node:path'
|
||||
@@ -268,7 +269,7 @@ export const SERVE_SERVICE_HANDLERS: Record<string, CommandHandler> = {
|
||||
'Use either --allow-public or --refuse-public, not both.'
|
||||
)
|
||||
}
|
||||
const result = await installServeService({
|
||||
const options = {
|
||||
command: optionalString(flags.get('command')),
|
||||
// Validate before launchd can loop on a bad value and trip the supervisor brake.
|
||||
port: getOptionalServePort(flags),
|
||||
@@ -277,7 +278,9 @@ export const SERVE_SERVICE_HANDLERS: Record<string, CommandHandler> = {
|
||||
allowPublic: flags.get('allow-public') === true,
|
||||
refusePublic: flags.get('refuse-public') === true,
|
||||
dryRun: flags.get('dry-run') === true
|
||||
})
|
||||
}
|
||||
reenterPackagedElectron()
|
||||
const result = await installServeService(options)
|
||||
emit(result, json, formatServeServiceInstall)
|
||||
},
|
||||
'serve service status': async ({ json }) => {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import { chmod, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { promisify } from 'node:util'
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
|
||||
const run = promisify(execFile)
|
||||
let root: string | undefined
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
root = undefined
|
||||
}
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32').each([
|
||||
['13.5', 'node', true],
|
||||
['13.4', 'electron', true],
|
||||
['27.2', 'node', true],
|
||||
['unknown', 'electron', true],
|
||||
['13.5', 'electron', false]
|
||||
] as const)(
|
||||
'runs the packaged CLI on %s with %s (helper executable: %s)',
|
||||
async (version, runtime, executable) => {
|
||||
root = await realpath(await mkdtemp(join(tmpdir(), 'orca-mac-cli-')))
|
||||
const contents = join(root, 'Test.app', 'Contents')
|
||||
const bin = join(contents, 'Resources', 'bin')
|
||||
const helper = join(contents, 'Helpers', 'Orca Agents.app', 'Contents')
|
||||
await Promise.all([
|
||||
mkdir(bin, { recursive: true }),
|
||||
mkdir(join(contents, 'MacOS'), { recursive: true }),
|
||||
mkdir(join(helper, 'MacOS'), { recursive: true })
|
||||
])
|
||||
const systemVersion = join(root, 'SystemVersion.plist')
|
||||
await writeFile(
|
||||
systemVersion,
|
||||
`<plist><dict>\n<key>ProductVersion</key>\n<string>${version}</string>\n</dict></plist>\n`
|
||||
)
|
||||
await writeFile(
|
||||
join(helper, 'Info.plist'),
|
||||
'<plist><dict>\n<key>LSMinimumSystemVersion</key>\n<string>13.5</string>\n</dict></plist>\n'
|
||||
)
|
||||
const shim = await readFile(join(__dirname, '../../../../resources/darwin/bin/orca'), 'utf8')
|
||||
const launcher = join(bin, 'orca')
|
||||
await writeFile(
|
||||
launcher,
|
||||
shim.replace('/System/Library/CoreServices/SystemVersion.plist', systemVersion)
|
||||
)
|
||||
for (const [path, name] of [
|
||||
[join(contents, 'MacOS', 'Orca'), 'electron'],
|
||||
[join(helper, 'MacOS', 'Orca Agents'), 'node']
|
||||
]) {
|
||||
await writeFile(
|
||||
path,
|
||||
`#!/bin/sh\nprintf '%s\\n' '${name}' "$@" "NODE_OPTIONS:\${NODE_OPTIONS-unset}" "ORCA_NODE_OPTIONS:\${ORCA_NODE_OPTIONS-unset}"\n`
|
||||
)
|
||||
await chmod(path, name === 'node' && !executable ? 0o644 : 0o755)
|
||||
}
|
||||
const result = await run('/bin/bash', [launcher, 'status', '--json'], {
|
||||
env: {
|
||||
PATH: '/usr/bin:/bin',
|
||||
HOME: root,
|
||||
NODE_OPTIONS: '--max-old-space-size=2048',
|
||||
ORCA_BACKGROUND_LAUNCH: '1'
|
||||
}
|
||||
})
|
||||
expect(result.stdout.split('\n')).toEqual([
|
||||
runtime,
|
||||
join(contents, 'Resources', 'app.asar.unpacked', 'out', 'cli', 'index.js'),
|
||||
'status',
|
||||
'--json',
|
||||
'NODE_OPTIONS:unset',
|
||||
'ORCA_NODE_OPTIONS:--max-old-space-size=2048',
|
||||
''
|
||||
])
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user