feat: expose workspace registry API

This commit is contained in:
2026-08-04 01:01:57 +02:00
parent 49fa7030a5
commit f95a18ab0d
9 changed files with 829 additions and 50 deletions
+37 -2
View File
@@ -1,7 +1,7 @@
import { execFile, spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
import { lstatSync, mkdirSync } from "node:fs";
import { mkdir } from "node:fs/promises";
import { basename, isAbsolute, join } from "node:path";
import { mkdir, rm, writeFile } from "node:fs/promises";
import { basename, dirname, isAbsolute, join } from "node:path";
import { promisify } from "node:util";
import type { WorkspaceErrorCode, WorkspaceRegistryConfig } from "./types.js";
@@ -154,6 +154,30 @@ export class GitWorkspaceRepository {
return (await this.git(["rev-parse", `HEAD:${path}`])).trim();
}
/** Write only a validated registry artifact below the checked-out repository. */
async writeRegistryFile(path: string, source: string): Promise<void> {
this.assertRegistryArtifactPath(path);
const target = join(this.repoPath, path);
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
await writeFile(target, source, { encoding: "utf8", mode: 0o600 });
}
async removeRegistryFile(path: string): Promise<void> {
this.assertRegistryArtifactPath(path);
await rm(join(this.repoPath, path), { force: true });
}
/** Commit and push a fixed set of validated artifact paths without exposing Git output. */
async commitAndPush(paths: readonly string[], message: string): Promise<GitStatus> {
if (paths.length === 0 || paths.some((path) => !this.isRegistryArtifactPath(path))) {
throw new WorkspaceRegistryError("workspace_invalid", "Workspace repository path is invalid");
}
await this.git(["add", "--", ...paths]);
await this.git(["commit", "-m", message]);
await this.git(["push", "origin", `HEAD:${this.config.branch}`]);
return await this.status();
}
private async clone(): Promise<void> {
try {
await execFileAsync("git", [
@@ -166,6 +190,17 @@ export class GitWorkspaceRepository {
}
}
private isRegistryArtifactPath(path: string): boolean {
return /^workspaces\/[a-z][a-z0-9-]{2,62}\.yaml$/.test(path)
|| /^workspace-docs\/[a-z][a-z0-9-]{2,62}\/(?:contract\.env\.example|README\.md)$/.test(path);
}
private assertRegistryArtifactPath(path: string): void {
if (!this.isRegistryArtifactPath(path)) {
throw new WorkspaceRegistryError("workspace_invalid", "Workspace repository path is invalid");
}
}
private async refresh(): Promise<void> {
if ((await this.git(["status", "--porcelain"])).trim() !== "") {
throw new WorkspaceRegistryError("workspace_stale", "Workspace checkout has local changes");