Add SSH launcher and cached remote runtime

This commit is contained in:
2026-05-10 22:48:11 +00:00
parent 138eea3231
commit 61f6d37960
11 changed files with 1513 additions and 102 deletions
+70 -4
View File
@@ -1,9 +1,13 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import {
createRemoteEphemeralBootstrapCommand,
createRemoteEphemeralReadinessCommand,
createRemoteEphemeralRuntimeCacheCheckCommand,
createRemoteBootstrapCommand,
createRemoteInstallCommand,
createRemoteServerInstallPlan,
joinRemotePath,
quoteRemotePath,
quoteShellArg,
} from '../packages/server-installer/ts/index.js';
@@ -11,20 +15,21 @@ tap.test('should create stable remote install paths', async () => {
const plan = createRemoteServerInstallPlan({
serverVersion: '0.1.0',
artifactName: 'remote-theia-linux-x64.tgz',
installRoot: '~/.git.zone/ide-server',
platform: 'linux',
arch: 'x64',
});
expect(plan.paths.versionRoot).toEqual('~/.git.zone/ide-server/0.1.0');
expect(plan.paths.currentLink).toEqual('~/.git.zone/ide-server/current');
expect(plan.paths.versionRoot).toEqual('~/.git.zone/ide/server/0.1.0');
expect(plan.paths.currentLink).toEqual('~/.git.zone/ide/server/current');
expect(plan.manifest.protocolVersion).toEqual(1);
expect(plan.manifest.artifactName).toEqual('remote-theia-linux-x64.tgz');
});
tap.test('should quote shell arguments safely', async () => {
expect(quoteShellArg("that's it")).toEqual("'that'\"'\"'s it'");
expect(joinRemotePath('~/.git.zone/', '/ide-server/', '/0.1.0')).toEqual('~/.git.zone/ide-server/0.1.0');
expect(quoteRemotePath('~/.git.zone/ide/server')).toEqual('"$HOME"/\'.git.zone/ide/server\'');
expect(quoteRemotePath('$HOME/work/project')).toEqual('"$HOME"/\'work/project\'');
expect(joinRemotePath('~/.git.zone/', '/ide/', '/0.1.0')).toEqual('~/.git.zone/ide/0.1.0');
});
tap.test('should render install and bootstrap commands', async () => {
@@ -43,8 +48,69 @@ tap.test('should render install and bootstrap commands', async () => {
});
expect(installCommand).toInclude('GITZONE_IDE_MANIFEST');
expect(installCommand).toInclude('"$HOME"/\'.git.zone/ide/server/0.1.0\'');
expect(bootstrapCommand).toInclude('GITZONE_IDE_OPENCODE_PORT');
expect(bootstrapCommand).toInclude('pnpm --dir');
});
tap.test('should render remote home paths as expandable shell paths', async () => {
const bootstrapCommand = createRemoteBootstrapCommand({
serverVersion: '0.1.0',
workspacePath: '$HOME',
theiaPort: 33990,
opencodePort: 4096,
opencodeUsername: 'opencode',
opencodePassword: 'secret',
});
expect(bootstrapCommand).toInclude('test -d "$HOME"');
expect(bootstrapCommand).toInclude('export GITZONE_IDE_WORKSPACE="$HOME"');
expect(bootstrapCommand).toInclude('"$HOME"/\'.git.zone/ide/server/0.1.0/applications/remote-theia\'');
});
tap.test('should render ephemeral runtime bootstrap without remote pnpm', async () => {
const bootstrapCommand = createRemoteEphemeralBootstrapCommand({
serverVersion: '0.1.0',
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
workspacePath: '$HOME',
theiaPort: 33990,
opencodePort: 4096,
opencodeUsername: 'opencode',
opencodePassword: 'secret',
});
expect(bootstrapCommand).toInclude('/tmp/gitzone-ide-runtime-test/node/bin/node');
expect(bootstrapCommand).toInclude('lib/backend/main.js');
expect(bootstrapCommand).not.toInclude('pnpm');
expect(bootstrapCommand).toInclude('LD_LIBRARY_PATH');
expect(bootstrapCommand).toInclude('THEIA_CONFIG_DIR="$HOME"/\'.git.zone/ide/theia\'');
expect(bootstrapCommand).toInclude('"$HOME"/\'.git.zone/ide/logs\'');
expect(bootstrapCommand).toInclude('runtimeRoot=');
});
tap.test('should render ephemeral readiness check with remote logs', async () => {
const readinessCommand = createRemoteEphemeralReadinessCommand({
runtimeRoot: '/tmp/gitzone-ide-runtime-test',
theiaPort: 33990,
waitSeconds: 2,
});
expect(readinessCommand).toInclude('fetch');
expect(readinessCommand).toInclude('theia-33990.log');
expect(readinessCommand).toInclude('"$HOME"/\'.git.zone/ide/logs/theia-33990.log\'');
expect(readinessCommand).toInclude('LD_LIBRARY_PATH');
});
tap.test('should render ephemeral runtime cache check command', async () => {
const cacheCheckCommand = createRemoteEphemeralRuntimeCacheCheckCommand({
runtimeRoot: '/tmp/gitzone-ide-0.1.0-deadbeef',
runtimeSha256: 'deadbeef',
});
expect(cacheCheckCommand).toInclude('.gitzone-runtime-sha256');
expect(cacheCheckCommand).toInclude('test "$(cat');
expect(cacheCheckCommand).toInclude("'deadbeef'");
expect(cacheCheckCommand).toInclude('runtimeCache=hit');
});
export default tap.start();
+67 -1
View File
@@ -1,5 +1,14 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import { buildSshArgs, listConnectableHosts, parseSshConfig } from '../packages/ssh/ts/index.js';
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import {
buildSshArgs,
listConnectableHosts,
parseSshConfig,
readSshConfig,
saveSshHostConfig,
} from '../packages/ssh/ts/index.js';
tap.test('should parse ssh config hosts', async () => {
const hosts = parseSshConfig(`
@@ -44,4 +53,61 @@ tap.test('should build ssh args with destination and command', async () => {
expect(args[args.length - 1]).toEqual('uname -a');
});
tap.test('should build ssh args for one-time hostname overrides', async () => {
const args = buildSshArgs(
{
id: 'manual-box',
hostAlias: 'manual-box',
hostName: '192.168.1.20',
user: 'root',
},
'uname -a',
);
expect(args).toContain('-o');
expect(args).toContain('HostName=192.168.1.20');
expect(args).toContain('root@manual-box');
});
tap.test('should save and update ssh host config', async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitzone-ssh-'));
const configPath = path.join(tempDir, '.ssh', 'config');
await saveSshHostConfig({
alias: 'dev-box',
hostName: 'dev.example.com',
user: 'deploy',
port: 22,
identityFile: '~/.ssh/id_ed25519',
}, configPath);
await saveSshHostConfig({
alias: 'dev-box',
hostName: 'dev2.example.com',
user: 'deploy',
port: 2200,
}, configPath);
const configText = await fs.readFile(configPath, 'utf8');
const hosts = parseSshConfig(configText);
expect(hosts).toHaveLength(1);
expect(hosts[0]!.hostName).toEqual('dev2.example.com');
expect(hosts[0]!.port).toEqual(2200);
expect(configText.includes('dev.example.com')).toEqual(false);
});
tap.test('should read included ssh config files', async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitzone-ssh-'));
const sshDir = path.join(tempDir, '.ssh');
const includeDir = path.join(sshDir, 'config.d');
await fs.mkdir(includeDir, { recursive: true });
await fs.writeFile(path.join(sshDir, 'config'), 'Include config.d/*\n');
await fs.writeFile(path.join(includeDir, 'dev.conf'), 'Host included-box\n HostName included.example.com\n');
const hosts = await readSshConfig(path.join(sshDir, 'config'));
const connectableHosts = listConnectableHosts(hosts);
expect(connectableHosts).toHaveLength(1);
expect(connectableHosts[0]!.alias).toEqual('included-box');
expect(connectableHosts[0]!.hostName).toEqual('included.example.com');
});
export default tap.start();