feat: activate workspaces from the root catalog and bootstrap-only publication
This commit is contained in:
@@ -129,19 +129,25 @@ export class GitWorkspaceRepository {
|
||||
};
|
||||
}
|
||||
|
||||
async workspacePaths(): Promise<string[]> {
|
||||
async workspaceDirectories(): Promise<string[]> {
|
||||
const output = await this.git(["ls-tree", "-d", "--name-only", "HEAD"]);
|
||||
const directories = output.trim() === "" ? [] : output.trim().split("\n");
|
||||
const paths: string[] = [];
|
||||
for (const id of directories) {
|
||||
if (id === "workspace-docs") continue;
|
||||
if (!/^[a-z][a-z0-9-]{2,62}$/.test(id)) {
|
||||
throw new WorkspaceRegistryError("workspace_invalid", "Workspace repository contains an invalid path");
|
||||
}
|
||||
}
|
||||
return directories.filter((id) => id !== "workspace-docs").sort();
|
||||
}
|
||||
|
||||
async workspacePaths(): Promise<string[]> {
|
||||
const paths: string[] = [];
|
||||
for (const id of await this.workspaceDirectories()) {
|
||||
const path = `${id}/workspace.yaml`;
|
||||
const type = (await this.git(["cat-file", "-t", `HEAD:${path}`], {},
|
||||
"Workspace descriptor is invalid")).trim();
|
||||
if (type !== "blob") {
|
||||
const type = await this.gitOptional(["cat-file", "-t", `HEAD:${path}`]);
|
||||
if (type === undefined) continue;
|
||||
if (type.trim() !== "blob") {
|
||||
throw new WorkspaceRegistryError("workspace_invalid", "Workspace descriptor is invalid");
|
||||
}
|
||||
paths.push(path);
|
||||
@@ -216,11 +222,14 @@ export class GitWorkspaceRepository {
|
||||
await rm(join(this.repoPath, path), { force: true });
|
||||
}
|
||||
|
||||
private pendingPublicationPaths: string[] = [];
|
||||
|
||||
/** 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");
|
||||
}
|
||||
this.pendingPublicationPaths = [...paths];
|
||||
try {
|
||||
await this.git(["add", "--", ...paths]);
|
||||
await this.git(["commit", "-m", message], this.publicationIdentity());
|
||||
@@ -291,7 +300,19 @@ export class GitWorkspaceRepository {
|
||||
private async restoreFailedPublication(): Promise<void> {
|
||||
try {
|
||||
await this.git(["reset", "--hard", `refs/remotes/origin/${this.config.branch}`]);
|
||||
await this.git(["clean", "-fd", "--", "workspace-docs"]);
|
||||
// Remove only the exact untracked files this publication created, never curated content.
|
||||
const untracked = this.pendingPublicationPaths.filter((path) => {
|
||||
try {
|
||||
lstatSync(join(this.repoPath, path));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (untracked.length > 0) {
|
||||
await this.git(["clean", "-fd", "--", ...untracked]);
|
||||
}
|
||||
this.pendingPublicationPaths = [];
|
||||
} catch {
|
||||
// Keep the original sanitized publish failure. A future refresh will surface any recovery
|
||||
// problem without leaking the Git failure details through the API.
|
||||
|
||||
Reference in New Issue
Block a user