Files
openwork/apps/server/src/workspaces.ts
T

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() !== "");
}