tegami 1.0.0-beta.2 → 1.0.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/cli/index.d.ts +7 -5
- package/dist/cli/index.js +207 -220
- package/dist/draft-DtFyGxe8.js +455 -0
- package/dist/{error-D3dwQnwR.js → error-We7chQVJ.js} +12 -1
- package/dist/{generate-CaCDNfVk.js → generate-BfYdNTFi.js} +1 -1
- package/dist/generators/simple.d.ts +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +28 -462
- package/dist/{npm-B_43doHi.js → npm-CMOyacwf.js} +4 -4
- package/dist/plugins/git.d.ts +1 -1
- package/dist/plugins/git.js +4 -4
- package/dist/plugins/github.d.ts +1 -1
- package/dist/plugins/github.js +388 -126
- package/dist/plugins/gitlab.d.ts +84 -0
- package/dist/plugins/gitlab.js +554 -0
- package/dist/plugins/go.d.ts +1 -1
- package/dist/plugins/go.js +5 -4
- package/dist/providers/cargo.d.ts +1 -1
- package/dist/providers/cargo.js +3 -3
- package/dist/providers/npm.d.ts +1 -1
- package/dist/providers/npm.js +1 -1
- package/dist/{types-DKeF_CDv.d.ts → types-DKZsadC8.d.ts} +128 -14
- package/dist/version-request-cFS0Suzd.js +69 -0
- package/package.json +2 -5
- package/dist/api-D-jf_8xY.js +0 -116
- package/dist/index-CQXmwyLn.d.ts +0 -64
package/dist/plugins/github.js
CHANGED
|
@@ -1,10 +1,316 @@
|
|
|
1
1
|
import { i as formatPackageVersion, r as formatNpmDistTag } from "../semver-EKJ8yK5U.js";
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
2
|
+
import { t as changelogFilename } from "../generate-BfYdNTFi.js";
|
|
3
|
+
import { a as cached, n as execFailure, o as isCI } from "../error-We7chQVJ.js";
|
|
4
|
+
import { n as createDraft, o as readChangelogEntries } from "../draft-DtFyGxe8.js";
|
|
4
5
|
import { git } from "./git.js";
|
|
5
|
-
import {
|
|
6
|
+
import { n as createVersionRequestBody, r as hasGitChanges, t as commitVersionBranchChanges } from "../version-request-cFS0Suzd.js";
|
|
7
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
6
9
|
import { x } from "tinyexec";
|
|
7
10
|
import semver from "semver";
|
|
11
|
+
import z from "zod";
|
|
12
|
+
import { resolveCommand } from "package-manager-detector";
|
|
13
|
+
import { note, outro } from "@clack/prompts";
|
|
14
|
+
//#region src/plugins/github/api.ts
|
|
15
|
+
function parseGitHubRepo(repo) {
|
|
16
|
+
const [owner, name] = repo.split("/", 2);
|
|
17
|
+
if (!owner || !name) throw new Error(`Invalid GitHub repository: ${repo}`);
|
|
18
|
+
return {
|
|
19
|
+
owner,
|
|
20
|
+
repo: name
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function githubRequest(path, token, init = {}) {
|
|
24
|
+
const headers = new Headers(init.headers);
|
|
25
|
+
headers.set("Accept", "application/vnd.github+json");
|
|
26
|
+
headers.set("X-GitHub-Api-Version", "2022-11-28");
|
|
27
|
+
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
28
|
+
return fetch(`https://api.github.com${path}`, {
|
|
29
|
+
...init,
|
|
30
|
+
headers
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function releaseExistsByTag(repo, tag, token) {
|
|
34
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
35
|
+
const response = await githubRequest(`/repos/${owner}/${name}/releases/tags/${encodeURIComponent(tag)}`, token, { method: "HEAD" });
|
|
36
|
+
if (response.status === 404) return false;
|
|
37
|
+
if (!response.ok) throw new Error(`Failed to get GitHub release for ${tag}.`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
async function createRelease(repo, options) {
|
|
41
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
42
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/releases`, options.token, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json" },
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
tag_name: options.tag,
|
|
47
|
+
name: options.title,
|
|
48
|
+
body: options.notes,
|
|
49
|
+
prerelease: options.prerelease ?? false
|
|
50
|
+
})
|
|
51
|
+
})).ok) throw new Error(`Failed to create GitHub release for ${options.tag}.`);
|
|
52
|
+
}
|
|
53
|
+
async function findOpenPullRequest(repo, headBranch, token) {
|
|
54
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
55
|
+
const response = await githubRequest(`/repos/${owner}/${name}/pulls?${new URLSearchParams({
|
|
56
|
+
head: `${owner}:${headBranch}`,
|
|
57
|
+
state: "open"
|
|
58
|
+
})}`, token);
|
|
59
|
+
if (!response.ok) throw new Error("Failed to check for an existing version pull request.");
|
|
60
|
+
return (await response.json())[0]?.number;
|
|
61
|
+
}
|
|
62
|
+
async function updatePullRequest(repo, number, options) {
|
|
63
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
64
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/pulls/${number}`, options.token, {
|
|
65
|
+
method: "PATCH",
|
|
66
|
+
headers: { "Content-Type": "application/json" },
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
title: options.title,
|
|
69
|
+
body: options.body
|
|
70
|
+
})
|
|
71
|
+
})).ok) throw new Error("Failed to update the version pull request.");
|
|
72
|
+
}
|
|
73
|
+
async function createPullRequest(repo, options) {
|
|
74
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
75
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/pulls`, options.token, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "Content-Type": "application/json" },
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
title: options.title,
|
|
80
|
+
body: options.body,
|
|
81
|
+
head: options.head,
|
|
82
|
+
base: options.base
|
|
83
|
+
})
|
|
84
|
+
})).ok) throw new Error("Failed to create the version pull request.");
|
|
85
|
+
}
|
|
86
|
+
async function listPullRequestsForCommit(repo, commitSha, token) {
|
|
87
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
88
|
+
const response = await githubRequest(`/repos/${owner}/${name}/commits/${commitSha}/pulls`, token);
|
|
89
|
+
if (!response.ok) throw new Error(`Failed to list pull requests for commit ${commitSha.slice(0, 7)}.`);
|
|
90
|
+
return await response.json();
|
|
91
|
+
}
|
|
92
|
+
async function getPullRequest(repo, number, token) {
|
|
93
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
94
|
+
const response = await githubRequest(`/repos/${owner}/${name}/pulls/${number}`, token);
|
|
95
|
+
if (!response.ok) throw new Error(`Failed to resolve pull request #${number}.`);
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
return {
|
|
98
|
+
headRefName: data.head.ref,
|
|
99
|
+
baseRefOid: data.base.sha,
|
|
100
|
+
headRefOid: data.head.sha,
|
|
101
|
+
headRepository: data.head.repo
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function findIssueCommentByPrefix(repo, issueNumber, prefix, token) {
|
|
105
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
106
|
+
let page = 1;
|
|
107
|
+
while (true) {
|
|
108
|
+
const response = await githubRequest(`/repos/${owner}/${name}/issues/${issueNumber}/comments?per_page=100&page=${page}`, token);
|
|
109
|
+
if (!response.ok) throw new Error("Failed to list pull request comments.");
|
|
110
|
+
const batch = await response.json();
|
|
111
|
+
if (batch.length === 0) break;
|
|
112
|
+
const comment = batch.find((comment) => comment.body.startsWith(prefix));
|
|
113
|
+
if (comment) return comment.id;
|
|
114
|
+
if (batch.length < 100) break;
|
|
115
|
+
page += 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function updateIssueComment(repo, commentId, body, token) {
|
|
119
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
120
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/issues/comments/${commentId}`, token, {
|
|
121
|
+
method: "PATCH",
|
|
122
|
+
headers: { "Content-Type": "application/json" },
|
|
123
|
+
body: JSON.stringify({ body })
|
|
124
|
+
})).ok) throw new Error("Failed to update pull request comment.");
|
|
125
|
+
}
|
|
126
|
+
async function createIssueComment(repo, issueNumber, body, token) {
|
|
127
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
128
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/issues/${issueNumber}/comments`, token, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { "Content-Type": "application/json" },
|
|
131
|
+
body: JSON.stringify({ body })
|
|
132
|
+
})).ok) throw new Error("Failed to create pull request comment.");
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/plugins/github/cli.ts
|
|
136
|
+
const COMMENT_MARKER = "<!-- tegami -->";
|
|
137
|
+
function registerPrCli(cli) {
|
|
138
|
+
cli.command("pr preview", { description: "show a pull request release preview and changelog guidance" }).option("artifact", {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "write preview markdown to a file"
|
|
141
|
+
}).option("number", {
|
|
142
|
+
type: "string",
|
|
143
|
+
description: "pull request number"
|
|
144
|
+
}).action(async ({ context, values }) => {
|
|
145
|
+
const number = values.number ? parsePositiveInt(values.number, "--number") : void 0;
|
|
146
|
+
const artifact = values.artifact;
|
|
147
|
+
const body = await buildPrPreview(context, await createDraft(await readChangelogEntries(context), context), { number });
|
|
148
|
+
if (artifact) {
|
|
149
|
+
const artifactPath = resolve(context.cwd, artifact);
|
|
150
|
+
await writeFile(artifactPath, body);
|
|
151
|
+
if (!isCI()) {
|
|
152
|
+
note(relative(context.cwd, artifactPath) || artifact, "Release preview");
|
|
153
|
+
outro("Release preview ready.");
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (isCI()) {
|
|
158
|
+
process.stdout.write(`${body}\n`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
note(body, "Release preview");
|
|
162
|
+
outro("Release preview ready.");
|
|
163
|
+
});
|
|
164
|
+
cli.command("pr comment", { description: "post the pull request release preview as a comment" }).positional("artifact").action(async ({ context, positionals }) => {
|
|
165
|
+
await postPrComment(context, await readFile(positionals.artifact, "utf8"));
|
|
166
|
+
outro("Pull request comment updated.");
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async function buildPrPreview(context, draft, options = {}) {
|
|
170
|
+
const pullRequest = await resolvePullRequest(context, options);
|
|
171
|
+
const tegamiCommandRaw = resolveCommand(context.npm?.client ?? "npm", "run", ["tegami"]);
|
|
172
|
+
const tegamiCommand = [tegamiCommandRaw.command, ...tegamiCommandRaw.args].join(" ");
|
|
173
|
+
const changelogFiles = await listPullRequestChangelogFiles(context, pullRequest.baseSha, pullRequest.headSha);
|
|
174
|
+
const createLink = createChangelogUrl(context, pullRequest.headRepo, pullRequest.headRef, changelogFilename());
|
|
175
|
+
const pendingPackages = [];
|
|
176
|
+
const lines = [
|
|
177
|
+
"### Tegami",
|
|
178
|
+
"",
|
|
179
|
+
`This repository uses [Tegami](https://tegami.fuma-nama.dev) to manage releases. When your changes affect published packages, add a changelog file under \`.tegami/\` before merging.`,
|
|
180
|
+
"",
|
|
181
|
+
`[**Create a changelog →**](${createLink}) · [Changelog format](https://tegami.fuma-nama.dev/changelog)`,
|
|
182
|
+
""
|
|
183
|
+
];
|
|
184
|
+
for (const pkg of context.graph.getPackages()) {
|
|
185
|
+
const plan = draft.getPackageDraft(pkg.id);
|
|
186
|
+
if (!plan) continue;
|
|
187
|
+
const bumped = plan.bumpVersion(pkg);
|
|
188
|
+
if (!bumped || !pkg.version || bumped === pkg.version) continue;
|
|
189
|
+
pendingPackages.push({
|
|
190
|
+
name: pkg.name,
|
|
191
|
+
type: plan.type ?? "—",
|
|
192
|
+
from: pkg.version,
|
|
193
|
+
to: bumped,
|
|
194
|
+
distTag: plan.npm?.distTag
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const requestChangelogs = draft.getChangelogs().filter((entry) => changelogFiles.has(entry.filename));
|
|
198
|
+
if (pendingPackages.length > 0) {
|
|
199
|
+
lines.push("#### Release preview", "", "| Package | Bump | Version |", "| --- | --- | --- |");
|
|
200
|
+
for (const { name, type, from, to, distTag } of pendingPackages) lines.push(`| \`${name}\` | ${type} | \`${from}\` → \`${to}\`${formatNpmDistTag(distTag)} |`);
|
|
201
|
+
lines.push("");
|
|
202
|
+
}
|
|
203
|
+
if (requestChangelogs.length > 0) {
|
|
204
|
+
lines.push("#### Changelogs in this PR", "", "| Changelog | Title |", "| --- | --- |");
|
|
205
|
+
for (const entry of requestChangelogs) for (const section of entry.sections) lines.push(`| \`${entry.filename}\` | ${section.title} |`);
|
|
206
|
+
lines.push("");
|
|
207
|
+
} else if (pendingPackages.length === 0) lines.push("#### No changelogs yet", "", "This PR has no pending changelog files. If your changes require a release, add a changelog before merging.", "");
|
|
208
|
+
else if (changelogFiles.size === 0) lines.push("This PR does not add changelog files. Pending changelogs from other branches are included in the preview above.", "");
|
|
209
|
+
lines.push(`Run \`${tegamiCommand}\` locally to create a changelog interactively.`, "", `<sub>Managed by [Tegami](https://tegami.fuma-nama.dev).</sub>`, "");
|
|
210
|
+
return lines.join("\n");
|
|
211
|
+
}
|
|
212
|
+
async function postPrComment(context, body) {
|
|
213
|
+
const { repo, token } = context.github ?? {};
|
|
214
|
+
if (!repo) {
|
|
215
|
+
outro("GitHub plugin context is required.");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const pr = await readPullRequestFromWorkflowRunEvent();
|
|
219
|
+
if (!pr.found) {
|
|
220
|
+
outro(pr.reason);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const markedBody = `${COMMENT_MARKER}\n${body}`;
|
|
224
|
+
const existingId = await findIssueCommentByPrefix(repo, pr.number, COMMENT_MARKER, token);
|
|
225
|
+
if (existingId) {
|
|
226
|
+
await updateIssueComment(repo, existingId, markedBody, token);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
await createIssueComment(repo, pr.number, markedBody, token);
|
|
230
|
+
}
|
|
231
|
+
async function resolvePullRequest(context, options) {
|
|
232
|
+
const { repo, token } = context.github ?? {};
|
|
233
|
+
if (!repo) throw new Error("GitHub plugin context is required.");
|
|
234
|
+
if (options.number !== void 0) {
|
|
235
|
+
parsePositiveInt(String(options.number), "--number");
|
|
236
|
+
const data = await getPullRequest(repo, options.number, token);
|
|
237
|
+
return {
|
|
238
|
+
headRepo: data.headRepository ? `${data.headRepository.owner.login}/${data.headRepository.name}` : repo,
|
|
239
|
+
headRef: data.headRefName,
|
|
240
|
+
baseSha: data.baseRefOid,
|
|
241
|
+
headSha: data.headRefOid
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
245
|
+
if (eventPath) try {
|
|
246
|
+
const pullRequest = JSON.parse(await readFile(eventPath, "utf8")).pull_request;
|
|
247
|
+
if (pullRequest) return {
|
|
248
|
+
headRepo: pullRequest.head.repo.full_name,
|
|
249
|
+
headRef: pullRequest.head.ref,
|
|
250
|
+
baseSha: pullRequest.base.sha,
|
|
251
|
+
headSha: pullRequest.head.sha
|
|
252
|
+
};
|
|
253
|
+
} catch {}
|
|
254
|
+
throw new Error("A pull request event or --number is required.");
|
|
255
|
+
}
|
|
256
|
+
const workflowRunEventSchema = z.object({ workflow_run: z.object({
|
|
257
|
+
event: z.literal("pull_request", { error: "The preview workflow was not triggered by a pull request." }),
|
|
258
|
+
conclusion: z.literal("success", { error: "The preview workflow did not complete successfully." }),
|
|
259
|
+
pull_requests: z.array(z.object({ number: z.int() }), { error: "The preview workflow is not associated with a pull request." }).min(1)
|
|
260
|
+
}, { error: "A workflow_run event is required." }) });
|
|
261
|
+
async function readPullRequestFromWorkflowRunEvent() {
|
|
262
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
263
|
+
if (!eventPath) return {
|
|
264
|
+
found: false,
|
|
265
|
+
reason: "GITHUB_EVENT_PATH is required."
|
|
266
|
+
};
|
|
267
|
+
let raw;
|
|
268
|
+
try {
|
|
269
|
+
raw = JSON.parse(await readFile(eventPath, "utf8"));
|
|
270
|
+
} catch {
|
|
271
|
+
return {
|
|
272
|
+
found: false,
|
|
273
|
+
reason: "Failed to read workflow_run event."
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const { error, data } = workflowRunEventSchema.safeParse(raw);
|
|
277
|
+
if (error) return {
|
|
278
|
+
found: false,
|
|
279
|
+
reason: z.prettifyError(error)
|
|
280
|
+
};
|
|
281
|
+
return {
|
|
282
|
+
found: true,
|
|
283
|
+
number: data.workflow_run.pull_requests[0].number
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async function listPullRequestChangelogFiles(context, baseSha, headSha) {
|
|
287
|
+
const dir = relative(context.cwd, context.changelogDir);
|
|
288
|
+
const result = await x("git", [
|
|
289
|
+
"diff",
|
|
290
|
+
"--name-only",
|
|
291
|
+
"--diff-filter=ACMRD",
|
|
292
|
+
`${baseSha}...${headSha}`,
|
|
293
|
+
"--",
|
|
294
|
+
`${dir}/`
|
|
295
|
+
], { nodeOptions: { cwd: context.cwd } });
|
|
296
|
+
if (result.exitCode !== 0) throw execFailure("Failed to list pull request changelog files.", result);
|
|
297
|
+
const files = /* @__PURE__ */ new Set();
|
|
298
|
+
for (const line of result.stdout.split("\n")) {
|
|
299
|
+
const trimmed = line.trim();
|
|
300
|
+
if (trimmed.endsWith(".md")) files.add(basename(trimmed));
|
|
301
|
+
}
|
|
302
|
+
return files;
|
|
303
|
+
}
|
|
304
|
+
function createChangelogUrl(context, repo, branch, filename) {
|
|
305
|
+
const filePath = join(relative(context.cwd, context.changelogDir), filename).replaceAll("\\", "/");
|
|
306
|
+
return `https://github.com/${repo}/new/${branch.split("/").map(encodeURIComponent).join("/")}?${new URLSearchParams({ filename: filePath })}`;
|
|
307
|
+
}
|
|
308
|
+
function parsePositiveInt(value, option) {
|
|
309
|
+
const number = Number(value);
|
|
310
|
+
if (!Number.isInteger(number) || number <= 0) throw new Error(`${option} must be a positive integer.`);
|
|
311
|
+
return number;
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
8
314
|
//#region src/plugins/github.ts
|
|
9
315
|
/** Create GitHub releases for successfully published packages after the whole plan succeeds. */
|
|
10
316
|
function github(options = {}) {
|
|
@@ -13,48 +319,7 @@ function github(options = {}) {
|
|
|
13
319
|
function getRenderer(context) {
|
|
14
320
|
return renderer ??= createChangelogRenderer(context);
|
|
15
321
|
}
|
|
16
|
-
|
|
17
|
-
const config = options.versionPr ?? {};
|
|
18
|
-
if (config === false) return [false];
|
|
19
|
-
if (config.forceCreate || isCI()) return [true, config];
|
|
20
|
-
return [false];
|
|
21
|
-
}
|
|
22
|
-
function defaultVersionPRBody(draft, context) {
|
|
23
|
-
const packageLines = [];
|
|
24
|
-
const changesets = /* @__PURE__ */ new Map();
|
|
25
|
-
for (const [id, packageDraft] of draft.getPackageDrafts()) {
|
|
26
|
-
const pkg = context.graph.get(id);
|
|
27
|
-
if (!pkg) continue;
|
|
28
|
-
for (const entry of packageDraft.changelogs ?? []) {
|
|
29
|
-
const list = changesets.get(entry);
|
|
30
|
-
if (list) list.push(pkg);
|
|
31
|
-
else changesets.set(entry, [pkg]);
|
|
32
|
-
}
|
|
33
|
-
const originalVersion = cliOriginalPackageVersions.get(pkg.id);
|
|
34
|
-
if (!originalVersion || originalVersion === pkg.version) continue;
|
|
35
|
-
packageLines.push(`| \`${pkg.name}\` | \`${originalVersion}\` | \`${pkg.version}\`${formatNpmDistTag(packageDraft.npm?.distTag)} |`);
|
|
36
|
-
}
|
|
37
|
-
const changelogLines = [];
|
|
38
|
-
for (const [entry, linkedPackages] of changesets) {
|
|
39
|
-
changelogLines.push(`### ${entry.subject ?? `\`${entry.filename}\``}`, "");
|
|
40
|
-
changelogLines.push("<details>", `<summary>Show Bumped Packages (${linkedPackages.length})</summary>`, "", ...linkedPackages.map((pkg) => `- \`${pkg.id}\``), "", "</details>", "");
|
|
41
|
-
for (const section of entry.sections) {
|
|
42
|
-
changelogLines.push(`#### ${section.title}`, "");
|
|
43
|
-
if (section.content) changelogLines.push(section.content);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const sections = [
|
|
47
|
-
"## Summary",
|
|
48
|
-
"",
|
|
49
|
-
"Merge this PR to publish the versioned packages.",
|
|
50
|
-
""
|
|
51
|
-
];
|
|
52
|
-
if (packageLines.length > 0) sections.push("| Package | From | To |", "| --- | --- | --- |", ...packageLines);
|
|
53
|
-
if (changelogLines.length > 0) sections.push("", "## Changelogs", ...changelogLines);
|
|
54
|
-
sections.push("");
|
|
55
|
-
return sections.join("\n");
|
|
56
|
-
}
|
|
57
|
-
const cliOriginalPackageVersions = /* @__PURE__ */ new Map();
|
|
322
|
+
const cliSnapshots = /* @__PURE__ */ new Map();
|
|
58
323
|
return [git(options), {
|
|
59
324
|
name: "github",
|
|
60
325
|
init() {
|
|
@@ -124,84 +389,57 @@ function github(options = {}) {
|
|
|
124
389
|
});
|
|
125
390
|
}));
|
|
126
391
|
},
|
|
127
|
-
cli
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
], gitOptions);
|
|
160
|
-
if (result.exitCode !== 0) throw execFailure("Failed to create the version pull request branch.", result);
|
|
161
|
-
result = await x("git", ["add", "-A"], gitOptions);
|
|
162
|
-
if (result.exitCode !== 0) throw execFailure("Failed to stage version changes.", result);
|
|
163
|
-
result = await x("git", [
|
|
164
|
-
"commit",
|
|
165
|
-
"-m",
|
|
166
|
-
pr.title
|
|
167
|
-
], gitOptions);
|
|
168
|
-
if (result.exitCode !== 0) throw execFailure("Failed to commit version changes.", result);
|
|
169
|
-
result = await x("git", [
|
|
170
|
-
"push",
|
|
171
|
-
"--force",
|
|
172
|
-
"-u",
|
|
173
|
-
"origin",
|
|
174
|
-
branch
|
|
175
|
-
], gitOptions);
|
|
176
|
-
if (result.exitCode !== 0) throw execFailure("Failed to push the version branch to origin. Ensure `origin` is configured and you have push access.", result);
|
|
177
|
-
const token = this.github?.token;
|
|
178
|
-
if (!repo) return;
|
|
179
|
-
const openPr = await findOpenPullRequest(repo, branch, token);
|
|
180
|
-
if (openPr !== void 0) {
|
|
181
|
-
await updatePullRequest(repo, openPr, {
|
|
182
|
-
title: pr.title,
|
|
183
|
-
body: pr.body,
|
|
184
|
-
token
|
|
185
|
-
});
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
await createPullRequest(repo, {
|
|
392
|
+
async initCli(cli) {
|
|
393
|
+
registerPrCli(cli);
|
|
394
|
+
if (!isCI()) return;
|
|
395
|
+
const { repo, token } = this.github ?? {};
|
|
396
|
+
if (!token || !repo) return;
|
|
397
|
+
const result = await x("git", [
|
|
398
|
+
"remote",
|
|
399
|
+
"set-url",
|
|
400
|
+
"origin",
|
|
401
|
+
`https://x-access-token:${token}@github.com/${repo}.git`
|
|
402
|
+
], { nodeOptions: { cwd: this.cwd } });
|
|
403
|
+
if (result.exitCode !== 0) throw execFailure("Failed to configure git remote for GitHub Actions.", result);
|
|
404
|
+
},
|
|
405
|
+
initCliDraft() {
|
|
406
|
+
for (const pkg of this.graph.getPackages()) cliSnapshots.set(pkg.id, pkg.version);
|
|
407
|
+
},
|
|
408
|
+
async applyCliDraft(draft) {
|
|
409
|
+
const config = options.versionPr ?? {};
|
|
410
|
+
if (config === false || !(config.forceCreate || isCI()) || !await hasGitChanges(this.cwd)) return;
|
|
411
|
+
const repo = this.github?.repo;
|
|
412
|
+
const { branch = "tegami/version-packages", base = "main" } = config;
|
|
413
|
+
const basePR = await config.create?.call(this, { draft });
|
|
414
|
+
const pr = {
|
|
415
|
+
title: basePR?.title ?? "Version Packages",
|
|
416
|
+
body: basePR?.body ?? createVersionRequestBody(draft, this, cliSnapshots, "Merge this PR to publish the versioned packages.")
|
|
417
|
+
};
|
|
418
|
+
await commitVersionBranchChanges(this.cwd, branch, pr.title);
|
|
419
|
+
const token = this.github?.token;
|
|
420
|
+
if (!repo) return;
|
|
421
|
+
const openPr = await findOpenPullRequest(repo, branch, token);
|
|
422
|
+
if (openPr !== void 0) {
|
|
423
|
+
await updatePullRequest(repo, openPr, {
|
|
189
424
|
title: pr.title,
|
|
190
425
|
body: pr.body,
|
|
191
|
-
head: branch,
|
|
192
|
-
base,
|
|
193
426
|
token
|
|
194
427
|
});
|
|
428
|
+
return;
|
|
195
429
|
}
|
|
430
|
+
await createPullRequest(repo, {
|
|
431
|
+
title: pr.title,
|
|
432
|
+
body: pr.body,
|
|
433
|
+
head: branch,
|
|
434
|
+
base,
|
|
435
|
+
token
|
|
436
|
+
});
|
|
196
437
|
}
|
|
197
438
|
}];
|
|
198
439
|
}
|
|
199
|
-
async function hasGitChanges(cwd) {
|
|
200
|
-
return (await x("git", ["status", "--porcelain"], { nodeOptions: { cwd } })).stdout.trim().length > 0;
|
|
201
|
-
}
|
|
202
440
|
function createChangelogRenderer(context) {
|
|
203
|
-
const
|
|
204
|
-
|
|
441
|
+
const { repo, token } = context.github;
|
|
442
|
+
const resolveFileCommit = cached((filename) => filename, async (filename) => {
|
|
205
443
|
const result = await x("git", [
|
|
206
444
|
"log",
|
|
207
445
|
"--diff-filter=A",
|
|
@@ -212,26 +450,50 @@ function createChangelogRenderer(context) {
|
|
|
212
450
|
], { nodeOptions: { cwd: context.cwd } });
|
|
213
451
|
if (result.exitCode !== 0) return;
|
|
214
452
|
return result.stdout.trim() || void 0;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
453
|
+
});
|
|
454
|
+
const resolveEntryMeta = cached((entry) => entry.id, async (entry) => {
|
|
455
|
+
const commit = await resolveFileCommit(entry.filename);
|
|
456
|
+
if (!commit || !repo) return {
|
|
457
|
+
commit,
|
|
458
|
+
pullRequests: []
|
|
459
|
+
};
|
|
460
|
+
return {
|
|
461
|
+
commit,
|
|
462
|
+
pullRequests: await listPullRequestsForCommit(repo, commit, token).catch(() => [])
|
|
463
|
+
};
|
|
464
|
+
});
|
|
465
|
+
function formatEntryDetails(meta) {
|
|
466
|
+
if (meta.pullRequests.length === 0) return;
|
|
467
|
+
const lines = [];
|
|
468
|
+
for (const pr of meta.pullRequests) {
|
|
469
|
+
let line = repo ? `- [#${pr.number} ${pr.title}](https://github.com/${repo}/pull/${pr.number})` : `- #${pr.number} ${pr.title}`;
|
|
470
|
+
if (pr.user) line += ` by @${pr.user.login}`;
|
|
471
|
+
lines.push(line);
|
|
472
|
+
}
|
|
473
|
+
return [
|
|
474
|
+
"<details>",
|
|
475
|
+
"<summary>Pull request & contributors</summary>",
|
|
476
|
+
"",
|
|
477
|
+
...lines,
|
|
478
|
+
"",
|
|
479
|
+
"</details>"
|
|
480
|
+
].join("\n");
|
|
221
481
|
}
|
|
222
482
|
return async (entry) => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
483
|
+
const meta = await resolveEntryMeta(entry);
|
|
484
|
+
let commitSuffix = "";
|
|
485
|
+
if (meta.commit) {
|
|
486
|
+
const short = meta.commit.slice(0, 7);
|
|
487
|
+
const link = repo ? `[${short}](https://github.com/${repo}/commit/${meta.commit})` : `\`${short}\``;
|
|
488
|
+
commitSuffix += ` (${link})`;
|
|
227
489
|
}
|
|
228
|
-
const commit = await commitPromise;
|
|
229
|
-
const commitSuffix = commit ? ` (${formatCommitLink(commit)})` : "";
|
|
230
490
|
const lines = [];
|
|
231
491
|
for (const section of entry.sections) {
|
|
232
492
|
lines.push(`### ${section.title}${commitSuffix}`, "");
|
|
233
493
|
if (section.content) lines.push(section.content, "");
|
|
234
494
|
}
|
|
495
|
+
const details = formatEntryDetails(meta);
|
|
496
|
+
if (details) lines.push(details);
|
|
235
497
|
return lines.join("\n").trim();
|
|
236
498
|
};
|
|
237
499
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { D as WorkspacePackage, O as Draft, _ as PublishPlan, s as TegamiPlugin, t as Awaitable, w as TegamiContext } from "../types-DKZsadC8.js";
|
|
2
|
+
import { GitPluginOptions } from "./git.js";
|
|
3
|
+
|
|
4
|
+
//#region src/plugins/gitlab.d.ts
|
|
5
|
+
interface GitlabRelease {
|
|
6
|
+
/** Release title */
|
|
7
|
+
title?: string;
|
|
8
|
+
/** Release notes */
|
|
9
|
+
notes?: string;
|
|
10
|
+
}
|
|
11
|
+
interface VersionMergeRequest {
|
|
12
|
+
/** Merge request title. */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** Merge request body. */
|
|
15
|
+
body?: string;
|
|
16
|
+
}
|
|
17
|
+
interface VersionMergeRequestOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Create the MR even outside of CI.
|
|
20
|
+
*
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
forceCreate?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Merge request branch.
|
|
26
|
+
*
|
|
27
|
+
* @default "tegami/version-packages"
|
|
28
|
+
*/
|
|
29
|
+
branch?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Merge request base branch.
|
|
32
|
+
*
|
|
33
|
+
* @default "main"
|
|
34
|
+
*/
|
|
35
|
+
base?: string;
|
|
36
|
+
/** Override details for "Version Packages" MR. */
|
|
37
|
+
create?: (this: TegamiContext, opts: {
|
|
38
|
+
draft: Draft;
|
|
39
|
+
}) => Awaitable<VersionMergeRequest>;
|
|
40
|
+
}
|
|
41
|
+
/** Options for creating GitLab releases after a successful publish. */
|
|
42
|
+
interface GitLabPluginOptions extends GitPluginOptions {
|
|
43
|
+
/** GitLab repository. */
|
|
44
|
+
repo?: string;
|
|
45
|
+
/** Optional GitLab token for Git & GitLab operations. */
|
|
46
|
+
token?: string;
|
|
47
|
+
/** GitLab API URL. Defaults to `https://gitlab.com/api/v4`. */
|
|
48
|
+
apiUrl?: string;
|
|
49
|
+
/** GitLab web URL. Defaults to `https://gitlab.com`. */
|
|
50
|
+
webUrl?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Create GitLab release for published packages.
|
|
53
|
+
*
|
|
54
|
+
* @default true
|
|
55
|
+
*/
|
|
56
|
+
release?: boolean | {
|
|
57
|
+
/**
|
|
58
|
+
* Create GitLab release immediately after successful publish, without waiting for others.
|
|
59
|
+
*
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
eager?: boolean; /** Override release details for a single package. */
|
|
63
|
+
create?: (this: TegamiContext, opts: {
|
|
64
|
+
tag: string;
|
|
65
|
+
pkg: WorkspacePackage;
|
|
66
|
+
plan: PublishPlan;
|
|
67
|
+
}) => Awaitable<GitlabRelease>; /** Override release details when multiple packages share a git tag. */
|
|
68
|
+
createGrouped?: (this: TegamiContext, opts: {
|
|
69
|
+
tag: string;
|
|
70
|
+
packages: WorkspacePackage[];
|
|
71
|
+
plan: PublishPlan;
|
|
72
|
+
}) => Awaitable<GitlabRelease>;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* (CLI only) Open a version merge request after versioning.
|
|
76
|
+
*
|
|
77
|
+
* Defaults to enabled in CI and disabled locally.
|
|
78
|
+
*/
|
|
79
|
+
versionMr?: VersionMergeRequestOptions | false;
|
|
80
|
+
}
|
|
81
|
+
/** Create GitLab releases for successfully published packages after the whole plan succeeds. */
|
|
82
|
+
declare function gitlab(options?: GitLabPluginOptions): TegamiPlugin[];
|
|
83
|
+
//#endregion
|
|
84
|
+
export { GitLabPluginOptions, gitlab };
|