@ucdjs/release-scripts 0.1.0-beta.51 → 0.1.0-beta.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +197 -44
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import farver from "farver";
|
|
|
8
8
|
import mri from "mri";
|
|
9
9
|
import { exec } from "tinyexec";
|
|
10
10
|
import { dedent } from "@luxass/utils";
|
|
11
|
-
import semver, {
|
|
11
|
+
import semver, { gt } from "semver";
|
|
12
12
|
import prompts from "prompts";
|
|
13
13
|
|
|
14
14
|
//#region src/operations/changelog-format.ts
|
|
@@ -524,7 +524,8 @@ function normalizeReleaseScriptsOptions(options) {
|
|
|
524
524
|
changelog: {
|
|
525
525
|
enabled: changelog.enabled ?? true,
|
|
526
526
|
template: changelog.template ?? DEFAULT_CHANGELOG_TEMPLATE,
|
|
527
|
-
emojis: changelog.emojis ?? true
|
|
527
|
+
emojis: changelog.emojis ?? true,
|
|
528
|
+
combinePrereleaseIntoFirstStable: changelog.combinePrereleaseIntoFirstStable ?? false
|
|
528
529
|
},
|
|
529
530
|
types: types ? {
|
|
530
531
|
...DEFAULT_TYPES,
|
|
@@ -740,6 +741,42 @@ async function commitChanges(message, workspaceRoot) {
|
|
|
740
741
|
return err(gitError);
|
|
741
742
|
}
|
|
742
743
|
}
|
|
744
|
+
async function commitPaths(paths, message, workspaceRoot) {
|
|
745
|
+
try {
|
|
746
|
+
if (paths.length === 0) return ok(false);
|
|
747
|
+
await run("git", [
|
|
748
|
+
"add",
|
|
749
|
+
"--",
|
|
750
|
+
...paths
|
|
751
|
+
], { nodeOptions: {
|
|
752
|
+
cwd: workspaceRoot,
|
|
753
|
+
stdio: "pipe"
|
|
754
|
+
} });
|
|
755
|
+
if ((await run("git", [
|
|
756
|
+
"diff",
|
|
757
|
+
"--cached",
|
|
758
|
+
"--name-only"
|
|
759
|
+
], { nodeOptions: {
|
|
760
|
+
cwd: workspaceRoot,
|
|
761
|
+
stdio: "pipe"
|
|
762
|
+
} })).stdout.trim() === "") return ok(false);
|
|
763
|
+
logger.info(`Committing changes: ${farver.dim(message)}`);
|
|
764
|
+
await runIfNotDry("git", [
|
|
765
|
+
"commit",
|
|
766
|
+
"-m",
|
|
767
|
+
message
|
|
768
|
+
], { nodeOptions: {
|
|
769
|
+
cwd: workspaceRoot,
|
|
770
|
+
stdio: "pipe"
|
|
771
|
+
} });
|
|
772
|
+
return ok(true);
|
|
773
|
+
} catch (error) {
|
|
774
|
+
const gitError = toGitError("commitPaths", error);
|
|
775
|
+
logger.error(`Git commit failed: ${gitError.message}`);
|
|
776
|
+
if (gitError.stderr) logger.error(`Git stderr: ${gitError.stderr}`);
|
|
777
|
+
return err(gitError);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
743
780
|
async function pushBranch(branch, workspaceRoot, options) {
|
|
744
781
|
try {
|
|
745
782
|
const args = [
|
|
@@ -804,6 +841,28 @@ async function getMostRecentPackageTag(workspaceRoot, packageName) {
|
|
|
804
841
|
return err(toGitError("getMostRecentPackageTag", error));
|
|
805
842
|
}
|
|
806
843
|
}
|
|
844
|
+
async function getMostRecentPackageStableTag(workspaceRoot, packageName) {
|
|
845
|
+
try {
|
|
846
|
+
const { stdout } = await run("git", [
|
|
847
|
+
"tag",
|
|
848
|
+
"--list",
|
|
849
|
+
`${packageName}@*`
|
|
850
|
+
], { nodeOptions: {
|
|
851
|
+
cwd: workspaceRoot,
|
|
852
|
+
stdio: "pipe"
|
|
853
|
+
} });
|
|
854
|
+
const tags = stdout.split("\n").map((tag) => tag.trim()).filter(Boolean).reverse();
|
|
855
|
+
for (const tag of tags) {
|
|
856
|
+
const atIndex = tag.lastIndexOf("@");
|
|
857
|
+
if (atIndex === -1) continue;
|
|
858
|
+
const version = tag.slice(atIndex + 1);
|
|
859
|
+
if (semver.valid(version) && semver.prerelease(version) == null) return ok(tag);
|
|
860
|
+
}
|
|
861
|
+
return ok(void 0);
|
|
862
|
+
} catch (error) {
|
|
863
|
+
return err(toGitError("getMostRecentPackageStableTag", error));
|
|
864
|
+
}
|
|
865
|
+
}
|
|
807
866
|
/**
|
|
808
867
|
* Builds a mapping of commit SHAs to the list of files changed in each commit
|
|
809
868
|
* within a given inclusive range.
|
|
@@ -1120,6 +1179,7 @@ async function selectVersionPrompt(workspaceRoot, pkg, currentVersion, suggested
|
|
|
1120
1179
|
const prePatchAlpha = getNextPrereleaseVersion(currentVersion, "prepatch", "alpha");
|
|
1121
1180
|
const preMinorAlpha = getNextPrereleaseVersion(currentVersion, "preminor", "alpha");
|
|
1122
1181
|
const preMajorAlpha = getNextPrereleaseVersion(currentVersion, "premajor", "alpha");
|
|
1182
|
+
const isCurrentPrerelease = prereleaseIdentifier != null;
|
|
1123
1183
|
const choices = [
|
|
1124
1184
|
{
|
|
1125
1185
|
value: "skip",
|
|
@@ -1133,6 +1193,10 @@ async function selectVersionPrompt(workspaceRoot, pkg, currentVersion, suggested
|
|
|
1133
1193
|
value: "as-is",
|
|
1134
1194
|
title: `as-is ${farver.dim("(keep current version)")}`
|
|
1135
1195
|
},
|
|
1196
|
+
...isCurrentPrerelease ? [{
|
|
1197
|
+
value: "next-prerelease",
|
|
1198
|
+
title: `next prerelease ${farver.bold(nextDefaultPrerelease)}`
|
|
1199
|
+
}] : [],
|
|
1136
1200
|
{
|
|
1137
1201
|
value: "patch",
|
|
1138
1202
|
title: `patch ${farver.bold(getNextVersion(pkg.version, "patch"))}`
|
|
@@ -1157,6 +1221,7 @@ async function selectVersionPrompt(workspaceRoot, pkg, currentVersion, suggested
|
|
|
1157
1221
|
const initialValue = defaultChoice === "auto" ? suggestedVersion === currentVersion ? "skip" : "suggested" : defaultChoice;
|
|
1158
1222
|
const initial = Math.max(0, choices.findIndex((choice) => choice.value === initialValue));
|
|
1159
1223
|
const prereleaseVersionByChoice = {
|
|
1224
|
+
"next-prerelease": nextDefaultPrerelease,
|
|
1160
1225
|
"next": nextDefaultPrerelease,
|
|
1161
1226
|
"next-beta": nextBeta,
|
|
1162
1227
|
"next-alpha": nextAlpha,
|
|
@@ -1410,6 +1475,16 @@ async function getWorkspacePackageGroupedCommits(workspaceRoot, packages) {
|
|
|
1410
1475
|
for (const { pkgName, commits } of results) changedPackages.set(pkgName, commits);
|
|
1411
1476
|
return changedPackages;
|
|
1412
1477
|
}
|
|
1478
|
+
async function getPackageCommitsSinceTag(workspaceRoot, pkg, fromTag) {
|
|
1479
|
+
const allCommits = await getCommits({
|
|
1480
|
+
from: fromTag,
|
|
1481
|
+
to: "HEAD",
|
|
1482
|
+
cwd: workspaceRoot,
|
|
1483
|
+
folder: pkg.path
|
|
1484
|
+
});
|
|
1485
|
+
logger.verbose(`Found ${farver.cyan(allCommits.length)} commits for package ${farver.bold(pkg.name)} since ${farver.cyan(fromTag ?? "start")}`);
|
|
1486
|
+
return allCommits;
|
|
1487
|
+
}
|
|
1413
1488
|
/**
|
|
1414
1489
|
* Check if a file path touches any package folder.
|
|
1415
1490
|
* @param file - The file path to check
|
|
@@ -2052,25 +2127,13 @@ async function prepareWorkflow(options) {
|
|
|
2052
2127
|
logger.error("Failed to write version overrides file:", e);
|
|
2053
2128
|
}
|
|
2054
2129
|
} else if (Object.keys(newOverrides).length > 0) logger.step("Version overrides unchanged. Skipping write.");
|
|
2055
|
-
if (Object.keys(newOverrides).length === 0 &&
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
break;
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
if (shouldRemoveOverrides) {
|
|
2067
|
-
logger.info("Removing obsolete version overrides file...");
|
|
2068
|
-
try {
|
|
2069
|
-
await rm(overridesPath);
|
|
2070
|
-
logger.success("Successfully removed obsolete version overrides file.");
|
|
2071
|
-
} catch (e) {
|
|
2072
|
-
logger.error("Failed to remove obsolete version overrides file:", e);
|
|
2073
|
-
}
|
|
2130
|
+
if (Object.keys(newOverrides).length === 0 && hasOverrideChanges) {
|
|
2131
|
+
logger.info("Removing obsolete version overrides file...");
|
|
2132
|
+
try {
|
|
2133
|
+
await rm(overridesPath);
|
|
2134
|
+
logger.success("Successfully removed obsolete version overrides file.");
|
|
2135
|
+
} catch (e) {
|
|
2136
|
+
if (formatUnknownError(e).code !== "ENOENT") logger.error("Failed to remove obsolete version overrides file:", e);
|
|
2074
2137
|
}
|
|
2075
2138
|
}
|
|
2076
2139
|
if (allUpdates.filter((u) => u.hasDirectChanges).length === 0) logger.warn("No packages have changes requiring a release");
|
|
@@ -2086,26 +2149,44 @@ async function prepareWorkflow(options) {
|
|
|
2086
2149
|
const groupedPackageCommits = await getWorkspacePackageGroupedCommits(options.workspaceRoot, workspacePackages);
|
|
2087
2150
|
const globalCommitsPerPackage = await getGlobalCommitsPerPackage(options.workspaceRoot, groupedPackageCommits, workspacePackages, options.globalCommitMode === "none" ? false : options.globalCommitMode);
|
|
2088
2151
|
const changelogPromises = allUpdates.map((update) => {
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2152
|
+
return (async () => {
|
|
2153
|
+
let pkgCommits = groupedPackageCommits.get(update.package.name) || [];
|
|
2154
|
+
let globalCommits = globalCommitsPerPackage.get(update.package.name) || [];
|
|
2155
|
+
let previousVersionForChangelog = update.currentVersion !== "0.0.0" ? update.currentVersion : void 0;
|
|
2156
|
+
if (options.changelog.combinePrereleaseIntoFirstStable && semver.prerelease(update.currentVersion) != null && semver.prerelease(update.newVersion) == null) {
|
|
2157
|
+
const stableTagResult = await getMostRecentPackageStableTag(options.workspaceRoot, update.package.name);
|
|
2158
|
+
if (!stableTagResult.ok) logger.warn(`Failed to resolve stable tag for ${update.package.name}: ${stableTagResult.error.message}`);
|
|
2159
|
+
else {
|
|
2160
|
+
const stableTag = stableTagResult.value;
|
|
2161
|
+
if (stableTag) {
|
|
2162
|
+
logger.verbose(`Combining prerelease changelog entries into stable release for ${update.package.name} using base tag ${stableTag}`);
|
|
2163
|
+
const stableBaseCommits = await getPackageCommitsSinceTag(options.workspaceRoot, update.package, stableTag);
|
|
2164
|
+
pkgCommits = stableBaseCommits;
|
|
2165
|
+
globalCommits = (await getGlobalCommitsPerPackage(options.workspaceRoot, new Map([[update.package.name, stableBaseCommits]]), workspacePackages, options.globalCommitMode === "none" ? false : options.globalCommitMode)).get(update.package.name) || [];
|
|
2166
|
+
const atIndex = stableTag.lastIndexOf("@");
|
|
2167
|
+
if (atIndex !== -1) previousVersionForChangelog = stableTag.slice(atIndex + 1);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
const allCommits = [...pkgCommits, ...globalCommits];
|
|
2172
|
+
if (allCommits.length === 0) {
|
|
2173
|
+
logger.verbose(`No commits for ${update.package.name}, skipping changelog`);
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
logger.verbose(`Updating changelog for ${farver.cyan(update.package.name)}`);
|
|
2177
|
+
await updateChangelog({
|
|
2178
|
+
normalizedOptions: {
|
|
2179
|
+
...options,
|
|
2180
|
+
workspaceRoot: options.workspaceRoot
|
|
2181
|
+
},
|
|
2182
|
+
githubClient: options.githubClient,
|
|
2183
|
+
workspacePackage: update.package,
|
|
2184
|
+
version: update.newVersion,
|
|
2185
|
+
previousVersion: previousVersionForChangelog,
|
|
2186
|
+
commits: allCommits,
|
|
2187
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
2188
|
+
});
|
|
2189
|
+
})();
|
|
2109
2190
|
}).filter((p) => p != null);
|
|
2110
2191
|
const updates = await Promise.all(changelogPromises);
|
|
2111
2192
|
logger.success(`Updated ${updates.length} changelog(s)`);
|
|
@@ -2176,6 +2257,16 @@ function toNPMError(operation, error, code) {
|
|
|
2176
2257
|
status: formatted.status
|
|
2177
2258
|
};
|
|
2178
2259
|
}
|
|
2260
|
+
function classifyPublishErrorCode(error) {
|
|
2261
|
+
const formatted = formatUnknownError(error);
|
|
2262
|
+
const combined = [formatted.message, formatted.stderr].filter(Boolean).join("\n");
|
|
2263
|
+
if (combined.includes("E403") || combined.toLowerCase().includes("access token expired or revoked")) return "E403";
|
|
2264
|
+
if (combined.includes("EPUBLISHCONFLICT") || combined.includes("E409") || combined.includes("409 Conflict") || combined.includes("Failed to save packument")) return "EPUBLISHCONFLICT";
|
|
2265
|
+
if (combined.includes("EOTP")) return "EOTP";
|
|
2266
|
+
}
|
|
2267
|
+
function wait(ms) {
|
|
2268
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2269
|
+
}
|
|
2179
2270
|
/**
|
|
2180
2271
|
* Get the NPM registry URL
|
|
2181
2272
|
* Respects NPM_CONFIG_REGISTRY environment variable, defaults to npmjs.org
|
|
@@ -2247,7 +2338,13 @@ async function publishPackage(packageName, version, workspaceRoot, options) {
|
|
|
2247
2338
|
if (publishTag) args.push("--tag", publishTag);
|
|
2248
2339
|
const env = { ...process.env };
|
|
2249
2340
|
if (options.npm.provenance) env.NPM_CONFIG_PROVENANCE = "true";
|
|
2250
|
-
|
|
2341
|
+
const maxAttempts = 4;
|
|
2342
|
+
const backoffMs = [
|
|
2343
|
+
3e3,
|
|
2344
|
+
8e3,
|
|
2345
|
+
15e3
|
|
2346
|
+
];
|
|
2347
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
2251
2348
|
await runIfNotDry("pnpm", args, { nodeOptions: {
|
|
2252
2349
|
cwd: workspaceRoot,
|
|
2253
2350
|
stdio: "inherit",
|
|
@@ -2255,13 +2352,57 @@ async function publishPackage(packageName, version, workspaceRoot, options) {
|
|
|
2255
2352
|
} });
|
|
2256
2353
|
return ok(void 0);
|
|
2257
2354
|
} catch (error) {
|
|
2258
|
-
const
|
|
2259
|
-
|
|
2355
|
+
const code = classifyPublishErrorCode(error);
|
|
2356
|
+
if (code === "EPUBLISHCONFLICT" && attempt < maxAttempts) {
|
|
2357
|
+
const delay = backoffMs[attempt - 1] ?? backoffMs[backoffMs.length - 1];
|
|
2358
|
+
logger.warn(`Publish conflict for ${packageName}@${version} (attempt ${attempt}/${maxAttempts}). Retrying in ${Math.ceil(delay / 1e3)}s...`);
|
|
2359
|
+
await wait(delay);
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
return err(toNPMError("publishPackage", error, code));
|
|
2260
2363
|
}
|
|
2364
|
+
return err(toNPMError("publishPackage", /* @__PURE__ */ new Error(`Failed to publish ${packageName}@${version} after ${maxAttempts} attempts`), "EPUBLISHCONFLICT"));
|
|
2261
2365
|
}
|
|
2262
2366
|
|
|
2263
2367
|
//#endregion
|
|
2264
2368
|
//#region src/workflows/publish.ts
|
|
2369
|
+
async function cleanupPublishedOverrides(options, workspacePackages, publishedPackageNames) {
|
|
2370
|
+
if (publishedPackageNames.length === 0) return false;
|
|
2371
|
+
if (options.dryRun) {
|
|
2372
|
+
logger.verbose("Dry-run: skipping override cleanup");
|
|
2373
|
+
return false;
|
|
2374
|
+
}
|
|
2375
|
+
const overridesPath = join(options.workspaceRoot, ucdjsReleaseOverridesPath);
|
|
2376
|
+
let overrides;
|
|
2377
|
+
try {
|
|
2378
|
+
overrides = JSON.parse(await readFile(overridesPath, "utf-8"));
|
|
2379
|
+
} catch {
|
|
2380
|
+
return false;
|
|
2381
|
+
}
|
|
2382
|
+
const versionsByPackage = new Map(workspacePackages.map((pkg) => [pkg.name, pkg.version]));
|
|
2383
|
+
const publishedSet = new Set(publishedPackageNames);
|
|
2384
|
+
const removed = [];
|
|
2385
|
+
for (const [pkgName, override] of Object.entries(overrides)) {
|
|
2386
|
+
if (!publishedSet.has(pkgName)) continue;
|
|
2387
|
+
const currentVersion = versionsByPackage.get(pkgName);
|
|
2388
|
+
const current = currentVersion ? semver.valid(currentVersion) : null;
|
|
2389
|
+
const target = semver.valid(override.version);
|
|
2390
|
+
if (current && target && semver.gte(current, target)) {
|
|
2391
|
+
delete overrides[pkgName];
|
|
2392
|
+
removed.push(pkgName);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
if (removed.length === 0) return false;
|
|
2396
|
+
logger.step(`Cleaning up satisfied overrides (${removed.length})...`);
|
|
2397
|
+
if (Object.keys(overrides).length === 0) {
|
|
2398
|
+
await rm(overridesPath, { force: true });
|
|
2399
|
+
logger.success("Removed release override file (all entries satisfied)");
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
await writeFile(overridesPath, JSON.stringify(overrides, null, 2), "utf-8");
|
|
2403
|
+
logger.success(`Removed satisfied overrides: ${removed.join(", ")}`);
|
|
2404
|
+
return true;
|
|
2405
|
+
}
|
|
2265
2406
|
async function publishWorkflow(options) {
|
|
2266
2407
|
logger.section("📦 Publishing Packages");
|
|
2267
2408
|
const discovered = await discoverWorkspacePackages(options.workspaceRoot, options);
|
|
@@ -2346,6 +2487,18 @@ async function publishWorkflow(options) {
|
|
|
2346
2487
|
for (const pkg of status.failed) logger.item(` ${farver.red("•")} ${pkg}`);
|
|
2347
2488
|
}
|
|
2348
2489
|
if (status.failed.length > 0) exitWithError(`Publishing completed with ${status.failed.length} failure(s)`);
|
|
2490
|
+
if (await cleanupPublishedOverrides(options, workspacePackages, status.published) && !options.dryRun) {
|
|
2491
|
+
logger.step("Committing override cleanup...");
|
|
2492
|
+
const commitResult = await commitPaths([ucdjsReleaseOverridesPath], "chore: cleanup release overrides", options.workspaceRoot);
|
|
2493
|
+
if (!commitResult.ok) exitWithError("Failed to commit override cleanup.", void 0, commitResult.error);
|
|
2494
|
+
if (commitResult.value) {
|
|
2495
|
+
const currentBranch = await getCurrentBranch(options.workspaceRoot);
|
|
2496
|
+
if (!currentBranch.ok) exitWithError("Failed to detect current branch for override cleanup push.", void 0, currentBranch.error);
|
|
2497
|
+
const pushResult = await pushBranch(currentBranch.value, options.workspaceRoot);
|
|
2498
|
+
if (!pushResult.ok) exitWithError("Failed to push override cleanup commit.", void 0, pushResult.error);
|
|
2499
|
+
logger.success(`Pushed override cleanup commit to ${farver.cyan(currentBranch.value)}`);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2349
2502
|
logger.success("All packages published successfully!");
|
|
2350
2503
|
}
|
|
2351
2504
|
|