Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export { pathExists, readJson, writeJson } from "./lib/fs.js";
export * from "./lib/error.js";
export * from "./lib/exec.js";
export { execute, resolvePlugins } from "./lib/planner.js";
export { captureRollbackSnapshot, finalizeRollback } from "./lib/rollback.js";
export { DefaultStages } from "./lib/stage.js";
export type * from "./types.js";
29 changes: 29 additions & 0 deletions packages/core/src/lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,33 @@ export interface Context {
getData<T>(key: string): T;
hasData(key: string): boolean;
setData(key: string, value: any): void;

/**
* State used to roll back local changes if the release fails.
* Populated by `captureRollbackSnapshot` before the first non-`check` stage
* that may mutate the working tree.
*/
rollback?: RollbackState;
}

export interface RollbackState {
/** SHA of HEAD before any release-related modifications were made */
originalHead: string;
/**
* SHA of the stash commit that holds the user's pre-release uncommitted
* changes (only set when the working tree was dirty, i.e. with --all).
*/
stashSha?: string;
/**
* Unique message used to identify the snapshot stash in `git stash list`,
* since `git stash drop` requires a `stash@{N}` ref rather than a SHA.
*/
stashMessage?: string;
/** Set to true once the push stage starts. Disables rollback afterwards. */
pushAttempted: boolean;
/**
* Name of the release tag created during this run (e.g. "v1.2.3"). Only set
* after `git tag` succeeds, so rollback never deletes a pre-existing tag.
*/
createdTag?: string;
}
10 changes: 10 additions & 0 deletions packages/core/src/lib/planner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ReleaseError } from "../index.js";
import type { Context } from "./context.js";
import { GraphNode, topologicalSort } from "./graph.js";
import type { Plugin } from "./plugin.js";
import { captureRollbackSnapshot } from "./rollback.js";
import { DefaultStages, type Stage } from "./stage.js";

/** Resolve all plugins that are required by the chosen plugins */
Expand Down Expand Up @@ -165,8 +166,17 @@ export async function execute(context: Context): Promise<void> {
),
);
}
let snapshotTaken = false;
for (const stage of stages) {
context.cli.prefix = `${stage.id}`;
// Snapshot the pre-release state before the first stage that may mutate
// the working tree. The `check` stage is read-only by convention; any
// later stage (including exec hooks like `after_check` / `before_edit`)
// might write files, so we capture before any of those run.
if (!snapshotTaken && stage.id !== DefaultStages.check.id) {
await captureRollbackSnapshot(context);
snapshotTaken = true;
}
const plugins = await planStage(context, stage);
if (context.argv.verbose) {
context.cli.log(
Expand Down
Loading
Loading