mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day minimum-release-age supply-chain guard; nothing here needs it). The bump is a no-op on the existing config. Enable 3 error rules (backlog autofixed to zero in this commit) and 4 warn rules (surface signal without gating CI): error (autofixed, behavior-preserving): - unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:) - typescript/no-import-type-side-effects (~36: all-inline-type -> import type) - unicorn/no-array-reverse (19: copy-then-reverse -> toReversed) warn (real signal, current fires are test-only/correct): - unicorn/no-array-fill-with-reference-type (aliasing footgun guard) - typescript/no-unsafe-function-type (bans bare Function type) - unicorn/prefer-array-flat-map (map().flat() -> flatMap()) - unicorn/prefer-regexp-test (.match() in bool ctx -> .test()) mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix ran from root and covered mobile/ too. Verification (all green): oxlint 0 errors (root+mobile+aux configs), oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed, builds (electron-vite + web + cli) succeed. node: rewrites confirmed to skip embedded SSH/CLI string payloads (AST-only); all toReversed sites verified to operate on fresh copies or write-once locals. * chore(lint): bump mobile oxlint to 1.71 so inherited rules parse mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile && oxlint' failed to parse the new rule. Bump mobile to match root (1.71). Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit pass, vitest 978 passed / 0 failed. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
240 lines
7.3 KiB
TypeScript
240 lines
7.3 KiB
TypeScript
import { mkdtempSync, writeFileSync, mkdirSync, chmodSync, rmSync, readFileSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
vi.mock('electron', () => ({
|
|
app: { getAppPath: () => '/mock/app' }
|
|
}))
|
|
|
|
import { SshConnection } from './ssh-connection'
|
|
import { deployAndLaunchRelay } from './ssh-relay-deploy'
|
|
import { SshChannelMultiplexer } from './ssh-channel-multiplexer'
|
|
import { uploadDirectoryViaSystemSsh } from './ssh-system-fallback'
|
|
import type { SshTarget } from '../../shared/ssh-types'
|
|
|
|
const RELAY_VERSION = '0.1.0+systemtransport'
|
|
|
|
function makeTarget(): SshTarget {
|
|
return {
|
|
id: 'system-transport-target',
|
|
label: 'System Transport Target',
|
|
configHost: 'fdpass-host',
|
|
host: 'ignored.example.com',
|
|
port: 22,
|
|
username: ''
|
|
}
|
|
}
|
|
|
|
function writeFakeSsh(dir: string): string {
|
|
const path = join(dir, 'fake-ssh')
|
|
writeFileSync(
|
|
path,
|
|
`#!/bin/sh
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
-o|-p|-i|-J) shift 2 ;;
|
|
-T) shift ;;
|
|
--) shift; break ;;
|
|
-*) shift ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
if [ "$#" -gt 0 ]; then
|
|
shift
|
|
fi
|
|
cmd="$1"
|
|
if [ -z "$cmd" ]; then
|
|
exit 0
|
|
fi
|
|
exec /bin/sh -c "$cmd"
|
|
`
|
|
)
|
|
chmodSync(path, 0o755)
|
|
return path
|
|
}
|
|
|
|
function writeFakeRelay(dir: string): void {
|
|
writeFileSync(
|
|
join(dir, 'relay.js'),
|
|
`
|
|
const fs = require('fs');
|
|
const net = require('net');
|
|
const sentinel = 'ORCA-RELAY v0.1.0 READY\\n';
|
|
const sockPath = process.argv[process.argv.indexOf('--sock-path') + 1];
|
|
|
|
function encode(msg) {
|
|
const payload = Buffer.from(JSON.stringify(msg), 'utf8');
|
|
const header = Buffer.alloc(13);
|
|
header[0] = 1;
|
|
header.writeUInt32BE(1, 1);
|
|
header.writeUInt32BE(0, 5);
|
|
header.writeUInt32BE(payload.length, 9);
|
|
return Buffer.concat([header, payload]);
|
|
}
|
|
|
|
function serve(socket) {
|
|
socket.write(sentinel);
|
|
let buffer = Buffer.alloc(0);
|
|
socket.on('data', (chunk) => {
|
|
buffer = Buffer.concat([buffer, chunk]);
|
|
while (buffer.length >= 13) {
|
|
const type = buffer[0];
|
|
const length = buffer.readUInt32BE(9);
|
|
if (buffer.length < 13 + length) return;
|
|
const payload = buffer.subarray(13, 13 + length);
|
|
buffer = buffer.subarray(13 + length);
|
|
if (type !== 1) continue;
|
|
const message = JSON.parse(payload.toString('utf8'));
|
|
if (message.method === 'session.resolveHome') {
|
|
socket.write(encode({ jsonrpc: '2.0', id: message.id, result: process.env.HOME }));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (process.argv.includes('--detached')) {
|
|
try { fs.unlinkSync(sockPath); } catch {}
|
|
const server = net.createServer(serve);
|
|
server.listen(sockPath);
|
|
setTimeout(() => process.exit(0), 20000).unref();
|
|
} else if (process.argv.includes('--connect')) {
|
|
process.stdout.write(sentinel);
|
|
let buffer = Buffer.alloc(0);
|
|
process.stdin.on('data', (chunk) => {
|
|
buffer = Buffer.concat([buffer, chunk]);
|
|
while (buffer.length >= 13) {
|
|
const type = buffer[0];
|
|
const length = buffer.readUInt32BE(9);
|
|
if (buffer.length < 13 + length) return;
|
|
const payload = buffer.subarray(13, 13 + length);
|
|
buffer = buffer.subarray(13 + length);
|
|
if (type !== 1) continue;
|
|
const message = JSON.parse(payload.toString('utf8'));
|
|
if (message.method === 'session.resolveHome') {
|
|
process.stdout.write(encode({ jsonrpc: '2.0', id: message.id, result: process.env.HOME }));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
`
|
|
)
|
|
}
|
|
|
|
function createRelayTree(root: string, remoteHome: string): void {
|
|
const platforms = [
|
|
'linux-x64',
|
|
'linux-arm64',
|
|
'darwin-x64',
|
|
'darwin-arm64',
|
|
'win32-x64',
|
|
'win32-arm64'
|
|
]
|
|
for (const platform of platforms) {
|
|
const localDir = join(root, platform)
|
|
mkdirSync(localDir, { recursive: true })
|
|
writeFileSync(join(localDir, '.version'), RELAY_VERSION)
|
|
writeFakeRelay(localDir)
|
|
}
|
|
|
|
const remoteDir = join(remoteHome, '.orca-remote', `relay-${RELAY_VERSION}`)
|
|
mkdirSync(join(remoteDir, 'node_modules', 'node-pty'), { recursive: true })
|
|
mkdirSync(join(remoteDir, 'node_modules', '@parcel', 'watcher'), { recursive: true })
|
|
writeFileSync(join(remoteDir, 'node_modules', 'node-pty', 'index.js'), '')
|
|
writeFileSync(join(remoteDir, 'node_modules', '@parcel', 'watcher', 'index.js'), '')
|
|
writeFileSync(join(remoteDir, '.install-complete'), '')
|
|
writeFakeRelay(remoteDir)
|
|
}
|
|
|
|
describe('system SSH transport integration', () => {
|
|
let tempDir: string
|
|
let oldHome: string | undefined
|
|
let oldRelayPath: string | undefined
|
|
let oldSystemSshPath: string | undefined
|
|
let oldForceSystemTransport: string | undefined
|
|
|
|
beforeEach(() => {
|
|
if (process.platform === 'win32') {
|
|
return
|
|
}
|
|
tempDir = mkdtempSync(join('/tmp', 'orca-ssh-'))
|
|
oldHome = process.env.HOME
|
|
oldRelayPath = process.env.ORCA_RELAY_PATH
|
|
oldSystemSshPath = process.env.ORCA_SYSTEM_SSH_PATH
|
|
oldForceSystemTransport = process.env.ORCA_SSH_FORCE_SYSTEM_TRANSPORT
|
|
const remoteHome = join(tempDir, 'remote-home')
|
|
const relayRoot = join(tempDir, 'relay')
|
|
mkdirSync(remoteHome, { recursive: true })
|
|
createRelayTree(relayRoot, remoteHome)
|
|
process.env.HOME = remoteHome
|
|
process.env.ORCA_RELAY_PATH = relayRoot
|
|
process.env.ORCA_SYSTEM_SSH_PATH = writeFakeSsh(tempDir)
|
|
process.env.ORCA_SSH_FORCE_SYSTEM_TRANSPORT = '1'
|
|
})
|
|
|
|
afterEach(() => {
|
|
if (process.platform === 'win32') {
|
|
return
|
|
}
|
|
if (oldHome === undefined) {
|
|
delete process.env.HOME
|
|
} else {
|
|
process.env.HOME = oldHome
|
|
}
|
|
if (oldRelayPath === undefined) {
|
|
delete process.env.ORCA_RELAY_PATH
|
|
} else {
|
|
process.env.ORCA_RELAY_PATH = oldRelayPath
|
|
}
|
|
if (oldSystemSshPath === undefined) {
|
|
delete process.env.ORCA_SYSTEM_SSH_PATH
|
|
} else {
|
|
process.env.ORCA_SYSTEM_SSH_PATH = oldSystemSshPath
|
|
}
|
|
if (oldForceSystemTransport === undefined) {
|
|
delete process.env.ORCA_SSH_FORCE_SYSTEM_TRANSPORT
|
|
} else {
|
|
process.env.ORCA_SSH_FORCE_SYSTEM_TRANSPORT = oldForceSystemTransport
|
|
}
|
|
rmSync(tempDir, { recursive: true, force: true })
|
|
})
|
|
|
|
// Why: this fixture writes a POSIX fake ssh script to exercise stdin/stdout
|
|
// transport semantics; Windows coverage stays in argument/unit tests.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'deploys and speaks relay RPC over a system ssh process for ProxyUseFdpass targets',
|
|
async () => {
|
|
const conn = new SshConnection(makeTarget(), { onStateChange: vi.fn() })
|
|
await conn.connect()
|
|
expect(conn.usesSystemSshTransport()).toBe(true)
|
|
|
|
const result = await deployAndLaunchRelay(conn, undefined, 60, makeTarget().id)
|
|
const mux = new SshChannelMultiplexer(result.transport)
|
|
try {
|
|
await expect(mux.request('session.resolveHome', { path: '~' })).resolves.toBe(
|
|
join(tempDir, 'remote-home')
|
|
)
|
|
} finally {
|
|
mux.dispose()
|
|
await conn.disconnect()
|
|
}
|
|
},
|
|
20_000
|
|
)
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'uploads a directory through the system ssh stdin/stdout path',
|
|
async () => {
|
|
const source = join(tempDir, 'source')
|
|
const destination = join(tempDir, 'uploaded')
|
|
mkdirSync(source, { recursive: true })
|
|
writeFileSync(join(source, 'payload.txt'), 'uploaded over system ssh')
|
|
|
|
await uploadDirectoryViaSystemSsh(makeTarget(), source, destination)
|
|
|
|
expect(readFileSync(join(destination, 'payload.txt'), 'utf8')).toBe(
|
|
'uploaded over system ssh'
|
|
)
|
|
}
|
|
)
|
|
})
|