Initialize remote IDE scaffold
This commit is contained in:
@@ -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, '');
|
||||
Reference in New Issue
Block a user