88 lines
3.6 KiB
TypeScript
88 lines
3.6 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { basename, resolve } from "node:path";
|
|
import type { WorkspaceConfig, WorkspaceInfo } from "./types.js";
|
|
|
|
function workspaceIdForKey(key: string): string {
|
|
const hash = createHash("sha256").update(key).digest("hex");
|
|
return `ws_${hash.slice(0, 12)}`;
|
|
}
|
|
|
|
export function workspaceIdForPath(path: string): string {
|
|
return workspaceIdForKey(path);
|
|
}
|
|
|
|
export function workspaceIdForRemote(baseUrl: string, directory?: string | null): string {
|
|
const normalizedBaseUrl = baseUrl.trim();
|
|
const normalizedDirectory = directory?.trim() ?? "";
|
|
const key = normalizedDirectory
|
|
? `remote::${normalizedBaseUrl}::${normalizedDirectory}`
|
|
: `remote::${normalizedBaseUrl}`;
|
|
return workspaceIdForKey(key);
|
|
}
|
|
|
|
export function workspaceIdForOpenwork(hostUrl: string, workspaceId?: string | null): string {
|
|
const normalizedHostUrl = hostUrl.trim();
|
|
const normalizedWorkspaceId = workspaceId?.trim() ?? "";
|
|
const key = normalizedWorkspaceId
|
|
? `openwork::${normalizedHostUrl}::${normalizedWorkspaceId}`
|
|
: `openwork::${normalizedHostUrl}`;
|
|
return workspaceIdForKey(key);
|
|
}
|
|
|
|
export function buildWorkspaceInfos(
|
|
workspaces: WorkspaceConfig[],
|
|
cwd: string,
|
|
): WorkspaceInfo[] {
|
|
return workspaces.map((workspace) => {
|
|
const rawPath = workspace.path?.trim() ?? "";
|
|
const workspaceType = workspace.workspaceType ?? "local";
|
|
const resolvedPath = rawPath ? resolve(cwd, rawPath) : "";
|
|
const remoteType = workspace.remoteType;
|
|
const id = workspace.id?.trim()
|
|
|| (workspaceType === "remote"
|
|
? remoteType === "openwork"
|
|
? workspaceIdForOpenwork(workspace.openworkHostUrl ?? workspace.baseUrl ?? "", workspace.openworkWorkspaceId)
|
|
: workspaceIdForRemote(workspace.baseUrl ?? "", workspace.directory)
|
|
: workspaceIdForPath(resolvedPath));
|
|
const name = workspace.name?.trim()
|
|
|| workspace.displayName?.trim()
|
|
|| workspace.openworkWorkspaceName?.trim()
|
|
|| basename(resolvedPath || workspace.directory?.trim() || workspace.baseUrl?.trim() || "Workspace");
|
|
return {
|
|
id,
|
|
name,
|
|
path: resolvedPath,
|
|
preset: workspace.preset?.trim() || (workspaceType === "remote" ? "remote" : "starter"),
|
|
workspaceType,
|
|
remoteType,
|
|
baseUrl: workspace.baseUrl,
|
|
directory: workspace.directory,
|
|
displayName: workspace.displayName,
|
|
openworkHostUrl: workspace.openworkHostUrl,
|
|
openworkToken: workspace.openworkToken,
|
|
openworkWorkspaceId: workspace.openworkWorkspaceId,
|
|
openworkWorkspaceName: workspace.openworkWorkspaceName,
|
|
sandboxBackend: workspace.sandboxBackend,
|
|
sandboxRunId: workspace.sandboxRunId,
|
|
sandboxContainerName: workspace.sandboxContainerName,
|
|
opencodeUsername: workspace.opencodeUsername,
|
|
opencodePassword: workspace.opencodePassword,
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pick the workspace the server-managed OpenCode engine should boot in.
|
|
*
|
|
* The engine serves every workspace but needs one local directory to start in.
|
|
* `config.workspaces[0]` is not reliably that: a freshly added remote worker is
|
|
* prepended to the list, so index 0 can be a remote workspace (no local path)
|
|
* even when local workspaces exist — which would leave the engine unstarted.
|
|
* Select the first non-remote workspace with a resolved local path so the engine
|
|
* starts regardless of ordering; returns undefined for remote-only setups (which
|
|
* need no local engine).
|
|
*/
|
|
export function findManagedEngineWorkspace(workspaces: WorkspaceInfo[]): WorkspaceInfo | undefined {
|
|
return workspaces.find((workspace) => workspace.workspaceType !== "remote" && workspace.path.trim() !== "");
|
|
}
|