187 lines
6.4 KiB
TypeScript
187 lines
6.4 KiB
TypeScript
import type { IIdeSshTarget } from '@git.zone/ide-protocol';
|
|
import * as plugins from './plugins.js';
|
|
|
|
const defaultRemoteTheiaPort = 33990;
|
|
const defaultOpenCodePort = 4096;
|
|
|
|
class GitZoneIdeElectronShell {
|
|
private readonly tunnels: plugins.ideSsh.ISshTunnelHandle[] = [];
|
|
|
|
async start() {
|
|
await plugins.electron.app.whenReady();
|
|
this.registerIpcHandlers();
|
|
|
|
const remoteUrl = getArgValue('--remote-url');
|
|
if (remoteUrl) {
|
|
this.openWorkspaceWindow(remoteUrl);
|
|
} else {
|
|
await this.openLauncherWindow();
|
|
}
|
|
|
|
plugins.electron.app.on('activate', async () => {
|
|
if (plugins.electron.BrowserWindow.getAllWindows().length === 0) {
|
|
await this.openLauncherWindow();
|
|
}
|
|
});
|
|
plugins.electron.app.on('before-quit', () => {
|
|
for (const tunnel of this.tunnels) {
|
|
void tunnel.dispose();
|
|
}
|
|
});
|
|
}
|
|
|
|
private registerIpcHandlers() {
|
|
plugins.electron.ipcMain.handle('gitzone:list-hosts', async () => {
|
|
const hosts = await plugins.ideSsh.readSshConfig();
|
|
return plugins.ideSsh.listConnectableHosts(hosts).map((host) => ({
|
|
alias: host.alias,
|
|
hostName: host.hostName,
|
|
user: host.user,
|
|
port: host.port,
|
|
}));
|
|
});
|
|
|
|
plugins.electron.ipcMain.handle('gitzone:connect', async (_event, input: IConnectInput) => {
|
|
const localPort = await plugins.ideSsh.findFreePort();
|
|
const target: IIdeSshTarget = {
|
|
id: input.hostAlias,
|
|
hostAlias: input.hostAlias,
|
|
workspacePath: input.workspacePath,
|
|
};
|
|
const opencodePassword = plugins.crypto.randomBytes(24).toString('base64url');
|
|
const remoteTheiaPort = input.remoteTheiaPort ?? defaultRemoteTheiaPort;
|
|
const opencodePort = input.openCodePort ?? defaultOpenCodePort;
|
|
const bootstrapCommand = plugins.ideServerInstaller.createRemoteBootstrapCommand({
|
|
serverVersion: plugins.electron.app.getVersion(),
|
|
workspacePath: input.workspacePath,
|
|
theiaPort: remoteTheiaPort,
|
|
opencodePort,
|
|
opencodeUsername: 'opencode',
|
|
opencodePassword,
|
|
});
|
|
|
|
const bootstrapResult = await plugins.ideSsh.runSshCommand(target, bootstrapCommand, {
|
|
timeoutMs: 30000,
|
|
batchMode: input.batchMode ?? true,
|
|
});
|
|
if (bootstrapResult.exitCode !== 0) {
|
|
throw new Error(bootstrapResult.stderr || `Remote bootstrap failed with ${bootstrapResult.exitCode}`);
|
|
}
|
|
|
|
const tunnel = plugins.ideSsh.startSshTunnel(target, {
|
|
localPort,
|
|
remotePort: remoteTheiaPort,
|
|
batchMode: input.batchMode ?? true,
|
|
});
|
|
this.tunnels.push(tunnel);
|
|
const url = `http://127.0.0.1:${localPort}`;
|
|
this.openWorkspaceWindow(url);
|
|
return { url, localPort, remoteTheiaPort, opencodePort };
|
|
});
|
|
}
|
|
|
|
private async openLauncherWindow() {
|
|
const window = new plugins.electron.BrowserWindow({
|
|
width: 960,
|
|
height: 720,
|
|
title: 'Git.Zone IDE',
|
|
webPreferences: {
|
|
contextIsolation: false,
|
|
nodeIntegration: true,
|
|
},
|
|
});
|
|
await window.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(renderLauncherHtml())}`);
|
|
}
|
|
|
|
private openWorkspaceWindow(url: string) {
|
|
const window = new plugins.electron.BrowserWindow({
|
|
width: 1440,
|
|
height: 960,
|
|
title: 'Git.Zone IDE',
|
|
webPreferences: {
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
},
|
|
});
|
|
void window.loadURL(url);
|
|
}
|
|
}
|
|
|
|
interface IConnectInput {
|
|
hostAlias: string;
|
|
workspacePath: string;
|
|
remoteTheiaPort?: number;
|
|
openCodePort?: number;
|
|
batchMode?: boolean;
|
|
}
|
|
|
|
const getArgValue = (name: string) => {
|
|
const index = process.argv.indexOf(name);
|
|
if (index === -1) {
|
|
return undefined;
|
|
}
|
|
return process.argv[index + 1];
|
|
};
|
|
|
|
const renderLauncherHtml = () => `<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Git.Zone IDE</title>
|
|
<style>
|
|
body { margin: 0; font: 14px system-ui, sans-serif; background: #111827; color: #f9fafb; }
|
|
main { max-width: 720px; margin: 72px auto; padding: 32px; background: #1f2937; border-radius: 16px; }
|
|
h1 { margin-top: 0; font-size: 32px; }
|
|
label { display: block; margin-top: 18px; color: #d1d5db; }
|
|
input, select, button { width: 100%; box-sizing: border-box; margin-top: 8px; padding: 12px; border-radius: 10px; border: 1px solid #4b5563; background: #111827; color: #f9fafb; }
|
|
button { margin-top: 24px; background: #22c55e; border: 0; color: #052e16; font-weight: 700; cursor: pointer; }
|
|
pre { white-space: pre-wrap; color: #fca5a5; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<h1>Git.Zone IDE</h1>
|
|
<p>Connect to an SSH host and open a remote Theia workspace powered by OpenCode server.</p>
|
|
<label>SSH Host</label>
|
|
<select id="host"></select>
|
|
<label>Remote Workspace Path</label>
|
|
<input id="workspace" value="$HOME" />
|
|
<label>Remote Theia Port</label>
|
|
<input id="theiaPort" value="${defaultRemoteTheiaPort}" />
|
|
<label>OpenCode Port</label>
|
|
<input id="opencodePort" value="${defaultOpenCodePort}" />
|
|
<button id="connect">Connect</button>
|
|
<pre id="output"></pre>
|
|
</main>
|
|
<script>
|
|
const { ipcRenderer } = require('electron');
|
|
const hostSelect = document.getElementById('host');
|
|
const output = document.getElementById('output');
|
|
ipcRenderer.invoke('gitzone:list-hosts').then((hosts) => {
|
|
for (const host of hosts) {
|
|
const option = document.createElement('option');
|
|
option.value = host.alias;
|
|
option.textContent = host.alias + (host.hostName ? ' (' + host.hostName + ')' : '');
|
|
hostSelect.appendChild(option);
|
|
}
|
|
}).catch((error) => output.textContent = error.stack || String(error));
|
|
document.getElementById('connect').addEventListener('click', async () => {
|
|
output.textContent = 'Connecting...';
|
|
try {
|
|
const result = await ipcRenderer.invoke('gitzone:connect', {
|
|
hostAlias: hostSelect.value,
|
|
workspacePath: document.getElementById('workspace').value,
|
|
remoteTheiaPort: Number(document.getElementById('theiaPort').value),
|
|
openCodePort: Number(document.getElementById('opencodePort').value),
|
|
});
|
|
output.textContent = 'Opened ' + result.url;
|
|
} catch (error) {
|
|
output.textContent = error.stack || String(error);
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
|
|
void new GitZoneIdeElectronShell().start();
|