feat(cloudly): add service runtime and onboarding

This commit is contained in:
2026-05-23 10:46:52 +00:00
parent 59043b7281
commit bef236cd86
18 changed files with 1500 additions and 19 deletions
@@ -0,0 +1,110 @@
import * as plugins from '../plugins.js';
type IExecutionEnvironment = import('@design.estate/dees-catalog').IExecutionEnvironment;
type IFileEntry = import('@design.estate/dees-catalog').IFileEntry;
type IFileWatcher = import('@design.estate/dees-catalog').IFileWatcher;
type IProcessHandle = import('@design.estate/dees-catalog').IProcessHandle;
type TTypedRequestShape = {
method: string;
request: Record<string, unknown>;
response: Record<string, unknown>;
};
export class DeploymentExecutionEnvironment implements IExecutionEnvironment {
public readonly type = 'backend' as const;
private readyState = false;
constructor(
private deploymentId: string,
private identity: plugins.interfaces.data.IIdentity,
) {}
get ready(): boolean {
return this.readyState;
}
public async init(): Promise<void> {
const result = await this.fireRequest('deploymentWorkspaceExists', { path: '/' }) as { exists: boolean };
if (!result.exists) {
throw new Error(`Cannot access deployment filesystem for ${this.deploymentId}`);
}
this.readyState = true;
}
public async destroy(): Promise<void> {
this.readyState = false;
}
public async readFile(pathArg: string): Promise<string> {
const result = await this.fireRequest('deploymentWorkspaceReadFile', { path: pathArg }) as { content: string };
return result.content;
}
public async writeFile(pathArg: string, contentsArg: string): Promise<void> {
await this.fireRequest('deploymentWorkspaceWriteFile', { path: pathArg, content: contentsArg });
}
public async readDir(pathArg: string): Promise<IFileEntry[]> {
const result = await this.fireRequest('deploymentWorkspaceReadDir', { path: pathArg }) as { entries: IFileEntry[] };
return result.entries;
}
public async mkdir(pathArg: string): Promise<void> {
await this.fireRequest('deploymentWorkspaceMkdir', { path: pathArg });
}
public async rm(pathArg: string, optionsArg?: { recursive?: boolean }): Promise<void> {
await this.fireRequest('deploymentWorkspaceRm', {
path: pathArg,
recursive: optionsArg?.recursive,
});
}
public async exists(pathArg: string): Promise<boolean> {
const result = await this.fireRequest('deploymentWorkspaceExists', { path: pathArg }) as { exists: boolean };
return result.exists;
}
public watch(
_pathArg: string,
_callbackArg: (eventArg: 'rename' | 'change', filenameArg: string | null) => void,
_optionsArg?: { recursive?: boolean },
): IFileWatcher {
return { stop: () => {} };
}
public async spawn(commandArg: string, argsArg: string[] = []): Promise<IProcessHandle> {
const result = await this.fireRequest('deploymentWorkspaceExec', {
command: commandArg,
args: argsArg,
}) as { stdout?: string; stderr?: string; exitCode: number };
const output = new ReadableStream<string>({
start(controllerArg) {
if (result.stdout) controllerArg.enqueue(result.stdout);
if (result.stderr) controllerArg.enqueue(result.stderr);
controllerArg.close();
},
});
return {
output,
input: new WritableStream<string>(),
exit: Promise.resolve(result.exitCode),
kill: () => {},
};
}
private async fireRequest(methodArg: string, dataArg: Record<string, unknown>) {
const typedRequest = new plugins.typedrequest.TypedRequest<TTypedRequestShape>(
'/typedrequest',
methodArg,
);
return await typedRequest.fire({
identity: this.identity,
deploymentId: this.deploymentId,
...dataArg,
});
}
}