Skip to content

Commit b24d9b7

Browse files
committed
chore(scripts): auto-determine if latest version & add confirmation
1 parent bfd8a68 commit b24d9b7

File tree

1 file changed

+109
-2
lines changed

1 file changed

+109
-2
lines changed

scripts/release/main.ts

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,64 @@ async function getCurrentVersion(): Promise<string> {
6060
return packageJson.version;
6161
}
6262

63+
async function getLatestGitVersion(): Promise<string | null> {
64+
try {
65+
// Fetch tags to ensure we have the latest
66+
// Use --force to overwrite local tags that conflict with remote
67+
await $`git fetch --tags --force --quiet`;
68+
69+
// Get all version tags
70+
const result = await $`git tag -l "v*"`;
71+
const tags = result.stdout.trim().split("\n").filter(Boolean);
72+
73+
if (tags.length === 0) {
74+
return null;
75+
}
76+
77+
// Parse and find the latest version (excluding prereleases)
78+
const versions = tags
79+
.map(tag => tag.replace(/^v/, ""))
80+
.filter(v => semver.valid(v))
81+
.sort((a, b) => semver.rcompare(a, b));
82+
83+
return versions[0] || null;
84+
} catch (error) {
85+
console.warn("Warning: Could not fetch git tags:", error);
86+
return null;
87+
}
88+
}
89+
90+
async function shouldTagAsLatest(newVersion: string): Promise<boolean> {
91+
// Check if version has prerelease identifier (e.g., 1.0.0-rc.1)
92+
const parsedVersion = semver.parse(newVersion);
93+
if (!parsedVersion) {
94+
throw new Error(`Invalid semantic version: ${newVersion}`);
95+
}
96+
97+
// If it has a prerelease identifier, it's not latest
98+
if (parsedVersion.prerelease.length > 0) {
99+
return false;
100+
}
101+
102+
// Get the latest version from git tags
103+
const latestGitVersion = await getLatestGitVersion();
104+
105+
// If no previous versions exist, this is the latest
106+
if (!latestGitVersion) {
107+
return true;
108+
}
109+
110+
// Check if new version is greater than the latest git version
111+
return semver.gt(newVersion, latestGitVersion);
112+
}
113+
63114
async function validateReuseVersion(version: string): Promise<void> {
64115
console.log(`Validating that version ${version} exists...`);
65116

66117
// Fetch tags to ensure we have the latest
118+
// Use --force to overwrite local tags that conflict with remote
67119
console.log(`Fetching tags...`);
68-
await $({ stdio: "inherit" })`git fetch --tags`;
120+
await $({ stdio: "inherit" })`git fetch --tags --force`;
69121

70122
// Get short commit from version tag
71123
let shortCommit: string;
@@ -195,6 +247,7 @@ async function getVersionFromArgs(opts: {
195247

196248
// Available steps
197249
const STEPS = [
250+
"confirm-release",
198251
"update-version",
199252
"generate-fern",
200253
"git-commit",
@@ -224,6 +277,7 @@ const PHASE_MAP: Record<Phase, Step[]> = {
224277
// These steps modify the source code, so they need to be ran & committed
225278
// locally. CI cannot push commits.
226279
"setup-local": [
280+
"confirm-release",
227281
"update-version",
228282
"generate-fern",
229283
"git-commit",
@@ -331,6 +385,18 @@ async function main() {
331385
"version must be a valid semantic version",
332386
);
333387

388+
// Automatically determine if this should be tagged as latest
389+
// Can be overridden by --latest or --no-latest flags
390+
let isLatest: boolean;
391+
if (opts.latest !== undefined) {
392+
// User explicitly set the flag
393+
isLatest = opts.latest;
394+
} else {
395+
// Auto-determine based on version
396+
isLatest = await shouldTagAsLatest(version);
397+
console.log(`Auto-determined latest flag: ${isLatest} (version: ${version})`);
398+
}
399+
334400
// Setup opts
335401
let commit: string;
336402
if (opts.overrideCommit) {
@@ -345,7 +411,7 @@ async function main() {
345411
const releaseOpts: ReleaseOpts = {
346412
root: ROOT_DIR,
347413
version: version,
348-
latest: opts.latest,
414+
latest: isLatest,
349415
commit,
350416
reuseEngineVersion: opts.reuseEngineVersion,
351417
};
@@ -361,6 +427,47 @@ async function main() {
361427
await validateGit(releaseOpts);
362428
}
363429

430+
if (shouldRunStep("confirm-release")) {
431+
console.log("==> Release Confirmation");
432+
console.log(`\nRelease Details:`);
433+
console.log(` Version: ${releaseOpts.version}`);
434+
console.log(` Latest: ${releaseOpts.latest}`);
435+
console.log(` Commit: ${releaseOpts.commit}`);
436+
if (releaseOpts.reuseEngineVersion) {
437+
console.log(` Reusing engine version: ${releaseOpts.reuseEngineVersion}`);
438+
}
439+
440+
// Get current branch
441+
const branchResult = await $`git rev-parse --abbrev-ref HEAD`;
442+
const branch = branchResult.stdout.trim();
443+
console.log(` Branch: ${branch}`);
444+
445+
// Get latest git version for context
446+
const latestGitVersion = await getLatestGitVersion();
447+
if (latestGitVersion) {
448+
console.log(` Current latest version: ${latestGitVersion}`);
449+
}
450+
451+
// Prompt for confirmation
452+
const readline = await import("node:readline");
453+
const rl = readline.createInterface({
454+
input: process.stdin,
455+
output: process.stdout,
456+
});
457+
458+
const answer = await new Promise<string>((resolve) => {
459+
rl.question("\nProceed with release? (yes/no): ", resolve);
460+
});
461+
rl.close();
462+
463+
if (answer.toLowerCase() !== "yes" && answer.toLowerCase() !== "y") {
464+
console.log("Release cancelled");
465+
process.exit(0);
466+
}
467+
468+
console.log("✅ Release confirmed");
469+
}
470+
364471
if (shouldRunStep("update-version")) {
365472
console.log("==> Updating Version");
366473
await updateVersion(releaseOpts);

0 commit comments

Comments
 (0)