Skip to content
Open
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/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"node": ">=12.20"
},
"dependencies": {
"alcalzone-shared": "^4.0.1",
"execa": "^5.1.1"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { stripColors } from "./lib/cli";
export * from "./lib/error";
export * from "./lib/exec";
export { execute, resolvePlugins } from "./lib/planner";
export { execute, resolvePlugins, rollback } from "./lib/planner";
export { cloneDeep } from "./lib/shared";
export { DefaultStages } from "./lib/stage";
export * from "./types";
4 changes: 4 additions & 0 deletions packages/core/src/lib/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CLI } from "./cli";
import type { Plugin } from "./plugin";
import type { Stage } from "./stage";
import type { System } from "./system";

export interface Context {
Expand Down Expand Up @@ -43,4 +44,7 @@ export interface Context {
getData<T>(key: string): T;
hasData(key: string): boolean;
setData(key: string, value: any): void;

/** Keeps track of which stages have been fully or partially executed */
executedStages: Stage[];
}
1 change: 1 addition & 0 deletions packages/core/src/lib/planner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ describe("execute", () => {
},
argv: {},
errors: [],
executedStages: [],
} as unknown as Context;

await execute(context);
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/lib/planner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ export async function execute(context: Context): Promise<void> {
}
for (const stage of stages) {
context.cli.prefix = `${stage.id}`;
context.executedStages.push(stage);

const plugins = await planStage(context, stage);
if (context.argv.verbose) {
context.cli.log(
Expand Down Expand Up @@ -200,3 +202,14 @@ export async function execute(context: Context): Promise<void> {
if (!isTest) console.log();
}
}

/** Undo all the changes that have already been made to the file system */
export async function rollback(context: Context): Promise<void> {
for (const plugin of context.plugins) {
try {
await plugin.rollback?.(context);
} catch {
// ignore
}
}
}
3 changes: 3 additions & 0 deletions packages/core/src/lib/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ export interface Plugin {

/** Execute the plugin for the given stage */
executeStage(context: Context, stage: Stage): Promise<void>;

/** Reset the previous state in case of an execution error */
rollback?: (context: Context) => Promise<void> | void;
}
18 changes: 18 additions & 0 deletions packages/core/src/lib/shared.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
import { isArray, isObject } from "alcalzone-shared/typeguards";
import type { Context } from "./context";

export type ConstOrDynamic<T> = T | ((context: Context) => T | Promise<T>);

/**
* Creates a deep copy of the given object
*/
export function cloneDeep<T>(source: T): T {
if (isArray(source)) {
return source.map((i) => cloneDeep(i)) as any;
} else if (isObject(source)) {
const target: any = {};
for (const [key, value] of Object.entries(source)) {
target[key] = cloneDeep(value);
}
return target;
} else {
return source;
}
}
18 changes: 18 additions & 0 deletions packages/plugin-changelog/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,12 @@ class ChangelogPlugin implements Plugin {
let parsedOld: typeof parsed | undefined;
if (changelogOld) {
parsedOld = parseChangelogFile(changelogOld, changelogPlaceholderPrefix.substr(1));
context.setData("changelog_old_raw", changelogOld);
}

const entries = [...parsed.entries, ...(parsedOld?.entries ?? [])];

context.setData("changelog_raw", changelog);
context.setData("changelog_filename", changelogFilename);
context.setData("changelog_before", parsed.before);
context.setData("changelog_after", parsed.after);
Expand Down Expand Up @@ -314,6 +316,22 @@ ${fileContent.slice(changelogBefore.length)}`; // The part after the new placeho
}
}
}

async rollback(context: Context): Promise<void> {
if (!context.executedStages.some((s) => s.id === "edit")) {
// Nothing has been changed yet
return;
}

const changelog_raw = context.getData<string>("changelog_raw");
const changelogFilename = context.getData<string>("changelog_filename");
await fs.writeFile(path.join(context.cwd, changelogFilename), changelog_raw);

if (context.hasData("changelog_old_raw")) {
const changelog_old_raw = context.getData<string>("changelog_old_raw");
await fs.writeFile(path.join(context.cwd, "CHANGELOG_OLD.md"), changelog_old_raw);
}
}
}

export default ChangelogPlugin;
19 changes: 17 additions & 2 deletions packages/plugin-iobroker/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DefaultStages } from "@alcalzone/release-script-core";
import { cloneDeep, DefaultStages } from "@alcalzone/release-script-core";
import type { Context, Plugin, Stage } from "@alcalzone/release-script-core/types";
import { isObject } from "alcalzone-shared/typeguards";
import fs from "fs-extra";
Expand Down Expand Up @@ -127,7 +127,7 @@ You can suppress this check with the ${colors.bold("--no-workflow-check")} flag.

private async executeEditStage(context: Context): Promise<void> {
const newVersion = context.getData<string>("version_new");
const ioPack = context.getData<any>("io-package.json");
const ioPack = cloneDeep(context.getData<any>("io-package.json"));

if (context.argv.dryRun) {
context.cli.log(
Expand Down Expand Up @@ -205,6 +205,21 @@ You can suppress this check with the ${colors.bold("--no-workflow-check")} flag.
await this.executeEditStage(context);
}
}

async rollback(context: Context): Promise<void> {
if (!context.executedStages.some((s) => s.id === "edit")) {
// Nothing has been changed yet
return;
}

const ioPack = context.getData<Readonly<any>>("io-package.json");
let ioPackDirectory = context.cwd;
if (context.argv.ioPackage) {
ioPackDirectory = path.join(ioPackDirectory, context.argv.ioPackage as string);
}
const ioPackPath = path.join(ioPackDirectory, "io-package.json");
await fs.writeJson(ioPackPath, ioPack, { spaces: 2 });
}
}

export default IoBrokerPlugin;
15 changes: 13 additions & 2 deletions packages/plugin-package/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { detectPackageManager } from "@alcalzone/pak";
import { DefaultStages } from "@alcalzone/release-script-core";
import { cloneDeep, DefaultStages } from "@alcalzone/release-script-core";
import type { Context, Plugin, Stage } from "@alcalzone/release-script-core/types";
import { isArray, isObject } from "alcalzone-shared/typeguards";
import fs from "fs-extra";
Expand Down Expand Up @@ -197,7 +197,7 @@ Alternatively, you can use ${context.cli.colors.blue("lerna")} to manage the mon

private async executeEditStage(context: Context): Promise<void> {
const newVersion = context.getData<string>("version_new");
const pack = context.getData<any>("package.json");
const pack = cloneDeep(context.getData<any>("package.json"));

if (context.argv.dryRun) {
context.cli.log(
Expand Down Expand Up @@ -333,6 +333,17 @@ Alternatively, you can use ${context.cli.colors.blue("lerna")} to manage the mon
}
}
}

async rollback(context: Context): Promise<void> {
if (!context.executedStages.some((s) => s.id === "edit")) {
// Nothing has been changed yet
return;
}

const pack = context.getData<Readonly<any>>("package.json");
const packPath = path.join(context.cwd, "package.json");
await fs.writeJson(packPath, pack, { spaces: 2 });
}
}

export default PackagePlugin;
4 changes: 4 additions & 0 deletions packages/release-script/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Plugin,
ReleaseError,
resolvePlugins,
rollback,
SelectOption,
stripColors,
} from "@alcalzone/release-script-core";
Expand Down Expand Up @@ -268,6 +269,7 @@ export async function main(): Promise<void> {
setData: (key: string, value: any) => {
data.set(key, value);
},
executedStages: [],
};
context.cli = new CLI(context);

Expand Down Expand Up @@ -295,6 +297,7 @@ export async function main(): Promise<void> {
message += "!";
console.error();
console.error(message);
await rollback(context);
process.exit(1);
}
} catch (e: any) {
Expand All @@ -318,6 +321,7 @@ export async function main(): Promise<void> {
),
);
}
await rollback(context);
process.exit((e as any).code ?? 1);
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/testing/src/lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const defaultContextOptions: Omit<
},
plugins: [],
sys: new MockSystem(),
executedStages: [],
};

export function createMockContext(
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ __metadata:
resolution: "@alcalzone/release-script-core@workspace:packages/core"
dependencies:
"@types/yargs": ^17.0.9
alcalzone-shared: ^4.0.1
execa: ^5.1.1
picocolors: 1.0.0
typescript: ~4.6.2
Expand Down