@ucdjs/release-scripts 0.1.0-beta.35 → 0.1.0-beta.36
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 -3
- package/dist/index.mjs +555 -743
- package/package.json +2 -5
package/dist/index.mjs
CHANGED
|
@@ -1,128 +1,57 @@
|
|
|
1
1
|
import { t as Eta } from "./eta-DAZlmVBQ.mjs";
|
|
2
2
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join, relative } from "node:path";
|
|
4
|
-
import
|
|
5
|
-
import prompts from "prompts";
|
|
4
|
+
import { getCommits, groupByType } from "commit-parser";
|
|
6
5
|
import process from "node:process";
|
|
7
6
|
import readline from "node:readline";
|
|
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
|
|
11
|
+
import prompts from "prompts";
|
|
12
12
|
import { compare, gt } from "semver";
|
|
13
13
|
|
|
14
|
-
//#region src/operations/
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
case "major":
|
|
29
|
-
newMajor += 1;
|
|
30
|
-
newMinor = 0;
|
|
31
|
-
newPatch = 0;
|
|
32
|
-
break;
|
|
33
|
-
case "minor":
|
|
34
|
-
newMinor += 1;
|
|
35
|
-
newPatch = 0;
|
|
36
|
-
break;
|
|
37
|
-
case "patch":
|
|
38
|
-
newPatch += 1;
|
|
39
|
-
break;
|
|
14
|
+
//#region src/operations/changelog-format.ts
|
|
15
|
+
function formatCommitLine({ commit, owner, repo, authors }) {
|
|
16
|
+
const commitUrl = `https://github.com/${owner}/${repo}/commit/${commit.hash}`;
|
|
17
|
+
let line = `${commit.description}`;
|
|
18
|
+
const references = commit.references ?? [];
|
|
19
|
+
for (const ref of references) {
|
|
20
|
+
if (!ref.value) continue;
|
|
21
|
+
const number = Number.parseInt(ref.value.replace(/^#/, ""), 10);
|
|
22
|
+
if (Number.isNaN(number)) continue;
|
|
23
|
+
if (ref.type === "issue") {
|
|
24
|
+
line += ` ([Issue ${ref.value}](https://github.com/${owner}/${repo}/issues/${number}))`;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
line += ` ([PR ${ref.value}](https://github.com/${owner}/${repo}/pull/${number}))`;
|
|
40
28
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (newParts[0] > oldParts[0]) return "major";
|
|
48
|
-
if (newParts[1] > oldParts[1]) return "minor";
|
|
49
|
-
if (newParts[2] > oldParts[2]) return "patch";
|
|
50
|
-
return "none";
|
|
29
|
+
line += ` ([${commit.shortHash}](${commitUrl}))`;
|
|
30
|
+
if (authors.length > 0) {
|
|
31
|
+
const authorList = authors.map((author) => author.login ? `[@${author.login}](https://github.com/${author.login})` : author.name).join(", ");
|
|
32
|
+
line += ` (by ${authorList})`;
|
|
33
|
+
}
|
|
34
|
+
return line;
|
|
51
35
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}))
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
36
|
+
function buildTemplateGroups(options) {
|
|
37
|
+
const { commits, owner, repo, types, commitAuthors } = options;
|
|
38
|
+
const grouped = groupByType(commits, {
|
|
39
|
+
includeNonConventional: false,
|
|
40
|
+
mergeKeys: Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.types ?? [key]]))
|
|
41
|
+
});
|
|
42
|
+
return Object.entries(types).map(([key, value]) => {
|
|
43
|
+
const formattedCommits = (grouped.get(key) ?? []).map((commit) => ({ line: formatCommitLine({
|
|
44
|
+
commit,
|
|
45
|
+
owner,
|
|
46
|
+
repo,
|
|
47
|
+
authors: commitAuthors.get(commit.hash) ?? []
|
|
48
|
+
}) }));
|
|
49
|
+
return {
|
|
50
|
+
name: key,
|
|
51
|
+
title: value.title,
|
|
52
|
+
commits: formattedCommits
|
|
53
|
+
};
|
|
68
54
|
});
|
|
69
|
-
if (!response.selectedPackages || response.selectedPackages.length === 0) return [];
|
|
70
|
-
return response.selectedPackages;
|
|
71
|
-
}
|
|
72
|
-
async function selectVersionPrompt(workspaceRoot, pkg, currentVersion, suggestedVersion) {
|
|
73
|
-
const answers = await prompts([{
|
|
74
|
-
type: "autocomplete",
|
|
75
|
-
name: "version",
|
|
76
|
-
message: `${pkg.name}: ${farver.green(pkg.version)}`,
|
|
77
|
-
choices: [
|
|
78
|
-
{
|
|
79
|
-
value: "skip",
|
|
80
|
-
title: `skip ${farver.dim("(no change)")}`
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
value: "major",
|
|
84
|
-
title: `major ${farver.bold(getNextVersion(pkg.version, "major"))}`
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
value: "minor",
|
|
88
|
-
title: `minor ${farver.bold(getNextVersion(pkg.version, "minor"))}`
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
value: "patch",
|
|
92
|
-
title: `patch ${farver.bold(getNextVersion(pkg.version, "patch"))}`
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
value: "suggested",
|
|
96
|
-
title: `suggested ${farver.bold(suggestedVersion)}`
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
value: "as-is",
|
|
100
|
-
title: `as-is ${farver.dim("(keep current version)")}`
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
value: "custom",
|
|
104
|
-
title: "custom"
|
|
105
|
-
}
|
|
106
|
-
],
|
|
107
|
-
initial: suggestedVersion === currentVersion ? 0 : 4
|
|
108
|
-
}, {
|
|
109
|
-
type: (prev) => prev === "custom" ? "text" : null,
|
|
110
|
-
name: "custom",
|
|
111
|
-
message: "Enter the new version number:",
|
|
112
|
-
initial: suggestedVersion,
|
|
113
|
-
validate: (custom) => {
|
|
114
|
-
if (isValidSemver(custom)) return true;
|
|
115
|
-
return "That's not a valid version number";
|
|
116
|
-
}
|
|
117
|
-
}]);
|
|
118
|
-
if (!answers.version) return null;
|
|
119
|
-
if (answers.version === "skip") return null;
|
|
120
|
-
else if (answers.version === "suggested") return suggestedVersion;
|
|
121
|
-
else if (answers.version === "custom") {
|
|
122
|
-
if (!answers.custom) return null;
|
|
123
|
-
return answers.custom;
|
|
124
|
-
} else if (answers.version === "as-is") return currentVersion;
|
|
125
|
-
else return getNextVersion(pkg.version, answers.version);
|
|
126
55
|
}
|
|
127
56
|
|
|
128
57
|
//#endregion
|
|
@@ -210,118 +139,135 @@ if (isDryRun || isVerbose || isForce) {
|
|
|
210
139
|
}
|
|
211
140
|
|
|
212
141
|
//#endregion
|
|
213
|
-
//#region src/
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
error
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
//#endregion
|
|
228
|
-
//#region src/core/workspace.ts
|
|
229
|
-
async function discoverWorkspacePackages(workspaceRoot, options) {
|
|
230
|
-
let workspaceOptions;
|
|
231
|
-
let explicitPackages;
|
|
232
|
-
if (options.packages == null || options.packages === true) workspaceOptions = { excludePrivate: false };
|
|
233
|
-
else if (Array.isArray(options.packages)) {
|
|
234
|
-
workspaceOptions = {
|
|
235
|
-
excludePrivate: false,
|
|
236
|
-
include: options.packages
|
|
237
|
-
};
|
|
238
|
-
explicitPackages = options.packages;
|
|
239
|
-
} else {
|
|
240
|
-
workspaceOptions = options.packages;
|
|
241
|
-
if (options.packages.include) explicitPackages = options.packages.include;
|
|
142
|
+
//#region src/core/github.ts
|
|
143
|
+
var GitHubClient = class {
|
|
144
|
+
owner;
|
|
145
|
+
repo;
|
|
146
|
+
githubToken;
|
|
147
|
+
apiBase = "https://api.github.com";
|
|
148
|
+
constructor({ owner, repo, githubToken }) {
|
|
149
|
+
this.owner = owner;
|
|
150
|
+
this.repo = repo;
|
|
151
|
+
this.githubToken = githubToken;
|
|
242
152
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
153
|
+
async request(path, init = {}) {
|
|
154
|
+
const url = path.startsWith("http") ? path : `${this.apiBase}${path}`;
|
|
155
|
+
const res = await fetch(url, {
|
|
156
|
+
...init,
|
|
157
|
+
headers: {
|
|
158
|
+
...init.headers,
|
|
159
|
+
"Accept": "application/vnd.github.v3+json",
|
|
160
|
+
"Authorization": `token ${this.githubToken}`,
|
|
161
|
+
"User-Agent": "ucdjs-release-scripts (+https://github.com/ucdjs/ucdjs-release-scripts)"
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
const errorText = await res.text();
|
|
166
|
+
throw new Error(`GitHub API request failed with status ${res.status}: ${errorText || "No response body"}`);
|
|
167
|
+
}
|
|
168
|
+
if (res.status === 204) return;
|
|
169
|
+
return res.json();
|
|
248
170
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
171
|
+
async getExistingPullRequest(branch) {
|
|
172
|
+
const head = branch.includes(":") ? branch : `${this.owner}:${branch}`;
|
|
173
|
+
const endpoint = `/repos/${this.owner}/${this.repo}/pulls?state=open&head=${encodeURIComponent(head)}`;
|
|
174
|
+
logger.verbose(`Requesting pull request for branch: ${branch} (url: ${this.apiBase}${endpoint})`);
|
|
175
|
+
const pulls = await this.request(endpoint);
|
|
176
|
+
if (!Array.isArray(pulls) || pulls.length === 0) return null;
|
|
177
|
+
const firstPullRequest = pulls[0];
|
|
178
|
+
if (typeof firstPullRequest !== "object" || firstPullRequest === null || !("number" in firstPullRequest) || typeof firstPullRequest.number !== "number" || !("title" in firstPullRequest) || typeof firstPullRequest.title !== "string" || !("body" in firstPullRequest) || typeof firstPullRequest.body !== "string" || !("draft" in firstPullRequest) || typeof firstPullRequest.draft !== "boolean" || !("html_url" in firstPullRequest) || typeof firstPullRequest.html_url !== "string") throw new TypeError("Pull request data validation failed");
|
|
179
|
+
const pullRequest = {
|
|
180
|
+
number: firstPullRequest.number,
|
|
181
|
+
title: firstPullRequest.title,
|
|
182
|
+
body: firstPullRequest.body,
|
|
183
|
+
draft: firstPullRequest.draft,
|
|
184
|
+
html_url: firstPullRequest.html_url,
|
|
185
|
+
head: "head" in firstPullRequest && typeof firstPullRequest.head === "object" && firstPullRequest.head !== null && "sha" in firstPullRequest.head && typeof firstPullRequest.head.sha === "string" ? { sha: firstPullRequest.head.sha } : void 0
|
|
186
|
+
};
|
|
187
|
+
logger.info(`Found existing pull request: ${farver.yellow(`#${pullRequest.number}`)}`);
|
|
188
|
+
return pullRequest;
|
|
253
189
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
190
|
+
async upsertPullRequest({ title, body, head, base, pullNumber }) {
|
|
191
|
+
const isUpdate = typeof pullNumber === "number";
|
|
192
|
+
const endpoint = isUpdate ? `/repos/${this.owner}/${this.repo}/pulls/${pullNumber}` : `/repos/${this.owner}/${this.repo}/pulls`;
|
|
193
|
+
const requestBody = isUpdate ? {
|
|
194
|
+
title,
|
|
195
|
+
body
|
|
196
|
+
} : {
|
|
197
|
+
title,
|
|
198
|
+
body,
|
|
199
|
+
head,
|
|
200
|
+
base,
|
|
201
|
+
draft: true
|
|
202
|
+
};
|
|
203
|
+
logger.verbose(`${isUpdate ? "Updating" : "Creating"} pull request (url: ${this.apiBase}${endpoint})`);
|
|
204
|
+
const pr = await this.request(endpoint, {
|
|
205
|
+
method: isUpdate ? "PATCH" : "POST",
|
|
206
|
+
body: JSON.stringify(requestBody)
|
|
207
|
+
});
|
|
208
|
+
if (typeof pr !== "object" || pr === null || !("number" in pr) || typeof pr.number !== "number" || !("title" in pr) || typeof pr.title !== "string" || !("body" in pr) || typeof pr.body !== "string" || !("draft" in pr) || typeof pr.draft !== "boolean" || !("html_url" in pr) || typeof pr.html_url !== "string") throw new TypeError("Pull request data validation failed");
|
|
209
|
+
const action = isUpdate ? "Updated" : "Created";
|
|
210
|
+
logger.info(`${action} pull request: ${farver.yellow(`#${pr.number}`)}`);
|
|
211
|
+
return {
|
|
212
|
+
number: pr.number,
|
|
213
|
+
title: pr.title,
|
|
214
|
+
body: pr.body,
|
|
215
|
+
draft: pr.draft,
|
|
216
|
+
html_url: pr.html_url
|
|
217
|
+
};
|
|
268
218
|
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
"ls",
|
|
281
|
-
"--json"
|
|
282
|
-
], { nodeOptions: {
|
|
283
|
-
cwd: workspaceRoot,
|
|
284
|
-
stdio: "pipe"
|
|
285
|
-
} });
|
|
286
|
-
const rawProjects = JSON.parse(result.stdout);
|
|
287
|
-
const allPackageNames = new Set(rawProjects.map((p) => p.name));
|
|
288
|
-
const excludedPackages = /* @__PURE__ */ new Set();
|
|
289
|
-
const promises = rawProjects.map(async (rawProject) => {
|
|
290
|
-
const content = await readFile(join(rawProject.path, "package.json"), "utf-8");
|
|
291
|
-
const packageJson = JSON.parse(content);
|
|
292
|
-
if (!shouldIncludePackage(packageJson, options)) {
|
|
293
|
-
excludedPackages.add(rawProject.name);
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
return {
|
|
297
|
-
name: rawProject.name,
|
|
298
|
-
version: rawProject.version,
|
|
299
|
-
path: rawProject.path,
|
|
300
|
-
packageJson,
|
|
301
|
-
workspaceDependencies: Object.keys(rawProject.dependencies || []).filter((dep) => {
|
|
302
|
-
return allPackageNames.has(dep);
|
|
303
|
-
}),
|
|
304
|
-
workspaceDevDependencies: Object.keys(rawProject.devDependencies || []).filter((dep) => {
|
|
305
|
-
return allPackageNames.has(dep);
|
|
306
|
-
})
|
|
307
|
-
};
|
|
219
|
+
async setCommitStatus({ sha, state, targetUrl, description, context }) {
|
|
220
|
+
const endpoint = `/repos/${this.owner}/${this.repo}/statuses/${sha}`;
|
|
221
|
+
logger.verbose(`Setting commit status on ${sha} to ${state} (url: ${this.apiBase}${endpoint})`);
|
|
222
|
+
await this.request(endpoint, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
body: JSON.stringify({
|
|
225
|
+
state,
|
|
226
|
+
target_url: targetUrl,
|
|
227
|
+
description: description || "",
|
|
228
|
+
context
|
|
229
|
+
})
|
|
308
230
|
});
|
|
309
|
-
|
|
310
|
-
if (excludedPackages.size > 0) logger.info(`Excluded packages: ${farver.green(Array.from(excludedPackages).join(", "))}`);
|
|
311
|
-
return packages.filter((pkg) => pkg !== null);
|
|
312
|
-
} catch (err) {
|
|
313
|
-
logger.error("Error discovering workspace packages:", err);
|
|
314
|
-
throw err;
|
|
231
|
+
logger.info(`Commit status set to ${farver.cyan(state)} for ${farver.gray(sha.substring(0, 7))}`);
|
|
315
232
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
233
|
+
async resolveAuthorInfo(info) {
|
|
234
|
+
if (info.login) return info;
|
|
235
|
+
try {
|
|
236
|
+
const q = encodeURIComponent(`${info.email} type:user in:email`);
|
|
237
|
+
const data = await this.request(`/search/users?q=${q}`);
|
|
238
|
+
if (!data.items || data.items.length === 0) return info;
|
|
239
|
+
info.login = data.items[0].login;
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logger.warn(`Failed to resolve author info for email ${info.email}: ${err.message}`);
|
|
242
|
+
}
|
|
243
|
+
if (info.login) return info;
|
|
244
|
+
if (info.commits.length > 0) try {
|
|
245
|
+
const data = await this.request(`/repos/${this.owner}/${this.repo}/commits/${info.commits[0]}`);
|
|
246
|
+
if (data.author && data.author.login) info.login = data.author.login;
|
|
247
|
+
} catch (err) {
|
|
248
|
+
logger.warn(`Failed to resolve author info from commits for email ${info.email}: ${err.message}`);
|
|
249
|
+
}
|
|
250
|
+
return info;
|
|
322
251
|
}
|
|
323
|
-
|
|
324
|
-
|
|
252
|
+
};
|
|
253
|
+
function createGitHubClient(options) {
|
|
254
|
+
return new GitHubClient(options);
|
|
255
|
+
}
|
|
256
|
+
function dedentString(str) {
|
|
257
|
+
const lines = str.split("\n");
|
|
258
|
+
const minIndent = lines.filter((line) => line.trim().length > 0).reduce((min, line) => Math.min(min, line.search(/\S/)), Infinity);
|
|
259
|
+
return lines.map((line) => minIndent === Infinity ? line : line.slice(minIndent)).join("\n").trim();
|
|
260
|
+
}
|
|
261
|
+
function generatePullRequestBody(updates, body) {
|
|
262
|
+
const eta = new Eta();
|
|
263
|
+
const bodyTemplate = body ? dedentString(body) : DEFAULT_PR_BODY_TEMPLATE;
|
|
264
|
+
return eta.renderString(bodyTemplate, { packages: updates.map((u) => ({
|
|
265
|
+
name: u.package.name,
|
|
266
|
+
currentVersion: u.currentVersion,
|
|
267
|
+
newVersion: u.newVersion,
|
|
268
|
+
bumpType: u.bumpType,
|
|
269
|
+
hasDirectChanges: u.hasDirectChanges
|
|
270
|
+
})) });
|
|
325
271
|
}
|
|
326
272
|
|
|
327
273
|
//#endregion
|
|
@@ -389,6 +335,11 @@ function normalizeReleaseScriptsOptions(options) {
|
|
|
389
335
|
githubToken: token,
|
|
390
336
|
owner,
|
|
391
337
|
repo,
|
|
338
|
+
githubClient: createGitHubClient({
|
|
339
|
+
owner,
|
|
340
|
+
repo,
|
|
341
|
+
githubToken: token
|
|
342
|
+
}),
|
|
392
343
|
packages: normalizedPackages,
|
|
393
344
|
branch: {
|
|
394
345
|
release: branch.release ?? "release/next",
|
|
@@ -414,75 +365,29 @@ function normalizeReleaseScriptsOptions(options) {
|
|
|
414
365
|
provenance: npm.provenance ?? true
|
|
415
366
|
},
|
|
416
367
|
prompts: {
|
|
417
|
-
versions: prompts.versions ?? !isCI,
|
|
418
|
-
packages: prompts.packages ?? !isCI
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
//#endregion
|
|
424
|
-
//#region src/operations/changelog-format.ts
|
|
425
|
-
function formatCommitLine({ commit, owner, repo, authors }) {
|
|
426
|
-
const commitUrl = `https://github.com/${owner}/${repo}/commit/${commit.hash}`;
|
|
427
|
-
let line = `${commit.description}`;
|
|
428
|
-
const references = commit.references ?? [];
|
|
429
|
-
for (const ref of references) {
|
|
430
|
-
if (!ref.value) continue;
|
|
431
|
-
const number = Number.parseInt(ref.value.replace(/^#/, ""), 10);
|
|
432
|
-
if (Number.isNaN(number)) continue;
|
|
433
|
-
if (ref.type === "issue") {
|
|
434
|
-
line += ` ([Issue ${ref.value}](https://github.com/${owner}/${repo}/issues/${number}))`;
|
|
435
|
-
continue;
|
|
368
|
+
versions: prompts.versions ?? !isCI,
|
|
369
|
+
packages: prompts.packages ?? !isCI
|
|
436
370
|
}
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
line += ` ([${commit.shortHash}](${commitUrl}))`;
|
|
440
|
-
if (authors.length > 0) {
|
|
441
|
-
const authorList = authors.map((author) => author.login ? `[@${author.login}](https://github.com/${author.login})` : author.name).join(", ");
|
|
442
|
-
line += ` (by ${authorList})`;
|
|
443
|
-
}
|
|
444
|
-
return line;
|
|
371
|
+
};
|
|
445
372
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
name: key,
|
|
461
|
-
title: value.title,
|
|
462
|
-
commits: formattedCommits
|
|
463
|
-
};
|
|
464
|
-
});
|
|
373
|
+
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/types.ts
|
|
376
|
+
function ok(value) {
|
|
377
|
+
return {
|
|
378
|
+
ok: true,
|
|
379
|
+
value
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function err(error) {
|
|
383
|
+
return {
|
|
384
|
+
ok: false,
|
|
385
|
+
error
|
|
386
|
+
};
|
|
465
387
|
}
|
|
466
388
|
|
|
467
389
|
//#endregion
|
|
468
390
|
//#region src/core/git.ts
|
|
469
|
-
/**
|
|
470
|
-
* Check if the working directory is clean (no uncommitted changes)
|
|
471
|
-
* @param {string} workspaceRoot - The root directory of the workspace
|
|
472
|
-
* @returns {Promise<boolean>} A Promise resolving to true if clean, false otherwise
|
|
473
|
-
*/
|
|
474
|
-
async function isWorkingDirectoryClean(workspaceRoot) {
|
|
475
|
-
try {
|
|
476
|
-
if ((await run("git", ["status", "--porcelain"], { nodeOptions: {
|
|
477
|
-
cwd: workspaceRoot,
|
|
478
|
-
stdio: "pipe"
|
|
479
|
-
} })).stdout.trim() !== "") return false;
|
|
480
|
-
return true;
|
|
481
|
-
} catch (err) {
|
|
482
|
-
logger.error("Error checking git status:", err);
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
391
|
function toGitError(operation, error) {
|
|
487
392
|
return {
|
|
488
393
|
type: "git",
|
|
@@ -491,71 +396,16 @@ function toGitError(operation, error) {
|
|
|
491
396
|
stderr: (typeof error === "object" && error && "stderr" in error ? String(error.stderr ?? "") : void 0)?.trim() || void 0
|
|
492
397
|
};
|
|
493
398
|
}
|
|
494
|
-
async function
|
|
399
|
+
async function isWorkingDirectoryClean(workspaceRoot) {
|
|
495
400
|
try {
|
|
496
|
-
return ok(await
|
|
401
|
+
return ok((await run("git", ["status", "--porcelain"], { nodeOptions: {
|
|
402
|
+
cwd: workspaceRoot,
|
|
403
|
+
stdio: "pipe"
|
|
404
|
+
} })).stdout.trim() === "");
|
|
497
405
|
} catch (error) {
|
|
498
|
-
return err(toGitError(
|
|
406
|
+
return err(toGitError("isWorkingDirectoryClean", error));
|
|
499
407
|
}
|
|
500
408
|
}
|
|
501
|
-
function createGitOperations(overrides = {}) {
|
|
502
|
-
return {
|
|
503
|
-
isWorkingDirectoryClean: (workspaceRoot) => wrapGit("isWorkingDirectoryClean", async () => {
|
|
504
|
-
if (overrides.isWorkingDirectoryClean) return overrides.isWorkingDirectoryClean(workspaceRoot);
|
|
505
|
-
return isWorkingDirectoryClean(workspaceRoot);
|
|
506
|
-
}),
|
|
507
|
-
doesBranchExist: (branch, workspaceRoot) => wrapGit("doesBranchExist", async () => {
|
|
508
|
-
if (overrides.doesBranchExist) return overrides.doesBranchExist(branch, workspaceRoot);
|
|
509
|
-
return doesBranchExist(branch, workspaceRoot);
|
|
510
|
-
}),
|
|
511
|
-
getCurrentBranch: (workspaceRoot) => wrapGit("getCurrentBranch", async () => {
|
|
512
|
-
if (overrides.getCurrentBranch) return overrides.getCurrentBranch(workspaceRoot);
|
|
513
|
-
return getCurrentBranch(workspaceRoot);
|
|
514
|
-
}),
|
|
515
|
-
checkoutBranch: (branch, workspaceRoot) => wrapGit("checkoutBranch", async () => {
|
|
516
|
-
if (overrides.checkoutBranch) return overrides.checkoutBranch(branch, workspaceRoot);
|
|
517
|
-
return checkoutBranch(branch, workspaceRoot);
|
|
518
|
-
}),
|
|
519
|
-
createBranch: (branch, base, workspaceRoot) => wrapGit("createBranch", async () => {
|
|
520
|
-
if (overrides.createBranch) {
|
|
521
|
-
await overrides.createBranch(branch, base, workspaceRoot);
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
await createBranch(branch, base, workspaceRoot);
|
|
525
|
-
}),
|
|
526
|
-
pullLatestChanges: (branch, workspaceRoot) => wrapGit("pullLatestChanges", async () => {
|
|
527
|
-
if (overrides.pullLatestChanges) return overrides.pullLatestChanges(branch, workspaceRoot);
|
|
528
|
-
return pullLatestChanges(branch, workspaceRoot);
|
|
529
|
-
}),
|
|
530
|
-
rebaseBranch: (ontoBranch, workspaceRoot) => wrapGit("rebaseBranch", async () => {
|
|
531
|
-
if (overrides.rebaseBranch) {
|
|
532
|
-
await overrides.rebaseBranch(ontoBranch, workspaceRoot);
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
await rebaseBranch(ontoBranch, workspaceRoot);
|
|
536
|
-
}),
|
|
537
|
-
isBranchAheadOfRemote: (branch, workspaceRoot) => wrapGit("isBranchAheadOfRemote", async () => {
|
|
538
|
-
if (overrides.isBranchAheadOfRemote) return overrides.isBranchAheadOfRemote(branch, workspaceRoot);
|
|
539
|
-
return isBranchAheadOfRemote(branch, workspaceRoot);
|
|
540
|
-
}),
|
|
541
|
-
commitChanges: (message, workspaceRoot) => wrapGit("commitChanges", async () => {
|
|
542
|
-
if (overrides.commitChanges) return overrides.commitChanges(message, workspaceRoot);
|
|
543
|
-
return commitChanges(message, workspaceRoot);
|
|
544
|
-
}),
|
|
545
|
-
pushBranch: (branch, workspaceRoot, options) => wrapGit("pushBranch", async () => {
|
|
546
|
-
if (overrides.pushBranch) return overrides.pushBranch(branch, workspaceRoot, options);
|
|
547
|
-
return pushBranch(branch, workspaceRoot, options);
|
|
548
|
-
}),
|
|
549
|
-
readFileFromGit: (workspaceRoot, ref, filePath) => wrapGit("readFileFromGit", async () => {
|
|
550
|
-
if (overrides.readFileFromGit) return overrides.readFileFromGit(workspaceRoot, ref, filePath);
|
|
551
|
-
return readFileFromGit(workspaceRoot, ref, filePath);
|
|
552
|
-
}),
|
|
553
|
-
getMostRecentPackageTag: (workspaceRoot, packageName) => wrapGit("getMostRecentPackageTag", async () => {
|
|
554
|
-
if (overrides.getMostRecentPackageTag) return overrides.getMostRecentPackageTag(workspaceRoot, packageName);
|
|
555
|
-
return getMostRecentPackageTag(workspaceRoot, packageName);
|
|
556
|
-
})
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
409
|
/**
|
|
560
410
|
* Check if a git branch exists locally
|
|
561
411
|
* @param {string} branch - The branch name to check
|
|
@@ -572,9 +422,9 @@ async function doesBranchExist(branch, workspaceRoot) {
|
|
|
572
422
|
cwd: workspaceRoot,
|
|
573
423
|
stdio: "pipe"
|
|
574
424
|
} });
|
|
575
|
-
return true;
|
|
425
|
+
return ok(true);
|
|
576
426
|
} catch {
|
|
577
|
-
return false;
|
|
427
|
+
return ok(false);
|
|
578
428
|
}
|
|
579
429
|
}
|
|
580
430
|
/**
|
|
@@ -584,17 +434,16 @@ async function doesBranchExist(branch, workspaceRoot) {
|
|
|
584
434
|
*/
|
|
585
435
|
async function getCurrentBranch(workspaceRoot) {
|
|
586
436
|
try {
|
|
587
|
-
return (await run("git", [
|
|
437
|
+
return ok((await run("git", [
|
|
588
438
|
"rev-parse",
|
|
589
439
|
"--abbrev-ref",
|
|
590
440
|
"HEAD"
|
|
591
441
|
], { nodeOptions: {
|
|
592
442
|
cwd: workspaceRoot,
|
|
593
443
|
stdio: "pipe"
|
|
594
|
-
} })).stdout.trim();
|
|
595
|
-
} catch (
|
|
596
|
-
|
|
597
|
-
throw err;
|
|
444
|
+
} })).stdout.trim());
|
|
445
|
+
} catch (error) {
|
|
446
|
+
return err(toGitError("getCurrentBranch", error));
|
|
598
447
|
}
|
|
599
448
|
}
|
|
600
449
|
/**
|
|
@@ -615,8 +464,9 @@ async function createBranch(branch, base, workspaceRoot) {
|
|
|
615
464
|
cwd: workspaceRoot,
|
|
616
465
|
stdio: "pipe"
|
|
617
466
|
} });
|
|
618
|
-
|
|
619
|
-
|
|
467
|
+
return ok(void 0);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
return err(toGitError("createBranch", error));
|
|
620
470
|
}
|
|
621
471
|
}
|
|
622
472
|
async function checkoutBranch(branch, workspaceRoot) {
|
|
@@ -628,11 +478,11 @@ async function checkoutBranch(branch, workspaceRoot) {
|
|
|
628
478
|
} })).stderr.trim().match(/Switched to branch '(.+)'/);
|
|
629
479
|
if (match && match[1] === branch) {
|
|
630
480
|
logger.info(`Successfully switched to branch: ${farver.green(branch)}`);
|
|
631
|
-
return true;
|
|
481
|
+
return ok(true);
|
|
632
482
|
}
|
|
633
|
-
return false;
|
|
634
|
-
} catch {
|
|
635
|
-
return
|
|
483
|
+
return ok(false);
|
|
484
|
+
} catch (error) {
|
|
485
|
+
return err(toGitError("checkoutBranch", error));
|
|
636
486
|
}
|
|
637
487
|
}
|
|
638
488
|
async function pullLatestChanges(branch, workspaceRoot) {
|
|
@@ -645,9 +495,9 @@ async function pullLatestChanges(branch, workspaceRoot) {
|
|
|
645
495
|
cwd: workspaceRoot,
|
|
646
496
|
stdio: "pipe"
|
|
647
497
|
} });
|
|
648
|
-
return true;
|
|
649
|
-
} catch {
|
|
650
|
-
return
|
|
498
|
+
return ok(true);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
return err(toGitError("pullLatestChanges", error));
|
|
651
501
|
}
|
|
652
502
|
}
|
|
653
503
|
async function rebaseBranch(ontoBranch, workspaceRoot) {
|
|
@@ -657,9 +507,9 @@ async function rebaseBranch(ontoBranch, workspaceRoot) {
|
|
|
657
507
|
cwd: workspaceRoot,
|
|
658
508
|
stdio: "pipe"
|
|
659
509
|
} });
|
|
660
|
-
return
|
|
661
|
-
} catch {
|
|
662
|
-
|
|
510
|
+
return ok(void 0);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
return err(toGitError("rebaseBranch", error));
|
|
663
513
|
}
|
|
664
514
|
}
|
|
665
515
|
async function isBranchAheadOfRemote(branch, workspaceRoot) {
|
|
@@ -672,9 +522,9 @@ async function isBranchAheadOfRemote(branch, workspaceRoot) {
|
|
|
672
522
|
cwd: workspaceRoot,
|
|
673
523
|
stdio: "pipe"
|
|
674
524
|
} });
|
|
675
|
-
return Number.parseInt(result.stdout.trim(), 10) > 0;
|
|
525
|
+
return ok(Number.parseInt(result.stdout.trim(), 10) > 0);
|
|
676
526
|
} catch {
|
|
677
|
-
return true;
|
|
527
|
+
return ok(true);
|
|
678
528
|
}
|
|
679
529
|
}
|
|
680
530
|
async function commitChanges(message, workspaceRoot) {
|
|
@@ -683,7 +533,8 @@ async function commitChanges(message, workspaceRoot) {
|
|
|
683
533
|
cwd: workspaceRoot,
|
|
684
534
|
stdio: "pipe"
|
|
685
535
|
} });
|
|
686
|
-
|
|
536
|
+
const isClean = await isWorkingDirectoryClean(workspaceRoot);
|
|
537
|
+
if (!isClean.ok || isClean.value) return ok(false);
|
|
687
538
|
logger.info(`Committing changes: ${farver.dim(message)}`);
|
|
688
539
|
await runIfNotDry("git", [
|
|
689
540
|
"commit",
|
|
@@ -693,9 +544,9 @@ async function commitChanges(message, workspaceRoot) {
|
|
|
693
544
|
cwd: workspaceRoot,
|
|
694
545
|
stdio: "pipe"
|
|
695
546
|
} });
|
|
696
|
-
return true;
|
|
697
|
-
} catch {
|
|
698
|
-
|
|
547
|
+
return ok(true);
|
|
548
|
+
} catch (error) {
|
|
549
|
+
return err(toGitError("commitChanges", error));
|
|
699
550
|
}
|
|
700
551
|
}
|
|
701
552
|
async function pushBranch(branch, workspaceRoot, options) {
|
|
@@ -706,6 +557,14 @@ async function pushBranch(branch, workspaceRoot, options) {
|
|
|
706
557
|
branch
|
|
707
558
|
];
|
|
708
559
|
if (options?.forceWithLease) {
|
|
560
|
+
await run("git", [
|
|
561
|
+
"fetch",
|
|
562
|
+
"origin",
|
|
563
|
+
branch
|
|
564
|
+
], { nodeOptions: {
|
|
565
|
+
cwd: workspaceRoot,
|
|
566
|
+
stdio: "pipe"
|
|
567
|
+
} });
|
|
709
568
|
args.push("--force-with-lease");
|
|
710
569
|
logger.info(`Pushing branch: ${farver.green(branch)} ${farver.dim("(with lease)")}`);
|
|
711
570
|
} else if (options?.force) {
|
|
@@ -716,19 +575,19 @@ async function pushBranch(branch, workspaceRoot, options) {
|
|
|
716
575
|
cwd: workspaceRoot,
|
|
717
576
|
stdio: "pipe"
|
|
718
577
|
} });
|
|
719
|
-
return true;
|
|
720
|
-
} catch {
|
|
721
|
-
|
|
578
|
+
return ok(true);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return err(toGitError("pushBranch", error));
|
|
722
581
|
}
|
|
723
582
|
}
|
|
724
583
|
async function readFileFromGit(workspaceRoot, ref, filePath) {
|
|
725
584
|
try {
|
|
726
|
-
return (await run("git", ["show", `${ref}:${filePath}`], { nodeOptions: {
|
|
585
|
+
return ok((await run("git", ["show", `${ref}:${filePath}`], { nodeOptions: {
|
|
727
586
|
cwd: workspaceRoot,
|
|
728
587
|
stdio: "pipe"
|
|
729
|
-
} })).stdout;
|
|
588
|
+
} })).stdout);
|
|
730
589
|
} catch {
|
|
731
|
-
return null;
|
|
590
|
+
return ok(null);
|
|
732
591
|
}
|
|
733
592
|
}
|
|
734
593
|
async function getMostRecentPackageTag(workspaceRoot, packageName) {
|
|
@@ -742,11 +601,10 @@ async function getMostRecentPackageTag(workspaceRoot, packageName) {
|
|
|
742
601
|
stdio: "pipe"
|
|
743
602
|
} });
|
|
744
603
|
const tags = stdout.split("\n").map((tag) => tag.trim()).filter(Boolean);
|
|
745
|
-
if (tags.length === 0) return;
|
|
746
|
-
return tags.reverse()[0];
|
|
747
|
-
} catch (
|
|
748
|
-
|
|
749
|
-
return;
|
|
604
|
+
if (tags.length === 0) return ok(void 0);
|
|
605
|
+
return ok(tags.reverse()[0]);
|
|
606
|
+
} catch (error) {
|
|
607
|
+
return err(toGitError("getMostRecentPackageTag", error));
|
|
750
608
|
}
|
|
751
609
|
}
|
|
752
610
|
/**
|
|
@@ -793,9 +651,9 @@ async function getGroupedFilesByCommitSha(workspaceRoot, from, to) {
|
|
|
793
651
|
if (currentSha === null) continue;
|
|
794
652
|
commitsMap.get(currentSha).push(trimmedLine);
|
|
795
653
|
}
|
|
796
|
-
return commitsMap;
|
|
797
|
-
} catch {
|
|
798
|
-
return
|
|
654
|
+
return ok(commitsMap);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
return err(toGitError("getGroupedFilesByCommitSha", error));
|
|
799
657
|
}
|
|
800
658
|
}
|
|
801
659
|
|
|
@@ -833,7 +691,7 @@ async function updateChangelog(options) {
|
|
|
833
691
|
const changelogPath = join(workspacePackage.path, "CHANGELOG.md");
|
|
834
692
|
const changelogRelativePath = relative(normalizedOptions.workspaceRoot, join(workspacePackage.path, "CHANGELOG.md"));
|
|
835
693
|
const existingContent = await readFileFromGit(normalizedOptions.workspaceRoot, normalizedOptions.branch.default, changelogRelativePath);
|
|
836
|
-
logger.verbose("Existing content found: ", Boolean(existingContent));
|
|
694
|
+
logger.verbose("Existing content found: ", existingContent.ok && Boolean(existingContent.value));
|
|
837
695
|
const newEntry = await generateChangelogEntry({
|
|
838
696
|
packageName: workspacePackage.name,
|
|
839
697
|
version,
|
|
@@ -847,13 +705,13 @@ async function updateChangelog(options) {
|
|
|
847
705
|
githubClient
|
|
848
706
|
});
|
|
849
707
|
let updatedContent;
|
|
850
|
-
if (!existingContent) {
|
|
708
|
+
if (!existingContent.ok || !existingContent.value) {
|
|
851
709
|
updatedContent = `# ${workspacePackage.name}\n\n${newEntry}\n`;
|
|
852
710
|
await writeFile(changelogPath, updatedContent, "utf-8");
|
|
853
711
|
return;
|
|
854
712
|
}
|
|
855
|
-
const parsed = parseChangelog(existingContent);
|
|
856
|
-
const lines = existingContent.split("\n");
|
|
713
|
+
const parsed = parseChangelog(existingContent.value);
|
|
714
|
+
const lines = existingContent.value.split("\n");
|
|
857
715
|
const existingVersionIndex = parsed.versions.findIndex((v) => v.version === version);
|
|
858
716
|
if (existingVersionIndex !== -1) {
|
|
859
717
|
const existingVersion = parsed.versions[existingVersionIndex];
|
|
@@ -944,269 +802,252 @@ function parseChangelog(content) {
|
|
|
944
802
|
}
|
|
945
803
|
|
|
946
804
|
//#endregion
|
|
947
|
-
//#region src/
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
if (res.status === 204) return;
|
|
974
|
-
return res.json();
|
|
975
|
-
}
|
|
976
|
-
async getExistingPullRequest(branch) {
|
|
977
|
-
const head = branch.includes(":") ? branch : `${this.owner}:${branch}`;
|
|
978
|
-
const endpoint = `/repos/${this.owner}/${this.repo}/pulls?state=open&head=${encodeURIComponent(head)}`;
|
|
979
|
-
logger.verbose(`Requesting pull request for branch: ${branch} (url: ${this.apiBase}${endpoint})`);
|
|
980
|
-
const pulls = await this.request(endpoint);
|
|
981
|
-
if (!Array.isArray(pulls) || pulls.length === 0) return null;
|
|
982
|
-
const firstPullRequest = pulls[0];
|
|
983
|
-
if (typeof firstPullRequest !== "object" || firstPullRequest === null || !("number" in firstPullRequest) || typeof firstPullRequest.number !== "number" || !("title" in firstPullRequest) || typeof firstPullRequest.title !== "string" || !("body" in firstPullRequest) || typeof firstPullRequest.body !== "string" || !("draft" in firstPullRequest) || typeof firstPullRequest.draft !== "boolean" || !("html_url" in firstPullRequest) || typeof firstPullRequest.html_url !== "string") throw new TypeError("Pull request data validation failed");
|
|
984
|
-
const pullRequest = {
|
|
985
|
-
number: firstPullRequest.number,
|
|
986
|
-
title: firstPullRequest.title,
|
|
987
|
-
body: firstPullRequest.body,
|
|
988
|
-
draft: firstPullRequest.draft,
|
|
989
|
-
html_url: firstPullRequest.html_url,
|
|
990
|
-
head: "head" in firstPullRequest && typeof firstPullRequest.head === "object" && firstPullRequest.head !== null && "sha" in firstPullRequest.head && typeof firstPullRequest.head.sha === "string" ? { sha: firstPullRequest.head.sha } : void 0
|
|
991
|
-
};
|
|
992
|
-
logger.info(`Found existing pull request: ${farver.yellow(`#${pullRequest.number}`)}`);
|
|
993
|
-
return pullRequest;
|
|
994
|
-
}
|
|
995
|
-
async upsertPullRequest({ title, body, head, base, pullNumber }) {
|
|
996
|
-
const isUpdate = typeof pullNumber === "number";
|
|
997
|
-
const endpoint = isUpdate ? `/repos/${this.owner}/${this.repo}/pulls/${pullNumber}` : `/repos/${this.owner}/${this.repo}/pulls`;
|
|
998
|
-
const requestBody = isUpdate ? {
|
|
999
|
-
title,
|
|
1000
|
-
body
|
|
1001
|
-
} : {
|
|
1002
|
-
title,
|
|
1003
|
-
body,
|
|
1004
|
-
head,
|
|
1005
|
-
base,
|
|
1006
|
-
draft: true
|
|
1007
|
-
};
|
|
1008
|
-
logger.verbose(`${isUpdate ? "Updating" : "Creating"} pull request (url: ${this.apiBase}${endpoint})`);
|
|
1009
|
-
const pr = await this.request(endpoint, {
|
|
1010
|
-
method: isUpdate ? "PATCH" : "POST",
|
|
1011
|
-
body: JSON.stringify(requestBody)
|
|
1012
|
-
});
|
|
1013
|
-
if (typeof pr !== "object" || pr === null || !("number" in pr) || typeof pr.number !== "number" || !("title" in pr) || typeof pr.title !== "string" || !("body" in pr) || typeof pr.body !== "string" || !("draft" in pr) || typeof pr.draft !== "boolean" || !("html_url" in pr) || typeof pr.html_url !== "string") throw new TypeError("Pull request data validation failed");
|
|
1014
|
-
const action = isUpdate ? "Updated" : "Created";
|
|
1015
|
-
logger.info(`${action} pull request: ${farver.yellow(`#${pr.number}`)}`);
|
|
1016
|
-
return {
|
|
1017
|
-
number: pr.number,
|
|
1018
|
-
title: pr.title,
|
|
1019
|
-
body: pr.body,
|
|
1020
|
-
draft: pr.draft,
|
|
1021
|
-
html_url: pr.html_url
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
async setCommitStatus({ sha, state, targetUrl, description, context }) {
|
|
1025
|
-
const endpoint = `/repos/${this.owner}/${this.repo}/statuses/${sha}`;
|
|
1026
|
-
logger.verbose(`Setting commit status on ${sha} to ${state} (url: ${this.apiBase}${endpoint})`);
|
|
1027
|
-
await this.request(endpoint, {
|
|
1028
|
-
method: "POST",
|
|
1029
|
-
body: JSON.stringify({
|
|
1030
|
-
state,
|
|
1031
|
-
target_url: targetUrl,
|
|
1032
|
-
description: description || "",
|
|
1033
|
-
context
|
|
1034
|
-
})
|
|
1035
|
-
});
|
|
1036
|
-
logger.info(`Commit status set to ${farver.cyan(state)} for ${farver.gray(sha.substring(0, 7))}`);
|
|
805
|
+
//#region src/operations/semver.ts
|
|
806
|
+
function isValidSemver(version) {
|
|
807
|
+
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(version);
|
|
808
|
+
}
|
|
809
|
+
function getNextVersion(currentVersion, bump) {
|
|
810
|
+
if (bump === "none") return currentVersion;
|
|
811
|
+
if (!isValidSemver(currentVersion)) throw new Error(`Cannot bump version for invalid semver: ${currentVersion}`);
|
|
812
|
+
const match = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
813
|
+
if (!match) throw new Error(`Invalid semver version: ${currentVersion}`);
|
|
814
|
+
const [, major, minor, patch] = match;
|
|
815
|
+
let newMajor = Number.parseInt(major, 10);
|
|
816
|
+
let newMinor = Number.parseInt(minor, 10);
|
|
817
|
+
let newPatch = Number.parseInt(patch, 10);
|
|
818
|
+
switch (bump) {
|
|
819
|
+
case "major":
|
|
820
|
+
newMajor += 1;
|
|
821
|
+
newMinor = 0;
|
|
822
|
+
newPatch = 0;
|
|
823
|
+
break;
|
|
824
|
+
case "minor":
|
|
825
|
+
newMinor += 1;
|
|
826
|
+
newPatch = 0;
|
|
827
|
+
break;
|
|
828
|
+
case "patch":
|
|
829
|
+
newPatch += 1;
|
|
830
|
+
break;
|
|
1037
831
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
832
|
+
return `${newMajor}.${newMinor}.${newPatch}`;
|
|
833
|
+
}
|
|
834
|
+
function calculateBumpType(oldVersion, newVersion) {
|
|
835
|
+
if (!isValidSemver(oldVersion) || !isValidSemver(newVersion)) throw new Error(`Cannot calculate bump type for invalid semver: ${oldVersion} or ${newVersion}`);
|
|
836
|
+
const oldParts = oldVersion.split(".").map(Number);
|
|
837
|
+
const newParts = newVersion.split(".").map(Number);
|
|
838
|
+
if (newParts[0] > oldParts[0]) return "major";
|
|
839
|
+
if (newParts[1] > oldParts[1]) return "minor";
|
|
840
|
+
if (newParts[2] > oldParts[2]) return "patch";
|
|
841
|
+
return "none";
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
//#endregion
|
|
845
|
+
//#region src/core/prompts.ts
|
|
846
|
+
async function selectPackagePrompt(packages) {
|
|
847
|
+
const response = await prompts({
|
|
848
|
+
type: "multiselect",
|
|
849
|
+
name: "selectedPackages",
|
|
850
|
+
message: "Select packages to release",
|
|
851
|
+
choices: packages.map((pkg) => ({
|
|
852
|
+
title: `${pkg.name} (${farver.bold(pkg.version)})`,
|
|
853
|
+
value: pkg.name,
|
|
854
|
+
selected: true
|
|
855
|
+
})),
|
|
856
|
+
min: 1,
|
|
857
|
+
hint: "Space to select/deselect. Return to submit.",
|
|
858
|
+
instructions: false
|
|
859
|
+
});
|
|
860
|
+
if (!response.selectedPackages || response.selectedPackages.length === 0) return [];
|
|
861
|
+
return response.selectedPackages;
|
|
862
|
+
}
|
|
863
|
+
async function selectVersionPrompt(workspaceRoot, pkg, currentVersion, suggestedVersion) {
|
|
864
|
+
const answers = await prompts([{
|
|
865
|
+
type: "autocomplete",
|
|
866
|
+
name: "version",
|
|
867
|
+
message: `${pkg.name}: ${farver.green(pkg.version)}`,
|
|
868
|
+
choices: [
|
|
869
|
+
{
|
|
870
|
+
value: "skip",
|
|
871
|
+
title: `skip ${farver.dim("(no change)")}`
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
value: "major",
|
|
875
|
+
title: `major ${farver.bold(getNextVersion(pkg.version, "major"))}`
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
value: "minor",
|
|
879
|
+
title: `minor ${farver.bold(getNextVersion(pkg.version, "minor"))}`
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
value: "patch",
|
|
883
|
+
title: `patch ${farver.bold(getNextVersion(pkg.version, "patch"))}`
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
value: "suggested",
|
|
887
|
+
title: `suggested ${farver.bold(suggestedVersion)}`
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
value: "as-is",
|
|
891
|
+
title: `as-is ${farver.dim("(keep current version)")}`
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
value: "custom",
|
|
895
|
+
title: "custom"
|
|
896
|
+
}
|
|
897
|
+
],
|
|
898
|
+
initial: suggestedVersion === currentVersion ? 0 : 4
|
|
899
|
+
}, {
|
|
900
|
+
type: (prev) => prev === "custom" ? "text" : null,
|
|
901
|
+
name: "custom",
|
|
902
|
+
message: "Enter the new version number:",
|
|
903
|
+
initial: suggestedVersion,
|
|
904
|
+
validate: (custom) => {
|
|
905
|
+
if (isValidSemver(custom)) return true;
|
|
906
|
+
return "That's not a valid version number";
|
|
1054
907
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
908
|
+
}]);
|
|
909
|
+
if (!answers.version) return null;
|
|
910
|
+
if (answers.version === "skip") return null;
|
|
911
|
+
else if (answers.version === "suggested") return suggestedVersion;
|
|
912
|
+
else if (answers.version === "custom") {
|
|
913
|
+
if (!answers.custom) return null;
|
|
914
|
+
return answers.custom;
|
|
915
|
+
} else if (answers.version === "as-is") return currentVersion;
|
|
916
|
+
else return getNextVersion(pkg.version, answers.version);
|
|
1060
917
|
}
|
|
1061
|
-
|
|
918
|
+
|
|
919
|
+
//#endregion
|
|
920
|
+
//#region src/core/workspace.ts
|
|
921
|
+
function toWorkspaceError(operation, error) {
|
|
1062
922
|
return {
|
|
1063
|
-
type: "
|
|
923
|
+
type: "workspace",
|
|
1064
924
|
operation,
|
|
1065
925
|
message: error instanceof Error ? error.message : String(error)
|
|
1066
926
|
};
|
|
1067
927
|
}
|
|
1068
|
-
async function
|
|
928
|
+
async function discoverWorkspacePackages(workspaceRoot, options) {
|
|
929
|
+
let workspaceOptions;
|
|
930
|
+
let explicitPackages;
|
|
931
|
+
if (options.packages == null || options.packages === true) workspaceOptions = { excludePrivate: false };
|
|
932
|
+
else if (Array.isArray(options.packages)) {
|
|
933
|
+
workspaceOptions = {
|
|
934
|
+
excludePrivate: false,
|
|
935
|
+
include: options.packages
|
|
936
|
+
};
|
|
937
|
+
explicitPackages = options.packages;
|
|
938
|
+
} else {
|
|
939
|
+
workspaceOptions = options.packages;
|
|
940
|
+
if (options.packages.include) explicitPackages = options.packages.include;
|
|
941
|
+
}
|
|
942
|
+
let workspacePackages;
|
|
1069
943
|
try {
|
|
1070
|
-
|
|
944
|
+
workspacePackages = await findWorkspacePackages(workspaceRoot, workspaceOptions);
|
|
1071
945
|
} catch (error) {
|
|
1072
|
-
return err(
|
|
946
|
+
return err(toWorkspaceError("discoverWorkspacePackages", error));
|
|
947
|
+
}
|
|
948
|
+
if (explicitPackages) {
|
|
949
|
+
const foundNames = new Set(workspacePackages.map((p) => p.name));
|
|
950
|
+
const missing = explicitPackages.filter((p) => !foundNames.has(p));
|
|
951
|
+
if (missing.length > 0) exitWithError(`Package${missing.length > 1 ? "s" : ""} not found in workspace: ${missing.join(", ")}`, "Check your package names or run 'pnpm ls' to see available packages");
|
|
952
|
+
}
|
|
953
|
+
const isPackagePromptEnabled = options.prompts?.packages !== false;
|
|
954
|
+
if (!isCI && isPackagePromptEnabled && !explicitPackages) {
|
|
955
|
+
const selectedNames = await selectPackagePrompt(workspacePackages);
|
|
956
|
+
workspacePackages = workspacePackages.filter((pkg) => selectedNames.includes(pkg.name));
|
|
1073
957
|
}
|
|
958
|
+
return ok(workspacePackages);
|
|
1074
959
|
}
|
|
1075
|
-
function
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
960
|
+
async function findWorkspacePackages(workspaceRoot, options) {
|
|
961
|
+
try {
|
|
962
|
+
const result = await run("pnpm", [
|
|
963
|
+
"-r",
|
|
964
|
+
"ls",
|
|
965
|
+
"--json"
|
|
966
|
+
], { nodeOptions: {
|
|
967
|
+
cwd: workspaceRoot,
|
|
968
|
+
stdio: "pipe"
|
|
969
|
+
} });
|
|
970
|
+
const rawProjects = JSON.parse(result.stdout);
|
|
971
|
+
const allPackageNames = new Set(rawProjects.map((p) => p.name));
|
|
972
|
+
const excludedPackages = /* @__PURE__ */ new Set();
|
|
973
|
+
const promises = rawProjects.map(async (rawProject) => {
|
|
974
|
+
const content = await readFile(join(rawProject.path, "package.json"), "utf-8");
|
|
975
|
+
const packageJson = JSON.parse(content);
|
|
976
|
+
if (!shouldIncludePackage(packageJson, options)) {
|
|
977
|
+
excludedPackages.add(rawProject.name);
|
|
978
|
+
return null;
|
|
1090
979
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
980
|
+
return {
|
|
981
|
+
name: rawProject.name,
|
|
982
|
+
version: rawProject.version,
|
|
983
|
+
path: rawProject.path,
|
|
984
|
+
packageJson,
|
|
985
|
+
workspaceDependencies: Object.keys(rawProject.dependencies || []).filter((dep) => {
|
|
986
|
+
return allPackageNames.has(dep);
|
|
987
|
+
}),
|
|
988
|
+
workspaceDevDependencies: Object.keys(rawProject.devDependencies || []).filter((dep) => {
|
|
989
|
+
return allPackageNames.has(dep);
|
|
990
|
+
})
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
const packages = await Promise.all(promises);
|
|
994
|
+
if (excludedPackages.size > 0) logger.info(`Excluded packages: ${farver.green(Array.from(excludedPackages).join(", "))}`);
|
|
995
|
+
return packages.filter((pkg) => pkg !== null);
|
|
996
|
+
} catch (err) {
|
|
997
|
+
logger.error("Error discovering workspace packages:", err);
|
|
998
|
+
throw err;
|
|
999
|
+
}
|
|
1103
1000
|
}
|
|
1104
|
-
function
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
hasDirectChanges: u.hasDirectChanges
|
|
1113
|
-
})) });
|
|
1001
|
+
function shouldIncludePackage(pkg, options) {
|
|
1002
|
+
if (!options) return true;
|
|
1003
|
+
if (options.excludePrivate && pkg.private) return false;
|
|
1004
|
+
if (options.include && options.include.length > 0) {
|
|
1005
|
+
if (!options.include.includes(pkg.name)) return false;
|
|
1006
|
+
}
|
|
1007
|
+
if (options.exclude?.includes(pkg.name)) return false;
|
|
1008
|
+
return true;
|
|
1114
1009
|
}
|
|
1115
1010
|
|
|
1116
1011
|
//#endregion
|
|
1117
1012
|
//#region src/operations/branch.ts
|
|
1118
1013
|
async function prepareReleaseBranch(options) {
|
|
1119
|
-
const {
|
|
1120
|
-
const currentBranch = await
|
|
1014
|
+
const { workspaceRoot, releaseBranch, defaultBranch } = options;
|
|
1015
|
+
const currentBranch = await getCurrentBranch(workspaceRoot);
|
|
1121
1016
|
if (!currentBranch.ok) return currentBranch;
|
|
1122
1017
|
if (currentBranch.value !== defaultBranch) return err({
|
|
1123
1018
|
type: "git",
|
|
1124
1019
|
operation: "validateBranch",
|
|
1125
1020
|
message: `Current branch is '${currentBranch.value}'. Please switch to '${defaultBranch}'.`
|
|
1126
1021
|
});
|
|
1127
|
-
const branchExists = await
|
|
1022
|
+
const branchExists = await doesBranchExist(releaseBranch, workspaceRoot);
|
|
1128
1023
|
if (!branchExists.ok) return branchExists;
|
|
1129
1024
|
if (!branchExists.value) {
|
|
1130
|
-
const created = await
|
|
1025
|
+
const created = await createBranch(releaseBranch, defaultBranch, workspaceRoot);
|
|
1131
1026
|
if (!created.ok) return created;
|
|
1132
1027
|
}
|
|
1133
|
-
const checkedOut = await
|
|
1028
|
+
const checkedOut = await checkoutBranch(releaseBranch, workspaceRoot);
|
|
1134
1029
|
if (!checkedOut.ok) return checkedOut;
|
|
1135
1030
|
if (branchExists.value) {
|
|
1136
|
-
const pulled = await
|
|
1031
|
+
const pulled = await pullLatestChanges(releaseBranch, workspaceRoot);
|
|
1137
1032
|
if (!pulled.ok) return pulled;
|
|
1138
1033
|
if (!pulled.value) logger.warn("Failed to pull latest changes, continuing anyway.");
|
|
1139
1034
|
}
|
|
1140
|
-
const rebased = await
|
|
1035
|
+
const rebased = await rebaseBranch(defaultBranch, workspaceRoot);
|
|
1141
1036
|
if (!rebased.ok) return rebased;
|
|
1142
1037
|
return ok(void 0);
|
|
1143
1038
|
}
|
|
1144
1039
|
async function syncReleaseChanges(options) {
|
|
1145
|
-
const {
|
|
1146
|
-
const committed = hasChanges ? await
|
|
1040
|
+
const { workspaceRoot, releaseBranch, commitMessage, hasChanges } = options;
|
|
1041
|
+
const committed = hasChanges ? await commitChanges(commitMessage, workspaceRoot) : ok(false);
|
|
1147
1042
|
if (!committed.ok) return committed;
|
|
1148
|
-
const isAhead = await
|
|
1043
|
+
const isAhead = await isBranchAheadOfRemote(releaseBranch, workspaceRoot);
|
|
1149
1044
|
if (!isAhead.ok) return isAhead;
|
|
1150
1045
|
if (!committed.value && !isAhead.value) return ok(false);
|
|
1151
|
-
const pushed = await
|
|
1046
|
+
const pushed = await pushBranch(releaseBranch, workspaceRoot, { forceWithLease: true });
|
|
1152
1047
|
if (!pushed.ok) return pushed;
|
|
1153
1048
|
return ok(true);
|
|
1154
1049
|
}
|
|
1155
1050
|
|
|
1156
|
-
//#endregion
|
|
1157
|
-
//#region src/operations/calculate.ts
|
|
1158
|
-
async function calculateUpdates(options) {
|
|
1159
|
-
const { versioning, workspacePackages, workspaceRoot, showPrompt, overrides, globalCommitMode } = options;
|
|
1160
|
-
const grouped = await versioning.getWorkspacePackageGroupedCommits(workspaceRoot, workspacePackages);
|
|
1161
|
-
if (!grouped.ok) return grouped;
|
|
1162
|
-
const global = await versioning.getGlobalCommitsPerPackage(workspaceRoot, grouped.value, workspacePackages, globalCommitMode);
|
|
1163
|
-
if (!global.ok) return global;
|
|
1164
|
-
const updates = await versioning.calculateAndPrepareVersionUpdates({
|
|
1165
|
-
workspacePackages,
|
|
1166
|
-
packageCommits: grouped.value,
|
|
1167
|
-
workspaceRoot,
|
|
1168
|
-
showPrompt,
|
|
1169
|
-
globalCommitsPerPackage: global.value,
|
|
1170
|
-
overrides
|
|
1171
|
-
});
|
|
1172
|
-
if (!updates.ok) return updates;
|
|
1173
|
-
return updates;
|
|
1174
|
-
}
|
|
1175
|
-
function ensureHasPackages(packages) {
|
|
1176
|
-
if (packages.length === 0) return err({
|
|
1177
|
-
type: "git",
|
|
1178
|
-
operation: "discoverPackages",
|
|
1179
|
-
message: "No packages found to release"
|
|
1180
|
-
});
|
|
1181
|
-
return {
|
|
1182
|
-
ok: true,
|
|
1183
|
-
value: packages
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
//#endregion
|
|
1188
|
-
//#region src/operations/pr.ts
|
|
1189
|
-
async function syncPullRequest(options) {
|
|
1190
|
-
const { github, releaseBranch, defaultBranch, pullRequestTitle, pullRequestBody, updates } = options;
|
|
1191
|
-
const existing = await github.getExistingPullRequest(releaseBranch);
|
|
1192
|
-
if (!existing.ok) return existing;
|
|
1193
|
-
const doesExist = !!existing.value;
|
|
1194
|
-
const title = existing.value?.title || pullRequestTitle || "chore: update package versions";
|
|
1195
|
-
const body = generatePullRequestBody(updates, pullRequestBody);
|
|
1196
|
-
const pr = await github.upsertPullRequest({
|
|
1197
|
-
pullNumber: existing.value?.number,
|
|
1198
|
-
title,
|
|
1199
|
-
body,
|
|
1200
|
-
head: releaseBranch,
|
|
1201
|
-
base: defaultBranch
|
|
1202
|
-
});
|
|
1203
|
-
if (!pr.ok) return pr;
|
|
1204
|
-
return ok({
|
|
1205
|
-
pullRequest: pr.value,
|
|
1206
|
-
created: !doesExist
|
|
1207
|
-
});
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
1051
|
//#endregion
|
|
1211
1052
|
//#region src/versioning/commits.ts
|
|
1212
1053
|
/**
|
|
@@ -1220,7 +1061,8 @@ async function syncPullRequest(options) {
|
|
|
1220
1061
|
async function getWorkspacePackageGroupedCommits(workspaceRoot, packages) {
|
|
1221
1062
|
const changedPackages = /* @__PURE__ */ new Map();
|
|
1222
1063
|
const promises = packages.map(async (pkg) => {
|
|
1223
|
-
const
|
|
1064
|
+
const lastTagResult = await getMostRecentPackageTag(workspaceRoot, pkg.name);
|
|
1065
|
+
const lastTag = lastTagResult.ok ? lastTagResult.value : void 0;
|
|
1224
1066
|
const allCommits = await getCommits({
|
|
1225
1067
|
from: lastTag,
|
|
1226
1068
|
to: "HEAD",
|
|
@@ -1320,17 +1162,17 @@ async function getGlobalCommitsPerPackage(workspaceRoot, packageCommits, allPack
|
|
|
1320
1162
|
}
|
|
1321
1163
|
logger.verbose("Fetching files for commits range", `${farver.cyan(commitRange.oldest)}..${farver.cyan(commitRange.newest)}`);
|
|
1322
1164
|
const commitFilesMap = await getGroupedFilesByCommitSha(workspaceRoot, commitRange.oldest, commitRange.newest);
|
|
1323
|
-
if (!commitFilesMap) {
|
|
1165
|
+
if (!commitFilesMap.ok) {
|
|
1324
1166
|
logger.warn("Failed to get commit file list, returning empty global commits");
|
|
1325
1167
|
return result;
|
|
1326
1168
|
}
|
|
1327
|
-
logger.verbose("Got file lists for commits", `${farver.cyan(commitFilesMap.size)} commits in ONE git call`);
|
|
1169
|
+
logger.verbose("Got file lists for commits", `${farver.cyan(commitFilesMap.value.size)} commits in ONE git call`);
|
|
1328
1170
|
const packagePaths = new Set(allPackages.map((p) => p.path));
|
|
1329
1171
|
for (const [pkgName, commits] of packageCommits) {
|
|
1330
1172
|
const globalCommitsAffectingPackage = [];
|
|
1331
1173
|
logger.verbose("Filtering global commits for package", `${farver.bold(pkgName)} from ${farver.cyan(commits.length)} commits`);
|
|
1332
1174
|
for (const commit of commits) {
|
|
1333
|
-
const files = commitFilesMap.get(commit.shortHash);
|
|
1175
|
+
const files = commitFilesMap.value.get(commit.shortHash);
|
|
1334
1176
|
if (!files) continue;
|
|
1335
1177
|
if (isGlobalCommit(workspaceRoot, files, packagePaths)) globalCommitsAffectingPackage.push(commit);
|
|
1336
1178
|
}
|
|
@@ -1341,7 +1183,7 @@ async function getGlobalCommitsPerPackage(workspaceRoot, packageCommits, allPack
|
|
|
1341
1183
|
}
|
|
1342
1184
|
const dependencyCommits = [];
|
|
1343
1185
|
for (const commit of globalCommitsAffectingPackage) {
|
|
1344
|
-
const files = commitFilesMap.get(commit.shortHash);
|
|
1186
|
+
const files = commitFilesMap.value.get(commit.shortHash);
|
|
1345
1187
|
if (!files) continue;
|
|
1346
1188
|
if (files.some((file) => DEPENDENCY_FILES.includes(file.startsWith("./") ? file.slice(2) : file))) {
|
|
1347
1189
|
logger.verbose("Global commit affects dependencies", `${farver.bold(pkgName)}: commit ${farver.cyan(commit.shortHash)} affects dependencies`);
|
|
@@ -1663,79 +1505,89 @@ function getDependencyUpdates(pkg, allUpdates) {
|
|
|
1663
1505
|
}
|
|
1664
1506
|
|
|
1665
1507
|
//#endregion
|
|
1666
|
-
//#region src/
|
|
1667
|
-
function
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1508
|
+
//#region src/operations/calculate.ts
|
|
1509
|
+
async function calculateUpdates(options) {
|
|
1510
|
+
const { workspacePackages, workspaceRoot, showPrompt, overrides, globalCommitMode } = options;
|
|
1511
|
+
try {
|
|
1512
|
+
const grouped = await getWorkspacePackageGroupedCommits(workspaceRoot, workspacePackages);
|
|
1513
|
+
return ok(await calculateAndPrepareVersionUpdates({
|
|
1514
|
+
workspacePackages,
|
|
1515
|
+
packageCommits: grouped,
|
|
1516
|
+
workspaceRoot,
|
|
1517
|
+
showPrompt,
|
|
1518
|
+
globalCommitsPerPackage: await getGlobalCommitsPerPackage(workspaceRoot, grouped, workspacePackages, globalCommitMode),
|
|
1519
|
+
overrides
|
|
1520
|
+
}));
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
return err({
|
|
1523
|
+
type: "git",
|
|
1524
|
+
operation: "calculateUpdates",
|
|
1525
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
function ensureHasPackages(packages) {
|
|
1530
|
+
if (packages.length === 0) return err({
|
|
1531
|
+
type: "git",
|
|
1532
|
+
operation: "discoverWorkspacePackages",
|
|
1533
|
+
message: "No packages found to release"
|
|
1534
|
+
});
|
|
1535
|
+
return ok(packages);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
//#endregion
|
|
1539
|
+
//#region src/operations/pr.ts
|
|
1540
|
+
async function syncPullRequest(options) {
|
|
1541
|
+
const { github, releaseBranch, defaultBranch, pullRequestTitle, pullRequestBody, updates } = options;
|
|
1542
|
+
let existing = null;
|
|
1543
|
+
try {
|
|
1544
|
+
existing = await github.getExistingPullRequest(releaseBranch);
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
return {
|
|
1547
|
+
ok: false,
|
|
1548
|
+
error: {
|
|
1549
|
+
type: "github",
|
|
1550
|
+
operation: "getExistingPullRequest",
|
|
1551
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1701
1552
|
}
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
const doesExist = !!existing;
|
|
1556
|
+
const title = existing?.title || pullRequestTitle || "chore: update package versions";
|
|
1557
|
+
const body = generatePullRequestBody(updates, pullRequestBody);
|
|
1558
|
+
let pr = null;
|
|
1559
|
+
try {
|
|
1560
|
+
pr = await github.upsertPullRequest({
|
|
1561
|
+
pullNumber: existing?.number,
|
|
1562
|
+
title,
|
|
1563
|
+
body,
|
|
1564
|
+
head: releaseBranch,
|
|
1565
|
+
base: defaultBranch
|
|
1566
|
+
});
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
return {
|
|
1569
|
+
ok: false,
|
|
1570
|
+
error: {
|
|
1571
|
+
type: "github",
|
|
1572
|
+
operation: "upsertPullRequest",
|
|
1573
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1718
1574
|
}
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
return ok({
|
|
1578
|
+
pullRequest: pr,
|
|
1579
|
+
created: !doesExist
|
|
1580
|
+
});
|
|
1721
1581
|
}
|
|
1722
1582
|
|
|
1723
1583
|
//#endregion
|
|
1724
1584
|
//#region src/workflows/prepare.ts
|
|
1725
1585
|
async function prepareWorkflow(options) {
|
|
1726
|
-
const gitOps = createGitOperations();
|
|
1727
|
-
const githubOps = createGitHubOperations({
|
|
1728
|
-
owner: options.owner,
|
|
1729
|
-
repo: options.repo,
|
|
1730
|
-
githubToken: options.githubToken
|
|
1731
|
-
});
|
|
1732
|
-
const workspaceOps = createWorkspaceOperations();
|
|
1733
|
-
const versioningOps = createVersioningOperations();
|
|
1734
1586
|
if (options.safeguards) {
|
|
1735
|
-
const clean = await
|
|
1587
|
+
const clean = await isWorkingDirectoryClean(options.workspaceRoot);
|
|
1736
1588
|
if (!clean.ok || !clean.value) exitWithError("Working directory is not clean. Please commit or stash your changes before proceeding.");
|
|
1737
1589
|
}
|
|
1738
|
-
const discovered = await
|
|
1590
|
+
const discovered = await discoverWorkspacePackages(options.workspaceRoot, options);
|
|
1739
1591
|
if (!discovered.ok) exitWithError(`Failed to discover packages: ${discovered.error.message}`);
|
|
1740
1592
|
const ensured = ensureHasPackages(discovered.value);
|
|
1741
1593
|
if (!ensured.ok) {
|
|
@@ -1751,7 +1603,6 @@ async function prepareWorkflow(options) {
|
|
|
1751
1603
|
}
|
|
1752
1604
|
logger.emptyLine();
|
|
1753
1605
|
const prepareBranchResult = await prepareReleaseBranch({
|
|
1754
|
-
git: gitOps,
|
|
1755
1606
|
workspaceRoot: options.workspaceRoot,
|
|
1756
1607
|
releaseBranch: options.branch.release,
|
|
1757
1608
|
defaultBranch: options.branch.default
|
|
@@ -1767,7 +1618,6 @@ async function prepareWorkflow(options) {
|
|
|
1767
1618
|
logger.info("No existing version overrides file found. Continuing...");
|
|
1768
1619
|
}
|
|
1769
1620
|
const updatesResult = await calculateUpdates({
|
|
1770
|
-
versioning: versioningOps,
|
|
1771
1621
|
workspacePackages,
|
|
1772
1622
|
workspaceRoot: options.workspaceRoot,
|
|
1773
1623
|
showPrompt: options.prompts?.versions !== false,
|
|
@@ -1814,13 +1664,11 @@ async function prepareWorkflow(options) {
|
|
|
1814
1664
|
await applyUpdates();
|
|
1815
1665
|
if (options.changelog?.enabled) {
|
|
1816
1666
|
logger.step("Updating changelogs");
|
|
1817
|
-
const groupedPackageCommits = await
|
|
1818
|
-
|
|
1819
|
-
const globalCommitsPerPackage = await versioningOps.getGlobalCommitsPerPackage(options.workspaceRoot, groupedPackageCommits.value, workspacePackages, options.globalCommitMode === "none" ? false : options.globalCommitMode);
|
|
1820
|
-
if (!globalCommitsPerPackage.ok) exitWithError(globalCommitsPerPackage.error.message);
|
|
1667
|
+
const groupedPackageCommits = await getWorkspacePackageGroupedCommits(options.workspaceRoot, workspacePackages);
|
|
1668
|
+
const globalCommitsPerPackage = await getGlobalCommitsPerPackage(options.workspaceRoot, groupedPackageCommits, workspacePackages, options.globalCommitMode === "none" ? false : options.globalCommitMode);
|
|
1821
1669
|
const changelogPromises = allUpdates.map((update) => {
|
|
1822
|
-
const pkgCommits = groupedPackageCommits.
|
|
1823
|
-
const globalCommits = globalCommitsPerPackage.
|
|
1670
|
+
const pkgCommits = groupedPackageCommits.get(update.package.name) || [];
|
|
1671
|
+
const globalCommits = globalCommitsPerPackage.get(update.package.name) || [];
|
|
1824
1672
|
const allCommits = [...pkgCommits, ...globalCommits];
|
|
1825
1673
|
if (allCommits.length === 0) {
|
|
1826
1674
|
logger.verbose(`No commits for ${update.package.name}, skipping changelog`);
|
|
@@ -1832,11 +1680,7 @@ async function prepareWorkflow(options) {
|
|
|
1832
1680
|
...options,
|
|
1833
1681
|
workspaceRoot: options.workspaceRoot
|
|
1834
1682
|
},
|
|
1835
|
-
githubClient:
|
|
1836
|
-
owner: options.owner,
|
|
1837
|
-
repo: options.repo,
|
|
1838
|
-
githubToken: options.githubToken
|
|
1839
|
-
}),
|
|
1683
|
+
githubClient: options.githubClient,
|
|
1840
1684
|
workspacePackage: update.package,
|
|
1841
1685
|
version: update.newVersion,
|
|
1842
1686
|
previousVersion: update.currentVersion !== "0.0.0" ? update.currentVersion : void 0,
|
|
@@ -1848,7 +1692,6 @@ async function prepareWorkflow(options) {
|
|
|
1848
1692
|
logger.success(`Updated ${updates.length} changelog(s)`);
|
|
1849
1693
|
}
|
|
1850
1694
|
const hasChangesToPush = await syncReleaseChanges({
|
|
1851
|
-
git: gitOps,
|
|
1852
1695
|
workspaceRoot: options.workspaceRoot,
|
|
1853
1696
|
releaseBranch: options.branch.release,
|
|
1854
1697
|
commitMessage: "chore: update release versions",
|
|
@@ -1857,7 +1700,7 @@ async function prepareWorkflow(options) {
|
|
|
1857
1700
|
if (!hasChangesToPush.ok) exitWithError(hasChangesToPush.error.message);
|
|
1858
1701
|
if (!hasChangesToPush.value) {
|
|
1859
1702
|
const prResult = await syncPullRequest({
|
|
1860
|
-
github:
|
|
1703
|
+
github: options.githubClient,
|
|
1861
1704
|
releaseBranch: options.branch.release,
|
|
1862
1705
|
defaultBranch: options.branch.default,
|
|
1863
1706
|
pullRequestTitle: options.pullRequest?.title,
|
|
@@ -1877,7 +1720,7 @@ async function prepareWorkflow(options) {
|
|
|
1877
1720
|
return null;
|
|
1878
1721
|
}
|
|
1879
1722
|
const prResult = await syncPullRequest({
|
|
1880
|
-
github:
|
|
1723
|
+
github: options.githubClient,
|
|
1881
1724
|
releaseBranch: options.branch.release,
|
|
1882
1725
|
defaultBranch: options.branch.default,
|
|
1883
1726
|
pullRequestTitle: options.pullRequest?.title,
|
|
@@ -1889,6 +1732,8 @@ async function prepareWorkflow(options) {
|
|
|
1889
1732
|
logger.section("🚀 Pull Request");
|
|
1890
1733
|
logger.success(`Pull request ${prResult.value.created ? "created" : "updated"}: ${prResult.value.pullRequest.html_url}`);
|
|
1891
1734
|
}
|
|
1735
|
+
const returnToDefault = await checkoutBranch(options.branch.default, options.workspaceRoot);
|
|
1736
|
+
if (!returnToDefault.ok || !returnToDefault.value) exitWithError(`Failed to checkout branch: ${options.branch.default}`);
|
|
1892
1737
|
return {
|
|
1893
1738
|
updates: allUpdates,
|
|
1894
1739
|
prUrl: prResult.value.pullRequest?.html_url,
|
|
@@ -1896,12 +1741,6 @@ async function prepareWorkflow(options) {
|
|
|
1896
1741
|
};
|
|
1897
1742
|
}
|
|
1898
1743
|
|
|
1899
|
-
//#endregion
|
|
1900
|
-
//#region src/prepare.ts
|
|
1901
|
-
async function release(options) {
|
|
1902
|
-
return prepareWorkflow(options);
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
1744
|
//#endregion
|
|
1906
1745
|
//#region src/workflows/publish.ts
|
|
1907
1746
|
async function publishWorkflow(options) {
|
|
@@ -1909,50 +1748,30 @@ async function publishWorkflow(options) {
|
|
|
1909
1748
|
logger.verbose("Publish options:", options);
|
|
1910
1749
|
}
|
|
1911
1750
|
|
|
1912
|
-
//#endregion
|
|
1913
|
-
//#region src/publish.ts
|
|
1914
|
-
async function publish(options) {
|
|
1915
|
-
return publishWorkflow(options);
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
//#endregion
|
|
1919
|
-
//#region src/operations/discover.ts
|
|
1920
|
-
async function discoverPackages({ workspace, workspaceRoot, options }) {
|
|
1921
|
-
return workspace.discoverWorkspacePackages(workspaceRoot, options);
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
1751
|
//#endregion
|
|
1925
1752
|
//#region src/workflows/verify.ts
|
|
1926
1753
|
async function verifyWorkflow(options) {
|
|
1927
|
-
const gitOps = createGitOperations();
|
|
1928
|
-
const githubOps = createGitHubOperations({
|
|
1929
|
-
owner: options.owner,
|
|
1930
|
-
repo: options.repo,
|
|
1931
|
-
githubToken: options.githubToken
|
|
1932
|
-
});
|
|
1933
|
-
const workspaceOps = createWorkspaceOperations();
|
|
1934
1754
|
if (options.safeguards) {
|
|
1935
|
-
const clean = await
|
|
1755
|
+
const clean = await isWorkingDirectoryClean(options.workspaceRoot);
|
|
1936
1756
|
if (!clean.ok || !clean.value) exitWithError("Working directory is not clean. Please commit or stash your changes before proceeding.");
|
|
1937
1757
|
}
|
|
1938
1758
|
const releaseBranch = options.branch.release;
|
|
1939
1759
|
const defaultBranch = options.branch.default;
|
|
1940
|
-
const releasePr = await
|
|
1941
|
-
if (!releasePr
|
|
1942
|
-
if (!releasePr.value || !releasePr.value.head) {
|
|
1760
|
+
const releasePr = await options.githubClient.getExistingPullRequest(releaseBranch);
|
|
1761
|
+
if (!releasePr || !releasePr.head) {
|
|
1943
1762
|
logger.warn(`No open release pull request found for branch "${releaseBranch}". Nothing to verify.`);
|
|
1944
1763
|
return;
|
|
1945
1764
|
}
|
|
1946
|
-
logger.info(`Found release PR #${releasePr.
|
|
1947
|
-
const originalBranch = await
|
|
1765
|
+
logger.info(`Found release PR #${releasePr.number}. Verifying against default branch "${defaultBranch}"...`);
|
|
1766
|
+
const originalBranch = await getCurrentBranch(options.workspaceRoot);
|
|
1948
1767
|
if (!originalBranch.ok) exitWithError(originalBranch.error.message);
|
|
1949
1768
|
if (originalBranch.value !== defaultBranch) {
|
|
1950
|
-
const checkout = await
|
|
1769
|
+
const checkout = await checkoutBranch(defaultBranch, options.workspaceRoot);
|
|
1951
1770
|
if (!checkout.ok || !checkout.value) exitWithError(`Failed to checkout branch: ${defaultBranch}`);
|
|
1952
1771
|
}
|
|
1953
1772
|
let existingOverrides = {};
|
|
1954
1773
|
try {
|
|
1955
|
-
const overridesContent = await
|
|
1774
|
+
const overridesContent = await readFileFromGit(options.workspaceRoot, releasePr.head.sha, ucdjsReleaseOverridesPath);
|
|
1956
1775
|
if (overridesContent.ok && overridesContent.value) {
|
|
1957
1776
|
existingOverrides = JSON.parse(overridesContent.value);
|
|
1958
1777
|
logger.info("Found existing version overrides file on release branch.");
|
|
@@ -1960,11 +1779,7 @@ async function verifyWorkflow(options) {
|
|
|
1960
1779
|
} catch {
|
|
1961
1780
|
logger.info("No version overrides file found on release branch. Continuing...");
|
|
1962
1781
|
}
|
|
1963
|
-
const discovered = await
|
|
1964
|
-
workspace: workspaceOps,
|
|
1965
|
-
workspaceRoot: options.workspaceRoot,
|
|
1966
|
-
options
|
|
1967
|
-
});
|
|
1782
|
+
const discovered = await discoverWorkspacePackages(options.workspaceRoot, options);
|
|
1968
1783
|
if (!discovered.ok) exitWithError(`Failed to discover packages: ${discovered.error.message}`);
|
|
1969
1784
|
const ensured = ensureHasPackages(discovered.value);
|
|
1970
1785
|
if (!ensured.ok) {
|
|
@@ -1973,7 +1788,6 @@ async function verifyWorkflow(options) {
|
|
|
1973
1788
|
}
|
|
1974
1789
|
const mainPackages = ensured.value;
|
|
1975
1790
|
const updatesResult = await calculateUpdates({
|
|
1976
|
-
versioning: createVersioningOperations(),
|
|
1977
1791
|
workspacePackages: mainPackages,
|
|
1978
1792
|
workspaceRoot: options.workspaceRoot,
|
|
1979
1793
|
showPrompt: false,
|
|
@@ -1986,13 +1800,13 @@ async function verifyWorkflow(options) {
|
|
|
1986
1800
|
const prVersionMap = /* @__PURE__ */ new Map();
|
|
1987
1801
|
for (const pkg of mainPackages) {
|
|
1988
1802
|
const pkgJsonPath = relative(options.workspaceRoot, join(pkg.path, "package.json"));
|
|
1989
|
-
const pkgJsonContent = await
|
|
1803
|
+
const pkgJsonContent = await readFileFromGit(options.workspaceRoot, releasePr.head.sha, pkgJsonPath);
|
|
1990
1804
|
if (pkgJsonContent.ok && pkgJsonContent.value) {
|
|
1991
1805
|
const pkgJson = JSON.parse(pkgJsonContent.value);
|
|
1992
1806
|
prVersionMap.set(pkg.name, pkgJson.version);
|
|
1993
1807
|
}
|
|
1994
1808
|
}
|
|
1995
|
-
if (originalBranch.value !== defaultBranch) await
|
|
1809
|
+
if (originalBranch.value !== defaultBranch) await checkoutBranch(originalBranch.value, options.workspaceRoot);
|
|
1996
1810
|
let isOutOfSync = false;
|
|
1997
1811
|
for (const [pkgName, expectedVersion] of expectedVersionMap.entries()) {
|
|
1998
1812
|
const prVersion = prVersionMap.get(pkgName);
|
|
@@ -2007,51 +1821,49 @@ async function verifyWorkflow(options) {
|
|
|
2007
1821
|
}
|
|
2008
1822
|
const statusContext = "ucdjs/release-verify";
|
|
2009
1823
|
if (isOutOfSync) {
|
|
2010
|
-
await
|
|
2011
|
-
sha: releasePr.
|
|
1824
|
+
await options.githubClient.setCommitStatus({
|
|
1825
|
+
sha: releasePr.head.sha,
|
|
2012
1826
|
state: "failure",
|
|
2013
1827
|
context: statusContext,
|
|
2014
1828
|
description: "Release PR is out of sync with the default branch. Please re-run the release process."
|
|
2015
1829
|
});
|
|
2016
1830
|
logger.error("Verification failed. Commit status set to 'failure'.");
|
|
2017
1831
|
} else {
|
|
2018
|
-
await
|
|
2019
|
-
sha: releasePr.
|
|
1832
|
+
await options.githubClient.setCommitStatus({
|
|
1833
|
+
sha: releasePr.head.sha,
|
|
2020
1834
|
state: "success",
|
|
2021
1835
|
context: statusContext,
|
|
2022
1836
|
description: "Release PR is up to date.",
|
|
2023
|
-
targetUrl: `https://github.com/${options.owner}/${options.repo}/pull/${releasePr.
|
|
1837
|
+
targetUrl: `https://github.com/${options.owner}/${options.repo}/pull/${releasePr.number}`
|
|
2024
1838
|
});
|
|
2025
1839
|
logger.success("Verification successful. Commit status set to 'success'.");
|
|
2026
1840
|
}
|
|
2027
1841
|
}
|
|
2028
1842
|
|
|
2029
|
-
//#endregion
|
|
2030
|
-
//#region src/verify.ts
|
|
2031
|
-
async function verify(options) {
|
|
2032
|
-
return verifyWorkflow(options);
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
1843
|
//#endregion
|
|
2036
1844
|
//#region src/index.ts
|
|
2037
1845
|
async function createReleaseScripts(options) {
|
|
2038
1846
|
const normalizedOptions = normalizeReleaseScriptsOptions(options);
|
|
2039
1847
|
return {
|
|
2040
1848
|
async verify() {
|
|
2041
|
-
return
|
|
1849
|
+
return verifyWorkflow(normalizedOptions);
|
|
2042
1850
|
},
|
|
2043
1851
|
async prepare() {
|
|
2044
|
-
return
|
|
1852
|
+
return prepareWorkflow(normalizedOptions);
|
|
2045
1853
|
},
|
|
2046
1854
|
async publish() {
|
|
2047
|
-
return
|
|
1855
|
+
return publishWorkflow(normalizedOptions);
|
|
2048
1856
|
},
|
|
2049
1857
|
packages: {
|
|
2050
1858
|
async list() {
|
|
2051
|
-
|
|
1859
|
+
const result = await discoverWorkspacePackages(normalizedOptions.workspaceRoot, normalizedOptions);
|
|
1860
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
1861
|
+
return result.value;
|
|
2052
1862
|
},
|
|
2053
1863
|
async get(packageName) {
|
|
2054
|
-
|
|
1864
|
+
const result = await discoverWorkspacePackages(normalizedOptions.workspaceRoot, normalizedOptions);
|
|
1865
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
1866
|
+
return result.value.find((p) => p.name === packageName);
|
|
2055
1867
|
}
|
|
2056
1868
|
}
|
|
2057
1869
|
};
|