Initialize remote IDE scaffold
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
dist_*/
|
||||||
|
.nogit/
|
||||||
|
.theia/
|
||||||
|
.theia-electron/
|
||||||
|
applications/electron-shell/dist/
|
||||||
|
applications/remote-theia/lib/
|
||||||
|
applications/remote-theia/plugins/
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
engine-strict=false
|
||||||
|
auto-install-peers=true
|
||||||
|
shamefully-hoist=true
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
appId: global.foss.git-zone.ide
|
||||||
|
productName: Git.Zone IDE
|
||||||
|
directories:
|
||||||
|
output: dist
|
||||||
|
files:
|
||||||
|
- dist_ts/**/*
|
||||||
|
- package.json
|
||||||
|
linux:
|
||||||
|
target:
|
||||||
|
- AppImage
|
||||||
|
mac:
|
||||||
|
target:
|
||||||
|
- dmg
|
||||||
|
win:
|
||||||
|
target:
|
||||||
|
- nsis
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-electron-shell",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist_ts/main.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"start": "pnpm run build && electron dist_ts/main.js",
|
||||||
|
"package": "pnpm run build && electron-builder --config electron-builder.yml"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@git.zone/ide-protocol": "workspace:*",
|
||||||
|
"@git.zone/ide-server-installer": "workspace:*",
|
||||||
|
"@git.zone/ide-ssh": "workspace:*",
|
||||||
|
"electron": "^42.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron-builder": "^26.8.1"
|
||||||
|
},
|
||||||
|
"files": ["dist_ts/**/*", "electron-builder.yml"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
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();
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import * as crypto from 'node:crypto';
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import * as ideServerInstaller from '@git.zone/ide-server-installer';
|
||||||
|
import * as ideSsh from '@git.zone/ide-ssh';
|
||||||
|
|
||||||
|
export { crypto, electron, ideServerInstaller, ideSsh };
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "ts",
|
||||||
|
"outDir": "dist_ts",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["ts/**/*.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
/**
|
||||||
|
* Don't touch this file. It will be regenerated by theia build.
|
||||||
|
* To customize webpack configuration change /mnt/data/foss.global/git.zone/ide/applications/remote-theia/webpack.config.js
|
||||||
|
*/
|
||||||
|
// @ts-check
|
||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const yargs = require('yargs');
|
||||||
|
const resolvePackagePath = require('resolve-package-path');
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
const { MonacoWebpackPlugin } = require('@theia/native-webpack-plugin/lib/monaco-webpack-plugins.js');
|
||||||
|
|
||||||
|
const outputPath = path.resolve(__dirname, 'lib', 'frontend');
|
||||||
|
const { mode, staticCompression } = yargs.option('mode', {
|
||||||
|
description: "Mode to use",
|
||||||
|
choices: ["development", "production"],
|
||||||
|
default: "production"
|
||||||
|
}).option('static-compression', {
|
||||||
|
description: 'Controls whether to enable compression of static artifacts.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true
|
||||||
|
}).argv;
|
||||||
|
const development = mode === 'development';
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
new CopyWebpackPlugin({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
// copy secondary window html file to lib folder
|
||||||
|
from: path.resolve(__dirname, 'src-gen/frontend/secondary-window.html')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// copy webview files to lib folder
|
||||||
|
from: path.join(resolvePackagePath('@theia/plugin-ext', __dirname), '..', 'src', 'main', 'browser', 'webview', 'pre'),
|
||||||
|
to: path.resolve(__dirname, 'lib', 'webview', 'pre')
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
// copy frontend plugin host files
|
||||||
|
from: path.join(resolvePackagePath('@theia/plugin-ext-vscode', __dirname), '..', 'lib', 'node', 'context', 'plugin-vscode-init-fe.js'),
|
||||||
|
to: path.resolve(__dirname, 'lib', 'frontend', 'context', 'plugin-vscode-init-fe.js')
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
// copy shell integration scripts
|
||||||
|
from: path.join(resolvePackagePath('@theia/terminal', __dirname), '..', 'src', 'node', 'shell-integrations'),
|
||||||
|
to: path.resolve(__dirname, 'lib', 'backend', 'shell-integrations')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
// the Buffer class doesn't exist in the browser but some dependencies rely on it
|
||||||
|
Buffer: ['buffer', 'Buffer']
|
||||||
|
}),
|
||||||
|
new MonacoWebpackPlugin()
|
||||||
|
];
|
||||||
|
// it should go after copy-plugin in order to compress monaco as well
|
||||||
|
if (staticCompression) {
|
||||||
|
plugins.push(new CompressionPlugin({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = [{
|
||||||
|
mode,
|
||||||
|
plugins,
|
||||||
|
devtool: 'source-map',
|
||||||
|
entry: {
|
||||||
|
bundle: path.resolve(__dirname, 'src-gen/frontend/index.js'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: outputPath,
|
||||||
|
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
|
||||||
|
globalObject: 'self'
|
||||||
|
},
|
||||||
|
target: 'web',
|
||||||
|
cache: staticCompression,
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
exclude: /materialcolors\.css$|\.useable\.css$/,
|
||||||
|
use: ['style-loader', 'css-loader']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /materialcolors\.css$|\.useable\.css$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'style-loader',
|
||||||
|
options: {
|
||||||
|
esModule: false,
|
||||||
|
injectType: 'lazySingletonStyleTag',
|
||||||
|
attributes: {
|
||||||
|
id: 'theia-theme'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'css-loader'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
|
||||||
|
type: 'asset',
|
||||||
|
parser: {
|
||||||
|
dataUrlCondition: {
|
||||||
|
maxSize: 10000,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generator: {
|
||||||
|
dataUrl: {
|
||||||
|
mimetype: 'image/svg+xml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(jpg|png|gif)$/,
|
||||||
|
type: 'asset/resource',
|
||||||
|
generator: {
|
||||||
|
filename: '[hash].[ext]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// see https://github.com/eclipse-theia/theia/issues/556
|
||||||
|
test: /source-map-support/,
|
||||||
|
loader: 'ignore-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.d\.ts$/,
|
||||||
|
loader: 'ignore-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
enforce: 'pre',
|
||||||
|
loader: 'source-map-loader',
|
||||||
|
exclude: /jsonc-parser|fast-plist|onigasm/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
|
type: 'asset',
|
||||||
|
parser: {
|
||||||
|
dataUrlCondition: {
|
||||||
|
maxSize: 10000,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generator: {
|
||||||
|
dataUrl: {
|
||||||
|
mimetype: 'image/svg+xml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /node_modules[\\|/](vscode-languageserver-types|vscode-uri|jsonc-parser|vscode-languageserver-protocol)/,
|
||||||
|
loader: 'umd-compat-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.wasm$/,
|
||||||
|
type: 'asset/resource'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.plist$/,
|
||||||
|
type: 'asset/resource'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
fallback: {
|
||||||
|
'child_process': false,
|
||||||
|
'crypto': false,
|
||||||
|
'net': false,
|
||||||
|
'path': require.resolve('path-browserify'),
|
||||||
|
'process': false,
|
||||||
|
'os': false,
|
||||||
|
'timers': false
|
||||||
|
},
|
||||||
|
alias: {
|
||||||
|
// Replace Monaco's nls module with Theia's localization-aware version.
|
||||||
|
// ESM exports are immutable so we cannot override localize/localize2 at runtime.
|
||||||
|
// Using the resolved absolute path ensures that both external imports
|
||||||
|
// (e.g. '@theia/monaco-editor-core/esm/vs/nls') and internal relative
|
||||||
|
// imports within Monaco (e.g. '../nls.js') are redirected.
|
||||||
|
[path.join(resolvePackagePath('@theia/monaco-editor-core', __dirname), '..', 'esm', 'vs', 'nls.js')]:
|
||||||
|
path.join(resolvePackagePath('@theia/monaco', __dirname), '..', 'lib', 'browser', 'monaco-nls.js')
|
||||||
|
},
|
||||||
|
extensions: ['.js']
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
warnings: true,
|
||||||
|
children: true
|
||||||
|
},
|
||||||
|
ignoreWarnings: [
|
||||||
|
// Some packages do not have source maps, that's ok
|
||||||
|
/Failed to parse source map/,
|
||||||
|
{
|
||||||
|
// Monaco uses 'require' in a non-standard way
|
||||||
|
module: /@theia\/monaco-editor-core/,
|
||||||
|
message: /require function is used in a way in which dependencies cannot be statically extracted/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The Monaco editor worker must be built separately without the NLS alias.
|
||||||
|
// The NLS alias redirects to monaco-nls.ts which imports from @theia/core,
|
||||||
|
// and those modules are not available in the web worker context.
|
||||||
|
mode,
|
||||||
|
devtool: 'source-map',
|
||||||
|
entry: {
|
||||||
|
'editor.worker': '@theia/monaco-editor-core/esm/vs/editor/common/services/editorWebWorkerMain.js'
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: outputPath,
|
||||||
|
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
|
||||||
|
globalObject: 'self'
|
||||||
|
},
|
||||||
|
target: 'webworker',
|
||||||
|
cache: staticCompression,
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js']
|
||||||
|
},
|
||||||
|
ignoreWarnings: [
|
||||||
|
{
|
||||||
|
module: /@theia\/monaco-editor-core/,
|
||||||
|
message: /require function is used in a way in which dependencies cannot be statically extracted/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode,
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
// Options similar to the same options in webpackOptions.output
|
||||||
|
// both options are optional
|
||||||
|
filename: "[name].css",
|
||||||
|
chunkFilename: "[id].css",
|
||||||
|
}),
|
||||||
|
new MonacoWebpackPlugin(),
|
||||||
|
],
|
||||||
|
devtool: 'source-map',
|
||||||
|
entry: {
|
||||||
|
"secondary-window": path.resolve(__dirname, 'src-gen/frontend/secondary-index.js'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: outputPath,
|
||||||
|
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]?[loaders]',
|
||||||
|
globalObject: 'self'
|
||||||
|
},
|
||||||
|
target: 'web',
|
||||||
|
cache: staticCompression,
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /.css$/i,
|
||||||
|
use: [MiniCssExtractPlugin.loader, "css-loader"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /.wasm$/,
|
||||||
|
type: 'asset/resource'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
fallback: {
|
||||||
|
'child_process': false,
|
||||||
|
'crypto': false,
|
||||||
|
'net': false,
|
||||||
|
'path': require.resolve('path-browserify'),
|
||||||
|
'process': false,
|
||||||
|
'os': false,
|
||||||
|
'timers': false
|
||||||
|
},
|
||||||
|
alias: {
|
||||||
|
// Replace Monaco's nls module with Theia's localization-aware version.
|
||||||
|
// ESM exports are immutable so we cannot override localize/localize2 at runtime.
|
||||||
|
// Using the resolved absolute path ensures that both external imports
|
||||||
|
// (e.g. '@theia/monaco-editor-core/esm/vs/nls') and internal relative
|
||||||
|
// imports within Monaco (e.g. '../nls.js') are redirected.
|
||||||
|
[path.join(resolvePackagePath('@theia/monaco-editor-core', __dirname), '..', 'esm', 'vs', 'nls.js')]:
|
||||||
|
path.join(resolvePackagePath('@theia/monaco', __dirname), '..', 'lib', 'browser', 'monaco-nls.js')
|
||||||
|
},
|
||||||
|
extensions: ['.js']
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
warnings: true,
|
||||||
|
children: true
|
||||||
|
},
|
||||||
|
ignoreWarnings: [
|
||||||
|
{
|
||||||
|
// Monaco uses 'require' in a non-standard way
|
||||||
|
module: /@theia\/monaco-editor-core/,
|
||||||
|
message: /require function is used in a way in which dependencies cannot be statically extracted/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}];
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
/**
|
||||||
|
* Don't touch this file. It will be regenerated by theia build.
|
||||||
|
* To customize webpack configuration change /mnt/data/foss.global/git.zone/ide/applications/remote-theia/webpack.config.js
|
||||||
|
*/
|
||||||
|
// @ts-check
|
||||||
|
const path = require('path');
|
||||||
|
const yargs = require('yargs');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
|
const NativeWebpackPlugin = require('@theia/native-webpack-plugin');
|
||||||
|
const { MonacoWebpackPlugin } = require('@theia/native-webpack-plugin/lib/monaco-webpack-plugins.js');
|
||||||
|
|
||||||
|
const { mode } = yargs.option('mode', {
|
||||||
|
description: "Mode to use",
|
||||||
|
choices: ["development", "production"],
|
||||||
|
default: "production"
|
||||||
|
}).argv;
|
||||||
|
|
||||||
|
const production = mode === 'production';
|
||||||
|
|
||||||
|
/** @type {import('webpack').EntryObject} */
|
||||||
|
const commonJsLibraries = {};
|
||||||
|
for (const [entryPointName, entryPointPath] of Object.entries({
|
||||||
|
'backend-init-theia': '@theia/plugin-ext/lib/hosted/node/scanners/backend-init-theia',
|
||||||
|
'parcel-watcher': '@theia/filesystem/lib/node/parcel-watcher',
|
||||||
|
'plugin-vscode-init': '@theia/plugin-ext-vscode/lib/node/plugin-vscode-init',
|
||||||
|
|
||||||
|
})) {
|
||||||
|
commonJsLibraries[entryPointName] = {
|
||||||
|
import: require.resolve(entryPointPath),
|
||||||
|
library: {
|
||||||
|
type: 'commonjs2',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ignoredResources = new Set();
|
||||||
|
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
ignoredResources.add('@vscode/windows-ca-certs');
|
||||||
|
ignoredResources.add('@vscode/windows-ca-certs/build/Release/crypt32.node');
|
||||||
|
}
|
||||||
|
|
||||||
|
const nativePlugin = new NativeWebpackPlugin({
|
||||||
|
out: 'native',
|
||||||
|
trash: true,
|
||||||
|
ripgrep: true,
|
||||||
|
pty: true,
|
||||||
|
nativeBindings: {
|
||||||
|
drivelist: 'drivelist/build/Release/drivelist.node'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure that node-pty is correctly hoisted
|
||||||
|
try {
|
||||||
|
require.resolve('node-pty');
|
||||||
|
} catch {
|
||||||
|
console.error('"node-pty" dependency is not installed correctly. Ensure that it is available in the root node_modules directory.');
|
||||||
|
console.error('Exiting webpack build process.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('webpack').Configuration} */
|
||||||
|
const config = {
|
||||||
|
mode,
|
||||||
|
devtool: mode === 'development' ? 'source-map' : false,
|
||||||
|
target: 'node',
|
||||||
|
node: {
|
||||||
|
global: false,
|
||||||
|
__filename: false,
|
||||||
|
__dirname: false
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.json', '.wasm', '.node'],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: path.resolve(__dirname, 'lib', 'backend'),
|
||||||
|
devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]?[loaders]',
|
||||||
|
},
|
||||||
|
entry: {
|
||||||
|
// Main entry point of the Theia application backend:
|
||||||
|
'main': require.resolve('./src-gen/backend/main'),
|
||||||
|
// Theia's IPC mechanism:
|
||||||
|
'ipc-bootstrap': require.resolve('@theia/core/lib/node/messaging/ipc-bootstrap'),
|
||||||
|
// VS Code extension support:
|
||||||
|
'plugin-host': require.resolve('@theia/plugin-ext/lib/hosted/node/plugin-host'),
|
||||||
|
|
||||||
|
// Make sure the node-pty thread workers can be executed:
|
||||||
|
'worker/conoutSocketWorker': require.resolve('node-pty/lib/worker/conoutSocketWorker'),
|
||||||
|
'conpty_console_list_agent': require.resolve('node-pty/lib/conpty_console_list_agent'),
|
||||||
|
|
||||||
|
|
||||||
|
...commonJsLibraries
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
// Make sure we can still find and load our native addons.
|
||||||
|
{
|
||||||
|
test: /\.node$/,
|
||||||
|
loader: 'node-loader',
|
||||||
|
options: {
|
||||||
|
name: 'native/[name].[ext]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.d\.ts$/,
|
||||||
|
loader: 'ignore-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
enforce: 'pre',
|
||||||
|
loader: 'source-map-loader'
|
||||||
|
},
|
||||||
|
// node-pty uses a dynamic require which needs to be rewritten to work with webpack.
|
||||||
|
{
|
||||||
|
test: /node_modules[/\\]node-pty[/\\]lib[/\\]utils.js$/,
|
||||||
|
loader: 'string-replace-loader',
|
||||||
|
options: {
|
||||||
|
search: /require\(/,
|
||||||
|
replace: '__non_webpack_require__(',
|
||||||
|
flags: 'g'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// jsonc-parser exposes its UMD implementation by default, which
|
||||||
|
// confuses Webpack leading to missing js in the bundles.
|
||||||
|
{
|
||||||
|
test: /node_modules[\/](jsonc-parser)/,
|
||||||
|
loader: 'umd-compat-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
// Some native dependencies need special handling
|
||||||
|
nativePlugin,
|
||||||
|
// Optional node dependencies can be safely ignored
|
||||||
|
new webpack.IgnorePlugin({
|
||||||
|
checkResource: resource => ignoredResources.has(resource)
|
||||||
|
}),
|
||||||
|
new MonacoWebpackPlugin()
|
||||||
|
],
|
||||||
|
optimization: {
|
||||||
|
// Split and reuse code across the various entry points
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'all'
|
||||||
|
},
|
||||||
|
// Only minimize if we run webpack in production mode
|
||||||
|
minimize: production,
|
||||||
|
minimizer: [
|
||||||
|
new TerserPlugin({
|
||||||
|
exclude: /^(lib|builtins)\//
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
ignoreWarnings: [
|
||||||
|
// Some packages do not have source maps, that's ok
|
||||||
|
/Failed to parse source map/,
|
||||||
|
// require with expressions are not supported
|
||||||
|
/the request of a dependency is an expression/,
|
||||||
|
// Some packages use dynamic requires, we can safely ignore them (they are handled by the native webpack plugin)
|
||||||
|
/require function is used in a way in which dependencies cannot be statically extracted/, {
|
||||||
|
module: /yargs/
|
||||||
|
}, {
|
||||||
|
module: /node-pty/
|
||||||
|
}, {
|
||||||
|
module: /require-main-filename/
|
||||||
|
}, {
|
||||||
|
module: /ws/
|
||||||
|
}, {
|
||||||
|
module: /express/
|
||||||
|
}, {
|
||||||
|
module: /cross-spawn/
|
||||||
|
}, {
|
||||||
|
module: /@parcel\/watcher/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
config,
|
||||||
|
nativePlugin,
|
||||||
|
ignoredResources
|
||||||
|
};
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-remote-theia",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm run rebuild && theia build --mode development",
|
||||||
|
"rebuild": "theia rebuild:browser --cacheRoot ../..",
|
||||||
|
"start": "theia start",
|
||||||
|
"watch": "pnpm run rebuild && theia build --watch --mode development"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@git.zone/ide-extension-opencode": "workspace:*",
|
||||||
|
"@git.zone/ide-extension-product": "workspace:*",
|
||||||
|
"@git.zone/ide-extension-remote": "workspace:*",
|
||||||
|
"@theia/core": "1.71.0",
|
||||||
|
"@theia/editor": "1.71.0",
|
||||||
|
"@theia/filesystem": "1.71.0",
|
||||||
|
"@theia/markers": "1.71.0",
|
||||||
|
"@theia/messages": "1.71.0",
|
||||||
|
"@theia/monaco": "1.71.0",
|
||||||
|
"@theia/monaco-editor-core": "1.108.201",
|
||||||
|
"@theia/navigator": "1.71.0",
|
||||||
|
"@theia/output": "1.71.0",
|
||||||
|
"@theia/plugin-ext": "1.71.0",
|
||||||
|
"@theia/plugin-ext-vscode": "1.71.0",
|
||||||
|
"@theia/preferences": "1.71.0",
|
||||||
|
"@theia/process": "1.71.0",
|
||||||
|
"@theia/scm": "1.71.0",
|
||||||
|
"@theia/search-in-workspace": "1.71.0",
|
||||||
|
"@theia/task": "1.71.0",
|
||||||
|
"@theia/terminal": "1.71.0",
|
||||||
|
"@theia/vsx-registry": "1.71.0",
|
||||||
|
"@theia/workspace": "1.71.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@theia/cli": "1.71.0",
|
||||||
|
"source-map-loader": "^5.0.0",
|
||||||
|
"string-replace-loader": "^3.3.0"
|
||||||
|
},
|
||||||
|
"theia": {
|
||||||
|
"target": "browser",
|
||||||
|
"frontend": {
|
||||||
|
"config": {
|
||||||
|
"applicationName": "Git.Zone IDE",
|
||||||
|
"preferencesDirName": ".git-zone-ide"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { performance } = require('perf_hooks');
|
||||||
|
const startupLog = (milestone) => console.debug(`Backend main: ${milestone} [${(performance.now() / 1000).toFixed(3)} s since backend process start]`);
|
||||||
|
startupLog('entry point loaded');
|
||||||
|
const { BackendApplicationConfigProvider } = require('@theia/core/lib/node/backend-application-config-provider');
|
||||||
|
const main = require('@theia/core/lib/node/main');
|
||||||
|
|
||||||
|
BackendApplicationConfigProvider.set({
|
||||||
|
"singleInstance": true,
|
||||||
|
"frontendConnectionTimeout": 0,
|
||||||
|
"configurationFolder": ".theia"
|
||||||
|
});
|
||||||
|
|
||||||
|
globalThis.extensionInfo = [
|
||||||
|
{
|
||||||
|
"name": "@theia/core",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@git.zone/ide-extension-opencode",
|
||||||
|
"version": "0.1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@git.zone/ide-extension-product",
|
||||||
|
"version": "0.1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@git.zone/ide-extension-remote",
|
||||||
|
"version": "0.1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/variable-resolver",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/editor",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/filesystem",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/workspace",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/markers",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/messages",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/outline-view",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/monaco",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/navigator",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/output",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/ai-core",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/ai-mcp",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/bulk-edit",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/callhierarchy",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/console",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/process",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/file-search",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/terminal",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/userstorage",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/preferences",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/terminal-manager",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/task",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/test",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/debug",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/editor-preview",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/notebook",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/scm",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/search-in-workspace",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/timeline",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/typehierarchy",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/plugin-ext",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/plugin-ext-vscode",
|
||||||
|
"version": "1.71.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@theia/vsx-registry",
|
||||||
|
"version": "1.71.0"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const serverModule = require('./server');
|
||||||
|
const serverAddress = main.start(serverModule());
|
||||||
|
|
||||||
|
serverAddress.then((addressInfo) => {
|
||||||
|
if (process && process.send && addressInfo) {
|
||||||
|
process.send(addressInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
globalThis.serverAddress = serverAddress;
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// @ts-check
|
||||||
|
require('reflect-metadata');
|
||||||
|
const { performance } = require('perf_hooks');
|
||||||
|
const startupLog = (milestone) => console.debug(`Backend server: ${milestone} [${(performance.now() / 1000).toFixed(3)} s since backend process start]`);
|
||||||
|
startupLog('loading modules...');
|
||||||
|
|
||||||
|
// Erase the ELECTRON_RUN_AS_NODE variable from the environment, else Electron apps started using Theia will pick it up.
|
||||||
|
if ('ELECTRON_RUN_AS_NODE' in process.env) {
|
||||||
|
delete process.env.ELECTRON_RUN_AS_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
process.env.THEIA_APP_PROJECT_PATH = path.resolve(__dirname, '..', '..')
|
||||||
|
const express = require('@theia/core/shared/express');
|
||||||
|
const { Container } = require('@theia/core/shared/inversify');
|
||||||
|
const { BackendApplication, BackendApplicationServer, CliManager } = require('@theia/core/lib/node');
|
||||||
|
const { backendApplicationModule } = require('@theia/core/lib/node/backend-application-module');
|
||||||
|
const { messagingBackendModule } = require('@theia/core/lib/node/messaging/messaging-backend-module');
|
||||||
|
const { loggerBackendModule } = require('@theia/core/lib/node/logger-backend-module');
|
||||||
|
|
||||||
|
const container = new Container();
|
||||||
|
container.load(backendApplicationModule);
|
||||||
|
container.load(messagingBackendModule);
|
||||||
|
container.load(loggerBackendModule);
|
||||||
|
startupLog('container created');
|
||||||
|
|
||||||
|
function defaultServeStatic(app) {
|
||||||
|
app.use(express.static(path.resolve(__dirname, '../../lib/frontend')))
|
||||||
|
}
|
||||||
|
|
||||||
|
function load(raw) {
|
||||||
|
return Promise.resolve(raw).then(
|
||||||
|
module => container.load(module.default)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start(port, host, argv = process.argv) {
|
||||||
|
if (!container.isBound(BackendApplicationServer)) {
|
||||||
|
container.bind(BackendApplicationServer).toConstantValue({ configure: defaultServeStatic });
|
||||||
|
}
|
||||||
|
let result = undefined;
|
||||||
|
await container.get(CliManager).initializeCli(argv.slice(2),
|
||||||
|
() => {
|
||||||
|
startupLog('resolving application');
|
||||||
|
const application = container.get(BackendApplication);
|
||||||
|
startupLog('application resolved');
|
||||||
|
return application.configured;
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
result = container.get(BackendApplication).start(port, host);
|
||||||
|
});
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return Promise.reject(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async (port, host, argv) => {
|
||||||
|
try {
|
||||||
|
await load(require('@theia/core/lib/node/i18n/i18n-backend-module'));
|
||||||
|
await load(require('@theia/core/lib/node/hosting/backend-hosting-module'));
|
||||||
|
await load(require('@theia/core/lib/node/request/backend-request-module'));
|
||||||
|
await load(require('@git.zone/ide-extension-opencode/lib/node/gitzone-opencode-backend-module'));
|
||||||
|
await load(require('@git.zone/ide-extension-remote/lib/node/gitzone-remote-backend-module'));
|
||||||
|
await load(require('@theia/editor/lib/node/editor-backend-module'));
|
||||||
|
await load(require('@theia/filesystem/lib/node/filesystem-backend-module'));
|
||||||
|
await load(require('@theia/filesystem/lib/node/download/file-download-backend-module'));
|
||||||
|
await load(require('@theia/workspace/lib/node/workspace-backend-module'));
|
||||||
|
await load(require('@theia/markers/lib/node/problem-backend-module'));
|
||||||
|
await load(require('@theia/messages/lib/node/messages-backend-module'));
|
||||||
|
await load(require('@theia/navigator/lib/node/navigator-backend-module'));
|
||||||
|
await load(require('@theia/output/lib/node/output-backend-module'));
|
||||||
|
await load(require('@theia/ai-core/lib/node/ai-core-backend-module'));
|
||||||
|
await load(require('@theia/ai-mcp/lib/node/mcp-backend-module'));
|
||||||
|
await load(require('@theia/process/lib/common/process-common-module'));
|
||||||
|
await load(require('@theia/process/lib/node/process-backend-module'));
|
||||||
|
await load(require('@theia/file-search/lib/node/file-search-backend-module'));
|
||||||
|
await load(require('@theia/terminal/lib/node/terminal-backend-module'));
|
||||||
|
await load(require('@theia/preferences/lib/node/preference-backend-module'));
|
||||||
|
await load(require('@theia/task/lib/node/task-backend-module'));
|
||||||
|
await load(require('@theia/test/lib/node/test-backend-module'));
|
||||||
|
await load(require('@theia/debug/lib/node/debug-backend-module'));
|
||||||
|
await load(require('@theia/editor-preview/lib/node/editor-preview-backend-module'));
|
||||||
|
await load(require('@theia/notebook/lib/node/notebook-backend-module'));
|
||||||
|
await load(require('@theia/scm/lib/node/scm-backend-module'));
|
||||||
|
await load(require('@theia/search-in-workspace/lib/node/search-in-workspace-backend-module'));
|
||||||
|
await load(require('@theia/plugin-ext/lib/plugin-ext-backend-module'));
|
||||||
|
await load(require('@theia/plugin-ext-vscode/lib/node/plugin-vscode-backend-module'));
|
||||||
|
await load(require('@theia/vsx-registry/lib/common/vsx-registry-common-module'));
|
||||||
|
await load(require('@theia/vsx-registry/lib/node/vsx-registry-backend-module'));
|
||||||
|
startupLog('modules loaded');
|
||||||
|
return await start(port, host, argv);
|
||||||
|
} catch (error) {
|
||||||
|
if (typeof error !== 'number') {
|
||||||
|
console.error('Failed to start the backend application:');
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<title>Git.Zone IDE</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="theia-preload"></div>
|
||||||
|
<script type="text/javascript" src="./bundle.js" charset="utf-8"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// @ts-check
|
||||||
|
require('reflect-metadata');
|
||||||
|
const startupLog = (milestone) => console.debug(`Frontend: ${milestone} [${(performance.now() / 1000).toFixed(3)} s since frontend page start]`);
|
||||||
|
startupLog('loading modules...');
|
||||||
|
const { Container } = require('@theia/core/shared/inversify');
|
||||||
|
const { FrontendApplicationConfigProvider } = require('@theia/core/lib/browser/frontend-application-config-provider');
|
||||||
|
|
||||||
|
FrontendApplicationConfigProvider.set({
|
||||||
|
"applicationName": "Git.Zone IDE",
|
||||||
|
"defaultTheme": {
|
||||||
|
"light": "light",
|
||||||
|
"dark": "dark"
|
||||||
|
},
|
||||||
|
"defaultIconTheme": "theia-file-icons",
|
||||||
|
"electron": {
|
||||||
|
"windowOptions": {},
|
||||||
|
"showWindowEarly": true,
|
||||||
|
"splashScreenOptions": {},
|
||||||
|
"uriScheme": "theia"
|
||||||
|
},
|
||||||
|
"defaultLocale": "",
|
||||||
|
"validatePreferencesSchema": true,
|
||||||
|
"reloadOnReconnect": false,
|
||||||
|
"uriScheme": "theia",
|
||||||
|
"preferencesDirName": ".git-zone-ide"
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
self.MonacoEnvironment = {
|
||||||
|
getWorkerUrl: function (moduleId, label) {
|
||||||
|
return './editor.worker.js';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function load(container, jsModule) {
|
||||||
|
return Promise.resolve(jsModule)
|
||||||
|
.then(containerModule => container.load(containerModule.default));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function preload(container) {
|
||||||
|
try {
|
||||||
|
await load(container, import('@theia/core/lib/browser/preload/preload-module'));
|
||||||
|
const { Preloader } = require('@theia/core/lib/browser/preload/preloader');
|
||||||
|
const preloader = container.get(Preloader);
|
||||||
|
await preloader.initialize();
|
||||||
|
} catch (reason) {
|
||||||
|
console.error('Failed to run preload scripts.');
|
||||||
|
if (reason) {
|
||||||
|
console.error(reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (async () => {
|
||||||
|
const { messagingFrontendModule } = require('@theia/core/lib/browser/messaging/messaging-frontend-module');
|
||||||
|
const container = new Container();
|
||||||
|
container.load(messagingFrontendModule);
|
||||||
|
|
||||||
|
|
||||||
|
startupLog('container created');
|
||||||
|
|
||||||
|
await preload(container);
|
||||||
|
startupLog('preloaded');
|
||||||
|
|
||||||
|
|
||||||
|
const { MonacoInit } = require('@theia/monaco/lib/browser/monaco-init');
|
||||||
|
;
|
||||||
|
|
||||||
|
const { FrontendApplication } = require('@theia/core/lib/browser');
|
||||||
|
const { frontendApplicationModule } = require('@theia/core/lib/browser/frontend-application-module');
|
||||||
|
const { loggerFrontendModule } = require('@theia/core/lib/browser/logger-frontend-module');
|
||||||
|
|
||||||
|
container.load(frontendApplicationModule);
|
||||||
|
undefined
|
||||||
|
|
||||||
|
container.load(loggerFrontendModule);
|
||||||
|
|
||||||
|
|
||||||
|
startupLog('core modules loaded');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await load(container, import('@theia/core/lib/browser/i18n/i18n-frontend-module'));
|
||||||
|
await load(container, import('@theia/core/lib/browser/menu/browser-menu-module'));
|
||||||
|
await load(container, import('@theia/core/lib/browser/window/browser-window-module'));
|
||||||
|
await load(container, import('@theia/core/lib/browser/keyboard/browser-keyboard-module'));
|
||||||
|
await load(container, import('@theia/core/lib/browser/request/browser-request-module'));
|
||||||
|
await load(container, import('@git.zone/ide-extension-opencode/lib/browser/gitzone-opencode-frontend-module'));
|
||||||
|
await load(container, import('@git.zone/ide-extension-product/lib/browser/gitzone-product-frontend-module'));
|
||||||
|
await load(container, import('@git.zone/ide-extension-remote/lib/browser/gitzone-remote-frontend-module'));
|
||||||
|
await load(container, import('@theia/variable-resolver/lib/browser/variable-resolver-frontend-module'));
|
||||||
|
await load(container, import('@theia/editor/lib/browser/editor-frontend-module'));
|
||||||
|
await load(container, import('@theia/filesystem/lib/browser/filesystem-frontend-module'));
|
||||||
|
await load(container, import('@theia/filesystem/lib/browser/download/file-download-frontend-module'));
|
||||||
|
await load(container, import('@theia/filesystem/lib/browser/file-dialog/file-dialog-module'));
|
||||||
|
await load(container, import('@theia/workspace/lib/browser/workspace-frontend-module'));
|
||||||
|
await load(container, import('@theia/markers/lib/browser/problem/problem-frontend-module'));
|
||||||
|
await load(container, import('@theia/messages/lib/browser/messages-frontend-module'));
|
||||||
|
await load(container, import('@theia/outline-view/lib/browser/outline-view-frontend-module'));
|
||||||
|
await load(container, import('@theia/monaco/lib/browser/monaco-frontend-module'));
|
||||||
|
await load(container, import('@theia/navigator/lib/browser/navigator-frontend-module'));
|
||||||
|
await load(container, import('@theia/output/lib/browser/output-frontend-module'));
|
||||||
|
await load(container, import('@theia/ai-core/lib/browser/ai-core-frontend-module'));
|
||||||
|
await load(container, import('@theia/ai-mcp/lib/browser/mcp-frontend-module'));
|
||||||
|
await load(container, import('@theia/bulk-edit/lib/browser/bulk-edit-frontend-module'));
|
||||||
|
await load(container, import('@theia/callhierarchy/lib/browser/callhierarchy-frontend-module'));
|
||||||
|
await load(container, import('@theia/console/lib/browser/console-frontend-module'));
|
||||||
|
await load(container, import('@theia/process/lib/common/process-common-module'));
|
||||||
|
await load(container, import('@theia/file-search/lib/browser/file-search-frontend-module'));
|
||||||
|
await load(container, import('@theia/terminal/lib/browser/terminal-frontend-module'));
|
||||||
|
await load(container, import('@theia/userstorage/lib/browser/user-storage-frontend-module'));
|
||||||
|
await load(container, import('@theia/preferences/lib/browser/preference-frontend-module'));
|
||||||
|
await load(container, import('@theia/terminal-manager/lib/browser/terminal-manager-frontend-module'));
|
||||||
|
await load(container, import('@theia/task/lib/browser/task-frontend-module'));
|
||||||
|
await load(container, import('@theia/test/lib/browser/view/test-view-frontend-module'));
|
||||||
|
await load(container, import('@theia/debug/lib/browser/debug-frontend-module'));
|
||||||
|
await load(container, import('@theia/editor-preview/lib/browser/editor-preview-frontend-module'));
|
||||||
|
await load(container, import('@theia/notebook/lib/browser/notebook-frontend-module'));
|
||||||
|
await load(container, import('@theia/scm/lib/browser/scm-frontend-module'));
|
||||||
|
await load(container, import('@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-module'));
|
||||||
|
await load(container, import('@theia/timeline/lib/browser/timeline-frontend-module'));
|
||||||
|
await load(container, import('@theia/typehierarchy/lib/browser/typehierarchy-frontend-module'));
|
||||||
|
await load(container, import('@theia/plugin-ext/lib/plugin-ext-frontend-module'));
|
||||||
|
await load(container, import('@theia/plugin-ext-vscode/lib/browser/plugin-vscode-frontend-module'));
|
||||||
|
await load(container, import('@theia/vsx-registry/lib/common/vsx-registry-common-module'));
|
||||||
|
await load(container, import('@theia/vsx-registry/lib/browser/vsx-registry-frontend-module'));
|
||||||
|
|
||||||
|
MonacoInit.init(container);
|
||||||
|
;
|
||||||
|
startupLog('modules loaded');
|
||||||
|
await start();
|
||||||
|
} catch (reason) {
|
||||||
|
console.error('Failed to start the frontend application.');
|
||||||
|
if (reason) {
|
||||||
|
console.error(reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
(window['theia'] = window['theia'] || {}).container = container;
|
||||||
|
startupLog('resolving application');
|
||||||
|
const application = container.get(FrontendApplication);
|
||||||
|
startupLog('application resolved');
|
||||||
|
return application.start();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// @ts-check
|
||||||
|
require('reflect-metadata');
|
||||||
|
const { Container } = require('@theia/core/shared/inversify');
|
||||||
|
|
||||||
|
module.exports = Promise.resolve().then(() => {
|
||||||
|
const { frontendApplicationModule } = require('@theia/core/lib/browser/frontend-application-module');
|
||||||
|
const container = new Container();
|
||||||
|
container.load(frontendApplicationModule);
|
||||||
|
container.load(require('@theia/editor/lib/browser/editor-frontend-module').default);
|
||||||
|
container.load(require('@theia/filesystem/lib/browser/filesystem-frontend-module').default);
|
||||||
|
container.load(require('@theia/monaco/lib/browser/monaco-frontend-module').default);
|
||||||
|
container.load(require('@theia/terminal/lib/browser/terminal-frontend-module').default);
|
||||||
|
container.load(require('@theia/debug/lib/browser/debug-frontend-module').default);
|
||||||
|
});
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Theia — Secondary Window</title>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
overflow: hidden;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
head,
|
||||||
|
body,
|
||||||
|
.secondary-widget-root,
|
||||||
|
#widget-host {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="./secondary-window.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="widget-host"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* This file can be edited to customize webpack configuration.
|
||||||
|
* To reset delete this file and rerun theia build again.
|
||||||
|
*/
|
||||||
|
// @ts-check
|
||||||
|
const configs = require('./gen-webpack.config.js');
|
||||||
|
const nodeConfig = require('./gen-webpack.node.config.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose bundled modules on window.theia.moduleName namespace, e.g.
|
||||||
|
* window['theia']['@theia/core/lib/common/uri'].
|
||||||
|
* Such syntax can be used by external code, for instance, for testing.
|
||||||
|
configs[0].module.rules.push({
|
||||||
|
test: /\.js$/,
|
||||||
|
loader: require.resolve('@theia/application-manager/lib/expose-loader')
|
||||||
|
}); */
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
...configs,
|
||||||
|
nodeConfig.config
|
||||||
|
];
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
Git.Zone IDE is split into a local Electron shell and a remote Theia workspace server.
|
||||||
|
|
||||||
|
## Local Electron Shell
|
||||||
|
|
||||||
|
The Electron app is intentionally small. It owns SSH host selection, remote bootstrap execution, local SSH tunnel lifecycle, and workspace windows. It does not implement remote filesystem, terminal, Git, or OpenCode behavior itself.
|
||||||
|
|
||||||
|
## Remote Theia Server
|
||||||
|
|
||||||
|
The remote server is a Theia browser application installed under `~/.git.zone/ide-server/<version>`. It runs on the SSH host, bound to `127.0.0.1`, and is accessed only through an SSH local port forward.
|
||||||
|
|
||||||
|
## OpenCode Integration
|
||||||
|
|
||||||
|
The `@git.zone/ide-extension-opencode` backend starts or connects to `opencode serve` in the remote workspace. The Theia frontend talks to that backend over Theia JSON-RPC. The browser never receives the OpenCode server password and never talks to OpenCode HTTP directly.
|
||||||
|
|
||||||
|
## Execution Boundary
|
||||||
|
|
||||||
|
Files, terminal commands, Git, language servers, builds, tests, Git.Zone commands, and OpenCode tools all run on the remote SSH host. The local machine only displays UI and maintains SSH transport.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-workspace",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"description": "Remote-first Git.Zone IDE based on Theia, Electron, SSH, and OpenCode server.",
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm -r --filter '@git.zone/ide-protocol' --filter '@git.zone/ide-ssh' --filter '@git.zone/ide-server-installer' --filter '@git.zone/ide-opencode-bridge' --filter '@git.zone/ide-extension-*' --filter '@git.zone/ide-electron-shell' run build",
|
||||||
|
"build:theia": "pnpm --filter '@git.zone/ide-remote-theia' run build",
|
||||||
|
"start:electron": "pnpm --filter '@git.zone/ide-electron-shell' run start",
|
||||||
|
"start:remote": "pnpm --filter '@git.zone/ide-remote-theia' run start",
|
||||||
|
"test": "pnpm run build && tstest test/**/*.ts --verbose",
|
||||||
|
"test:unit": "tstest test/**/*.ts --verbose"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@git.zone/tsbuild": "^4.4.1",
|
||||||
|
"@git.zone/tsrun": "^2.0.4",
|
||||||
|
"@git.zone/tstest": "^3.6.6",
|
||||||
|
"@types/node": "^20.19.40",
|
||||||
|
"native-keymap": "^3.3.9",
|
||||||
|
"typescript": "~5.6.3"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"@vscode/ripgrep",
|
||||||
|
"drivelist",
|
||||||
|
"keytar"
|
||||||
|
],
|
||||||
|
"overrides": {
|
||||||
|
"@vscode/ripgrep": "1.17.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-opencode-bridge",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist_ts/index.js",
|
||||||
|
"typings": "dist_ts/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"ts/**/*",
|
||||||
|
"dist_ts/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export interface IOpenCodeClientOptions {
|
||||||
|
baseUrl: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
fetch?: typeof fetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOpenCodeMessagePart {
|
||||||
|
type: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOpenCodePromptBody {
|
||||||
|
messageID?: string;
|
||||||
|
model?: {
|
||||||
|
providerID: string;
|
||||||
|
modelID: string;
|
||||||
|
};
|
||||||
|
agent?: string;
|
||||||
|
noReply?: boolean;
|
||||||
|
system?: string;
|
||||||
|
tools?: Record<string, boolean>;
|
||||||
|
parts: IOpenCodeMessagePart[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOpenCodeCommandBody {
|
||||||
|
messageID?: string;
|
||||||
|
agent?: string;
|
||||||
|
model?: {
|
||||||
|
providerID: string;
|
||||||
|
modelID: string;
|
||||||
|
};
|
||||||
|
command: string;
|
||||||
|
arguments?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOpenCodeShellBody {
|
||||||
|
agent: string;
|
||||||
|
model?: {
|
||||||
|
providerID: string;
|
||||||
|
modelID: string;
|
||||||
|
};
|
||||||
|
command: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOpenCodeEvent {
|
||||||
|
type: string;
|
||||||
|
id?: string;
|
||||||
|
retry?: number;
|
||||||
|
data?: unknown;
|
||||||
|
raw: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenCodeHttpError extends Error {
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
public readonly status: number,
|
||||||
|
public readonly body: string,
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenCodeServerClient {
|
||||||
|
private readonly baseUrl: string;
|
||||||
|
private readonly fetchImpl: typeof fetch;
|
||||||
|
private readonly authorizationHeader?: string;
|
||||||
|
|
||||||
|
constructor(options: IOpenCodeClientOptions) {
|
||||||
|
this.baseUrl = options.baseUrl.replace(/\/+$/g, '');
|
||||||
|
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
||||||
|
if (options.username || options.password) {
|
||||||
|
const username = options.username ?? 'opencode';
|
||||||
|
const password = options.password ?? '';
|
||||||
|
this.authorizationHeader = `Basic ${plugins.Buffer.from(`${username}:${password}`).toString('base64')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async health<T = unknown>() {
|
||||||
|
return this.request<T>('/global/health');
|
||||||
|
}
|
||||||
|
|
||||||
|
async projects<T = unknown>() {
|
||||||
|
return this.request<T>('/project');
|
||||||
|
}
|
||||||
|
|
||||||
|
async currentProject<T = unknown>() {
|
||||||
|
return this.request<T>('/project/current');
|
||||||
|
}
|
||||||
|
|
||||||
|
async path<T = unknown>() {
|
||||||
|
return this.request<T>('/path');
|
||||||
|
}
|
||||||
|
|
||||||
|
async vcs<T = unknown>() {
|
||||||
|
return this.request<T>('/vcs');
|
||||||
|
}
|
||||||
|
|
||||||
|
async config<T = unknown>() {
|
||||||
|
return this.request<T>('/config');
|
||||||
|
}
|
||||||
|
|
||||||
|
async providers<T = unknown>() {
|
||||||
|
return this.request<T>('/provider');
|
||||||
|
}
|
||||||
|
|
||||||
|
async agents<T = unknown>() {
|
||||||
|
return this.request<T>('/agent');
|
||||||
|
}
|
||||||
|
|
||||||
|
async commands<T = unknown>() {
|
||||||
|
return this.request<T>('/command');
|
||||||
|
}
|
||||||
|
|
||||||
|
async sessions<T = unknown>() {
|
||||||
|
return this.request<T>('/session');
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession<T = unknown>(body: { parentID?: string; title?: string } = {}) {
|
||||||
|
return this.request<T>('/session', { method: 'POST', body });
|
||||||
|
}
|
||||||
|
|
||||||
|
async session<T = unknown>(id: string) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSession<T = unknown>(id: string, body: { title?: string }) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSession<T = unknown>(id: string) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async sessionStatus<T = unknown>() {
|
||||||
|
return this.request<T>('/session/status');
|
||||||
|
}
|
||||||
|
|
||||||
|
async children<T = unknown>(id: string) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/children`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async todo<T = unknown>(id: string) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/todo`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async messages<T = unknown>(id: string, limit?: number) {
|
||||||
|
const query = limit ? `?limit=${encodeURIComponent(`${limit}`)}` : '';
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/message${query}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async message<T = unknown>(id: string, messageID: string) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/message/${encodeURIComponent(messageID)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async prompt<T = unknown>(id: string, body: IOpenCodePromptBody) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/message`, { method: 'POST', body });
|
||||||
|
}
|
||||||
|
|
||||||
|
async promptAsync(id: string, body: IOpenCodePromptBody) {
|
||||||
|
return this.request<void>(`/session/${encodeURIComponent(id)}/prompt_async`, { method: 'POST', body });
|
||||||
|
}
|
||||||
|
|
||||||
|
async command<T = unknown>(id: string, body: IOpenCodeCommandBody) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/command`, { method: 'POST', body });
|
||||||
|
}
|
||||||
|
|
||||||
|
async shell<T = unknown>(id: string, body: IOpenCodeShellBody) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/shell`, { method: 'POST', body });
|
||||||
|
}
|
||||||
|
|
||||||
|
async abort<T = unknown>(id: string) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/abort`, { method: 'POST' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async diff<T = unknown>(id: string, messageID?: string) {
|
||||||
|
const query = messageID ? `?messageID=${encodeURIComponent(messageID)}` : '';
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/diff${query}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async revert<T = unknown>(id: string, body: { messageID: string; partID?: string }) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/revert`, { method: 'POST', body });
|
||||||
|
}
|
||||||
|
|
||||||
|
async unrevert<T = unknown>(id: string) {
|
||||||
|
return this.request<T>(`/session/${encodeURIComponent(id)}/unrevert`, { method: 'POST' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async respondToPermission<T = unknown>(
|
||||||
|
sessionID: string,
|
||||||
|
permissionID: string,
|
||||||
|
body: { response: string; remember?: boolean },
|
||||||
|
) {
|
||||||
|
return this.request<T>(
|
||||||
|
`/session/${encodeURIComponent(sessionID)}/permissions/${encodeURIComponent(permissionID)}`,
|
||||||
|
{ method: 'POST', body },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findFiles<T = unknown>(query: { query: string; type?: 'file' | 'directory'; directory?: string; limit?: number }) {
|
||||||
|
return this.request<T>(`/find/file?${new URLSearchParams(stringifyQuery(query))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findText<T = unknown>(query: { pattern: string }) {
|
||||||
|
return this.request<T>(`/find?${new URLSearchParams(stringifyQuery(query))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fileContent<T = unknown>(path: string) {
|
||||||
|
return this.request<T>(`/file/content?${new URLSearchParams({ path })}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fileStatus<T = unknown>() {
|
||||||
|
return this.request<T>('/file/status');
|
||||||
|
}
|
||||||
|
|
||||||
|
async *events(signal?: AbortSignal): AsyncGenerator<IOpenCodeEvent> {
|
||||||
|
const response = await this.fetchImpl(`${this.baseUrl}/event`, {
|
||||||
|
headers: this.createHeaders(),
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw await this.createHttpError(response, '/event');
|
||||||
|
}
|
||||||
|
if (!response.body) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buffer = '';
|
||||||
|
while (true) {
|
||||||
|
const { value, done } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buffer += decoder.decode(value, { stream: true });
|
||||||
|
const parts = buffer.split(/\n\n/);
|
||||||
|
buffer = parts.pop() ?? '';
|
||||||
|
for (const part of parts) {
|
||||||
|
const event = parseServerSentEvent(part);
|
||||||
|
if (event) {
|
||||||
|
yield event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const finalEvent = parseServerSentEvent(buffer);
|
||||||
|
if (finalEvent) {
|
||||||
|
yield finalEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async request<T>(path: string, options: { method?: string; body?: unknown } = {}) {
|
||||||
|
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
||||||
|
method: options.method ?? 'GET',
|
||||||
|
headers: this.createHeaders(options.body !== undefined),
|
||||||
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw await this.createHttpError(response, path);
|
||||||
|
}
|
||||||
|
if (response.status === 204) {
|
||||||
|
return undefined as T;
|
||||||
|
}
|
||||||
|
const text = await response.text();
|
||||||
|
if (!text) {
|
||||||
|
return undefined as T;
|
||||||
|
}
|
||||||
|
return JSON.parse(text) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createHeaders(hasJsonBody = false) {
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
if (hasJsonBody) {
|
||||||
|
headers['content-type'] = 'application/json';
|
||||||
|
}
|
||||||
|
if (this.authorizationHeader) {
|
||||||
|
headers.authorization = this.authorizationHeader;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createHttpError(response: Response, path: string) {
|
||||||
|
const body = await response.text();
|
||||||
|
return new OpenCodeHttpError(`OpenCode request failed: ${response.status} ${path}`, response.status, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseServerSentEvent = (raw: string): IOpenCodeEvent | undefined => {
|
||||||
|
const trimmed = raw.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataLines: string[] = [];
|
||||||
|
let type = 'message';
|
||||||
|
let id: string | undefined;
|
||||||
|
let retry: number | undefined;
|
||||||
|
|
||||||
|
for (const line of trimmed.split(/\r?\n/)) {
|
||||||
|
if (line.startsWith(':')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const separator = line.indexOf(':');
|
||||||
|
const field = separator === -1 ? line : line.slice(0, separator);
|
||||||
|
const value = separator === -1 ? '' : line.slice(separator + 1).replace(/^ /, '');
|
||||||
|
if (field === 'event') {
|
||||||
|
type = value;
|
||||||
|
} else if (field === 'data') {
|
||||||
|
dataLines.push(value);
|
||||||
|
} else if (field === 'id') {
|
||||||
|
id = value;
|
||||||
|
} else if (field === 'retry') {
|
||||||
|
const retryNumber = Number(value);
|
||||||
|
if (Number.isFinite(retryNumber)) {
|
||||||
|
retry = retryNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataText = dataLines.join('\n');
|
||||||
|
const data = parseJsonIfPossible(dataText);
|
||||||
|
if (type === 'message' && data && typeof data === 'object' && 'type' in data) {
|
||||||
|
type = String((data as { type: unknown }).type);
|
||||||
|
}
|
||||||
|
return { type, id, retry, data, raw };
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseJsonIfPossible = (value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(value) as unknown;
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stringifyQuery = (query: Record<string, string | number | boolean | undefined>) => {
|
||||||
|
const result: Record<string, string> = {};
|
||||||
|
for (const [key, value] of Object.entries(query)) {
|
||||||
|
if (value !== undefined) {
|
||||||
|
result[key] = String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { Buffer } from 'node:buffer';
|
||||||
|
|
||||||
|
export { Buffer };
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "ts",
|
||||||
|
"outDir": "dist_ts",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["ts/**/*.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-protocol",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist_ts/index.js",
|
||||||
|
"typings": "dist_ts/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"ts/**/*",
|
||||||
|
"dist_ts/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
export const gitZoneIdeProtocolVersion = 1;
|
||||||
|
|
||||||
|
export type TRemoteProcessStatus = 'starting' | 'running' | 'stopped' | 'failed';
|
||||||
|
|
||||||
|
export interface IIdeSshTarget {
|
||||||
|
id: string;
|
||||||
|
label?: string;
|
||||||
|
hostAlias: string;
|
||||||
|
hostName?: string;
|
||||||
|
user?: string;
|
||||||
|
port?: number;
|
||||||
|
workspacePath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteServerManifest {
|
||||||
|
protocolVersion: number;
|
||||||
|
serverVersion: string;
|
||||||
|
platform: string;
|
||||||
|
arch: string;
|
||||||
|
artifactName: string;
|
||||||
|
sha256?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteServerPaths {
|
||||||
|
installRoot: string;
|
||||||
|
versionRoot: string;
|
||||||
|
currentLink: string;
|
||||||
|
logsDir: string;
|
||||||
|
manifestPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteOpenCodeDescriptor {
|
||||||
|
baseUrl: string;
|
||||||
|
port: number;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
status: TRemoteProcessStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteTheiaDescriptor {
|
||||||
|
baseUrl: string;
|
||||||
|
localPort: number;
|
||||||
|
remotePort: number;
|
||||||
|
status: TRemoteProcessStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteSessionDescriptor {
|
||||||
|
sessionId: string;
|
||||||
|
target: IIdeSshTarget;
|
||||||
|
workspacePath: string;
|
||||||
|
serverVersion: string;
|
||||||
|
createdAt: string;
|
||||||
|
theia: IRemoteTheiaDescriptor;
|
||||||
|
opencode?: IRemoteOpenCodeDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteProbeResult {
|
||||||
|
ok: boolean;
|
||||||
|
hostAlias: string;
|
||||||
|
platform?: string;
|
||||||
|
arch?: string;
|
||||||
|
homeDir?: string;
|
||||||
|
shell?: string;
|
||||||
|
nodeVersion?: string;
|
||||||
|
pnpmVersion?: string;
|
||||||
|
gitVersion?: string;
|
||||||
|
opencodeVersion?: string;
|
||||||
|
errors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPortForwardDescriptor {
|
||||||
|
id: string;
|
||||||
|
label?: string;
|
||||||
|
localHost: string;
|
||||||
|
localPort: number;
|
||||||
|
remoteHost: string;
|
||||||
|
remotePort: number;
|
||||||
|
status: TRemoteProcessStatus;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "ts",
|
||||||
|
"outDir": "dist_ts",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["ts/**/*.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-server-installer",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist_ts/index.js",
|
||||||
|
"typings": "dist_ts/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@git.zone/ide-protocol": "workspace:*"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"ts/**/*",
|
||||||
|
"dist_ts/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import {
|
||||||
|
gitZoneIdeProtocolVersion,
|
||||||
|
type IRemoteServerManifest,
|
||||||
|
type IRemoteServerPaths,
|
||||||
|
} from '@git.zone/ide-protocol';
|
||||||
|
|
||||||
|
export interface IRemoteServerInstallPlanOptions {
|
||||||
|
serverVersion: string;
|
||||||
|
artifactName: string;
|
||||||
|
installRoot?: string;
|
||||||
|
platform?: string;
|
||||||
|
arch?: string;
|
||||||
|
sha256?: string;
|
||||||
|
protocolVersion?: number;
|
||||||
|
createdAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteServerInstallPlan {
|
||||||
|
manifest: IRemoteServerManifest;
|
||||||
|
paths: IRemoteServerPaths;
|
||||||
|
markerFile: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteServerBootstrapOptions {
|
||||||
|
serverVersion: string;
|
||||||
|
workspacePath: string;
|
||||||
|
theiaPort: number;
|
||||||
|
opencodePort: number;
|
||||||
|
opencodeUsername: string;
|
||||||
|
opencodePassword: string;
|
||||||
|
installRoot?: string;
|
||||||
|
nodeEnv?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultInstallRoot = '~/.git.zone/ide-server';
|
||||||
|
|
||||||
|
export const createRemoteServerInstallPlan = (
|
||||||
|
options: IRemoteServerInstallPlanOptions,
|
||||||
|
): IRemoteServerInstallPlan => {
|
||||||
|
const installRoot = trimTrailingSlash(options.installRoot ?? defaultInstallRoot);
|
||||||
|
const versionRoot = joinRemotePath(installRoot, options.serverVersion);
|
||||||
|
const paths: IRemoteServerPaths = {
|
||||||
|
installRoot,
|
||||||
|
versionRoot,
|
||||||
|
currentLink: joinRemotePath(installRoot, 'current'),
|
||||||
|
logsDir: joinRemotePath(installRoot, 'logs'),
|
||||||
|
manifestPath: joinRemotePath(versionRoot, 'manifest.json'),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
manifest: createRemoteServerManifest(options),
|
||||||
|
paths,
|
||||||
|
markerFile: joinRemotePath(versionRoot, '.installed'),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRemoteServerManifest = (
|
||||||
|
options: IRemoteServerInstallPlanOptions,
|
||||||
|
): IRemoteServerManifest => ({
|
||||||
|
protocolVersion: options.protocolVersion ?? gitZoneIdeProtocolVersion,
|
||||||
|
serverVersion: options.serverVersion,
|
||||||
|
platform: options.platform ?? 'unknown',
|
||||||
|
arch: options.arch ?? 'unknown',
|
||||||
|
artifactName: options.artifactName,
|
||||||
|
sha256: options.sha256,
|
||||||
|
createdAt: options.createdAt ?? new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createRemoteInstallCommand = (plan: IRemoteServerInstallPlan) => {
|
||||||
|
const manifestJson = JSON.stringify(plan.manifest, undefined, 2);
|
||||||
|
return [
|
||||||
|
'set -euo pipefail',
|
||||||
|
`mkdir -p ${quoteShellArg(plan.paths.versionRoot)} ${quoteShellArg(plan.paths.logsDir)}`,
|
||||||
|
`cat > ${quoteShellArg(plan.paths.manifestPath)} <<'GITZONE_IDE_MANIFEST'`,
|
||||||
|
manifestJson,
|
||||||
|
'GITZONE_IDE_MANIFEST',
|
||||||
|
`ln -sfn ${quoteShellArg(plan.paths.versionRoot)} ${quoteShellArg(plan.paths.currentLink)}`,
|
||||||
|
`touch ${quoteShellArg(plan.markerFile)}`,
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRemoteBootstrapCommand = (options: IRemoteServerBootstrapOptions) => {
|
||||||
|
const plan = createRemoteServerInstallPlan({
|
||||||
|
serverVersion: options.serverVersion,
|
||||||
|
artifactName: 'remote-theia',
|
||||||
|
installRoot: options.installRoot,
|
||||||
|
});
|
||||||
|
const appDir = joinRemotePath(plan.paths.versionRoot, 'applications/remote-theia');
|
||||||
|
const logFile = joinRemotePath(plan.paths.logsDir, `theia-${options.theiaPort}.log`);
|
||||||
|
const env = {
|
||||||
|
GITZONE_IDE_WORKSPACE: options.workspacePath,
|
||||||
|
GITZONE_IDE_OPENCODE_PORT: `${options.opencodePort}`,
|
||||||
|
OPENCODE_SERVER_USERNAME: options.opencodeUsername,
|
||||||
|
OPENCODE_SERVER_PASSWORD: options.opencodePassword,
|
||||||
|
NODE_ENV: options.nodeEnv ?? 'production',
|
||||||
|
} satisfies Record<string, string>;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'set -euo pipefail',
|
||||||
|
`mkdir -p ${quoteShellArg(plan.paths.logsDir)}`,
|
||||||
|
`test -d ${quoteShellArg(options.workspacePath)}`,
|
||||||
|
`cd ${quoteShellArg(options.workspacePath)}`,
|
||||||
|
...Object.entries(env).map(([key, value]) => `export ${key}=${quoteShellArg(value)}`),
|
||||||
|
`nohup pnpm --dir ${quoteShellArg(appDir)} start --hostname 127.0.0.1 --port ${options.theiaPort} ${quoteShellArg(options.workspacePath)} > ${quoteShellArg(logFile)} 2>&1 < /dev/null &`,
|
||||||
|
`printf 'theiaPort=%s\\n' ${options.theiaPort}`,
|
||||||
|
`printf 'opencodePort=%s\\n' ${options.opencodePort}`,
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRemoteHealthCommand = (serverVersion: string, installRoot = defaultInstallRoot) => {
|
||||||
|
const plan = createRemoteServerInstallPlan({
|
||||||
|
serverVersion,
|
||||||
|
artifactName: 'remote-theia',
|
||||||
|
installRoot,
|
||||||
|
});
|
||||||
|
return [
|
||||||
|
'set -euo pipefail',
|
||||||
|
`test -f ${quoteShellArg(plan.markerFile)}`,
|
||||||
|
`cat ${quoteShellArg(plan.paths.manifestPath)}`,
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const quoteShellArg = (value: string | number | boolean) => {
|
||||||
|
const stringValue = String(value);
|
||||||
|
if (stringValue.length === 0) {
|
||||||
|
return "''";
|
||||||
|
}
|
||||||
|
return `'${stringValue.replace(/'/g, `'"'"'`)}'`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const joinRemotePath = (...parts: string[]) => {
|
||||||
|
const [first, ...rest] = parts.filter(Boolean);
|
||||||
|
if (!first) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return [trimTrailingSlash(first), ...rest.map((part) => part.replace(/^\/+|\/+$/g, ''))]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
const trimTrailingSlash = (value: string) => value.replace(/\/+$/g, '');
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "ts",
|
||||||
|
"outDir": "dist_ts",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["ts/**/*.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-ssh",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist_ts/index.js",
|
||||||
|
"typings": "dist_ts/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@git.zone/ide-protocol": "workspace:*"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"ts/**/*",
|
||||||
|
"dist_ts/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,397 @@
|
|||||||
|
import type { IIdeSshTarget, IRemoteProbeResult } from '@git.zone/ide-protocol';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export interface ISshHostConfig {
|
||||||
|
alias: string;
|
||||||
|
patterns: string[];
|
||||||
|
hostName?: string;
|
||||||
|
user?: string;
|
||||||
|
port?: number;
|
||||||
|
identityFiles: string[];
|
||||||
|
proxyJump?: string;
|
||||||
|
forwardAgent?: boolean;
|
||||||
|
raw: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISshRunOptions {
|
||||||
|
executable?: string;
|
||||||
|
timeoutMs?: number;
|
||||||
|
batchMode?: boolean;
|
||||||
|
cwd?: string;
|
||||||
|
env?: NodeJS.ProcessEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISshRunResult {
|
||||||
|
exitCode: number;
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISshTunnelOptions extends ISshRunOptions {
|
||||||
|
localHost?: string;
|
||||||
|
localPort: number;
|
||||||
|
remoteHost?: string;
|
||||||
|
remotePort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISshTunnelHandle {
|
||||||
|
readonly target: IIdeSshTarget;
|
||||||
|
readonly localHost: string;
|
||||||
|
readonly localPort: number;
|
||||||
|
readonly remoteHost: string;
|
||||||
|
readonly remotePort: number;
|
||||||
|
readonly processId: number | undefined;
|
||||||
|
dispose(signal?: NodeJS.Signals): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directiveAliases: Record<string, keyof ISshHostConfig | 'identityFiles'> = {
|
||||||
|
hostname: 'hostName',
|
||||||
|
user: 'user',
|
||||||
|
port: 'port',
|
||||||
|
identityfile: 'identityFiles',
|
||||||
|
proxyjump: 'proxyJump',
|
||||||
|
forwardagent: 'forwardAgent',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultSshConfigPath = () => plugins.path.join(plugins.os.homedir(), '.ssh', 'config');
|
||||||
|
|
||||||
|
export const expandHome = (filePath: string) => {
|
||||||
|
if (filePath === '~') {
|
||||||
|
return plugins.os.homedir();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filePath.startsWith('~/')) {
|
||||||
|
return plugins.path.join(plugins.os.homedir(), filePath.slice(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseSshConfig = (configText: string): ISshHostConfig[] => {
|
||||||
|
const hosts: ISshHostConfig[] = [];
|
||||||
|
let currentHosts: ISshHostConfig[] = [];
|
||||||
|
|
||||||
|
for (const rawLine of configText.split(/\r?\n/)) {
|
||||||
|
const line = stripSshComment(rawLine).trim();
|
||||||
|
if (!line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = tokenizeSshConfigLine(line);
|
||||||
|
if (tokens.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = tokens[0]!.toLowerCase();
|
||||||
|
const values = tokens.slice(1);
|
||||||
|
if (key === 'host') {
|
||||||
|
currentHosts = values.map((alias) => ({
|
||||||
|
alias,
|
||||||
|
patterns: values,
|
||||||
|
identityFiles: [],
|
||||||
|
raw: {},
|
||||||
|
}));
|
||||||
|
hosts.push(...currentHosts);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const host of currentHosts) {
|
||||||
|
host.raw[key] = [...(host.raw[key] ?? []), ...values];
|
||||||
|
applySshDirective(host, key, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hosts;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readSshConfig = async (filePath = defaultSshConfigPath()) => {
|
||||||
|
try {
|
||||||
|
const configText = await plugins.fs.readFile(expandHome(filePath), 'utf8');
|
||||||
|
return parseSshConfig(configText);
|
||||||
|
} catch (error) {
|
||||||
|
const nodeError = error as NodeJS.ErrnoException;
|
||||||
|
if (nodeError.code === 'ENOENT') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listConnectableHosts = (hosts: ISshHostConfig[]) => {
|
||||||
|
return hosts.filter((host) => !isWildcardHostPattern(host.alias) && !host.alias.startsWith('!'));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildSshDestination = (target: Pick<IIdeSshTarget, 'hostAlias' | 'user'>) => {
|
||||||
|
return target.user ? `${target.user}@${target.hostAlias}` : target.hostAlias;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildSshArgs = (
|
||||||
|
target: IIdeSshTarget,
|
||||||
|
remoteCommand?: string,
|
||||||
|
options: ISshRunOptions = {},
|
||||||
|
) => {
|
||||||
|
const args = buildSshOptionArgs(target, options);
|
||||||
|
args.push(buildSshDestination(target));
|
||||||
|
if (remoteCommand) {
|
||||||
|
args.push(remoteCommand);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runSshCommand = async (
|
||||||
|
target: IIdeSshTarget,
|
||||||
|
remoteCommand: string,
|
||||||
|
options: ISshRunOptions = {},
|
||||||
|
): Promise<ISshRunResult> => {
|
||||||
|
const executable = options.executable ?? 'ssh';
|
||||||
|
const args = buildSshArgs(target, remoteCommand, options);
|
||||||
|
|
||||||
|
return new Promise<ISshRunResult>((resolve, reject) => {
|
||||||
|
const child = plugins.childProcess.spawn(executable, args, {
|
||||||
|
cwd: options.cwd,
|
||||||
|
env: options.env ?? process.env,
|
||||||
|
shell: false,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const stdout: Buffer[] = [];
|
||||||
|
const stderr: Buffer[] = [];
|
||||||
|
let finished = false;
|
||||||
|
const timeout = options.timeoutMs
|
||||||
|
? setTimeout(() => {
|
||||||
|
if (!finished) {
|
||||||
|
child.kill('SIGTERM');
|
||||||
|
}
|
||||||
|
}, options.timeoutMs)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));
|
||||||
|
child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));
|
||||||
|
child.on('error', (error) => {
|
||||||
|
finished = true;
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
child.on('close', (exitCode) => {
|
||||||
|
finished = true;
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
resolve({
|
||||||
|
exitCode: exitCode ?? 1,
|
||||||
|
stdout: Buffer.concat(stdout).toString('utf8'),
|
||||||
|
stderr: Buffer.concat(stderr).toString('utf8'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findFreePort = async (host = '127.0.0.1') => {
|
||||||
|
return new Promise<number>((resolve, reject) => {
|
||||||
|
const server = plugins.net.createServer();
|
||||||
|
server.unref();
|
||||||
|
server.on('error', reject);
|
||||||
|
server.listen(0, host, () => {
|
||||||
|
const address = server.address();
|
||||||
|
server.close(() => {
|
||||||
|
if (typeof address === 'object' && address) {
|
||||||
|
resolve(address.port);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Unable to allocate a free local port'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startSshTunnel = (
|
||||||
|
target: IIdeSshTarget,
|
||||||
|
options: ISshTunnelOptions,
|
||||||
|
): ISshTunnelHandle => {
|
||||||
|
const executable = options.executable ?? 'ssh';
|
||||||
|
const localHost = options.localHost ?? '127.0.0.1';
|
||||||
|
const remoteHost = options.remoteHost ?? '127.0.0.1';
|
||||||
|
const forward = `${localHost}:${options.localPort}:${remoteHost}:${options.remotePort}`;
|
||||||
|
const args = [
|
||||||
|
...buildSshOptionArgs(target, options),
|
||||||
|
'-N',
|
||||||
|
'-L',
|
||||||
|
forward,
|
||||||
|
buildSshDestination(target),
|
||||||
|
];
|
||||||
|
const child = plugins.childProcess.spawn(executable, args, {
|
||||||
|
cwd: options.cwd,
|
||||||
|
env: options.env ?? process.env,
|
||||||
|
shell: false,
|
||||||
|
stdio: ['ignore', 'ignore', 'pipe'],
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
target,
|
||||||
|
localHost,
|
||||||
|
localPort: options.localPort,
|
||||||
|
remoteHost,
|
||||||
|
remotePort: options.remotePort,
|
||||||
|
processId: child.pid,
|
||||||
|
dispose: async (signal: NodeJS.Signals = 'SIGTERM') => {
|
||||||
|
if (child.exitCode !== null || child.killed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (!child.killed) {
|
||||||
|
child.kill('SIGKILL');
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}, 5000);
|
||||||
|
child.once('close', () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
child.kill(signal);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const probeRemoteHost = async (
|
||||||
|
target: IIdeSshTarget,
|
||||||
|
options: ISshRunOptions = {},
|
||||||
|
): Promise<IRemoteProbeResult> => {
|
||||||
|
const command = [
|
||||||
|
`printf 'platform=%s\\n' "$(uname -s 2>/dev/null || printf unknown)"`,
|
||||||
|
`printf 'arch=%s\\n' "$(uname -m 2>/dev/null || printf unknown)"`,
|
||||||
|
`printf 'homeDir=%s\\n' "$HOME"`,
|
||||||
|
`printf 'shell=%s\\n' "$SHELL"`,
|
||||||
|
`printf 'nodeVersion=%s\\n' "$(node --version 2>/dev/null || true)"`,
|
||||||
|
`printf 'pnpmVersion=%s\\n' "$(pnpm --version 2>/dev/null || true)"`,
|
||||||
|
`printf 'gitVersion=%s\\n' "$(git --version 2>/dev/null || true)"`,
|
||||||
|
`printf 'opencodeVersion=%s\\n' "$(opencode --version 2>/dev/null || true)"`,
|
||||||
|
].join('; ');
|
||||||
|
|
||||||
|
const result = await runSshCommand(target, command, options);
|
||||||
|
const fields = parseKeyValueLines(result.stdout);
|
||||||
|
const errors: string[] = [];
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
errors.push(result.stderr || `ssh exited with code ${result.exitCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: result.exitCode === 0,
|
||||||
|
hostAlias: target.hostAlias,
|
||||||
|
platform: fields.platform,
|
||||||
|
arch: fields.arch,
|
||||||
|
homeDir: fields.homeDir,
|
||||||
|
shell: fields.shell,
|
||||||
|
nodeVersion: fields.nodeVersion,
|
||||||
|
pnpmVersion: fields.pnpmVersion,
|
||||||
|
gitVersion: fields.gitVersion,
|
||||||
|
opencodeVersion: fields.opencodeVersion,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const applySshDirective = (host: ISshHostConfig, key: string, values: string[]) => {
|
||||||
|
const targetKey = directiveAliases[key];
|
||||||
|
if (!targetKey || values.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetKey === 'identityFiles') {
|
||||||
|
host.identityFiles.push(...values.map(expandHome));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetKey === 'port') {
|
||||||
|
const port = Number(values[0]);
|
||||||
|
if (Number.isInteger(port) && port > 0) {
|
||||||
|
host.port = port;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetKey === 'forwardAgent') {
|
||||||
|
host.forwardAgent = /^(yes|true)$/i.test(values[0]!);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetKey === 'hostName') {
|
||||||
|
host.hostName = values.join(' ');
|
||||||
|
} else if (targetKey === 'user') {
|
||||||
|
host.user = values.join(' ');
|
||||||
|
} else if (targetKey === 'proxyJump') {
|
||||||
|
host.proxyJump = values.join(' ');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSshOptionArgs = (target: IIdeSshTarget, options: ISshRunOptions = {}) => {
|
||||||
|
const args: string[] = [];
|
||||||
|
if (options.batchMode !== false) {
|
||||||
|
args.push('-o', 'BatchMode=yes');
|
||||||
|
}
|
||||||
|
args.push('-o', 'ServerAliveInterval=30');
|
||||||
|
args.push('-o', 'ServerAliveCountMax=3');
|
||||||
|
if (target.port) {
|
||||||
|
args.push('-p', `${target.port}`);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stripSshComment = (line: string) => {
|
||||||
|
let quote: string | undefined;
|
||||||
|
for (let index = 0; index < line.length; index++) {
|
||||||
|
const character = line[index];
|
||||||
|
if ((character === '"' || character === "'") && line[index - 1] !== '\\') {
|
||||||
|
quote = quote === character ? undefined : character;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (character === '#' && !quote) {
|
||||||
|
return line.slice(0, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tokenizeSshConfigLine = (line: string) => {
|
||||||
|
const tokens: string[] = [];
|
||||||
|
let current = '';
|
||||||
|
let quote: string | undefined;
|
||||||
|
for (let index = 0; index < line.length; index++) {
|
||||||
|
const character = line[index]!;
|
||||||
|
if ((character === '"' || character === "'") && line[index - 1] !== '\\') {
|
||||||
|
quote = quote === character ? undefined : character;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/\s/.test(character) && !quote) {
|
||||||
|
if (current) {
|
||||||
|
tokens.push(current);
|
||||||
|
current = '';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current += character;
|
||||||
|
}
|
||||||
|
if (current) {
|
||||||
|
tokens.push(current);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isWildcardHostPattern = (alias: string) => /[*?!]/.test(alias);
|
||||||
|
|
||||||
|
const parseKeyValueLines = (text: string) => {
|
||||||
|
const fields: Record<string, string> = {};
|
||||||
|
for (const line of text.split(/\r?\n/)) {
|
||||||
|
const separator = line.indexOf('=');
|
||||||
|
if (separator === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fields[line.slice(0, separator)] = line.slice(separator + 1);
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import * as childProcess from 'node:child_process';
|
||||||
|
import * as fs from 'node:fs/promises';
|
||||||
|
import * as net from 'node:net';
|
||||||
|
import * as os from 'node:os';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
|
||||||
|
export { childProcess, fs, net, os, path };
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "ts",
|
||||||
|
"outDir": "dist_ts",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["ts/**/*.ts"]
|
||||||
|
}
|
||||||
Generated
+17275
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
|||||||
|
packages:
|
||||||
|
- "applications/*"
|
||||||
|
- "packages/*"
|
||||||
|
- "theia-extensions/*"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Git.Zone IDE
|
||||||
|
|
||||||
|
Git.Zone IDE is a remote-first desktop IDE based on Eclipse Theia, Electron, SSH, and OpenCode server.
|
||||||
|
|
||||||
|
The local Electron shell manages SSH sessions and tunnels. The remote host runs the Theia backend and OpenCode server inside the selected workspace, so files, terminals, Git, language servers, tests, and AI agent actions all execute where the code lives.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm install
|
||||||
|
pnpm run build
|
||||||
|
pnpm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
The full Theia product build is intentionally separate because it downloads and bundles the IDE runtime:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run build:theia
|
||||||
|
pnpm run start:remote
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- `applications/electron-shell`: local Electron launcher and SSH session shell.
|
||||||
|
- `applications/remote-theia`: remote Theia application served through an SSH tunnel.
|
||||||
|
- `packages/ssh`: OpenSSH config parsing, command execution, probes, and tunnel lifecycle.
|
||||||
|
- `packages/server-installer`: remote server manifest, install paths, bootstrap script generation, and command planning.
|
||||||
|
- `packages/opencode-bridge`: typed OpenCode server client wrapper and event normalization.
|
||||||
|
- `packages/protocol`: shared session and server protocol types.
|
||||||
|
- `theia-extensions/gitzone-*`: Theia product, remote, and OpenCode integration extensions.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Implementation Plan
|
||||||
|
|
||||||
|
1. Build a local Electron shell that reads SSH targets, starts SSH tunnels, and opens the remote Theia frontend.
|
||||||
|
2. Install a versioned Theia server bundle into `~/.git.zone/ide-server/<version>` on remote SSH hosts.
|
||||||
|
3. Run the Theia backend and OpenCode server on the remote host, bound to `127.0.0.1`.
|
||||||
|
4. Expose OpenCode through a Theia backend service, not directly to the Electron renderer.
|
||||||
|
5. Render OpenCode sessions, messages, permissions, diffs, todos, and Git.Zone commands inside Theia.
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import {
|
||||||
|
createRemoteBootstrapCommand,
|
||||||
|
createRemoteInstallCommand,
|
||||||
|
createRemoteServerInstallPlan,
|
||||||
|
joinRemotePath,
|
||||||
|
quoteShellArg,
|
||||||
|
} from '../packages/server-installer/ts/index.js';
|
||||||
|
|
||||||
|
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.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');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should render install and bootstrap commands', async () => {
|
||||||
|
const plan = createRemoteServerInstallPlan({
|
||||||
|
serverVersion: '0.1.0',
|
||||||
|
artifactName: 'remote-theia.tgz',
|
||||||
|
});
|
||||||
|
const installCommand = createRemoteInstallCommand(plan);
|
||||||
|
const bootstrapCommand = createRemoteBootstrapCommand({
|
||||||
|
serverVersion: '0.1.0',
|
||||||
|
workspacePath: '/srv/work/project',
|
||||||
|
theiaPort: 33990,
|
||||||
|
opencodePort: 4096,
|
||||||
|
opencodeUsername: 'opencode',
|
||||||
|
opencodePassword: 'secret',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(installCommand).toInclude('GITZONE_IDE_MANIFEST');
|
||||||
|
expect(bootstrapCommand).toInclude('GITZONE_IDE_OPENCODE_PORT');
|
||||||
|
expect(bootstrapCommand).toInclude('pnpm --dir');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { parseServerSentEvent } from '../packages/opencode-bridge/ts/index.js';
|
||||||
|
|
||||||
|
tap.test('should parse named opencode sse events', async () => {
|
||||||
|
const event = parseServerSentEvent('id: 1\nevent: server.connected\ndata: {"type":"server.connected"}\n');
|
||||||
|
expect(event!.id).toEqual('1');
|
||||||
|
expect(event!.type).toEqual('server.connected');
|
||||||
|
expect(event!.data).toEqual({ type: 'server.connected' });
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should infer opencode event type from json data', async () => {
|
||||||
|
const event = parseServerSentEvent('data: {"type":"session.updated","properties":{"id":"abc"}}\n');
|
||||||
|
expect(event!.type).toEqual('session.updated');
|
||||||
|
expect(event!.data).toEqual({ type: 'session.updated', properties: { id: 'abc' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { buildSshArgs, listConnectableHosts, parseSshConfig } from '../packages/ssh/ts/index.js';
|
||||||
|
|
||||||
|
tap.test('should parse ssh config hosts', async () => {
|
||||||
|
const hosts = parseSshConfig(`
|
||||||
|
Host *
|
||||||
|
ServerAliveInterval 30
|
||||||
|
|
||||||
|
Host dev-box staging-box
|
||||||
|
HostName dev.example.com
|
||||||
|
User deploy
|
||||||
|
Port 2222
|
||||||
|
IdentityFile ~/.ssh/id_ed25519
|
||||||
|
ProxyJump bastion
|
||||||
|
|
||||||
|
Host *.internal
|
||||||
|
User ignored
|
||||||
|
`);
|
||||||
|
|
||||||
|
const connectableHosts = listConnectableHosts(hosts);
|
||||||
|
expect(connectableHosts).toHaveLength(2);
|
||||||
|
expect(connectableHosts[0]!.alias).toEqual('dev-box');
|
||||||
|
expect(connectableHosts[0]!.hostName).toEqual('dev.example.com');
|
||||||
|
expect(connectableHosts[0]!.user).toEqual('deploy');
|
||||||
|
expect(connectableHosts[0]!.port).toEqual(2222);
|
||||||
|
expect(connectableHosts[0]!.identityFiles[0]!).toEndWith('/.ssh/id_ed25519');
|
||||||
|
expect(connectableHosts[0]!.proxyJump).toEqual('bastion');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should build ssh args with destination and command', async () => {
|
||||||
|
const args = buildSshArgs(
|
||||||
|
{
|
||||||
|
id: 'dev-box',
|
||||||
|
hostAlias: 'dev-box',
|
||||||
|
user: 'deploy',
|
||||||
|
port: 2222,
|
||||||
|
},
|
||||||
|
'uname -a',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(args).toContain('-p');
|
||||||
|
expect(args).toContain('2222');
|
||||||
|
expect(args).toContain('deploy@dev-box');
|
||||||
|
expect(args[args.length - 1]).toEqual('uname -a');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command.js';
|
||||||
|
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry.js';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service.js';
|
||||||
|
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||||
|
import { type IGitZoneOpenCodeServer } from '../common/gitzone-opencode-protocol.js';
|
||||||
|
export declare const GitZoneOpenCodeHealthCommand: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
export declare const GitZoneOpenCodeNewSessionCommand: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
export declare class GitZoneOpenCodeContribution implements CommandContribution, MenuContribution {
|
||||||
|
protected readonly openCodeServer: IGitZoneOpenCodeServer;
|
||||||
|
protected readonly messages: MessageService;
|
||||||
|
registerCommands(registry: CommandRegistry): void;
|
||||||
|
registerMenus(menus: MenuModelRegistry): void;
|
||||||
|
}
|
||||||
|
declare const _default: ContainerModule;
|
||||||
|
export default _default;
|
||||||
|
//# sourceMappingURL=gitzone-opencode-frontend-module.d.ts.map
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-opencode-frontend-module.d.ts","sourceRoot":"","sources":["../../src/browser/gitzone-opencode-frontend-module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oDAAoD,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAsB,MAAM,uCAAuC,CAAC;AAC5F,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,wCAAwC,CAAC;AAEhD,eAAO,MAAM,4BAA4B;;;CAGxC,CAAC;AAEF,eAAO,MAAM,gCAAgC;;;CAG5C,CAAC;AAEF,qBACa,2BAA4B,YAAW,mBAAmB,EAAE,gBAAgB;IAEvF,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAG,sBAAsB,CAAC;IAG3D,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,cAAc,CAAC;IAE7C,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAejD,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;CAU9C;;AAQD,wBAWG"}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
"use strict";
|
||||||
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||||
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||||
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||||
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||||
|
};
|
||||||
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.GitZoneOpenCodeContribution = exports.GitZoneOpenCodeNewSessionCommand = exports.GitZoneOpenCodeHealthCommand = void 0;
|
||||||
|
const common_menus_js_1 = require("@theia/core/lib/browser/common-menus.js");
|
||||||
|
const ws_connection_provider_js_1 = require("@theia/core/lib/browser/messaging/ws-connection-provider.js");
|
||||||
|
const command_js_1 = require("@theia/core/lib/common/command.js");
|
||||||
|
const menu_model_registry_js_1 = require("@theia/core/lib/common/menu/menu-model-registry.js");
|
||||||
|
const message_service_js_1 = require("@theia/core/lib/common/message-service.js");
|
||||||
|
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||||
|
const gitzone_opencode_protocol_js_1 = require("../common/gitzone-opencode-protocol.js");
|
||||||
|
exports.GitZoneOpenCodeHealthCommand = {
|
||||||
|
id: 'gitzone.opencode.health',
|
||||||
|
label: 'OpenCode: Check Health',
|
||||||
|
};
|
||||||
|
exports.GitZoneOpenCodeNewSessionCommand = {
|
||||||
|
id: 'gitzone.opencode.newSession',
|
||||||
|
label: 'OpenCode: New Session',
|
||||||
|
};
|
||||||
|
let GitZoneOpenCodeContribution = class GitZoneOpenCodeContribution {
|
||||||
|
openCodeServer;
|
||||||
|
messages;
|
||||||
|
registerCommands(registry) {
|
||||||
|
registry.registerCommand(exports.GitZoneOpenCodeHealthCommand, {
|
||||||
|
execute: async () => {
|
||||||
|
const health = await this.openCodeServer.health();
|
||||||
|
await this.messages.info(`OpenCode health: ${JSON.stringify(health)}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
registry.registerCommand(exports.GitZoneOpenCodeNewSessionCommand, {
|
||||||
|
execute: async () => {
|
||||||
|
const session = await this.openCodeServer.createSession('Git.Zone IDE Session');
|
||||||
|
await this.messages.info(`OpenCode session created: ${JSON.stringify(session)}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
registerMenus(menus) {
|
||||||
|
menus.registerMenuAction(common_menus_js_1.CommonMenus.VIEW_VIEWS, {
|
||||||
|
commandId: exports.GitZoneOpenCodeHealthCommand.id,
|
||||||
|
label: exports.GitZoneOpenCodeHealthCommand.label,
|
||||||
|
});
|
||||||
|
menus.registerMenuAction(common_menus_js_1.CommonMenus.VIEW_VIEWS, {
|
||||||
|
commandId: exports.GitZoneOpenCodeNewSessionCommand.id,
|
||||||
|
label: exports.GitZoneOpenCodeNewSessionCommand.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.GitZoneOpenCodeContribution = GitZoneOpenCodeContribution;
|
||||||
|
__decorate([
|
||||||
|
(0, index_js_1.inject)(gitzone_opencode_protocol_js_1.GitZoneOpenCodeServer),
|
||||||
|
__metadata("design:type", Object)
|
||||||
|
], GitZoneOpenCodeContribution.prototype, "openCodeServer", void 0);
|
||||||
|
__decorate([
|
||||||
|
(0, index_js_1.inject)(message_service_js_1.MessageService),
|
||||||
|
__metadata("design:type", message_service_js_1.MessageService)
|
||||||
|
], GitZoneOpenCodeContribution.prototype, "messages", void 0);
|
||||||
|
exports.GitZoneOpenCodeContribution = GitZoneOpenCodeContribution = __decorate([
|
||||||
|
(0, index_js_1.injectable)()
|
||||||
|
], GitZoneOpenCodeContribution);
|
||||||
|
const openCodeClient = {
|
||||||
|
onOpenCodeEvent: (event) => {
|
||||||
|
globalThis.dispatchEvent(new CustomEvent('gitzone-opencode-event', { detail: event }));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
exports.default = new index_js_1.ContainerModule((bind) => {
|
||||||
|
bind(gitzone_opencode_protocol_js_1.GitZoneOpenCodeServer)
|
||||||
|
.toDynamicValue((context) => context.container
|
||||||
|
.get(ws_connection_provider_js_1.WebSocketConnectionProvider)
|
||||||
|
.createProxy(gitzone_opencode_protocol_js_1.gitZoneOpenCodePath, openCodeClient))
|
||||||
|
.inSingletonScope();
|
||||||
|
bind(GitZoneOpenCodeContribution).toSelf().inSingletonScope();
|
||||||
|
bind(command_js_1.CommandContribution).toService(GitZoneOpenCodeContribution);
|
||||||
|
bind(menu_model_registry_js_1.MenuContribution).toService(GitZoneOpenCodeContribution);
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=gitzone-opencode-frontend-module.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-opencode-frontend-module.js","sourceRoot":"","sources":["../../src/browser/gitzone-opencode-frontend-module.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6EAAsE;AACtE,2GAA0G;AAC1G,kEAAyF;AACzF,+FAAyG;AACzG,kFAA2E;AAC3E,oEAA4F;AAC5F,yFAKgD;AAEnC,QAAA,4BAA4B,GAAG;IAC1C,EAAE,EAAE,yBAAyB;IAC7B,KAAK,EAAE,wBAAwB;CAChC,CAAC;AAEW,QAAA,gCAAgC,GAAG;IAC9C,EAAE,EAAE,6BAA6B;IACjC,KAAK,EAAE,uBAAuB;CAC/B,CAAC;AAGK,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;IAEnB,cAAc,CAA0B;IAGxC,QAAQ,CAAkB;IAE7C,gBAAgB,CAAC,QAAyB;QACxC,QAAQ,CAAC,eAAe,CAAC,oCAA4B,EAAE;YACrD,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC;gBAClD,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,oBAAoB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACzE,CAAC;SACF,CAAC,CAAC;QACH,QAAQ,CAAC,eAAe,CAAC,wCAAgC,EAAE;YACzD,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;gBAChF,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,6BAA6B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACnF,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,KAAwB;QACpC,KAAK,CAAC,kBAAkB,CAAC,6BAAW,CAAC,UAAU,EAAE;YAC/C,SAAS,EAAE,oCAA4B,CAAC,EAAE;YAC1C,KAAK,EAAE,oCAA4B,CAAC,KAAK;SAC1C,CAAC,CAAC;QACH,KAAK,CAAC,kBAAkB,CAAC,6BAAW,CAAC,UAAU,EAAE;YAC/C,SAAS,EAAE,wCAAgC,CAAC,EAAE;YAC9C,KAAK,EAAE,wCAAgC,CAAC,KAAK;SAC9C,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAhCY,kEAA2B;AAEnB;IADlB,IAAA,iBAAM,EAAC,oDAAqB,CAAC;;mEAC6B;AAGxC;IADlB,IAAA,iBAAM,EAAC,mCAAc,CAAC;8BACO,mCAAc;6DAAC;sCALlC,2BAA2B;IADvC,IAAA,qBAAU,GAAE;GACA,2BAA2B,CAgCvC;AAED,MAAM,cAAc,GAA2B;IAC7C,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;QACzB,UAAU,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,wBAAwB,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IACzF,CAAC;CACF,CAAC;AAEF,kBAAe,IAAI,0BAAe,CAAC,CAAC,IAAI,EAAE,EAAE;IAC1C,IAAI,CAAC,oDAAqB,CAAC;SACxB,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1B,OAAO,CAAC,SAAS;SACd,GAAG,CAAC,uDAA2B,CAAC;SAChC,WAAW,CAAyB,kDAAmB,EAAE,cAAc,CAAC,CAC5E;SACA,gBAAgB,EAAE,CAAC;IACtB,IAAI,CAAC,2BAA2B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAC9D,IAAI,CAAC,gCAAmB,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;IACjE,IAAI,CAAC,yCAAgB,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;AAChE,CAAC,CAAC,CAAC"}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
export declare const gitZoneOpenCodePath = "/services/git-zone/opencode";
|
||||||
|
export declare const GitZoneOpenCodeServer: unique symbol;
|
||||||
|
export interface IGitZoneOpenCodeConnectionInfo {
|
||||||
|
baseUrl: string;
|
||||||
|
port: number;
|
||||||
|
workspacePath: string;
|
||||||
|
autoStart: boolean;
|
||||||
|
}
|
||||||
|
export interface IGitZoneOpenCodeEvent {
|
||||||
|
type: string;
|
||||||
|
id?: string;
|
||||||
|
retry?: number;
|
||||||
|
data?: unknown;
|
||||||
|
raw: string;
|
||||||
|
}
|
||||||
|
export interface IGitZoneOpenCodeClient {
|
||||||
|
onOpenCodeEvent(event: IGitZoneOpenCodeEvent): void;
|
||||||
|
}
|
||||||
|
export interface IGitZoneOpenCodePromptBody {
|
||||||
|
messageID?: string;
|
||||||
|
model?: {
|
||||||
|
providerID: string;
|
||||||
|
modelID: string;
|
||||||
|
};
|
||||||
|
agent?: string;
|
||||||
|
noReply?: boolean;
|
||||||
|
system?: string;
|
||||||
|
tools?: Record<string, boolean>;
|
||||||
|
parts: Array<{
|
||||||
|
type: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
export interface IGitZoneOpenCodeServer {
|
||||||
|
setClient(client: IGitZoneOpenCodeClient | undefined): void;
|
||||||
|
getConnectionInfo(): Promise<IGitZoneOpenCodeConnectionInfo>;
|
||||||
|
health(): Promise<unknown>;
|
||||||
|
providers(): Promise<unknown>;
|
||||||
|
agents(): Promise<unknown>;
|
||||||
|
sessions(): Promise<unknown>;
|
||||||
|
createSession(title?: string): Promise<unknown>;
|
||||||
|
messages(sessionId: string, limit?: number): Promise<unknown>;
|
||||||
|
prompt(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<unknown>;
|
||||||
|
promptAsync(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<void>;
|
||||||
|
command(sessionId: string, command: string, commandArguments?: string): Promise<unknown>;
|
||||||
|
abort(sessionId: string): Promise<unknown>;
|
||||||
|
diff(sessionId: string, messageId?: string): Promise<unknown>;
|
||||||
|
todo(sessionId: string): Promise<unknown>;
|
||||||
|
respondToPermission(sessionId: string, permissionId: string, response: string, remember?: boolean): Promise<unknown>;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=gitzone-opencode-protocol.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-opencode-protocol.d.ts","sourceRoot":"","sources":["../../src/common/gitzone-opencode-protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE,eAAO,MAAM,qBAAqB,eAAkC,CAAC;AAErE,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAsB;IACrC,eAAe,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,MAAM,EAAE,sBAAsB,GAAG,SAAS,GAAG,IAAI,CAAC;IAC5D,iBAAiB,IAAI,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7D,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzF,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtH"}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.GitZoneOpenCodeServer = exports.gitZoneOpenCodePath = void 0;
|
||||||
|
exports.gitZoneOpenCodePath = '/services/git-zone/opencode';
|
||||||
|
exports.GitZoneOpenCodeServer = Symbol('GitZoneOpenCodeServer');
|
||||||
|
//# sourceMappingURL=gitzone-opencode-protocol.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-opencode-protocol.js","sourceRoot":"","sources":["../../src/common/gitzone-opencode-protocol.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG,6BAA6B,CAAC;AAEpD,QAAA,qBAAqB,GAAG,MAAM,CAAC,uBAAuB,CAAC,CAAC"}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||||
|
declare const _default: ContainerModule;
|
||||||
|
export default _default;
|
||||||
|
//# sourceMappingURL=gitzone-opencode-backend-module.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-opencode-backend-module.d.ts","sourceRoot":"","sources":["../../src/node/gitzone-opencode-backend-module.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;;AASxE,wBAaG"}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const backend_application_js_1 = require("@theia/core/lib/node/backend-application.js");
|
||||||
|
const handler_js_1 = require("@theia/core/lib/common/messaging/handler.js");
|
||||||
|
const proxy_factory_js_1 = require("@theia/core/lib/common/messaging/proxy-factory.js");
|
||||||
|
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||||
|
const gitzone_opencode_protocol_js_1 = require("../common/gitzone-opencode-protocol.js");
|
||||||
|
const gitzone_opencode_node_service_js_1 = require("./gitzone-opencode-node-service.js");
|
||||||
|
exports.default = new index_js_1.ContainerModule((bind) => {
|
||||||
|
bind(gitzone_opencode_node_service_js_1.GitZoneOpenCodeNodeService).toSelf().inSingletonScope();
|
||||||
|
bind(gitzone_opencode_protocol_js_1.GitZoneOpenCodeServer).toService(gitzone_opencode_node_service_js_1.GitZoneOpenCodeNodeService);
|
||||||
|
bind(backend_application_js_1.BackendApplicationContribution).toService(gitzone_opencode_node_service_js_1.GitZoneOpenCodeNodeService);
|
||||||
|
bind(handler_js_1.ConnectionHandler)
|
||||||
|
.toDynamicValue((context) => new proxy_factory_js_1.RpcConnectionHandler(gitzone_opencode_protocol_js_1.gitZoneOpenCodePath, (client) => {
|
||||||
|
const server = context.container.get(gitzone_opencode_protocol_js_1.GitZoneOpenCodeServer);
|
||||||
|
server.setClient(client);
|
||||||
|
return server;
|
||||||
|
}))
|
||||||
|
.inSingletonScope();
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=gitzone-opencode-backend-module.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-opencode-backend-module.js","sourceRoot":"","sources":["../../src/node/gitzone-opencode-backend-module.ts"],"names":[],"mappings":";;AAAA,wFAA6F;AAC7F,4EAAgF;AAChF,wFAAyF;AACzF,oEAAwE;AACxE,yFAKgD;AAChD,yFAAgF;AAEhF,kBAAe,IAAI,0BAAe,CAAC,CAAC,IAAI,EAAE,EAAE;IAC1C,IAAI,CAAC,6DAA0B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAC7D,IAAI,CAAC,oDAAqB,CAAC,CAAC,SAAS,CAAC,6DAA0B,CAAC,CAAC;IAClE,IAAI,CAAC,uDAA8B,CAAC,CAAC,SAAS,CAAC,6DAA0B,CAAC,CAAC;IAC3E,IAAI,CAAC,8BAAiB,CAAC;SACpB,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1B,IAAI,uCAAoB,CAAyB,kDAAmB,EAAE,CAAC,MAAM,EAAE,EAAE;QAC/E,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAyB,oDAAqB,CAAC,CAAC;QACpF,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CACH;SACA,gBAAgB,EAAE,CAAC;AACxB,CAAC,CAAC,CAAC"}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
import { OpenCodeServerClient } from '@git.zone/ide-opencode-bridge';
|
||||||
|
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application.js';
|
||||||
|
import type { IGitZoneOpenCodeClient, IGitZoneOpenCodeConnectionInfo, IGitZoneOpenCodePromptBody, IGitZoneOpenCodeServer } from '../common/gitzone-opencode-protocol.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
export declare class GitZoneOpenCodeNodeService implements IGitZoneOpenCodeServer, BackendApplicationContribution {
|
||||||
|
protected client: IGitZoneOpenCodeClient | undefined;
|
||||||
|
protected eventAbortController: AbortController | undefined;
|
||||||
|
protected openCodeProcess: plugins.childProcess.ChildProcess | undefined;
|
||||||
|
initialize(): void;
|
||||||
|
onStop(): void;
|
||||||
|
setClient(client: IGitZoneOpenCodeClient | undefined): void;
|
||||||
|
getConnectionInfo(): Promise<IGitZoneOpenCodeConnectionInfo>;
|
||||||
|
health(): Promise<unknown>;
|
||||||
|
providers(): Promise<unknown>;
|
||||||
|
agents(): Promise<unknown>;
|
||||||
|
sessions(): Promise<unknown>;
|
||||||
|
createSession(title?: string): Promise<unknown>;
|
||||||
|
messages(sessionId: string, limit?: number): Promise<unknown>;
|
||||||
|
prompt(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<unknown>;
|
||||||
|
promptAsync(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<void>;
|
||||||
|
command(sessionId: string, command: string, commandArguments?: string): Promise<unknown>;
|
||||||
|
abort(sessionId: string): Promise<unknown>;
|
||||||
|
diff(sessionId: string, messageId?: string): Promise<unknown>;
|
||||||
|
todo(sessionId: string): Promise<unknown>;
|
||||||
|
respondToPermission(sessionId: string, permissionId: string, response: string, remember?: boolean): Promise<unknown>;
|
||||||
|
protected ensureOpenCodeStarted(): Promise<void>;
|
||||||
|
protected restartEventStream(): void;
|
||||||
|
protected createClient(): OpenCodeServerClient;
|
||||||
|
protected get workspacePath(): string;
|
||||||
|
protected get port(): number;
|
||||||
|
protected get baseUrl(): string;
|
||||||
|
protected get username(): string;
|
||||||
|
protected get password(): string;
|
||||||
|
protected get autoStart(): boolean;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=gitzone-opencode-node-service.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-opencode-node-service.d.ts","sourceRoot":"","sources":["../../src/node/gitzone-opencode-node-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,8BAA8B,EAAE,MAAM,6CAA6C,CAAC;AAE7F,OAAO,KAAK,EACV,sBAAsB,EACtB,8BAA8B,EAC9B,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,wCAAwC,CAAC;AAChD,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAExC,qBACa,0BAA2B,YAAW,sBAAsB,EAAE,8BAA8B;IACvG,SAAS,CAAC,MAAM,EAAE,sBAAsB,GAAG,SAAS,CAAC;IACrD,SAAS,CAAC,oBAAoB,EAAE,eAAe,GAAG,SAAS,CAAC;IAC5D,SAAS,CAAC,eAAe,EAAE,OAAO,CAAC,YAAY,CAAC,YAAY,GAAG,SAAS,CAAC;IAEzE,UAAU,IAAI,IAAI;IAIlB,MAAM,IAAI,IAAI;IAOd,SAAS,CAAC,MAAM,EAAE,sBAAsB,GAAG,SAAS,GAAG,IAAI;IAKrD,iBAAiB,IAAI,OAAO,CAAC,8BAA8B,CAAC;IAS5D,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAK1B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAI7B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAI1B,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAI5B,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI/C,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7D,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/E,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,SAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAIpF,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1C,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIzC,mBAAmB,CACvB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,OAAO,GACjB,OAAO,CAAC,OAAO,CAAC;cAIH,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BtD,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAmBpC,SAAS,CAAC,YAAY;IAQtB,SAAS,KAAK,aAAa,WAE1B;IAED,SAAS,KAAK,IAAI,WAGjB;IAED,SAAS,KAAK,OAAO,WAEpB;IAED,SAAS,KAAK,QAAQ,WAErB;IAED,SAAS,KAAK,QAAQ,WAErB;IAED,SAAS,KAAK,SAAS,YAEtB;CACF"}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||||
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||||
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||||
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||||
|
};
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.GitZoneOpenCodeNodeService = void 0;
|
||||||
|
const ide_opencode_bridge_1 = require("@git.zone/ide-opencode-bridge");
|
||||||
|
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||||
|
const plugins = __importStar(require("./plugins.js"));
|
||||||
|
let GitZoneOpenCodeNodeService = class GitZoneOpenCodeNodeService {
|
||||||
|
client;
|
||||||
|
eventAbortController;
|
||||||
|
openCodeProcess;
|
||||||
|
initialize() {
|
||||||
|
void this.ensureOpenCodeStarted();
|
||||||
|
}
|
||||||
|
onStop() {
|
||||||
|
this.eventAbortController?.abort();
|
||||||
|
if (this.openCodeProcess && this.openCodeProcess.exitCode === null) {
|
||||||
|
this.openCodeProcess.kill('SIGTERM');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setClient(client) {
|
||||||
|
this.client = client;
|
||||||
|
this.restartEventStream();
|
||||||
|
}
|
||||||
|
async getConnectionInfo() {
|
||||||
|
return {
|
||||||
|
baseUrl: this.baseUrl,
|
||||||
|
port: this.port,
|
||||||
|
workspacePath: this.workspacePath,
|
||||||
|
autoStart: this.autoStart,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async health() {
|
||||||
|
await this.ensureOpenCodeStarted();
|
||||||
|
return this.createClient().health();
|
||||||
|
}
|
||||||
|
async providers() {
|
||||||
|
return this.createClient().providers();
|
||||||
|
}
|
||||||
|
async agents() {
|
||||||
|
return this.createClient().agents();
|
||||||
|
}
|
||||||
|
async sessions() {
|
||||||
|
return this.createClient().sessions();
|
||||||
|
}
|
||||||
|
async createSession(title) {
|
||||||
|
return this.createClient().createSession(title ? { title } : {});
|
||||||
|
}
|
||||||
|
async messages(sessionId, limit) {
|
||||||
|
return this.createClient().messages(sessionId, limit);
|
||||||
|
}
|
||||||
|
async prompt(sessionId, body) {
|
||||||
|
return this.createClient().prompt(sessionId, body);
|
||||||
|
}
|
||||||
|
async promptAsync(sessionId, body) {
|
||||||
|
await this.createClient().promptAsync(sessionId, body);
|
||||||
|
}
|
||||||
|
async command(sessionId, command, commandArguments = '') {
|
||||||
|
return this.createClient().command(sessionId, { command, arguments: commandArguments });
|
||||||
|
}
|
||||||
|
async abort(sessionId) {
|
||||||
|
return this.createClient().abort(sessionId);
|
||||||
|
}
|
||||||
|
async diff(sessionId, messageId) {
|
||||||
|
return this.createClient().diff(sessionId, messageId);
|
||||||
|
}
|
||||||
|
async todo(sessionId) {
|
||||||
|
return this.createClient().todo(sessionId);
|
||||||
|
}
|
||||||
|
async respondToPermission(sessionId, permissionId, response, remember) {
|
||||||
|
return this.createClient().respondToPermission(sessionId, permissionId, { response, remember });
|
||||||
|
}
|
||||||
|
async ensureOpenCodeStarted() {
|
||||||
|
try {
|
||||||
|
await this.createClient().health();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
if (!this.autoStart || this.openCodeProcess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.openCodeProcess = plugins.childProcess.spawn('opencode', ['serve', '--hostname', '127.0.0.1', '--port', `${this.port}`], {
|
||||||
|
cwd: this.workspacePath,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
OPENCODE_SERVER_USERNAME: this.username,
|
||||||
|
OPENCODE_SERVER_PASSWORD: this.password,
|
||||||
|
},
|
||||||
|
shell: false,
|
||||||
|
stdio: ['ignore', 'ignore', 'ignore'],
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
restartEventStream() {
|
||||||
|
this.eventAbortController?.abort();
|
||||||
|
if (!this.client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const abortController = new AbortController();
|
||||||
|
this.eventAbortController = abortController;
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
await this.ensureOpenCodeStarted();
|
||||||
|
for await (const event of this.createClient().events(abortController.signal)) {
|
||||||
|
this.client?.onOpenCodeEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// The UI can explicitly call health() for detailed connection diagnostics.
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
createClient() {
|
||||||
|
return new ide_opencode_bridge_1.OpenCodeServerClient({
|
||||||
|
baseUrl: this.baseUrl,
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get workspacePath() {
|
||||||
|
return process.env.GITZONE_IDE_WORKSPACE || process.cwd();
|
||||||
|
}
|
||||||
|
get port() {
|
||||||
|
const port = Number(process.env.GITZONE_IDE_OPENCODE_PORT || '4096');
|
||||||
|
return Number.isInteger(port) && port > 0 ? port : 4096;
|
||||||
|
}
|
||||||
|
get baseUrl() {
|
||||||
|
return `http://127.0.0.1:${this.port}`;
|
||||||
|
}
|
||||||
|
get username() {
|
||||||
|
return process.env.OPENCODE_SERVER_USERNAME || 'opencode';
|
||||||
|
}
|
||||||
|
get password() {
|
||||||
|
return process.env.OPENCODE_SERVER_PASSWORD || '';
|
||||||
|
}
|
||||||
|
get autoStart() {
|
||||||
|
return process.env.GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART !== '1';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.GitZoneOpenCodeNodeService = GitZoneOpenCodeNodeService;
|
||||||
|
exports.GitZoneOpenCodeNodeService = GitZoneOpenCodeNodeService = __decorate([
|
||||||
|
(0, index_js_1.injectable)()
|
||||||
|
], GitZoneOpenCodeNodeService);
|
||||||
|
//# sourceMappingURL=gitzone-opencode-node-service.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-opencode-node-service.js","sourceRoot":"","sources":["../../src/node/gitzone-opencode-node-service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uEAAqE;AAErE,oEAAmE;AAOnE,sDAAwC;AAGjC,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IAC3B,MAAM,CAAqC;IAC3C,oBAAoB,CAA8B;IAClD,eAAe,CAAgD;IAEzE,UAAU;QACR,KAAK,IAAI,CAAC,qBAAqB,EAAE,CAAC;IACpC,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACnE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,SAAS,CAAC,MAA0C;QAClD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,SAAS;QACb,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,SAAS,EAAE,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,QAAQ,EAAE,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAAc;QAChC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,SAAiB,EAAE,KAAc;QAC9C,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB,EAAE,IAAgC;QAC9D,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,IAAgC;QACnE,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,OAAe,EAAE,gBAAgB,GAAG,EAAE;QACrE,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB;QAC3B,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAiB,EAAE,SAAkB;QAC9C,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,mBAAmB,CACvB,SAAiB,EACjB,YAAoB,EACpB,QAAgB,EAChB,QAAkB;QAElB,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,mBAAmB,CAAC,SAAS,EAAE,YAAY,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClG,CAAC;IAES,KAAK,CAAC,qBAAqB;QACnC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,CAAC;YACnC,OAAO;QACT,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC5C,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAC/C,UAAU,EACV,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAC9D;YACE,GAAG,EAAE,IAAI,CAAC,aAAa;YACvB,GAAG,EAAE;gBACH,GAAG,OAAO,CAAC,GAAG;gBACd,wBAAwB,EAAE,IAAI,CAAC,QAAQ;gBACvC,wBAAwB,EAAE,IAAI,CAAC,QAAQ;aACxC;YACD,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;YACrC,WAAW,EAAE,IAAI;SAClB,CACF,CAAC;IACJ,CAAC;IAES,kBAAkB;QAC1B,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC9C,IAAI,CAAC,oBAAoB,GAAG,eAAe,CAAC;QAC5C,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBACnC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC7E,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,2EAA2E;YAC7E,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IAES,YAAY;QACpB,OAAO,IAAI,0CAAoB,CAAC;YAC9B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;IACL,CAAC;IAED,IAAc,aAAa;QACzB,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC5D,CAAC;IAED,IAAc,IAAI;QAChB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,MAAM,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;IAED,IAAc,OAAO;QACnB,OAAO,oBAAoB,IAAI,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,IAAc,QAAQ;QACpB,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,UAAU,CAAC;IAC5D,CAAC;IAED,IAAc,QAAQ;QACpB,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,EAAE,CAAC;IACpD,CAAC;IAED,IAAc,SAAS;QACrB,OAAO,OAAO,CAAC,GAAG,CAAC,sCAAsC,KAAK,GAAG,CAAC;IACpE,CAAC;CACF,CAAA;AAtKY,gEAA0B;qCAA1B,0BAA0B;IADtC,IAAA,qBAAU,GAAE;GACA,0BAA0B,CAsKtC"}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import * as childProcess from 'node:child_process';
|
||||||
|
export { childProcess };
|
||||||
|
//# sourceMappingURL=plugins.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/node/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.childProcess = void 0;
|
||||||
|
const childProcess = __importStar(require("node:child_process"));
|
||||||
|
exports.childProcess = childProcess;
|
||||||
|
//# sourceMappingURL=plugins.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../../src/node/plugins.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iEAAmD;AAE1C,oCAAY"}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-extension-opencode",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"keywords": ["theia-extension"],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@git.zone/ide-opencode-bridge": "workspace:*",
|
||||||
|
"@theia/core": "1.71.0"
|
||||||
|
},
|
||||||
|
"theiaExtensions": [
|
||||||
|
{
|
||||||
|
"frontend": "lib/browser/gitzone-opencode-frontend-module",
|
||||||
|
"backend": "lib/node/gitzone-opencode-backend-module"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": ["src", "lib"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { CommonMenus } from '@theia/core/lib/browser/common-menus.js';
|
||||||
|
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider.js';
|
||||||
|
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command.js';
|
||||||
|
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry.js';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service.js';
|
||||||
|
import { ContainerModule, inject, injectable } from '@theia/core/shared/inversify/index.js';
|
||||||
|
import {
|
||||||
|
GitZoneOpenCodeServer,
|
||||||
|
gitZoneOpenCodePath,
|
||||||
|
type IGitZoneOpenCodeClient,
|
||||||
|
type IGitZoneOpenCodeServer,
|
||||||
|
} from '../common/gitzone-opencode-protocol.js';
|
||||||
|
|
||||||
|
export const GitZoneOpenCodeHealthCommand = {
|
||||||
|
id: 'gitzone.opencode.health',
|
||||||
|
label: 'OpenCode: Check Health',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GitZoneOpenCodeNewSessionCommand = {
|
||||||
|
id: 'gitzone.opencode.newSession',
|
||||||
|
label: 'OpenCode: New Session',
|
||||||
|
};
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class GitZoneOpenCodeContribution implements CommandContribution, MenuContribution {
|
||||||
|
@inject(GitZoneOpenCodeServer)
|
||||||
|
protected readonly openCodeServer!: IGitZoneOpenCodeServer;
|
||||||
|
|
||||||
|
@inject(MessageService)
|
||||||
|
protected readonly messages!: MessageService;
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(GitZoneOpenCodeHealthCommand, {
|
||||||
|
execute: async () => {
|
||||||
|
const health = await this.openCodeServer.health();
|
||||||
|
await this.messages.info(`OpenCode health: ${JSON.stringify(health)}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
registry.registerCommand(GitZoneOpenCodeNewSessionCommand, {
|
||||||
|
execute: async () => {
|
||||||
|
const session = await this.openCodeServer.createSession('Git.Zone IDE Session');
|
||||||
|
await this.messages.info(`OpenCode session created: ${JSON.stringify(session)}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(menus: MenuModelRegistry): void {
|
||||||
|
menus.registerMenuAction(CommonMenus.VIEW_VIEWS, {
|
||||||
|
commandId: GitZoneOpenCodeHealthCommand.id,
|
||||||
|
label: GitZoneOpenCodeHealthCommand.label,
|
||||||
|
});
|
||||||
|
menus.registerMenuAction(CommonMenus.VIEW_VIEWS, {
|
||||||
|
commandId: GitZoneOpenCodeNewSessionCommand.id,
|
||||||
|
label: GitZoneOpenCodeNewSessionCommand.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openCodeClient: IGitZoneOpenCodeClient = {
|
||||||
|
onOpenCodeEvent: (event) => {
|
||||||
|
globalThis.dispatchEvent(new CustomEvent('gitzone-opencode-event', { detail: event }));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default new ContainerModule((bind) => {
|
||||||
|
bind(GitZoneOpenCodeServer)
|
||||||
|
.toDynamicValue((context) =>
|
||||||
|
context.container
|
||||||
|
.get(WebSocketConnectionProvider)
|
||||||
|
.createProxy<IGitZoneOpenCodeServer>(gitZoneOpenCodePath, openCodeClient),
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
bind(GitZoneOpenCodeContribution).toSelf().inSingletonScope();
|
||||||
|
bind(CommandContribution).toService(GitZoneOpenCodeContribution);
|
||||||
|
bind(MenuContribution).toService(GitZoneOpenCodeContribution);
|
||||||
|
});
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
export const gitZoneOpenCodePath = '/services/git-zone/opencode';
|
||||||
|
|
||||||
|
export const GitZoneOpenCodeServer = Symbol('GitZoneOpenCodeServer');
|
||||||
|
|
||||||
|
export interface IGitZoneOpenCodeConnectionInfo {
|
||||||
|
baseUrl: string;
|
||||||
|
port: number;
|
||||||
|
workspacePath: string;
|
||||||
|
autoStart: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitZoneOpenCodeEvent {
|
||||||
|
type: string;
|
||||||
|
id?: string;
|
||||||
|
retry?: number;
|
||||||
|
data?: unknown;
|
||||||
|
raw: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitZoneOpenCodeClient {
|
||||||
|
onOpenCodeEvent(event: IGitZoneOpenCodeEvent): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitZoneOpenCodePromptBody {
|
||||||
|
messageID?: string;
|
||||||
|
model?: {
|
||||||
|
providerID: string;
|
||||||
|
modelID: string;
|
||||||
|
};
|
||||||
|
agent?: string;
|
||||||
|
noReply?: boolean;
|
||||||
|
system?: string;
|
||||||
|
tools?: Record<string, boolean>;
|
||||||
|
parts: Array<{ type: string; [key: string]: unknown }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitZoneOpenCodeServer {
|
||||||
|
setClient(client: IGitZoneOpenCodeClient | undefined): void;
|
||||||
|
getConnectionInfo(): Promise<IGitZoneOpenCodeConnectionInfo>;
|
||||||
|
health(): Promise<unknown>;
|
||||||
|
providers(): Promise<unknown>;
|
||||||
|
agents(): Promise<unknown>;
|
||||||
|
sessions(): Promise<unknown>;
|
||||||
|
createSession(title?: string): Promise<unknown>;
|
||||||
|
messages(sessionId: string, limit?: number): Promise<unknown>;
|
||||||
|
prompt(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<unknown>;
|
||||||
|
promptAsync(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<void>;
|
||||||
|
command(sessionId: string, command: string, commandArguments?: string): Promise<unknown>;
|
||||||
|
abort(sessionId: string): Promise<unknown>;
|
||||||
|
diff(sessionId: string, messageId?: string): Promise<unknown>;
|
||||||
|
todo(sessionId: string): Promise<unknown>;
|
||||||
|
respondToPermission(sessionId: string, permissionId: string, response: string, remember?: boolean): Promise<unknown>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application.js';
|
||||||
|
import { ConnectionHandler } from '@theia/core/lib/common/messaging/handler.js';
|
||||||
|
import { RpcConnectionHandler } from '@theia/core/lib/common/messaging/proxy-factory.js';
|
||||||
|
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||||
|
import {
|
||||||
|
GitZoneOpenCodeServer,
|
||||||
|
gitZoneOpenCodePath,
|
||||||
|
type IGitZoneOpenCodeClient,
|
||||||
|
type IGitZoneOpenCodeServer,
|
||||||
|
} from '../common/gitzone-opencode-protocol.js';
|
||||||
|
import { GitZoneOpenCodeNodeService } from './gitzone-opencode-node-service.js';
|
||||||
|
|
||||||
|
export default new ContainerModule((bind) => {
|
||||||
|
bind(GitZoneOpenCodeNodeService).toSelf().inSingletonScope();
|
||||||
|
bind(GitZoneOpenCodeServer).toService(GitZoneOpenCodeNodeService);
|
||||||
|
bind(BackendApplicationContribution).toService(GitZoneOpenCodeNodeService);
|
||||||
|
bind(ConnectionHandler)
|
||||||
|
.toDynamicValue((context) =>
|
||||||
|
new RpcConnectionHandler<IGitZoneOpenCodeClient>(gitZoneOpenCodePath, (client) => {
|
||||||
|
const server = context.container.get<IGitZoneOpenCodeServer>(GitZoneOpenCodeServer);
|
||||||
|
server.setClient(client);
|
||||||
|
return server;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
});
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import { OpenCodeServerClient } from '@git.zone/ide-opencode-bridge';
|
||||||
|
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application.js';
|
||||||
|
import { injectable } from '@theia/core/shared/inversify/index.js';
|
||||||
|
import type {
|
||||||
|
IGitZoneOpenCodeClient,
|
||||||
|
IGitZoneOpenCodeConnectionInfo,
|
||||||
|
IGitZoneOpenCodePromptBody,
|
||||||
|
IGitZoneOpenCodeServer,
|
||||||
|
} from '../common/gitzone-opencode-protocol.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class GitZoneOpenCodeNodeService implements IGitZoneOpenCodeServer, BackendApplicationContribution {
|
||||||
|
protected client: IGitZoneOpenCodeClient | undefined;
|
||||||
|
protected eventAbortController: AbortController | undefined;
|
||||||
|
protected openCodeProcess: plugins.childProcess.ChildProcess | undefined;
|
||||||
|
|
||||||
|
initialize(): void {
|
||||||
|
void this.ensureOpenCodeStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
onStop(): void {
|
||||||
|
this.eventAbortController?.abort();
|
||||||
|
if (this.openCodeProcess && this.openCodeProcess.exitCode === null) {
|
||||||
|
this.openCodeProcess.kill('SIGTERM');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setClient(client: IGitZoneOpenCodeClient | undefined): void {
|
||||||
|
this.client = client;
|
||||||
|
this.restartEventStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConnectionInfo(): Promise<IGitZoneOpenCodeConnectionInfo> {
|
||||||
|
return {
|
||||||
|
baseUrl: this.baseUrl,
|
||||||
|
port: this.port,
|
||||||
|
workspacePath: this.workspacePath,
|
||||||
|
autoStart: this.autoStart,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async health(): Promise<unknown> {
|
||||||
|
await this.ensureOpenCodeStarted();
|
||||||
|
return this.createClient().health();
|
||||||
|
}
|
||||||
|
|
||||||
|
async providers(): Promise<unknown> {
|
||||||
|
return this.createClient().providers();
|
||||||
|
}
|
||||||
|
|
||||||
|
async agents(): Promise<unknown> {
|
||||||
|
return this.createClient().agents();
|
||||||
|
}
|
||||||
|
|
||||||
|
async sessions(): Promise<unknown> {
|
||||||
|
return this.createClient().sessions();
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession(title?: string): Promise<unknown> {
|
||||||
|
return this.createClient().createSession(title ? { title } : {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async messages(sessionId: string, limit?: number): Promise<unknown> {
|
||||||
|
return this.createClient().messages(sessionId, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
async prompt(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<unknown> {
|
||||||
|
return this.createClient().prompt(sessionId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
async promptAsync(sessionId: string, body: IGitZoneOpenCodePromptBody): Promise<void> {
|
||||||
|
await this.createClient().promptAsync(sessionId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
async command(sessionId: string, command: string, commandArguments = ''): Promise<unknown> {
|
||||||
|
return this.createClient().command(sessionId, { command, arguments: commandArguments });
|
||||||
|
}
|
||||||
|
|
||||||
|
async abort(sessionId: string): Promise<unknown> {
|
||||||
|
return this.createClient().abort(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async diff(sessionId: string, messageId?: string): Promise<unknown> {
|
||||||
|
return this.createClient().diff(sessionId, messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async todo(sessionId: string): Promise<unknown> {
|
||||||
|
return this.createClient().todo(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async respondToPermission(
|
||||||
|
sessionId: string,
|
||||||
|
permissionId: string,
|
||||||
|
response: string,
|
||||||
|
remember?: boolean,
|
||||||
|
): Promise<unknown> {
|
||||||
|
return this.createClient().respondToPermission(sessionId, permissionId, { response, remember });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async ensureOpenCodeStarted(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.createClient().health();
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
if (!this.autoStart || this.openCodeProcess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openCodeProcess = plugins.childProcess.spawn(
|
||||||
|
'opencode',
|
||||||
|
['serve', '--hostname', '127.0.0.1', '--port', `${this.port}`],
|
||||||
|
{
|
||||||
|
cwd: this.workspacePath,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
OPENCODE_SERVER_USERNAME: this.username,
|
||||||
|
OPENCODE_SERVER_PASSWORD: this.password,
|
||||||
|
},
|
||||||
|
shell: false,
|
||||||
|
stdio: ['ignore', 'ignore', 'ignore'],
|
||||||
|
windowsHide: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected restartEventStream(): void {
|
||||||
|
this.eventAbortController?.abort();
|
||||||
|
if (!this.client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const abortController = new AbortController();
|
||||||
|
this.eventAbortController = abortController;
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
await this.ensureOpenCodeStarted();
|
||||||
|
for await (const event of this.createClient().events(abortController.signal)) {
|
||||||
|
this.client?.onOpenCodeEvent(event);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// The UI can explicitly call health() for detailed connection diagnostics.
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createClient() {
|
||||||
|
return new OpenCodeServerClient({
|
||||||
|
baseUrl: this.baseUrl,
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get workspacePath() {
|
||||||
|
return process.env.GITZONE_IDE_WORKSPACE || process.cwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get port() {
|
||||||
|
const port = Number(process.env.GITZONE_IDE_OPENCODE_PORT || '4096');
|
||||||
|
return Number.isInteger(port) && port > 0 ? port : 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get baseUrl() {
|
||||||
|
return `http://127.0.0.1:${this.port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get username() {
|
||||||
|
return process.env.OPENCODE_SERVER_USERNAME || 'opencode';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get password() {
|
||||||
|
return process.env.OPENCODE_SERVER_PASSWORD || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get autoStart() {
|
||||||
|
return process.env.GITZONE_IDE_DISABLE_OPENCODE_AUTOSTART !== '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import * as childProcess from 'node:child_process';
|
||||||
|
|
||||||
|
export { childProcess };
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"verbatimModuleSyntax": false,
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "lib",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command.js';
|
||||||
|
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry.js';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service.js';
|
||||||
|
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||||
|
export declare const GitZoneWelcomeCommand: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
export declare class GitZoneProductContribution implements CommandContribution, MenuContribution {
|
||||||
|
protected readonly messages: MessageService;
|
||||||
|
registerCommands(registry: CommandRegistry): void;
|
||||||
|
registerMenus(menus: MenuModelRegistry): void;
|
||||||
|
}
|
||||||
|
declare const _default: ContainerModule;
|
||||||
|
export default _default;
|
||||||
|
//# sourceMappingURL=gitzone-product-frontend-module.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-product-frontend-module.d.ts","sourceRoot":"","sources":["../../src/browser/gitzone-product-frontend-module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oDAAoD,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAsB,MAAM,uCAAuC,CAAC;AAE5F,eAAO,MAAM,qBAAqB;;;CAGjC,CAAC;AAEF,qBACa,0BAA2B,YAAW,mBAAmB,EAAE,gBAAgB;IAEtF,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,cAAc,CAAC;IAE7C,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAMjD,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;CAM9C;;AAED,wBAIG"}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"use strict";
|
||||||
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||||
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||||
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||||
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||||
|
};
|
||||||
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.GitZoneProductContribution = exports.GitZoneWelcomeCommand = void 0;
|
||||||
|
const common_menus_js_1 = require("@theia/core/lib/browser/common-menus.js");
|
||||||
|
const command_js_1 = require("@theia/core/lib/common/command.js");
|
||||||
|
const menu_model_registry_js_1 = require("@theia/core/lib/common/menu/menu-model-registry.js");
|
||||||
|
const message_service_js_1 = require("@theia/core/lib/common/message-service.js");
|
||||||
|
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||||
|
exports.GitZoneWelcomeCommand = {
|
||||||
|
id: 'gitzone.product.welcome',
|
||||||
|
label: 'Git.Zone: Welcome',
|
||||||
|
};
|
||||||
|
let GitZoneProductContribution = class GitZoneProductContribution {
|
||||||
|
messages;
|
||||||
|
registerCommands(registry) {
|
||||||
|
registry.registerCommand(exports.GitZoneWelcomeCommand, {
|
||||||
|
execute: () => this.messages.info('Git.Zone IDE is connected to a remote-first Theia workspace.'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
registerMenus(menus) {
|
||||||
|
menus.registerMenuAction(common_menus_js_1.CommonMenus.HELP, {
|
||||||
|
commandId: exports.GitZoneWelcomeCommand.id,
|
||||||
|
label: exports.GitZoneWelcomeCommand.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.GitZoneProductContribution = GitZoneProductContribution;
|
||||||
|
__decorate([
|
||||||
|
(0, index_js_1.inject)(message_service_js_1.MessageService),
|
||||||
|
__metadata("design:type", message_service_js_1.MessageService)
|
||||||
|
], GitZoneProductContribution.prototype, "messages", void 0);
|
||||||
|
exports.GitZoneProductContribution = GitZoneProductContribution = __decorate([
|
||||||
|
(0, index_js_1.injectable)()
|
||||||
|
], GitZoneProductContribution);
|
||||||
|
exports.default = new index_js_1.ContainerModule((bind) => {
|
||||||
|
bind(GitZoneProductContribution).toSelf().inSingletonScope();
|
||||||
|
bind(command_js_1.CommandContribution).toService(GitZoneProductContribution);
|
||||||
|
bind(menu_model_registry_js_1.MenuContribution).toService(GitZoneProductContribution);
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=gitzone-product-frontend-module.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-product-frontend-module.js","sourceRoot":"","sources":["../../src/browser/gitzone-product-frontend-module.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6EAAsE;AACtE,kEAAyF;AACzF,+FAAyG;AACzG,kFAA2E;AAC3E,oEAA4F;AAE/E,QAAA,qBAAqB,GAAG;IACnC,EAAE,EAAE,yBAAyB;IAC7B,KAAK,EAAE,mBAAmB;CAC3B,CAAC;AAGK,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IAElB,QAAQ,CAAkB;IAE7C,gBAAgB,CAAC,QAAyB;QACxC,QAAQ,CAAC,eAAe,CAAC,6BAAqB,EAAE;YAC9C,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,8DAA8D,CAAC;SAClG,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,KAAwB;QACpC,KAAK,CAAC,kBAAkB,CAAC,6BAAW,CAAC,IAAI,EAAE;YACzC,SAAS,EAAE,6BAAqB,CAAC,EAAE;YACnC,KAAK,EAAE,6BAAqB,CAAC,KAAK;SACnC,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAhBY,gEAA0B;AAElB;IADlB,IAAA,iBAAM,EAAC,mCAAc,CAAC;8BACO,mCAAc;4DAAC;qCAFlC,0BAA0B;IADtC,IAAA,qBAAU,GAAE;GACA,0BAA0B,CAgBtC;AAED,kBAAe,IAAI,0BAAe,CAAC,CAAC,IAAI,EAAE,EAAE;IAC1C,IAAI,CAAC,0BAA0B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAC7D,IAAI,CAAC,gCAAmB,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;IAChE,IAAI,CAAC,yCAAgB,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;AAC/D,CAAC,CAAC,CAAC"}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-extension-product",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"keywords": ["theia-extension"],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@theia/core": "1.71.0"
|
||||||
|
},
|
||||||
|
"theiaExtensions": [
|
||||||
|
{
|
||||||
|
"frontend": "lib/browser/gitzone-product-frontend-module"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": ["src", "lib"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { CommonMenus } from '@theia/core/lib/browser/common-menus.js';
|
||||||
|
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command.js';
|
||||||
|
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry.js';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service.js';
|
||||||
|
import { ContainerModule, inject, injectable } from '@theia/core/shared/inversify/index.js';
|
||||||
|
|
||||||
|
export const GitZoneWelcomeCommand = {
|
||||||
|
id: 'gitzone.product.welcome',
|
||||||
|
label: 'Git.Zone: Welcome',
|
||||||
|
};
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class GitZoneProductContribution implements CommandContribution, MenuContribution {
|
||||||
|
@inject(MessageService)
|
||||||
|
protected readonly messages!: MessageService;
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(GitZoneWelcomeCommand, {
|
||||||
|
execute: () => this.messages.info('Git.Zone IDE is connected to a remote-first Theia workspace.'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(menus: MenuModelRegistry): void {
|
||||||
|
menus.registerMenuAction(CommonMenus.HELP, {
|
||||||
|
commandId: GitZoneWelcomeCommand.id,
|
||||||
|
label: GitZoneWelcomeCommand.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ContainerModule((bind) => {
|
||||||
|
bind(GitZoneProductContribution).toSelf().inSingletonScope();
|
||||||
|
bind(CommandContribution).toService(GitZoneProductContribution);
|
||||||
|
bind(MenuContribution).toService(GitZoneProductContribution);
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"verbatimModuleSyntax": false,
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "lib",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command.js';
|
||||||
|
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry.js';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service.js';
|
||||||
|
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||||
|
import { type IGitZoneRemoteServer } from '../common/gitzone-remote-protocol.js';
|
||||||
|
export declare const GitZoneRemoteEnvironmentCommand: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
export declare class GitZoneRemoteContribution implements CommandContribution, MenuContribution {
|
||||||
|
protected readonly remoteServer: IGitZoneRemoteServer;
|
||||||
|
protected readonly messages: MessageService;
|
||||||
|
registerCommands(registry: CommandRegistry): void;
|
||||||
|
registerMenus(menus: MenuModelRegistry): void;
|
||||||
|
}
|
||||||
|
declare const _default: ContainerModule;
|
||||||
|
export default _default;
|
||||||
|
//# sourceMappingURL=gitzone-remote-frontend-module.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-remote-frontend-module.d.ts","sourceRoot":"","sources":["../../src/browser/gitzone-remote-frontend-module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oDAAoD,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAsB,MAAM,uCAAuC,CAAC;AAC5F,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,sCAAsC,CAAC;AAE9C,eAAO,MAAM,+BAA+B;;;CAG3C,CAAC;AAEF,qBACa,yBAA0B,YAAW,mBAAmB,EAAE,gBAAgB;IAErF,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAG,oBAAoB,CAAC;IAGvD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,cAAc,CAAC;IAE7C,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IASjD,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;CAM9C;;AAED,wBAWG"}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"use strict";
|
||||||
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||||
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||||
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||||
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||||
|
};
|
||||||
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.GitZoneRemoteContribution = exports.GitZoneRemoteEnvironmentCommand = void 0;
|
||||||
|
const ws_connection_provider_js_1 = require("@theia/core/lib/browser/messaging/ws-connection-provider.js");
|
||||||
|
const common_menus_js_1 = require("@theia/core/lib/browser/common-menus.js");
|
||||||
|
const command_js_1 = require("@theia/core/lib/common/command.js");
|
||||||
|
const menu_model_registry_js_1 = require("@theia/core/lib/common/menu/menu-model-registry.js");
|
||||||
|
const message_service_js_1 = require("@theia/core/lib/common/message-service.js");
|
||||||
|
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||||
|
const gitzone_remote_protocol_js_1 = require("../common/gitzone-remote-protocol.js");
|
||||||
|
exports.GitZoneRemoteEnvironmentCommand = {
|
||||||
|
id: 'gitzone.remote.environment',
|
||||||
|
label: 'Git.Zone: Show Remote Environment',
|
||||||
|
};
|
||||||
|
let GitZoneRemoteContribution = class GitZoneRemoteContribution {
|
||||||
|
remoteServer;
|
||||||
|
messages;
|
||||||
|
registerCommands(registry) {
|
||||||
|
registry.registerCommand(exports.GitZoneRemoteEnvironmentCommand, {
|
||||||
|
execute: async () => {
|
||||||
|
const environment = await this.remoteServer.getEnvironment();
|
||||||
|
await this.messages.info(`Remote workspace: ${environment.workspacePath}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
registerMenus(menus) {
|
||||||
|
menus.registerMenuAction(common_menus_js_1.CommonMenus.FILE_OPEN, {
|
||||||
|
commandId: exports.GitZoneRemoteEnvironmentCommand.id,
|
||||||
|
label: exports.GitZoneRemoteEnvironmentCommand.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.GitZoneRemoteContribution = GitZoneRemoteContribution;
|
||||||
|
__decorate([
|
||||||
|
(0, index_js_1.inject)(gitzone_remote_protocol_js_1.GitZoneRemoteServer),
|
||||||
|
__metadata("design:type", Object)
|
||||||
|
], GitZoneRemoteContribution.prototype, "remoteServer", void 0);
|
||||||
|
__decorate([
|
||||||
|
(0, index_js_1.inject)(message_service_js_1.MessageService),
|
||||||
|
__metadata("design:type", message_service_js_1.MessageService)
|
||||||
|
], GitZoneRemoteContribution.prototype, "messages", void 0);
|
||||||
|
exports.GitZoneRemoteContribution = GitZoneRemoteContribution = __decorate([
|
||||||
|
(0, index_js_1.injectable)()
|
||||||
|
], GitZoneRemoteContribution);
|
||||||
|
exports.default = new index_js_1.ContainerModule((bind) => {
|
||||||
|
bind(gitzone_remote_protocol_js_1.GitZoneRemoteServer)
|
||||||
|
.toDynamicValue((context) => context.container
|
||||||
|
.get(ws_connection_provider_js_1.WebSocketConnectionProvider)
|
||||||
|
.createProxy(gitzone_remote_protocol_js_1.gitZoneRemotePath))
|
||||||
|
.inSingletonScope();
|
||||||
|
bind(GitZoneRemoteContribution).toSelf().inSingletonScope();
|
||||||
|
bind(command_js_1.CommandContribution).toService(GitZoneRemoteContribution);
|
||||||
|
bind(menu_model_registry_js_1.MenuContribution).toService(GitZoneRemoteContribution);
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=gitzone-remote-frontend-module.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-remote-frontend-module.js","sourceRoot":"","sources":["../../src/browser/gitzone-remote-frontend-module.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2GAA0G;AAC1G,6EAAsE;AACtE,kEAAyF;AACzF,+FAAyG;AACzG,kFAA2E;AAC3E,oEAA4F;AAC5F,qFAI8C;AAEjC,QAAA,+BAA+B,GAAG;IAC7C,EAAE,EAAE,4BAA4B;IAChC,KAAK,EAAE,mCAAmC;CAC3C,CAAC;AAGK,IAAM,yBAAyB,GAA/B,MAAM,yBAAyB;IAEjB,YAAY,CAAwB;IAGpC,QAAQ,CAAkB;IAE7C,gBAAgB,CAAC,QAAyB;QACxC,QAAQ,CAAC,eAAe,CAAC,uCAA+B,EAAE;YACxD,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;gBAC7D,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,qBAAqB,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC;YAC7E,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,KAAwB;QACpC,KAAK,CAAC,kBAAkB,CAAC,6BAAW,CAAC,SAAS,EAAE;YAC9C,SAAS,EAAE,uCAA+B,CAAC,EAAE;YAC7C,KAAK,EAAE,uCAA+B,CAAC,KAAK;SAC7C,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAtBY,8DAAyB;AAEjB;IADlB,IAAA,iBAAM,EAAC,gDAAmB,CAAC;;+DAC2B;AAGpC;IADlB,IAAA,iBAAM,EAAC,mCAAc,CAAC;8BACO,mCAAc;2DAAC;oCALlC,yBAAyB;IADrC,IAAA,qBAAU,GAAE;GACA,yBAAyB,CAsBrC;AAED,kBAAe,IAAI,0BAAe,CAAC,CAAC,IAAI,EAAE,EAAE;IAC1C,IAAI,CAAC,gDAAmB,CAAC;SACtB,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1B,OAAO,CAAC,SAAS;SACd,GAAG,CAAC,uDAA2B,CAAC;SAChC,WAAW,CAAuB,8CAAiB,CAAC,CACxD;SACA,gBAAgB,EAAE,CAAC;IACtB,IAAI,CAAC,yBAAyB,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAC5D,IAAI,CAAC,gCAAmB,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;IAC/D,IAAI,CAAC,yCAAgB,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;AAC9D,CAAC,CAAC,CAAC"}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export declare const gitZoneRemotePath = "/services/git-zone/remote";
|
||||||
|
export declare const GitZoneRemoteServer: unique symbol;
|
||||||
|
export interface IGitZoneRemoteEnvironment {
|
||||||
|
workspacePath: string;
|
||||||
|
processId: number;
|
||||||
|
opencodePort?: number;
|
||||||
|
theiaPort?: number;
|
||||||
|
serverVersion?: string;
|
||||||
|
}
|
||||||
|
export interface IGitZoneRemoteServer {
|
||||||
|
getEnvironment(): Promise<IGitZoneRemoteEnvironment>;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=gitzone-remote-protocol.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-remote-protocol.d.ts","sourceRoot":"","sources":["../../src/common/gitzone-remote-protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,8BAA8B,CAAC;AAE7D,eAAO,MAAM,mBAAmB,eAAgC,CAAC;AAEjE,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,cAAc,IAAI,OAAO,CAAC,yBAAyB,CAAC,CAAC;CACtD"}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.GitZoneRemoteServer = exports.gitZoneRemotePath = void 0;
|
||||||
|
exports.gitZoneRemotePath = '/services/git-zone/remote';
|
||||||
|
exports.GitZoneRemoteServer = Symbol('GitZoneRemoteServer');
|
||||||
|
//# sourceMappingURL=gitzone-remote-protocol.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-remote-protocol.js","sourceRoot":"","sources":["../../src/common/gitzone-remote-protocol.ts"],"names":[],"mappings":";;;AAAa,QAAA,iBAAiB,GAAG,2BAA2B,CAAC;AAEhD,QAAA,mBAAmB,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAAC"}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||||
|
declare const _default: ContainerModule;
|
||||||
|
export default _default;
|
||||||
|
//# sourceMappingURL=gitzone-remote-backend-module.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-remote-backend-module.d.ts","sourceRoot":"","sources":["../../src/node/gitzone-remote-backend-module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;;AAQxE,wBAUG"}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const handler_js_1 = require("@theia/core/lib/common/messaging/handler.js");
|
||||||
|
const proxy_factory_js_1 = require("@theia/core/lib/common/messaging/proxy-factory.js");
|
||||||
|
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||||
|
const gitzone_remote_protocol_js_1 = require("../common/gitzone-remote-protocol.js");
|
||||||
|
const gitzone_remote_node_service_js_1 = require("./gitzone-remote-node-service.js");
|
||||||
|
exports.default = new index_js_1.ContainerModule((bind) => {
|
||||||
|
bind(gitzone_remote_node_service_js_1.GitZoneRemoteNodeService).toSelf().inSingletonScope();
|
||||||
|
bind(gitzone_remote_protocol_js_1.GitZoneRemoteServer).toService(gitzone_remote_node_service_js_1.GitZoneRemoteNodeService);
|
||||||
|
bind(handler_js_1.ConnectionHandler)
|
||||||
|
.toDynamicValue((context) => new proxy_factory_js_1.RpcConnectionHandler(gitzone_remote_protocol_js_1.gitZoneRemotePath, () => context.container.get(gitzone_remote_protocol_js_1.GitZoneRemoteServer)))
|
||||||
|
.inSingletonScope();
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=gitzone-remote-backend-module.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-remote-backend-module.js","sourceRoot":"","sources":["../../src/node/gitzone-remote-backend-module.ts"],"names":[],"mappings":";;AAAA,4EAAgF;AAChF,wFAAyF;AACzF,oEAAwE;AACxE,qFAI8C;AAC9C,qFAA4E;AAE5E,kBAAe,IAAI,0BAAe,CAAC,CAAC,IAAI,EAAE,EAAE;IAC1C,IAAI,CAAC,yDAAwB,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAC3D,IAAI,CAAC,gDAAmB,CAAC,CAAC,SAAS,CAAC,yDAAwB,CAAC,CAAC;IAC9D,IAAI,CAAC,8BAAiB,CAAC;SACpB,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1B,IAAI,uCAAoB,CAAQ,8CAAiB,EAAE,GAAG,EAAE,CACtD,OAAO,CAAC,SAAS,CAAC,GAAG,CAAuB,gDAAmB,CAAC,CACjE,CACF;SACA,gBAAgB,EAAE,CAAC;AACxB,CAAC,CAAC,CAAC"}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { IGitZoneRemoteEnvironment, IGitZoneRemoteServer } from '../common/gitzone-remote-protocol.js';
|
||||||
|
export declare class GitZoneRemoteNodeService implements IGitZoneRemoteServer {
|
||||||
|
getEnvironment(): Promise<IGitZoneRemoteEnvironment>;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=gitzone-remote-node-service.d.ts.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-remote-node-service.d.ts","sourceRoot":"","sources":["../../src/node/gitzone-remote-node-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAE5G,qBACa,wBAAyB,YAAW,oBAAoB;IAC7D,cAAc,IAAI,OAAO,CAAC,yBAAyB,CAAC;CAS3D"}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"use strict";
|
||||||
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||||
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||||
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||||
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.GitZoneRemoteNodeService = void 0;
|
||||||
|
const index_js_1 = require("@theia/core/shared/inversify/index.js");
|
||||||
|
let GitZoneRemoteNodeService = class GitZoneRemoteNodeService {
|
||||||
|
async getEnvironment() {
|
||||||
|
return {
|
||||||
|
workspacePath: process.env.GITZONE_IDE_WORKSPACE || process.cwd(),
|
||||||
|
processId: process.pid,
|
||||||
|
opencodePort: parseOptionalPort(process.env.GITZONE_IDE_OPENCODE_PORT),
|
||||||
|
theiaPort: parseOptionalPort(process.env.THEIA_PORT),
|
||||||
|
serverVersion: process.env.GITZONE_IDE_SERVER_VERSION,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.GitZoneRemoteNodeService = GitZoneRemoteNodeService;
|
||||||
|
exports.GitZoneRemoteNodeService = GitZoneRemoteNodeService = __decorate([
|
||||||
|
(0, index_js_1.injectable)()
|
||||||
|
], GitZoneRemoteNodeService);
|
||||||
|
const parseOptionalPort = (value) => {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const port = Number(value);
|
||||||
|
return Number.isInteger(port) && port > 0 ? port : undefined;
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=gitzone-remote-node-service.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"gitzone-remote-node-service.js","sourceRoot":"","sources":["../../src/node/gitzone-remote-node-service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oEAAmE;AAI5D,IAAM,wBAAwB,GAA9B,MAAM,wBAAwB;IACnC,KAAK,CAAC,cAAc;QAClB,OAAO;YACL,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,CAAC,GAAG,EAAE;YACjE,SAAS,EAAE,OAAO,CAAC,GAAG;YACtB,YAAY,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;YACtE,SAAS,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;YACpD,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B;SACtD,CAAC;IACJ,CAAC;CACF,CAAA;AAVY,4DAAwB;mCAAxB,wBAAwB;IADpC,IAAA,qBAAU,GAAE;GACA,wBAAwB,CAUpC;AAED,MAAM,iBAAiB,GAAG,CAAC,KAAyB,EAAE,EAAE;IACtD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC,CAAC"}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@git.zone/ide-extension-remote",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"keywords": ["theia-extension"],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@git.zone/ide-protocol": "workspace:*",
|
||||||
|
"@theia/core": "1.71.0"
|
||||||
|
},
|
||||||
|
"theiaExtensions": [
|
||||||
|
{
|
||||||
|
"frontend": "lib/browser/gitzone-remote-frontend-module",
|
||||||
|
"backend": "lib/node/gitzone-remote-backend-module"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": ["src", "lib"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider.js';
|
||||||
|
import { CommonMenus } from '@theia/core/lib/browser/common-menus.js';
|
||||||
|
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command.js';
|
||||||
|
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry.js';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service.js';
|
||||||
|
import { ContainerModule, inject, injectable } from '@theia/core/shared/inversify/index.js';
|
||||||
|
import {
|
||||||
|
GitZoneRemoteServer,
|
||||||
|
gitZoneRemotePath,
|
||||||
|
type IGitZoneRemoteServer,
|
||||||
|
} from '../common/gitzone-remote-protocol.js';
|
||||||
|
|
||||||
|
export const GitZoneRemoteEnvironmentCommand = {
|
||||||
|
id: 'gitzone.remote.environment',
|
||||||
|
label: 'Git.Zone: Show Remote Environment',
|
||||||
|
};
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class GitZoneRemoteContribution implements CommandContribution, MenuContribution {
|
||||||
|
@inject(GitZoneRemoteServer)
|
||||||
|
protected readonly remoteServer!: IGitZoneRemoteServer;
|
||||||
|
|
||||||
|
@inject(MessageService)
|
||||||
|
protected readonly messages!: MessageService;
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(GitZoneRemoteEnvironmentCommand, {
|
||||||
|
execute: async () => {
|
||||||
|
const environment = await this.remoteServer.getEnvironment();
|
||||||
|
await this.messages.info(`Remote workspace: ${environment.workspacePath}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(menus: MenuModelRegistry): void {
|
||||||
|
menus.registerMenuAction(CommonMenus.FILE_OPEN, {
|
||||||
|
commandId: GitZoneRemoteEnvironmentCommand.id,
|
||||||
|
label: GitZoneRemoteEnvironmentCommand.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ContainerModule((bind) => {
|
||||||
|
bind(GitZoneRemoteServer)
|
||||||
|
.toDynamicValue((context) =>
|
||||||
|
context.container
|
||||||
|
.get(WebSocketConnectionProvider)
|
||||||
|
.createProxy<IGitZoneRemoteServer>(gitZoneRemotePath),
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
bind(GitZoneRemoteContribution).toSelf().inSingletonScope();
|
||||||
|
bind(CommandContribution).toService(GitZoneRemoteContribution);
|
||||||
|
bind(MenuContribution).toService(GitZoneRemoteContribution);
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const gitZoneRemotePath = '/services/git-zone/remote';
|
||||||
|
|
||||||
|
export const GitZoneRemoteServer = Symbol('GitZoneRemoteServer');
|
||||||
|
|
||||||
|
export interface IGitZoneRemoteEnvironment {
|
||||||
|
workspacePath: string;
|
||||||
|
processId: number;
|
||||||
|
opencodePort?: number;
|
||||||
|
theiaPort?: number;
|
||||||
|
serverVersion?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitZoneRemoteServer {
|
||||||
|
getEnvironment(): Promise<IGitZoneRemoteEnvironment>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ConnectionHandler } from '@theia/core/lib/common/messaging/handler.js';
|
||||||
|
import { RpcConnectionHandler } from '@theia/core/lib/common/messaging/proxy-factory.js';
|
||||||
|
import { ContainerModule } from '@theia/core/shared/inversify/index.js';
|
||||||
|
import {
|
||||||
|
GitZoneRemoteServer,
|
||||||
|
gitZoneRemotePath,
|
||||||
|
type IGitZoneRemoteServer,
|
||||||
|
} from '../common/gitzone-remote-protocol.js';
|
||||||
|
import { GitZoneRemoteNodeService } from './gitzone-remote-node-service.js';
|
||||||
|
|
||||||
|
export default new ContainerModule((bind) => {
|
||||||
|
bind(GitZoneRemoteNodeService).toSelf().inSingletonScope();
|
||||||
|
bind(GitZoneRemoteServer).toService(GitZoneRemoteNodeService);
|
||||||
|
bind(ConnectionHandler)
|
||||||
|
.toDynamicValue((context) =>
|
||||||
|
new RpcConnectionHandler<never>(gitZoneRemotePath, () =>
|
||||||
|
context.container.get<IGitZoneRemoteServer>(GitZoneRemoteServer),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { injectable } from '@theia/core/shared/inversify/index.js';
|
||||||
|
import type { IGitZoneRemoteEnvironment, IGitZoneRemoteServer } from '../common/gitzone-remote-protocol.js';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class GitZoneRemoteNodeService implements IGitZoneRemoteServer {
|
||||||
|
async getEnvironment(): Promise<IGitZoneRemoteEnvironment> {
|
||||||
|
return {
|
||||||
|
workspacePath: process.env.GITZONE_IDE_WORKSPACE || process.cwd(),
|
||||||
|
processId: process.pid,
|
||||||
|
opencodePort: parseOptionalPort(process.env.GITZONE_IDE_OPENCODE_PORT),
|
||||||
|
theiaPort: parseOptionalPort(process.env.THEIA_PORT),
|
||||||
|
serverVersion: process.env.GITZONE_IDE_SERVER_VERSION,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseOptionalPort = (value: string | undefined) => {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const port = Number(value);
|
||||||
|
return Number.isInteger(port) && port > 0 ? port : undefined;
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"verbatimModuleSyntax": false,
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "lib",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"strict": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"skipLibCheck": false,
|
||||||
|
"types": ["node"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {}
|
||||||
|
},
|
||||||
|
"exclude": ["dist_*/**/*.d.ts", "node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user