Skip to content

Commit f7b67ad

Browse files
committed
chore: ensure release starts from main branch
1 parent e4be5e9 commit f7b67ad

2 files changed

Lines changed: 106 additions & 2 deletions

File tree

automation/utils/bin/rui-prepare-release.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { bumpPackageJson, bumpXml, getNextVersion } from "../src/bump-version";
55
import { exec } from "../src/shell";
66
import { gh } from "../src/github";
77
import { printGithubAuthHelp } from "../src/cli-utils";
8-
import { printPkgInformation, selectPackageV2 } from "../src/prepare-release-helpers";
8+
import { printPkgInformation, selectPackageV2, ensureMainBranch } from "../src/prepare-release-helpers";
99

1010
async function main(): Promise<void> {
1111
try {
@@ -22,6 +22,9 @@ async function main(): Promise<void> {
2222
process.exit(1);
2323
}
2424

25+
// Check git branch: must be on main and in sync with origin/main
26+
await ensureMainBranch();
27+
2528
// Step 1: Initialize Jira client
2629
let jira: Jira | undefined;
2730
try {

automation/utils/src/prepare-release-helpers.ts

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
} from "./changelog-parser";
88
import { listPackages, PackageListing } from "./monorepo";
99
import chalk from "chalk";
10-
1110
import { prompt } from "enquirer";
11+
import { exec } from "./shell";
1212

1313
type WidgetPkg = {
1414
type: "widget";
@@ -123,6 +123,107 @@ function createPackagesTree(map: PackagesFullInfoMap, list: PackagesFullInfoList
123123
return tree;
124124
}
125125

126+
async function getCurrentBranch(): Promise<string> {
127+
const { stdout } = await exec("git rev-parse --abbrev-ref HEAD", { stdio: "pipe" });
128+
return stdout.trim();
129+
}
130+
131+
async function getRemoteSyncCounts(): Promise<{ behind: number; ahead: number }> {
132+
const [{ stdout: behindStr }, { stdout: aheadStr }] = await Promise.all([
133+
exec("git rev-list HEAD..origin/main --count", { stdio: "pipe" }),
134+
exec("git rev-list origin/main..HEAD --count", { stdio: "pipe" })
135+
]);
136+
return {
137+
behind: parseInt(behindStr.trim(), 10),
138+
ahead: parseInt(aheadStr.trim(), 10)
139+
};
140+
}
141+
142+
async function switchToMain(): Promise<void> {
143+
const { confirmSwitch } = await prompt<{ confirmSwitch: boolean }>({
144+
type: "confirm",
145+
name: "confirmSwitch",
146+
message: `❓ Switch to ${chalk.blue("main")} branch?`,
147+
initial: true
148+
});
149+
150+
if (!confirmSwitch) {
151+
console.log(chalk.red("❌ Release preparation must start from the main branch"));
152+
process.exit(1);
153+
}
154+
155+
await exec("git checkout main", { stdio: "pipe" });
156+
console.log(chalk.green("✅ Switched to main"));
157+
}
158+
159+
async function fastForwardMain(): Promise<void> {
160+
const { confirmFastForward } = await prompt<{ confirmFastForward: boolean }>({
161+
type: "confirm",
162+
name: "confirmFastForward",
163+
message: `❓ Fast-forward ${chalk.blue("main")} to ${chalk.blue("origin/main")}?`,
164+
initial: true
165+
});
166+
167+
if (!confirmFastForward) {
168+
console.log(chalk.yellow("⚠️ Continuing with an outdated main branch"));
169+
return;
170+
}
171+
172+
await exec("git merge --ff-only origin/main", { stdio: "pipe" });
173+
console.log(chalk.green("✅ main fast-forwarded to origin/main"));
174+
}
175+
176+
export async function ensureMainBranch(): Promise<void> {
177+
const branch = await getCurrentBranch();
178+
179+
if (branch !== "main") {
180+
console.log(chalk.yellow(`⚠️ Current branch is ${chalk.blue(branch)}, expected ${chalk.blue("main")}`));
181+
await switchToMain();
182+
}
183+
184+
console.log(chalk.blue("🔄 Fetching origin/main..."));
185+
await exec("git fetch origin main", { stdio: "pipe" });
186+
187+
const { behind, ahead } = await getRemoteSyncCounts();
188+
189+
if (behind === 0 && ahead === 0) {
190+
console.log(chalk.green("✅ main is up to date with origin/main"));
191+
return;
192+
}
193+
194+
if (behind > 0 && ahead === 0) {
195+
console.log(chalk.yellow(`⚠️ main is ${behind} commit(s) behind origin/main`));
196+
await fastForwardMain();
197+
return;
198+
}
199+
200+
if (ahead > 0 && behind === 0) {
201+
console.log(
202+
chalk.yellow(`⚠️ main is ${ahead} commit(s) ahead of origin/main (unpushed local commits detected)`)
203+
);
204+
console.log(chalk.yellow(" Proceeding, but consider pushing or resetting before releasing."));
205+
return;
206+
}
207+
208+
// Truly diverged: both sides have unique commits
209+
console.log(chalk.red(`❌ main has diverged from origin/main: ${ahead} ahead, ${behind} behind`));
210+
console.log(chalk.red(" You may need to reset or rebase before creating a release."));
211+
212+
const { continueAnyway } = await prompt<{ continueAnyway: boolean }>({
213+
type: "confirm",
214+
name: "continueAnyway",
215+
message: "❓ Continue anyway? (not recommended)",
216+
initial: false
217+
});
218+
219+
if (!continueAnyway) {
220+
console.log(chalk.red("❌ Release preparation canceled"));
221+
process.exit(1);
222+
}
223+
224+
console.log(chalk.yellow("⚠️ Continuing with a diverged main branch"));
225+
}
226+
126227
export async function selectPackageV2(): Promise<WidgetPkg | ModulePkg> {
127228
const pkgs = await listPackages(['"*"', '"!web-widgets"']);
128229
const pkgsList = await loadPackagesFullInfo(pkgs);

0 commit comments

Comments
 (0)