@ucdjs/release-scripts 0.1.0-beta.11 → 0.1.0-beta.12
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 +28 -0
- package/dist/index.mjs +186 -12
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "commit-parser";
|
|
2
|
+
|
|
1
3
|
//#region src/workspace.d.ts
|
|
2
4
|
interface WorkspacePackage {
|
|
3
5
|
name: string;
|
|
@@ -10,6 +12,7 @@ interface WorkspacePackage {
|
|
|
10
12
|
//#endregion
|
|
11
13
|
//#region src/types.d.ts
|
|
12
14
|
type BumpKind = "none" | "patch" | "minor" | "major";
|
|
15
|
+
type GlobalCommitMode = false | "dependencies" | "all";
|
|
13
16
|
interface SharedOptions {
|
|
14
17
|
/**
|
|
15
18
|
* Repository identifier (e.g., "owner/repo")
|
|
@@ -99,6 +102,26 @@ interface VersionUpdate {
|
|
|
99
102
|
interface PublishOptions extends SharedOptions {}
|
|
100
103
|
declare function publish(_options: PublishOptions): void;
|
|
101
104
|
//#endregion
|
|
105
|
+
//#region src/changelog.d.ts
|
|
106
|
+
interface ChangelogOptions {
|
|
107
|
+
/**
|
|
108
|
+
* Whether to generate changelogs
|
|
109
|
+
* @default false
|
|
110
|
+
*/
|
|
111
|
+
enabled?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Transform function to customize the changelog content
|
|
114
|
+
*/
|
|
115
|
+
transform?: (changelog: string, pkg: WorkspacePackage) => string | Promise<string>;
|
|
116
|
+
/**
|
|
117
|
+
* Repository information for generating links
|
|
118
|
+
*/
|
|
119
|
+
repository?: {
|
|
120
|
+
owner: string;
|
|
121
|
+
repo: string;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
102
125
|
//#region src/release.d.ts
|
|
103
126
|
interface ReleaseOptions extends SharedOptions {
|
|
104
127
|
branch?: {
|
|
@@ -139,6 +162,11 @@ interface ReleaseOptions extends SharedOptions {
|
|
|
139
162
|
*/
|
|
140
163
|
body?: string;
|
|
141
164
|
};
|
|
165
|
+
/**
|
|
166
|
+
* Changelog configuration
|
|
167
|
+
*/
|
|
168
|
+
changelog?: ChangelogOptions;
|
|
169
|
+
globalCommitMode?: GlobalCommitMode;
|
|
142
170
|
}
|
|
143
171
|
interface ReleaseResult {
|
|
144
172
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { t as Eta } from "./eta-Boh7yPZi.mjs";
|
|
2
2
|
import farver from "farver";
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
4
6
|
import process from "node:process";
|
|
5
7
|
import { exec } from "tinyexec";
|
|
8
|
+
import { getCommits } from "commit-parser";
|
|
6
9
|
import { dedent } from "@luxass/utils";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
9
10
|
import prompts from "prompts";
|
|
10
11
|
|
|
11
12
|
//#region src/publish.ts
|
|
@@ -77,6 +78,114 @@ function normalizeSharedOptions(options) {
|
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/changelog.ts
|
|
83
|
+
/**
|
|
84
|
+
* Get section label for commit type
|
|
85
|
+
*/
|
|
86
|
+
function getSectionLabel(type) {
|
|
87
|
+
return {
|
|
88
|
+
feat: "Features",
|
|
89
|
+
fix: "Bug Fixes",
|
|
90
|
+
docs: "Documentation",
|
|
91
|
+
style: "Styles",
|
|
92
|
+
refactor: "Code Refactoring",
|
|
93
|
+
perf: "Performance Improvements",
|
|
94
|
+
test: "Tests",
|
|
95
|
+
build: "Build System",
|
|
96
|
+
ci: "Continuous Integration",
|
|
97
|
+
chore: "Miscellaneous Chores",
|
|
98
|
+
revert: "Reverts"
|
|
99
|
+
}[type] || "Other Changes";
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Generate changelog content from commits
|
|
103
|
+
*/
|
|
104
|
+
function generateChangelog(pkg, newVersion, commits, previousVersion, repository) {
|
|
105
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
106
|
+
let versionHeader = `## `;
|
|
107
|
+
if (repository && previousVersion) {
|
|
108
|
+
const compareUrl = `https://github.com/${repository.owner}/${repository.repo}/compare/${pkg.name}@${previousVersion}...${pkg.name}@${newVersion}`;
|
|
109
|
+
versionHeader += `[${newVersion}](${compareUrl})`;
|
|
110
|
+
} else versionHeader += newVersion;
|
|
111
|
+
versionHeader += ` (${date})\n\n`;
|
|
112
|
+
let changelog = versionHeader;
|
|
113
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
114
|
+
for (const commit of commits) {
|
|
115
|
+
if (!commit.isConventional || !commit.type) continue;
|
|
116
|
+
const type = commit.type;
|
|
117
|
+
if (!grouped.has(type)) grouped.set(type, []);
|
|
118
|
+
grouped.get(type).push(commit);
|
|
119
|
+
}
|
|
120
|
+
for (const type of [
|
|
121
|
+
"feat",
|
|
122
|
+
"fix",
|
|
123
|
+
"perf",
|
|
124
|
+
"refactor",
|
|
125
|
+
"docs",
|
|
126
|
+
"test",
|
|
127
|
+
"build",
|
|
128
|
+
"ci",
|
|
129
|
+
"chore",
|
|
130
|
+
"revert",
|
|
131
|
+
"style"
|
|
132
|
+
]) {
|
|
133
|
+
const commits$1 = grouped.get(type);
|
|
134
|
+
if (!commits$1 || commits$1.length === 0) continue;
|
|
135
|
+
const label = getSectionLabel(type);
|
|
136
|
+
changelog += `### ${label}\n\n`;
|
|
137
|
+
for (const commit of commits$1) {
|
|
138
|
+
const scope = commit.scope ? `**${commit.scope}:** ` : "";
|
|
139
|
+
const breaking = commit.isBreaking ? " **BREAKING CHANGE**" : "";
|
|
140
|
+
let entry = `* ${scope}${commit.description}${breaking}`;
|
|
141
|
+
if (repository) {
|
|
142
|
+
const commitUrl = `https://github.com/${repository.owner}/${repository.repo}/commit/${commit.shortHash}`;
|
|
143
|
+
entry += ` ([${commit.shortHash}](${commitUrl}))`;
|
|
144
|
+
} else entry += ` (${commit.shortHash})`;
|
|
145
|
+
changelog += `${entry}\n`;
|
|
146
|
+
}
|
|
147
|
+
changelog += "\n";
|
|
148
|
+
}
|
|
149
|
+
return changelog.trim();
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Write changelog to package's CHANGELOG.md file
|
|
153
|
+
*/
|
|
154
|
+
async function writeChangelog(pkg, newContent, version) {
|
|
155
|
+
const changelogPath = join(pkg.path, "CHANGELOG.md");
|
|
156
|
+
let existingContent = "";
|
|
157
|
+
if (existsSync(changelogPath)) existingContent = await readFile(changelogPath, "utf-8");
|
|
158
|
+
let updatedContent;
|
|
159
|
+
if (existingContent) {
|
|
160
|
+
const withoutTitle = existingContent.replace(/^# Changelog\n\n/, "");
|
|
161
|
+
if (new RegExp(`^## ${version.replace(/\./g, "\\.")}(\\s|$)`, "m").test(withoutTitle)) {
|
|
162
|
+
const versionSectionRegex = new RegExp(`^## ${version.replace(/\./g, "\\.")}[\\s\\S]*?(?=^## |$)`, "m");
|
|
163
|
+
updatedContent = `# Changelog\n\n${withoutTitle.replace(versionSectionRegex, `${newContent}\n\n`)}`;
|
|
164
|
+
} else updatedContent = `# Changelog\n\n${newContent}\n\n${withoutTitle}`;
|
|
165
|
+
} else updatedContent = `# Changelog\n\n${newContent}\n`;
|
|
166
|
+
await writeFile(changelogPath, updatedContent, "utf-8");
|
|
167
|
+
logger.log(`Updated changelog: ${changelogPath}`);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Generate and write changelogs for all updated packages
|
|
171
|
+
*/
|
|
172
|
+
async function updateChangelogs(updates, packageCommits, options) {
|
|
173
|
+
if (!options?.enabled) {
|
|
174
|
+
logger.log("Changelog generation is disabled");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
logger.log("Generating changelogs...");
|
|
178
|
+
for (const update of updates) {
|
|
179
|
+
if (!update.hasDirectChanges) continue;
|
|
180
|
+
const commits = packageCommits.get(update.package.name) || [];
|
|
181
|
+
if (commits.length === 0) continue;
|
|
182
|
+
let changelog = generateChangelog(update.package, update.newVersion, commits, update.currentVersion, options.repository);
|
|
183
|
+
if (options.transform) changelog = await options.transform(changelog, update.package);
|
|
184
|
+
logger.info(`Generating changelog for package ${update.package.name}`);
|
|
185
|
+
await writeChangelog(update.package, changelog, update.newVersion);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
80
189
|
//#endregion
|
|
81
190
|
//#region src/commits.ts
|
|
82
191
|
async function getLastPackageTag(packageName, workspaceRoot) {
|
|
@@ -140,6 +249,58 @@ async function getWorkspacePackageCommits(workspaceRoot, packages) {
|
|
|
140
249
|
for (const { pkgName, commits } of results) changedPackages.set(pkgName, commits);
|
|
141
250
|
return changedPackages;
|
|
142
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Get all commits for the workspace (not filtered by package)
|
|
254
|
+
*/
|
|
255
|
+
async function getAllWorkspaceCommits(workspaceRoot, lastTag) {
|
|
256
|
+
return getCommits({
|
|
257
|
+
from: lastTag,
|
|
258
|
+
to: "HEAD",
|
|
259
|
+
cwd: workspaceRoot
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get files changed in a specific commit
|
|
264
|
+
*/
|
|
265
|
+
async function getFilesChangedInCommit(commitHash, workspaceRoot) {
|
|
266
|
+
try {
|
|
267
|
+
const { stdout } = await run("git", [
|
|
268
|
+
"diff-tree",
|
|
269
|
+
"--no-commit-id",
|
|
270
|
+
"--name-only",
|
|
271
|
+
"-r",
|
|
272
|
+
commitHash
|
|
273
|
+
], { nodeOptions: {
|
|
274
|
+
cwd: workspaceRoot,
|
|
275
|
+
stdio: "pipe"
|
|
276
|
+
} });
|
|
277
|
+
return stdout.split("\n").map((file) => file.trim()).filter(Boolean);
|
|
278
|
+
} catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Filter and combine package commits with global commits
|
|
284
|
+
*/
|
|
285
|
+
function combineWithGlobalCommits(workspaceRoot, packageCommits, allCommits, mode) {
|
|
286
|
+
if (!mode) return packageCommits;
|
|
287
|
+
const packageCommitShas = new Set(packageCommits.map((c) => c.shortHash));
|
|
288
|
+
const globalCommits = allCommits.filter((c) => !packageCommitShas.has(c.shortHash));
|
|
289
|
+
if (mode === "all") return [...packageCommits, ...globalCommits];
|
|
290
|
+
if (mode === "dependencies") {
|
|
291
|
+
const dependencyCommits = globalCommits.filter(async (c) => {
|
|
292
|
+
const affectedFiles = await getFilesChangedInCommit(c.shortHash, workspaceRoot);
|
|
293
|
+
if (affectedFiles == null) return false;
|
|
294
|
+
return affectedFiles.some((file) => [
|
|
295
|
+
"package.json",
|
|
296
|
+
"pnpm-lock.yaml",
|
|
297
|
+
"pnpm-workspace.yaml"
|
|
298
|
+
].includes(file));
|
|
299
|
+
});
|
|
300
|
+
return [...packageCommits, ...dependencyCommits];
|
|
301
|
+
}
|
|
302
|
+
return packageCommits;
|
|
303
|
+
}
|
|
143
304
|
function determineBumpType(commit) {
|
|
144
305
|
if (commit.isBreaking) return "major";
|
|
145
306
|
if (!commit.isConventional || !commit.type) return "none";
|
|
@@ -599,19 +760,15 @@ function createVersionUpdate(pkg, bump, hasDirectChanges) {
|
|
|
599
760
|
/**
|
|
600
761
|
* Infer version updates from package commits with optional interactive overrides
|
|
601
762
|
*
|
|
602
|
-
* @param workspacePackages - All workspace packages
|
|
603
|
-
* @param packageCommits - Map of package names to their commits
|
|
604
|
-
* @param workspaceRoot - Root directory for prompts
|
|
605
|
-
* @param showPrompt - Whether to show prompts for version overrides
|
|
606
763
|
* @returns Version updates for packages with changes
|
|
607
764
|
*/
|
|
608
|
-
async function inferVersionUpdates(workspacePackages, packageCommits, workspaceRoot, showPrompt) {
|
|
765
|
+
async function inferVersionUpdates({ workspacePackages, packageCommits, allCommits, workspaceRoot, showPrompt, globalCommitMode }) {
|
|
609
766
|
const versionUpdates = [];
|
|
610
|
-
for (const [pkgName,
|
|
611
|
-
if (
|
|
767
|
+
for (const [pkgName, pkgCommits] of packageCommits) {
|
|
768
|
+
if (pkgCommits.length === 0) continue;
|
|
612
769
|
const pkg = workspacePackages.find((p) => p.name === pkgName);
|
|
613
770
|
if (!pkg) continue;
|
|
614
|
-
const bump = determineHighestBump(
|
|
771
|
+
const bump = determineHighestBump(combineWithGlobalCommits(workspaceRoot, pkgCommits, allCommits, globalCommitMode));
|
|
615
772
|
if (bump === "none") {
|
|
616
773
|
logger.info(`No version bump needed for package ${pkg.name}`);
|
|
617
774
|
continue;
|
|
@@ -841,6 +998,8 @@ async function release(options) {
|
|
|
841
998
|
normalizedOptions.branch.release ??= "release/next";
|
|
842
999
|
normalizedOptions.branch.default = await getDefaultBranch();
|
|
843
1000
|
normalizedOptions.safeguards ??= true;
|
|
1001
|
+
normalizedOptions.changelog ??= { enabled: true };
|
|
1002
|
+
normalizedOptions.globalCommitMode ??= "dependencies";
|
|
844
1003
|
globalOptions.dryRun = normalizedOptions.dryRun;
|
|
845
1004
|
const workspaceRoot = normalizedOptions.workspaceRoot;
|
|
846
1005
|
if (normalizedOptions.safeguards && !await isWorkingDirectoryClean(workspaceRoot)) exitWithError("Working directory is not clean. Please commit or stash your changes before proceeding.");
|
|
@@ -849,7 +1008,15 @@ async function release(options) {
|
|
|
849
1008
|
logger.log("No packages found to release.");
|
|
850
1009
|
return null;
|
|
851
1010
|
}
|
|
852
|
-
const
|
|
1011
|
+
const packageCommits = await getWorkspacePackageCommits(workspaceRoot, workspacePackages);
|
|
1012
|
+
const versionUpdates = await inferVersionUpdates({
|
|
1013
|
+
workspacePackages,
|
|
1014
|
+
packageCommits,
|
|
1015
|
+
workspaceRoot,
|
|
1016
|
+
showPrompt: options.prompts?.versions !== false,
|
|
1017
|
+
allCommits: await getAllWorkspaceCommits(workspaceRoot),
|
|
1018
|
+
globalCommitMode: options.globalCommitMode
|
|
1019
|
+
});
|
|
853
1020
|
if (versionUpdates.length === 0) logger.warn("No packages have changes requiring a release");
|
|
854
1021
|
const allUpdates = createDependentUpdates(buildPackageDependencyGraph(workspacePackages), workspacePackages, versionUpdates);
|
|
855
1022
|
const currentBranch = await getCurrentBranch(workspaceRoot);
|
|
@@ -876,6 +1043,13 @@ async function release(options) {
|
|
|
876
1043
|
logger.log("Rebasing release branch onto", normalizedOptions.branch.default);
|
|
877
1044
|
await rebaseBranch(normalizedOptions.branch.default, workspaceRoot);
|
|
878
1045
|
await updateAllPackageJsonFiles(allUpdates);
|
|
1046
|
+
await updateChangelogs(versionUpdates, packageCommits, {
|
|
1047
|
+
...options.changelog,
|
|
1048
|
+
repository: options.changelog?.repository || {
|
|
1049
|
+
owner: normalizedOptions.owner,
|
|
1050
|
+
repo: normalizedOptions.repo
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
879
1053
|
const hasCommitted = await commitChanges("chore: update release versions", workspaceRoot);
|
|
880
1054
|
const isBranchAhead = await isBranchAheadOfRemote(normalizedOptions.branch.release, workspaceRoot);
|
|
881
1055
|
if (!hasCommitted && !isBranchAhead) {
|