mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 02:20:38 +09:00
119 lines
3.5 KiB
TypeScript
119 lines
3.5 KiB
TypeScript
|
|
import path from "node:path";
|
||
|
|
import {
|
||
|
|
type SshRemoteExecutionSpec,
|
||
|
|
prepareWorkspaceForSshExecution,
|
||
|
|
restoreWorkspaceFromSshExecution,
|
||
|
|
syncDirectoryToSsh,
|
||
|
|
} from "./ssh.js";
|
||
|
|
|
||
|
|
export interface RemoteManagedRuntimeAsset {
|
||
|
|
key: string;
|
||
|
|
localDir: string;
|
||
|
|
followSymlinks?: boolean;
|
||
|
|
exclude?: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface PreparedRemoteManagedRuntime {
|
||
|
|
spec: SshRemoteExecutionSpec;
|
||
|
|
workspaceLocalDir: string;
|
||
|
|
workspaceRemoteDir: string;
|
||
|
|
runtimeRootDir: string;
|
||
|
|
assetDirs: Record<string, string>;
|
||
|
|
restoreWorkspace(): Promise<void>;
|
||
|
|
}
|
||
|
|
|
||
|
|
function asObject(value: unknown): Record<string, unknown> {
|
||
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
||
|
|
? (value as Record<string, unknown>)
|
||
|
|
: {};
|
||
|
|
}
|
||
|
|
|
||
|
|
function asString(value: unknown): string {
|
||
|
|
return typeof value === "string" ? value : "";
|
||
|
|
}
|
||
|
|
|
||
|
|
function asNumber(value: unknown): number {
|
||
|
|
return typeof value === "number" ? value : Number(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function buildRemoteExecutionSessionIdentity(spec: SshRemoteExecutionSpec | null) {
|
||
|
|
if (!spec) return null;
|
||
|
|
return {
|
||
|
|
transport: "ssh",
|
||
|
|
host: spec.host,
|
||
|
|
port: spec.port,
|
||
|
|
username: spec.username,
|
||
|
|
remoteCwd: spec.remoteCwd,
|
||
|
|
...(spec.paperclipApiUrl ? { paperclipApiUrl: spec.paperclipApiUrl } : {}),
|
||
|
|
} as const;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function remoteExecutionSessionMatches(saved: unknown, current: SshRemoteExecutionSpec | null): boolean {
|
||
|
|
const currentIdentity = buildRemoteExecutionSessionIdentity(current);
|
||
|
|
if (!currentIdentity) return false;
|
||
|
|
|
||
|
|
const parsedSaved = asObject(saved);
|
||
|
|
return (
|
||
|
|
asString(parsedSaved.transport) === currentIdentity.transport &&
|
||
|
|
asString(parsedSaved.host) === currentIdentity.host &&
|
||
|
|
asNumber(parsedSaved.port) === currentIdentity.port &&
|
||
|
|
asString(parsedSaved.username) === currentIdentity.username &&
|
||
|
|
asString(parsedSaved.remoteCwd) === currentIdentity.remoteCwd &&
|
||
|
|
asString(parsedSaved.paperclipApiUrl) === asString(currentIdentity.paperclipApiUrl)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function prepareRemoteManagedRuntime(input: {
|
||
|
|
spec: SshRemoteExecutionSpec;
|
||
|
|
adapterKey: string;
|
||
|
|
workspaceLocalDir: string;
|
||
|
|
workspaceRemoteDir?: string;
|
||
|
|
assets?: RemoteManagedRuntimeAsset[];
|
||
|
|
}): Promise<PreparedRemoteManagedRuntime> {
|
||
|
|
const workspaceRemoteDir = input.workspaceRemoteDir ?? input.spec.remoteCwd;
|
||
|
|
const runtimeRootDir = path.posix.join(workspaceRemoteDir, ".paperclip-runtime", input.adapterKey);
|
||
|
|
|
||
|
|
await prepareWorkspaceForSshExecution({
|
||
|
|
spec: input.spec,
|
||
|
|
localDir: input.workspaceLocalDir,
|
||
|
|
remoteDir: workspaceRemoteDir,
|
||
|
|
});
|
||
|
|
|
||
|
|
const assetDirs: Record<string, string> = {};
|
||
|
|
try {
|
||
|
|
for (const asset of input.assets ?? []) {
|
||
|
|
const remoteDir = path.posix.join(runtimeRootDir, asset.key);
|
||
|
|
assetDirs[asset.key] = remoteDir;
|
||
|
|
await syncDirectoryToSsh({
|
||
|
|
spec: input.spec,
|
||
|
|
localDir: asset.localDir,
|
||
|
|
remoteDir,
|
||
|
|
followSymlinks: asset.followSymlinks,
|
||
|
|
exclude: asset.exclude,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
await restoreWorkspaceFromSshExecution({
|
||
|
|
spec: input.spec,
|
||
|
|
localDir: input.workspaceLocalDir,
|
||
|
|
remoteDir: workspaceRemoteDir,
|
||
|
|
});
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
spec: input.spec,
|
||
|
|
workspaceLocalDir: input.workspaceLocalDir,
|
||
|
|
workspaceRemoteDir,
|
||
|
|
runtimeRootDir,
|
||
|
|
assetDirs,
|
||
|
|
restoreWorkspace: async () => {
|
||
|
|
await restoreWorkspaceFromSshExecution({
|
||
|
|
spec: input.spec,
|
||
|
|
localDir: input.workspaceLocalDir,
|
||
|
|
remoteDir: workspaceRemoteDir,
|
||
|
|
});
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|