@ucdjs/release-scripts 0.1.0-beta.2 → 0.1.0-beta.4
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 +26 -8
- package/dist/index.mjs +400 -334
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
//#region src/workspace.d.ts
|
|
2
|
+
interface WorkspacePackage {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
path: string;
|
|
6
|
+
packageJson: PackageJson;
|
|
7
|
+
workspaceDependencies: string[];
|
|
8
|
+
workspaceDevDependencies: string[];
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
1
11
|
//#region src/types.d.ts
|
|
2
12
|
type BumpKind = "none" | "patch" | "minor" | "major";
|
|
3
13
|
interface PackageJson {
|
|
@@ -6,16 +16,9 @@ interface PackageJson {
|
|
|
6
16
|
dependencies?: Record<string, string>;
|
|
7
17
|
devDependencies?: Record<string, string>;
|
|
8
18
|
peerDependencies?: Record<string, string>;
|
|
19
|
+
private?: boolean;
|
|
9
20
|
[key: string]: unknown;
|
|
10
21
|
}
|
|
11
|
-
interface WorkspacePackage {
|
|
12
|
-
name: string;
|
|
13
|
-
version: string;
|
|
14
|
-
path: string;
|
|
15
|
-
packageJson: PackageJson;
|
|
16
|
-
workspaceDependencies: string[];
|
|
17
|
-
workspaceDevDependencies: string[];
|
|
18
|
-
}
|
|
19
22
|
interface FindWorkspacePackagesOptions {
|
|
20
23
|
/**
|
|
21
24
|
* Package names to exclude
|
|
@@ -99,6 +102,21 @@ interface ReleaseOptions {
|
|
|
99
102
|
* GitHub token for authentication
|
|
100
103
|
*/
|
|
101
104
|
githubToken: string;
|
|
105
|
+
pullRequest?: {
|
|
106
|
+
/**
|
|
107
|
+
* Title for the release pull request
|
|
108
|
+
*/
|
|
109
|
+
title?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Body for the release pull request
|
|
112
|
+
*
|
|
113
|
+
* If not provided, a default body will be generated.
|
|
114
|
+
*
|
|
115
|
+
* NOTE:
|
|
116
|
+
* You can use custom template expressions, see [h3js/rendu](https://github.com/h3js/rendu)
|
|
117
|
+
*/
|
|
118
|
+
body?: string;
|
|
119
|
+
};
|
|
102
120
|
}
|
|
103
121
|
interface ReleaseResult {
|
|
104
122
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { getCommits } from "commit-parser";
|
|
3
|
-
import createDebug from "debug";
|
|
4
3
|
import farver from "farver";
|
|
5
4
|
import { exec } from "tinyexec";
|
|
5
|
+
import { dedent } from "@luxass/utils";
|
|
6
|
+
import { Eta } from "eta";
|
|
6
7
|
import { readFile, writeFile } from "node:fs/promises";
|
|
7
8
|
import { join } from "node:path";
|
|
8
9
|
import prompts from "prompts";
|
|
9
10
|
|
|
10
|
-
//#region src/logger.ts
|
|
11
|
-
function createDebugger(namespace) {
|
|
12
|
-
const debug$2 = createDebug(namespace);
|
|
13
|
-
if (debug$2.enabled) return debug$2;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
//#endregion
|
|
17
11
|
//#region src/utils.ts
|
|
18
12
|
const globalOptions = { dryRun: false };
|
|
13
|
+
const isCI = typeof process.env.CI === "string" && process.env.CI !== "" && process.env.CI.toLowerCase() !== "false";
|
|
19
14
|
async function run(bin, args, opts = {}) {
|
|
20
15
|
return exec(bin, args, {
|
|
21
16
|
throwOnError: true,
|
|
@@ -33,7 +28,6 @@ const runIfNotDry = globalOptions.dryRun ? dryRun : run;
|
|
|
33
28
|
|
|
34
29
|
//#endregion
|
|
35
30
|
//#region src/commits.ts
|
|
36
|
-
const debug$1 = createDebugger("ucdjs:release-scripts:commits");
|
|
37
31
|
async function getLastPackageTag(packageName, workspaceRoot) {
|
|
38
32
|
const { stdout } = await run("git", ["tag", "--list"], { nodeOptions: {
|
|
39
33
|
cwd: workspaceRoot,
|
|
@@ -58,16 +52,31 @@ async function getPackageCommits(pkg, workspaceRoot) {
|
|
|
58
52
|
from: lastTag,
|
|
59
53
|
to: "HEAD"
|
|
60
54
|
});
|
|
61
|
-
|
|
55
|
+
console.log(`Found ${allCommits.length} commits for ${pkg.name} since ${lastTag || "beginning"}`);
|
|
62
56
|
const touchedCommitHashes = await getCommitsTouchingPackage(lastTag || "HEAD", "HEAD", pkg.path, workspaceRoot);
|
|
63
57
|
const touchedSet = new Set(touchedCommitHashes);
|
|
64
58
|
const packageCommits = allCommits.filter((commit) => touchedSet.has(commit.shortHash));
|
|
65
|
-
|
|
59
|
+
console.log(`${packageCommits.length} commits affect ${pkg.name}`);
|
|
66
60
|
return packageCommits;
|
|
67
61
|
}
|
|
68
62
|
async function analyzePackageCommits(pkg, workspaceRoot) {
|
|
69
63
|
return determineHighestBump(await getPackageCommits(pkg, workspaceRoot));
|
|
70
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Analyze commits for multiple packages to determine version bumps
|
|
67
|
+
*
|
|
68
|
+
* @param packages - Packages to analyze
|
|
69
|
+
* @param workspaceRoot - Root directory of the workspace
|
|
70
|
+
* @returns Map of package names to their bump types
|
|
71
|
+
*/
|
|
72
|
+
async function analyzeCommits(packages, workspaceRoot) {
|
|
73
|
+
const changedPackages = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const pkg of packages) {
|
|
75
|
+
const bump = await analyzePackageCommits(pkg, workspaceRoot);
|
|
76
|
+
if (bump !== "none") changedPackages.set(pkg.name, bump);
|
|
77
|
+
}
|
|
78
|
+
return changedPackages;
|
|
79
|
+
}
|
|
71
80
|
function determineBumpType(commit) {
|
|
72
81
|
if (commit.isBreaking) return "major";
|
|
73
82
|
if (!commit.isConventional || !commit.type) return "none";
|
|
@@ -100,135 +109,11 @@ async function getCommitsTouchingPackage(from, to, packagePath, workspaceRoot) {
|
|
|
100
109
|
} });
|
|
101
110
|
return stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
102
111
|
} catch (error) {
|
|
103
|
-
|
|
112
|
+
console.error(`Error getting commits touching package: ${error}`);
|
|
104
113
|
return [];
|
|
105
114
|
}
|
|
106
115
|
}
|
|
107
116
|
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/validation.ts
|
|
110
|
-
/**
|
|
111
|
-
* Validation utilities for release scripts
|
|
112
|
-
*/
|
|
113
|
-
function isValidSemver(version) {
|
|
114
|
-
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(version);
|
|
115
|
-
}
|
|
116
|
-
function validateSemver(version) {
|
|
117
|
-
if (!isValidSemver(version)) throw new Error(`Invalid semver version: ${version}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
//#endregion
|
|
121
|
-
//#region src/version.ts
|
|
122
|
-
/**
|
|
123
|
-
* Calculate the new version based on current version and bump type
|
|
124
|
-
* Pure function - no side effects, easily testable
|
|
125
|
-
*/
|
|
126
|
-
function calculateNewVersion(currentVersion, bump) {
|
|
127
|
-
if (bump === "none") return currentVersion;
|
|
128
|
-
validateSemver(currentVersion);
|
|
129
|
-
const match = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
130
|
-
if (!match) throw new Error(`Invalid semver version: ${currentVersion}`);
|
|
131
|
-
const [, major, minor, patch, suffix] = match;
|
|
132
|
-
let newMajor = Number.parseInt(major, 10);
|
|
133
|
-
let newMinor = Number.parseInt(minor, 10);
|
|
134
|
-
let newPatch = Number.parseInt(patch, 10);
|
|
135
|
-
switch (bump) {
|
|
136
|
-
case "major":
|
|
137
|
-
newMajor += 1;
|
|
138
|
-
newMinor = 0;
|
|
139
|
-
newPatch = 0;
|
|
140
|
-
break;
|
|
141
|
-
case "minor":
|
|
142
|
-
newMinor += 1;
|
|
143
|
-
newPatch = 0;
|
|
144
|
-
break;
|
|
145
|
-
case "patch":
|
|
146
|
-
newPatch += 1;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
return `${newMajor}.${newMinor}.${newPatch}`;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Create a version update object
|
|
153
|
-
*/
|
|
154
|
-
function createVersionUpdate(pkg, bump, hasDirectChanges) {
|
|
155
|
-
const newVersion = calculateNewVersion(pkg.version, bump);
|
|
156
|
-
return {
|
|
157
|
-
package: pkg,
|
|
158
|
-
currentVersion: pkg.version,
|
|
159
|
-
newVersion,
|
|
160
|
-
bumpType: bump,
|
|
161
|
-
hasDirectChanges
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Update a package.json file with new version and dependency versions
|
|
166
|
-
*/
|
|
167
|
-
async function updatePackageJson(pkg, newVersion, dependencyUpdates) {
|
|
168
|
-
const packageJsonPath = join(pkg.path, "package.json");
|
|
169
|
-
const content = await readFile(packageJsonPath, "utf-8");
|
|
170
|
-
const packageJson = JSON.parse(content);
|
|
171
|
-
packageJson.version = newVersion;
|
|
172
|
-
for (const [depName, depVersion] of dependencyUpdates) {
|
|
173
|
-
if (packageJson.dependencies?.[depName]) {
|
|
174
|
-
if (packageJson.dependencies[depName] === "workspace:*") continue;
|
|
175
|
-
packageJson.dependencies[depName] = `^${depVersion}`;
|
|
176
|
-
}
|
|
177
|
-
if (packageJson.devDependencies?.[depName]) {
|
|
178
|
-
if (packageJson.devDependencies[depName] === "workspace:*") continue;
|
|
179
|
-
packageJson.devDependencies[depName] = `^${depVersion}`;
|
|
180
|
-
}
|
|
181
|
-
if (packageJson.peerDependencies?.[depName]) {
|
|
182
|
-
if (packageJson.peerDependencies[depName] === "workspace:*") continue;
|
|
183
|
-
packageJson.peerDependencies[depName] = `^${depVersion}`;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf-8");
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Get all dependency updates needed for a package
|
|
190
|
-
*/
|
|
191
|
-
function getDependencyUpdates(pkg, allUpdates) {
|
|
192
|
-
const updates = /* @__PURE__ */ new Map();
|
|
193
|
-
const allDeps = [...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies];
|
|
194
|
-
for (const dep of allDeps) {
|
|
195
|
-
const update = allUpdates.find((u) => u.package.name === dep);
|
|
196
|
-
if (update) updates.set(dep, update.newVersion);
|
|
197
|
-
}
|
|
198
|
-
return updates;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
//#endregion
|
|
202
|
-
//#region src/dependencies.ts
|
|
203
|
-
/**
|
|
204
|
-
* Pure function: Determine which packages need updates due to dependency changes
|
|
205
|
-
*
|
|
206
|
-
* When a package is updated, all packages that depend on it should also be updated.
|
|
207
|
-
* This function calculates which additional packages need patch bumps.
|
|
208
|
-
*
|
|
209
|
-
* @param updateOrder - Packages in topological order with their dependency levels
|
|
210
|
-
* @param directUpdates - Packages with direct code changes
|
|
211
|
-
* @returns All updates including dependent packages
|
|
212
|
-
*/
|
|
213
|
-
function createDependentUpdates(updateOrder, directUpdates) {
|
|
214
|
-
const allUpdates = [...directUpdates];
|
|
215
|
-
const updatedPackages = new Set(directUpdates.map((u) => u.package.name));
|
|
216
|
-
for (const { package: pkg } of updateOrder) {
|
|
217
|
-
if (updatedPackages.has(pkg.name)) continue;
|
|
218
|
-
if (hasUpdatedDependencies(pkg, updatedPackages)) {
|
|
219
|
-
allUpdates.push(createVersionUpdate(pkg, "patch", false));
|
|
220
|
-
updatedPackages.add(pkg.name);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return allUpdates;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Pure function: Check if a package has any updated dependencies
|
|
227
|
-
*/
|
|
228
|
-
function hasUpdatedDependencies(pkg, updatedPackages) {
|
|
229
|
-
return [...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies].some((dep) => updatedPackages.has(dep));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
117
|
//#endregion
|
|
233
118
|
//#region src/git.ts
|
|
234
119
|
/**
|
|
@@ -407,34 +292,6 @@ async function pushBranch(branch, workspaceRoot, options) {
|
|
|
407
292
|
else if (options?.force) args.push("--force");
|
|
408
293
|
await run("git", args, { nodeOptions: { cwd: workspaceRoot } });
|
|
409
294
|
}
|
|
410
|
-
/**
|
|
411
|
-
* Generate PR body from version updates
|
|
412
|
-
* @param updates - Array of version updates to include in the PR body
|
|
413
|
-
* @returns Formatted PR body as a string
|
|
414
|
-
*/
|
|
415
|
-
function generatePRBody(updates) {
|
|
416
|
-
const lines = [];
|
|
417
|
-
lines.push("## Packages");
|
|
418
|
-
lines.push("");
|
|
419
|
-
const directChanges = updates.filter((u) => u.hasDirectChanges);
|
|
420
|
-
const dependencyUpdates = updates.filter((u) => !u.hasDirectChanges);
|
|
421
|
-
if (directChanges.length > 0) {
|
|
422
|
-
lines.push("### Direct Changes");
|
|
423
|
-
lines.push("");
|
|
424
|
-
for (const update of directChanges) lines.push(`- **${update.package.name}**: ${update.currentVersion} → ${update.newVersion} (${update.bumpType})`);
|
|
425
|
-
lines.push("");
|
|
426
|
-
}
|
|
427
|
-
if (dependencyUpdates.length > 0) {
|
|
428
|
-
lines.push("### Dependency Updates");
|
|
429
|
-
lines.push("");
|
|
430
|
-
for (const update of dependencyUpdates) lines.push(`- **${update.package.name}**: ${update.currentVersion} → ${update.newVersion} (dependencies changed)`);
|
|
431
|
-
lines.push("");
|
|
432
|
-
}
|
|
433
|
-
lines.push("---");
|
|
434
|
-
lines.push("");
|
|
435
|
-
lines.push("This release PR was automatically generated.");
|
|
436
|
-
return lines.join("\n");
|
|
437
|
-
}
|
|
438
295
|
|
|
439
296
|
//#endregion
|
|
440
297
|
//#region src/github.ts
|
|
@@ -502,26 +359,311 @@ async function upsertPullRequest({ owner, repo, title, body, head, base, pullNum
|
|
|
502
359
|
throw err;
|
|
503
360
|
}
|
|
504
361
|
}
|
|
362
|
+
const defaultTemplate = dedent`
|
|
363
|
+
This PR was automatically generated by the release script.
|
|
364
|
+
|
|
365
|
+
The following packages have been prepared for release:
|
|
366
|
+
|
|
367
|
+
<% it.packages.forEach((pkg) => { %>
|
|
368
|
+
- **<%= pkg.name %>**: <%= pkg.currentVersion %> → <%= pkg.newVersion %> (<%= pkg.bumpType %>)
|
|
369
|
+
<% }) %>
|
|
370
|
+
|
|
371
|
+
Please review the changes and merge when ready.
|
|
372
|
+
|
|
373
|
+
For a more in-depth look at the changes, please refer to the individual package changelogs.
|
|
374
|
+
|
|
375
|
+
> [!NOTE]
|
|
376
|
+
> When this PR is merged, the release process will be triggered automatically, publishing the new package versions to the registry.
|
|
377
|
+
`;
|
|
378
|
+
function dedentString(str) {
|
|
379
|
+
const lines = str.split("\n");
|
|
380
|
+
const minIndent = lines.filter((line) => line.trim().length > 0).reduce((min, line) => Math.min(min, line.search(/\S/)), Infinity);
|
|
381
|
+
return lines.map((line) => minIndent === Infinity ? line : line.slice(minIndent)).join("\n").trim();
|
|
382
|
+
}
|
|
383
|
+
function generatePullRequestBody(updates, body) {
|
|
384
|
+
const eta = new Eta();
|
|
385
|
+
const bodyTemplate = body ? dedentString(body) : defaultTemplate;
|
|
386
|
+
return eta.renderString(bodyTemplate, { packages: updates.map((u) => ({
|
|
387
|
+
name: u.package.name,
|
|
388
|
+
currentVersion: u.currentVersion,
|
|
389
|
+
newVersion: u.newVersion,
|
|
390
|
+
bumpType: u.bumpType,
|
|
391
|
+
hasDirectChanges: u.hasDirectChanges
|
|
392
|
+
})) });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/version.ts
|
|
397
|
+
function isValidSemver(version) {
|
|
398
|
+
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(version);
|
|
399
|
+
}
|
|
400
|
+
function validateSemver(version) {
|
|
401
|
+
if (!isValidSemver(version)) throw new Error(`Invalid semver version: ${version}`);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Calculate the new version based on current version and bump type
|
|
405
|
+
* Pure function - no side effects, easily testable
|
|
406
|
+
*/
|
|
407
|
+
function calculateNewVersion(currentVersion, bump) {
|
|
408
|
+
if (bump === "none") return currentVersion;
|
|
409
|
+
validateSemver(currentVersion);
|
|
410
|
+
const match = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
411
|
+
if (!match) throw new Error(`Invalid semver version: ${currentVersion}`);
|
|
412
|
+
const [, major, minor, patch] = match;
|
|
413
|
+
let newMajor = Number.parseInt(major, 10);
|
|
414
|
+
let newMinor = Number.parseInt(minor, 10);
|
|
415
|
+
let newPatch = Number.parseInt(patch, 10);
|
|
416
|
+
switch (bump) {
|
|
417
|
+
case "major":
|
|
418
|
+
newMajor += 1;
|
|
419
|
+
newMinor = 0;
|
|
420
|
+
newPatch = 0;
|
|
421
|
+
break;
|
|
422
|
+
case "minor":
|
|
423
|
+
newMinor += 1;
|
|
424
|
+
newPatch = 0;
|
|
425
|
+
break;
|
|
426
|
+
case "patch":
|
|
427
|
+
newPatch += 1;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
return `${newMajor}.${newMinor}.${newPatch}`;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Create a version update object
|
|
434
|
+
*/
|
|
435
|
+
function createVersionUpdate(pkg, bump, hasDirectChanges) {
|
|
436
|
+
const newVersion = calculateNewVersion(pkg.version, bump);
|
|
437
|
+
return {
|
|
438
|
+
package: pkg,
|
|
439
|
+
currentVersion: pkg.version,
|
|
440
|
+
newVersion,
|
|
441
|
+
bumpType: bump,
|
|
442
|
+
hasDirectChanges
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Update a package.json file with new version and dependency versions
|
|
447
|
+
*/
|
|
448
|
+
async function updatePackageJson(pkg, newVersion, dependencyUpdates) {
|
|
449
|
+
const packageJsonPath = join(pkg.path, "package.json");
|
|
450
|
+
const content = await readFile(packageJsonPath, "utf-8");
|
|
451
|
+
const packageJson = JSON.parse(content);
|
|
452
|
+
packageJson.version = newVersion;
|
|
453
|
+
for (const [depName, depVersion] of dependencyUpdates) {
|
|
454
|
+
if (packageJson.dependencies?.[depName]) {
|
|
455
|
+
if (packageJson.dependencies[depName] === "workspace:*") continue;
|
|
456
|
+
packageJson.dependencies[depName] = `^${depVersion}`;
|
|
457
|
+
}
|
|
458
|
+
if (packageJson.devDependencies?.[depName]) {
|
|
459
|
+
if (packageJson.devDependencies[depName] === "workspace:*") continue;
|
|
460
|
+
packageJson.devDependencies[depName] = `^${depVersion}`;
|
|
461
|
+
}
|
|
462
|
+
if (packageJson.peerDependencies?.[depName]) {
|
|
463
|
+
if (packageJson.peerDependencies[depName] === "workspace:*") continue;
|
|
464
|
+
packageJson.peerDependencies[depName] = `^${depVersion}`;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf-8");
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Get all dependency updates needed for a package
|
|
471
|
+
*/
|
|
472
|
+
function getDependencyUpdates(pkg, allUpdates) {
|
|
473
|
+
const updates = /* @__PURE__ */ new Map();
|
|
474
|
+
const allDeps = [...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies];
|
|
475
|
+
for (const dep of allDeps) {
|
|
476
|
+
const update = allUpdates.find((u) => u.package.name === dep);
|
|
477
|
+
if (update) updates.set(dep, update.newVersion);
|
|
478
|
+
}
|
|
479
|
+
return updates;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/package.ts
|
|
484
|
+
/**
|
|
485
|
+
* Build a dependency graph from workspace packages
|
|
486
|
+
*
|
|
487
|
+
* Creates a bidirectional graph that maps:
|
|
488
|
+
* - packages: Map of package name → WorkspacePackage
|
|
489
|
+
* - dependents: Map of package name → Set of packages that depend on it
|
|
490
|
+
*
|
|
491
|
+
* @param packages - All workspace packages
|
|
492
|
+
* @returns Dependency graph with packages and dependents maps
|
|
493
|
+
*/
|
|
494
|
+
function buildPackageDependencyGraph(packages) {
|
|
495
|
+
const packagesMap = /* @__PURE__ */ new Map();
|
|
496
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
497
|
+
for (const pkg of packages) {
|
|
498
|
+
packagesMap.set(pkg.name, pkg);
|
|
499
|
+
dependents.set(pkg.name, /* @__PURE__ */ new Set());
|
|
500
|
+
}
|
|
501
|
+
for (const pkg of packages) {
|
|
502
|
+
const allDeps = [...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies];
|
|
503
|
+
for (const dep of allDeps) {
|
|
504
|
+
const depSet = dependents.get(dep);
|
|
505
|
+
if (depSet) depSet.add(pkg.name);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
packages: packagesMap,
|
|
510
|
+
dependents
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Get all packages affected by changes (including transitive dependents)
|
|
515
|
+
*
|
|
516
|
+
* Uses graph traversal to find all packages that need updates:
|
|
517
|
+
* - Packages with direct changes
|
|
518
|
+
* - All packages that depend on changed packages (transitively)
|
|
519
|
+
*
|
|
520
|
+
* @param graph - Dependency graph
|
|
521
|
+
* @param changedPackages - Set of package names with direct changes
|
|
522
|
+
* @returns Set of all package names that need updates
|
|
523
|
+
*/
|
|
524
|
+
function getAllAffectedPackages(graph, changedPackages) {
|
|
525
|
+
const affected = /* @__PURE__ */ new Set();
|
|
526
|
+
function visitDependents(pkgName) {
|
|
527
|
+
if (affected.has(pkgName)) return;
|
|
528
|
+
affected.add(pkgName);
|
|
529
|
+
const dependents = graph.dependents.get(pkgName);
|
|
530
|
+
if (dependents) for (const dependent of dependents) visitDependents(dependent);
|
|
531
|
+
}
|
|
532
|
+
for (const pkg of changedPackages) visitDependents(pkg);
|
|
533
|
+
return affected;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Create version updates for all packages affected by dependency changes
|
|
537
|
+
*
|
|
538
|
+
* When a package is updated, all packages that depend on it should also be updated.
|
|
539
|
+
* This function calculates which additional packages need patch bumps due to dependency changes.
|
|
540
|
+
*
|
|
541
|
+
* @param graph - Dependency graph
|
|
542
|
+
* @param workspacePackages - All workspace packages
|
|
543
|
+
* @param directUpdates - Packages with direct code changes
|
|
544
|
+
* @returns All updates including dependent packages that need patch bumps
|
|
545
|
+
*/
|
|
546
|
+
function createDependentUpdates(graph, workspacePackages, directUpdates) {
|
|
547
|
+
const allUpdates = [...directUpdates];
|
|
548
|
+
const directUpdateMap = new Map(directUpdates.map((u) => [u.package.name, u]));
|
|
549
|
+
const affectedPackages = getAllAffectedPackages(graph, new Set(directUpdates.map((u) => u.package.name)));
|
|
550
|
+
for (const pkgName of affectedPackages) {
|
|
551
|
+
if (directUpdateMap.has(pkgName)) continue;
|
|
552
|
+
const pkg = workspacePackages.find((p) => p.name === pkgName);
|
|
553
|
+
if (!pkg) continue;
|
|
554
|
+
allUpdates.push(createVersionUpdate(pkg, "patch", false));
|
|
555
|
+
}
|
|
556
|
+
return allUpdates;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Update all package.json files with new versions and dependency updates
|
|
560
|
+
*
|
|
561
|
+
* Updates are performed in parallel for better performance.
|
|
562
|
+
*
|
|
563
|
+
* @param updates - Version updates to apply
|
|
564
|
+
*/
|
|
565
|
+
async function updateAllPackageJsonFiles(updates) {
|
|
566
|
+
await Promise.all(updates.map(async (update) => {
|
|
567
|
+
const depUpdates = getDependencyUpdates(update.package, updates);
|
|
568
|
+
await updatePackageJson(update.package, update.newVersion, depUpdates);
|
|
569
|
+
}));
|
|
570
|
+
}
|
|
505
571
|
|
|
506
572
|
//#endregion
|
|
507
573
|
//#region src/prompts.ts
|
|
508
|
-
|
|
574
|
+
/**
|
|
575
|
+
* Get commits for a package grouped by conventional commit type
|
|
576
|
+
*
|
|
577
|
+
* @param pkg - The workspace package
|
|
578
|
+
* @param workspaceRoot - Root directory of the workspace
|
|
579
|
+
* @param limit - Maximum number of commits to return (default: 10)
|
|
580
|
+
* @returns Commits grouped by type
|
|
581
|
+
*/
|
|
582
|
+
async function getCommitsForPackage(pkg, workspaceRoot, limit = 10) {
|
|
583
|
+
const limitedCommits = (await getPackageCommits(pkg, workspaceRoot)).slice(0, limit);
|
|
584
|
+
const grouped = {
|
|
585
|
+
feat: [],
|
|
586
|
+
fix: [],
|
|
587
|
+
perf: [],
|
|
588
|
+
chore: [],
|
|
589
|
+
docs: [],
|
|
590
|
+
style: [],
|
|
591
|
+
refactor: [],
|
|
592
|
+
test: [],
|
|
593
|
+
build: [],
|
|
594
|
+
ci: [],
|
|
595
|
+
revert: [],
|
|
596
|
+
other: []
|
|
597
|
+
};
|
|
598
|
+
for (const commit of limitedCommits) if (commit.type && commit.type in grouped) grouped[commit.type].push(commit);
|
|
599
|
+
else grouped.other.push(commit);
|
|
600
|
+
return grouped;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Format grouped commits into a readable string
|
|
604
|
+
*/
|
|
605
|
+
function formatCommitGroups(grouped) {
|
|
606
|
+
const lines = [];
|
|
607
|
+
const typeLabels = {
|
|
608
|
+
feat: "Features",
|
|
609
|
+
fix: "Bug Fixes",
|
|
610
|
+
perf: "Performance",
|
|
611
|
+
chore: "Chores",
|
|
612
|
+
docs: "Documentation",
|
|
613
|
+
style: "Styling",
|
|
614
|
+
refactor: "Refactoring",
|
|
615
|
+
test: "Tests",
|
|
616
|
+
build: "Build",
|
|
617
|
+
ci: "CI",
|
|
618
|
+
revert: "Reverts",
|
|
619
|
+
other: "Other"
|
|
620
|
+
};
|
|
621
|
+
for (const type of [
|
|
622
|
+
"feat",
|
|
623
|
+
"fix",
|
|
624
|
+
"perf",
|
|
625
|
+
"refactor",
|
|
626
|
+
"test",
|
|
627
|
+
"docs",
|
|
628
|
+
"style",
|
|
629
|
+
"build",
|
|
630
|
+
"ci",
|
|
631
|
+
"chore",
|
|
632
|
+
"revert",
|
|
633
|
+
"other"
|
|
634
|
+
]) {
|
|
635
|
+
const commits = grouped[type];
|
|
636
|
+
if (commits.length > 0) {
|
|
637
|
+
lines.push(`\n${typeLabels[type]}:`);
|
|
638
|
+
for (const commit of commits) {
|
|
639
|
+
const scope = commit.scope ? `(${commit.scope})` : "";
|
|
640
|
+
const breaking = commit.isBreaking ? " ⚠️ BREAKING" : "";
|
|
641
|
+
lines.push(` • ${commit.type}${scope}: ${commit.message}${breaking}`);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return lines.join("\n");
|
|
646
|
+
}
|
|
647
|
+
async function selectPackagePrompt(packages) {
|
|
509
648
|
const response = await prompts({
|
|
510
649
|
type: "multiselect",
|
|
511
650
|
name: "selectedPackages",
|
|
512
651
|
message: "Select packages to release",
|
|
513
652
|
choices: packages.map((pkg) => ({
|
|
514
|
-
title: `${pkg.name} (${pkg.version})`,
|
|
653
|
+
title: `${pkg.name} (${farver.bold(pkg.version)})`,
|
|
515
654
|
value: pkg.name,
|
|
516
655
|
selected: true
|
|
517
656
|
})),
|
|
518
657
|
min: 1,
|
|
519
|
-
hint: "Space to select/deselect. Return to submit."
|
|
658
|
+
hint: "Space to select/deselect. Return to submit.",
|
|
659
|
+
instructions: false
|
|
520
660
|
});
|
|
521
|
-
if (!response.selectedPackages || response.selectedPackages.length === 0)
|
|
661
|
+
if (!response.selectedPackages || response.selectedPackages.length === 0) return [];
|
|
522
662
|
return response.selectedPackages;
|
|
523
663
|
}
|
|
524
|
-
async function promptVersionOverride(
|
|
664
|
+
async function promptVersionOverride(pkg, workspaceRoot, currentVersion, suggestedVersion, suggestedBumpType) {
|
|
665
|
+
const commitSummary = formatCommitGroups(await getCommitsForPackage(pkg, workspaceRoot));
|
|
666
|
+
if (commitSummary.trim()) console.log(`\nRecent changes in ${pkg.name}:${commitSummary}\n`);
|
|
525
667
|
const choices = [{
|
|
526
668
|
title: `Use suggested: ${suggestedVersion} (${suggestedBumpType})`,
|
|
527
669
|
value: "suggested"
|
|
@@ -544,7 +686,7 @@ async function promptVersionOverride(packageName, currentVersion, suggestedVersi
|
|
|
544
686
|
const response = await prompts([{
|
|
545
687
|
type: "select",
|
|
546
688
|
name: "choice",
|
|
547
|
-
message: `${
|
|
689
|
+
message: `${pkg.name} (${currentVersion}):`,
|
|
548
690
|
choices,
|
|
549
691
|
initial: 0
|
|
550
692
|
}, {
|
|
@@ -560,18 +702,85 @@ async function promptVersionOverride(packageName, currentVersion, suggestedVersi
|
|
|
560
702
|
else if (response.choice === "custom") return response.customVersion;
|
|
561
703
|
else return calculateNewVersion(currentVersion, response.choice);
|
|
562
704
|
}
|
|
563
|
-
async function promptVersionOverrides(packages) {
|
|
705
|
+
async function promptVersionOverrides(packages, workspaceRoot) {
|
|
564
706
|
const overrides = /* @__PURE__ */ new Map();
|
|
565
|
-
for (const
|
|
566
|
-
const newVersion = await promptVersionOverride(
|
|
567
|
-
overrides.set(
|
|
707
|
+
for (const item of packages) {
|
|
708
|
+
const newVersion = await promptVersionOverride(item.package, workspaceRoot, item.currentVersion, item.suggestedVersion, item.bumpType);
|
|
709
|
+
overrides.set(item.package.name, newVersion);
|
|
568
710
|
}
|
|
569
711
|
return overrides;
|
|
570
712
|
}
|
|
571
713
|
|
|
572
714
|
//#endregion
|
|
573
715
|
//#region src/workspace.ts
|
|
574
|
-
|
|
716
|
+
async function discoverWorkspacePackages(workspaceRoot, options) {
|
|
717
|
+
let workspaceOptions;
|
|
718
|
+
let explicitPackages;
|
|
719
|
+
if (options.packages == null || options.packages === true) workspaceOptions = { excludePrivate: false };
|
|
720
|
+
else if (Array.isArray(options.packages)) {
|
|
721
|
+
workspaceOptions = {
|
|
722
|
+
excludePrivate: false,
|
|
723
|
+
included: options.packages
|
|
724
|
+
};
|
|
725
|
+
explicitPackages = options.packages;
|
|
726
|
+
} else {
|
|
727
|
+
workspaceOptions = options.packages;
|
|
728
|
+
if (options.packages.included) explicitPackages = options.packages.included;
|
|
729
|
+
}
|
|
730
|
+
const workspacePackages = await findWorkspacePackages(workspaceRoot, workspaceOptions);
|
|
731
|
+
if (explicitPackages) {
|
|
732
|
+
const foundNames = new Set(workspacePackages.map((p) => p.name));
|
|
733
|
+
const missing = explicitPackages.filter((p) => !foundNames.has(p));
|
|
734
|
+
if (missing.length > 0) throw new Error(`Packages not found in workspace: ${missing.join(", ")}`);
|
|
735
|
+
}
|
|
736
|
+
let packagesToAnalyze = workspacePackages;
|
|
737
|
+
const isPackagePromptEnabled = options.prompts?.packages !== false;
|
|
738
|
+
if (!isCI && isPackagePromptEnabled && !explicitPackages) {
|
|
739
|
+
const selectedNames = await selectPackagePrompt(workspacePackages);
|
|
740
|
+
packagesToAnalyze = workspacePackages.filter((pkg) => selectedNames.includes(pkg.name));
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
workspacePackages,
|
|
744
|
+
packagesToAnalyze
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
async function findWorkspacePackages(workspaceRoot, options) {
|
|
748
|
+
try {
|
|
749
|
+
const result = await run("pnpm", [
|
|
750
|
+
"-r",
|
|
751
|
+
"ls",
|
|
752
|
+
"--json"
|
|
753
|
+
], { nodeOptions: {
|
|
754
|
+
cwd: workspaceRoot,
|
|
755
|
+
stdio: "pipe"
|
|
756
|
+
} });
|
|
757
|
+
const rawProjects = JSON.parse(result.stdout);
|
|
758
|
+
const allPackageNames = new Set(rawProjects.map((p) => p.name));
|
|
759
|
+
const excludedPackages = /* @__PURE__ */ new Set();
|
|
760
|
+
const promises = rawProjects.map(async (rawProject) => {
|
|
761
|
+
const content = await readFile(join(rawProject.path, "package.json"), "utf-8");
|
|
762
|
+
const packageJson = JSON.parse(content);
|
|
763
|
+
if (!shouldIncludePackage(packageJson, options)) {
|
|
764
|
+
excludedPackages.add(rawProject.name);
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
return {
|
|
768
|
+
name: rawProject.name,
|
|
769
|
+
version: rawProject.version,
|
|
770
|
+
path: rawProject.path,
|
|
771
|
+
packageJson,
|
|
772
|
+
workspaceDependencies: extractWorkspaceDependencies(rawProject.dependencies, allPackageNames),
|
|
773
|
+
workspaceDevDependencies: extractWorkspaceDependencies(rawProject.devDependencies, allPackageNames)
|
|
774
|
+
};
|
|
775
|
+
});
|
|
776
|
+
const packages = await Promise.all(promises);
|
|
777
|
+
if (excludedPackages.size > 0) console.info(`${farver.cyan("[info]:")} Excluded packages: ${farver.green(Array.from(excludedPackages).join(", "))}`);
|
|
778
|
+
return packages.filter((pkg) => pkg !== null);
|
|
779
|
+
} catch (err) {
|
|
780
|
+
console.error("Error discovering workspace packages:", err);
|
|
781
|
+
throw err;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
575
784
|
function shouldIncludePackage(pkg, options) {
|
|
576
785
|
if (!options) return true;
|
|
577
786
|
if (options.excludePrivate && pkg.private) return false;
|
|
@@ -581,130 +790,45 @@ function shouldIncludePackage(pkg, options) {
|
|
|
581
790
|
if (options.excluded?.includes(pkg.name)) return false;
|
|
582
791
|
return true;
|
|
583
792
|
}
|
|
584
|
-
async function findWorkspacePackages(workspaceRoot, options) {
|
|
585
|
-
const result = await run("pnpm", [
|
|
586
|
-
"-r",
|
|
587
|
-
"ls",
|
|
588
|
-
"--json"
|
|
589
|
-
], { nodeOptions: {
|
|
590
|
-
cwd: workspaceRoot,
|
|
591
|
-
stdio: "pipe"
|
|
592
|
-
} });
|
|
593
|
-
const rawProjects = JSON.parse(result.stdout);
|
|
594
|
-
const packages = [];
|
|
595
|
-
const allPackageNames = new Set(rawProjects.map((p) => p.name));
|
|
596
|
-
for (const rawProject of rawProjects) {
|
|
597
|
-
const content = await readFile(join(rawProject.path, "package.json"), "utf-8");
|
|
598
|
-
const packageJson = JSON.parse(content);
|
|
599
|
-
if (!shouldIncludePackage(packageJson, options)) {
|
|
600
|
-
debug?.(`Excluding package ${rawProject.name}`);
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
const workspaceDeps = extractWorkspaceDependencies(rawProject.dependencies, allPackageNames);
|
|
604
|
-
const workspaceDevDeps = extractWorkspaceDependencies(rawProject.devDependencies, allPackageNames);
|
|
605
|
-
packages.push({
|
|
606
|
-
name: rawProject.name,
|
|
607
|
-
version: rawProject.version,
|
|
608
|
-
path: rawProject.path,
|
|
609
|
-
packageJson,
|
|
610
|
-
workspaceDependencies: workspaceDeps,
|
|
611
|
-
workspaceDevDependencies: workspaceDevDeps
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
return packages;
|
|
615
|
-
}
|
|
616
793
|
function extractWorkspaceDependencies(dependencies, workspacePackages) {
|
|
617
794
|
if (!dependencies) return [];
|
|
618
795
|
return Object.keys(dependencies).filter((dep) => {
|
|
619
796
|
return workspacePackages.has(dep);
|
|
620
797
|
});
|
|
621
798
|
}
|
|
622
|
-
function buildDependencyGraph(packages) {
|
|
623
|
-
const packagesMap = /* @__PURE__ */ new Map();
|
|
624
|
-
const dependents = /* @__PURE__ */ new Map();
|
|
625
|
-
for (const pkg of packages) {
|
|
626
|
-
packagesMap.set(pkg.name, pkg);
|
|
627
|
-
dependents.set(pkg.name, /* @__PURE__ */ new Set());
|
|
628
|
-
}
|
|
629
|
-
for (const pkg of packages) {
|
|
630
|
-
const allDeps = [...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies];
|
|
631
|
-
for (const dep of allDeps) {
|
|
632
|
-
const depSet = dependents.get(dep);
|
|
633
|
-
if (depSet) depSet.add(pkg.name);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return {
|
|
637
|
-
packages: packagesMap,
|
|
638
|
-
dependents
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
function getPackageUpdateOrder(graph, changedPackages) {
|
|
642
|
-
const result = [];
|
|
643
|
-
const visited = /* @__PURE__ */ new Set();
|
|
644
|
-
const toUpdate = new Set(changedPackages);
|
|
645
|
-
const packagesToProcess = new Set(changedPackages);
|
|
646
|
-
for (const pkg of changedPackages) {
|
|
647
|
-
const deps = graph.dependents.get(pkg);
|
|
648
|
-
if (deps) for (const dep of deps) {
|
|
649
|
-
packagesToProcess.add(dep);
|
|
650
|
-
toUpdate.add(dep);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
function visit(pkgName, level) {
|
|
654
|
-
if (visited.has(pkgName)) return;
|
|
655
|
-
visited.add(pkgName);
|
|
656
|
-
const pkg = graph.packages.get(pkgName);
|
|
657
|
-
if (!pkg) return;
|
|
658
|
-
const allDeps = [...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies];
|
|
659
|
-
let maxDepLevel = level;
|
|
660
|
-
for (const dep of allDeps) if (toUpdate.has(dep)) {
|
|
661
|
-
visit(dep, level);
|
|
662
|
-
const depResult = result.find((r) => r.package.name === dep);
|
|
663
|
-
if (depResult && depResult.level >= maxDepLevel) maxDepLevel = depResult.level + 1;
|
|
664
|
-
}
|
|
665
|
-
result.push({
|
|
666
|
-
package: pkg,
|
|
667
|
-
level: maxDepLevel
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
for (const pkg of toUpdate) visit(pkg, 0);
|
|
671
|
-
result.sort((a, b) => a.level - b.level);
|
|
672
|
-
return result;
|
|
673
|
-
}
|
|
674
799
|
|
|
675
800
|
//#endregion
|
|
676
801
|
//#region src/release.ts
|
|
677
|
-
const isCI = process.env.CI === "true";
|
|
678
802
|
async function release(options) {
|
|
679
803
|
const { dryRun: dryRun$1 = false, safeguards = true, workspaceRoot = process.cwd(), releaseBranch = "release/next", githubToken } = options;
|
|
680
804
|
globalOptions.dryRun = dryRun$1;
|
|
681
805
|
if (githubToken.trim() === "" || githubToken == null) throw new Error("GitHub token is required");
|
|
682
806
|
const [owner, repo] = options.repo.split("/");
|
|
683
807
|
if (!owner || !repo) throw new Error(`Invalid repo format: ${options.repo}. Expected "owner/repo".`);
|
|
684
|
-
if (safeguards && !isWorkingDirectoryClean(workspaceRoot)) {
|
|
808
|
+
if (safeguards && !await isWorkingDirectoryClean(workspaceRoot)) {
|
|
685
809
|
console.error("Working directory is not clean. Please commit or stash your changes before proceeding.");
|
|
686
810
|
return null;
|
|
687
811
|
}
|
|
688
|
-
const { workspacePackages, packagesToAnalyze
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
let packagesToAnalyze = initialPackages;
|
|
693
|
-
if (!isCI && isPackagePromptEnabled && !isPackagesPreConfigured) {
|
|
694
|
-
const selectedNames = await promptPackageSelection(initialPackages);
|
|
695
|
-
packagesToAnalyze = initialPackages.filter((pkg) => selectedNames.includes(pkg.name));
|
|
812
|
+
const { workspacePackages, packagesToAnalyze } = await discoverWorkspacePackages(workspaceRoot, options);
|
|
813
|
+
if (packagesToAnalyze.length === 0) {
|
|
814
|
+
console.log("No packages found to analyze for release.");
|
|
815
|
+
return null;
|
|
696
816
|
}
|
|
697
817
|
const changedPackages = await analyzeCommits(packagesToAnalyze, workspaceRoot);
|
|
698
818
|
if (changedPackages.size === 0) throw new Error("No packages have changes requiring a release");
|
|
699
|
-
let versionUpdates =
|
|
819
|
+
let versionUpdates = [];
|
|
820
|
+
for (const [pkgName, bump] of changedPackages) {
|
|
821
|
+
const pkg = workspacePackages.find((p) => p.name === pkgName);
|
|
822
|
+
if (pkg) versionUpdates.push(createVersionUpdate(pkg, bump, true));
|
|
823
|
+
}
|
|
700
824
|
const isVersionPromptEnabled = options.prompts?.versions !== false;
|
|
701
825
|
if (!isCI && isVersionPromptEnabled) {
|
|
702
826
|
const versionOverrides = await promptVersionOverrides(versionUpdates.map((u) => ({
|
|
703
|
-
|
|
827
|
+
package: u.package,
|
|
704
828
|
currentVersion: u.currentVersion,
|
|
705
829
|
suggestedVersion: u.newVersion,
|
|
706
830
|
bumpType: u.bumpType
|
|
707
|
-
})));
|
|
831
|
+
})), workspaceRoot);
|
|
708
832
|
versionUpdates = versionUpdates.map((update) => {
|
|
709
833
|
const overriddenVersion = versionOverrides.get(update.package.name);
|
|
710
834
|
if (overriddenVersion && overriddenVersion !== update.newVersion) return {
|
|
@@ -714,7 +838,7 @@ async function release(options) {
|
|
|
714
838
|
return update;
|
|
715
839
|
});
|
|
716
840
|
}
|
|
717
|
-
const allUpdates = createDependentUpdates(
|
|
841
|
+
const allUpdates = createDependentUpdates(buildPackageDependencyGraph(workspacePackages), workspacePackages, versionUpdates);
|
|
718
842
|
const currentBranch = await getCurrentBranch(workspaceRoot);
|
|
719
843
|
const existingPullRequest = await getExistingPullRequest({
|
|
720
844
|
owner,
|
|
@@ -737,7 +861,7 @@ async function release(options) {
|
|
|
737
861
|
}
|
|
738
862
|
console.log("Rebasing release branch onto", currentBranch);
|
|
739
863
|
await rebaseBranch(currentBranch, workspaceRoot);
|
|
740
|
-
await
|
|
864
|
+
await updateAllPackageJsonFiles(allUpdates);
|
|
741
865
|
const hasCommitted = await commitChanges("chore: update release versions", workspaceRoot);
|
|
742
866
|
const isBranchAhead = await isBranchAheadOfRemote(releaseBranch, workspaceRoot);
|
|
743
867
|
if (!hasCommitted && !isBranchAhead) {
|
|
@@ -757,8 +881,8 @@ async function release(options) {
|
|
|
757
881
|
}
|
|
758
882
|
console.log("Pushing changes to remote");
|
|
759
883
|
await pushBranch(releaseBranch, workspaceRoot, { forceWithLease: true });
|
|
760
|
-
const prTitle = existingPullRequest?.title || "
|
|
761
|
-
const prBody =
|
|
884
|
+
const prTitle = existingPullRequest?.title || options.pullRequest?.title || "chore: update package versions";
|
|
885
|
+
const prBody = generatePullRequestBody(allUpdates, options.pullRequest?.body);
|
|
762
886
|
const pullRequest = await upsertPullRequest({
|
|
763
887
|
owner,
|
|
764
888
|
repo,
|
|
@@ -777,64 +901,6 @@ async function release(options) {
|
|
|
777
901
|
created: !prExists
|
|
778
902
|
};
|
|
779
903
|
}
|
|
780
|
-
async function discoverPackages(workspaceRoot, options) {
|
|
781
|
-
let workspacePackages;
|
|
782
|
-
let packagesToAnalyze;
|
|
783
|
-
if (typeof options.packages === "boolean" && options.packages === true) {
|
|
784
|
-
workspacePackages = await findWorkspacePackages(workspaceRoot, { excludePrivate: false });
|
|
785
|
-
packagesToAnalyze = workspacePackages;
|
|
786
|
-
return {
|
|
787
|
-
workspacePackages,
|
|
788
|
-
packagesToAnalyze
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
if (Array.isArray(options.packages)) {
|
|
792
|
-
const packageNames = options.packages;
|
|
793
|
-
workspacePackages = await findWorkspacePackages(workspaceRoot, {
|
|
794
|
-
excludePrivate: false,
|
|
795
|
-
included: packageNames
|
|
796
|
-
});
|
|
797
|
-
packagesToAnalyze = workspacePackages.filter((pkg) => packageNames.includes(pkg.name));
|
|
798
|
-
if (packagesToAnalyze.length !== packageNames.length) {
|
|
799
|
-
const found = new Set(packagesToAnalyze.map((p) => p.name));
|
|
800
|
-
const missing = packageNames.filter((p) => !found.has(p));
|
|
801
|
-
throw new Error(`Packages not found in workspace: ${missing.join(", ")}`);
|
|
802
|
-
}
|
|
803
|
-
return {
|
|
804
|
-
workspacePackages,
|
|
805
|
-
packagesToAnalyze
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
workspacePackages = await findWorkspacePackages(workspaceRoot, options.packages);
|
|
809
|
-
packagesToAnalyze = workspacePackages;
|
|
810
|
-
return {
|
|
811
|
-
workspacePackages,
|
|
812
|
-
packagesToAnalyze
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
async function analyzeCommits(packages, workspaceRoot) {
|
|
816
|
-
const changedPackages = /* @__PURE__ */ new Map();
|
|
817
|
-
for (const pkg of packages) {
|
|
818
|
-
const bump = await analyzePackageCommits(pkg, workspaceRoot);
|
|
819
|
-
if (bump !== "none") changedPackages.set(pkg.name, bump);
|
|
820
|
-
}
|
|
821
|
-
return changedPackages;
|
|
822
|
-
}
|
|
823
|
-
function calculateVersions(allPackages, changedPackages) {
|
|
824
|
-
const updates = [];
|
|
825
|
-
for (const [pkgName, bump] of changedPackages) {
|
|
826
|
-
const pkg = allPackages.find((p) => p.name === pkgName);
|
|
827
|
-
if (!pkg) continue;
|
|
828
|
-
updates.push(createVersionUpdate(pkg, bump, true));
|
|
829
|
-
}
|
|
830
|
-
return updates;
|
|
831
|
-
}
|
|
832
|
-
async function updatePackageJsonFiles(updates) {
|
|
833
|
-
await Promise.all(updates.map(async (update) => {
|
|
834
|
-
const depUpdates = getDependencyUpdates(update.package, updates);
|
|
835
|
-
await updatePackageJson(update.package, update.newVersion, depUpdates);
|
|
836
|
-
}));
|
|
837
|
-
}
|
|
838
904
|
|
|
839
905
|
//#endregion
|
|
840
906
|
export { release };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ucdjs/release-scripts",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "@ucdjs release scripts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@luxass/utils": "2.7.2",
|
|
22
23
|
"commit-parser": "0.4.5",
|
|
23
|
-
"
|
|
24
|
+
"eta": "4.0.1",
|
|
24
25
|
"farver": "1.0.0-beta.1",
|
|
25
26
|
"prompts": "2.4.2",
|
|
26
27
|
"tinyexec": "1.0.2"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@luxass/eslint-config": "6.0.1",
|
|
30
|
-
"@types/debug": "4.1.12",
|
|
31
31
|
"@types/node": "22.18.12",
|
|
32
32
|
"@types/prompts": "2.4.9",
|
|
33
33
|
"eslint": "9.39.1",
|