fix: recover workspace publication failures
This commit is contained in:
@@ -172,10 +172,17 @@ export class GitWorkspaceRepository {
|
||||
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();
|
||||
try {
|
||||
await this.git(["add", "--", ...paths]);
|
||||
await this.git(["commit", "-m", message], this.publicationIdentity());
|
||||
await this.git(["push", "origin", `HEAD:${this.config.branch}`]);
|
||||
return await this.status();
|
||||
} catch (error) {
|
||||
// A failed commit leaves staged/working changes; a failed push leaves an ahead commit.
|
||||
// Restore the last fetched remote revision so the next refresh or explicit retry starts clean.
|
||||
await this.restoreFailedPublication();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async clone(): Promise<void> {
|
||||
@@ -223,12 +230,31 @@ export class GitWorkspaceRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private async git(args: string[]): Promise<string> {
|
||||
private publicationIdentity(): NodeJS.ProcessEnv {
|
||||
return {
|
||||
GIT_AUTHOR_NAME: this.config.gitAuthorName,
|
||||
GIT_AUTHOR_EMAIL: this.config.gitAuthorEmail,
|
||||
GIT_COMMITTER_NAME: this.config.gitAuthorName,
|
||||
GIT_COMMITTER_EMAIL: this.config.gitAuthorEmail,
|
||||
};
|
||||
}
|
||||
|
||||
private async restoreFailedPublication(): Promise<void> {
|
||||
try {
|
||||
await this.git(["reset", "--hard", `refs/remotes/origin/${this.config.branch}`]);
|
||||
await this.git(["clean", "-fd", "--", "workspaces", "workspace-docs"]);
|
||||
} catch {
|
||||
// Keep the original sanitized publish failure. A future refresh will surface any recovery
|
||||
// problem without leaking the Git failure details through the API.
|
||||
}
|
||||
}
|
||||
|
||||
private async git(args: string[], env: NodeJS.ProcessEnv = {}): Promise<string> {
|
||||
try {
|
||||
const { stdout } = await execFileAsync(
|
||||
"git",
|
||||
["-c", `core.hooksPath=${this.hooksPath}`, ...args],
|
||||
{ cwd: this.repoPath, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } },
|
||||
{ cwd: this.repoPath, env: { ...process.env, GIT_TERMINAL_PROMPT: "0", ...env } },
|
||||
);
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
|
||||
@@ -36,6 +36,8 @@ export type PublishWorkspaceRequest =
|
||||
export class WorkspaceConflictError extends WorkspaceRegistryError {
|
||||
constructor(
|
||||
readonly fields: string[],
|
||||
readonly expected: { commit: string; blob?: string },
|
||||
readonly actual: { commit: string; blob?: string },
|
||||
readonly base?: CanonicalWorkspace,
|
||||
readonly local?: CanonicalWorkspace,
|
||||
readonly remote?: CanonicalWorkspace,
|
||||
@@ -168,10 +170,10 @@ export class WorkspaceRegistry {
|
||||
if (request.baseCommit !== status.head || (
|
||||
request.action !== "create" && existing?.blob !== request.baseBlob
|
||||
)) {
|
||||
throw await this.conflictFor(request, existing, local);
|
||||
throw await this.conflictFor(request, status.head!, existing, local);
|
||||
}
|
||||
if (request.action === "create" && existing) throw await this.conflictFor(request, existing, local);
|
||||
if (request.action !== "create" && !existing) throw await this.conflictFor(request, existing, local);
|
||||
if (request.action === "create" && existing) throw await this.conflictFor(request, status.head!, existing, local);
|
||||
if (request.action !== "create" && !existing) throw await this.conflictFor(request, status.head!, existing, local);
|
||||
|
||||
const yamlPath = workspacePath(id);
|
||||
const docPaths = this.documentationPaths(id);
|
||||
@@ -205,6 +207,7 @@ export class WorkspaceRegistry {
|
||||
|
||||
private async conflictFor(
|
||||
request: PublishWorkspaceRequest,
|
||||
currentCommit: string,
|
||||
existing: WorkspaceRevision | undefined,
|
||||
local: CanonicalWorkspace | undefined,
|
||||
): Promise<WorkspaceConflictError> {
|
||||
@@ -215,7 +218,14 @@ export class WorkspaceRegistry {
|
||||
const read = await this.read(id);
|
||||
remote = isCanonicalWorkspace(read.workspace) ? read.workspace : undefined;
|
||||
}
|
||||
return new WorkspaceConflictError(this.changedFields(base, remote), base, local, remote);
|
||||
return new WorkspaceConflictError(
|
||||
this.changedFields(base, remote),
|
||||
{ commit: request.baseCommit, ...(request.action === "create" ? {} : { blob: request.baseBlob }) },
|
||||
{ commit: currentCommit, ...(existing ? { blob: existing.blob } : {}) },
|
||||
base,
|
||||
local,
|
||||
remote,
|
||||
);
|
||||
}
|
||||
|
||||
private async readSnapshotCanonical(commit: string, id: string): Promise<CanonicalWorkspace | undefined> {
|
||||
|
||||
Reference in New Issue
Block a user