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
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { i as formatPackageVersion, r as formatNpmDistTag } from "../semver-EKJ8yK5U.js";
|
|
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";
|
|
5
|
+
import { git } from "./git.js";
|
|
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";
|
|
9
|
+
import { x } from "tinyexec";
|
|
10
|
+
import { resolveCommand } from "package-manager-detector";
|
|
11
|
+
import { note, outro } from "@clack/prompts";
|
|
12
|
+
//#region src/plugins/gitlab/api.ts
|
|
13
|
+
function parseGitLabRepo(repo) {
|
|
14
|
+
const projectPath = repo.replace(/^\/+|\/+$/g, "");
|
|
15
|
+
if (!projectPath || !projectPath.includes("/")) throw new Error(`Invalid GitLab repository: ${repo}`);
|
|
16
|
+
return {
|
|
17
|
+
projectPath,
|
|
18
|
+
encodedProjectPath: encodeURIComponent(projectPath)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function gitlabApiUrl(apiUrl = "https://gitlab.com/api/v4") {
|
|
22
|
+
return apiUrl.replace(/\/+$/, "");
|
|
23
|
+
}
|
|
24
|
+
function gitlabWebUrl(webUrl = "https://gitlab.com") {
|
|
25
|
+
return webUrl.replace(/\/+$/, "");
|
|
26
|
+
}
|
|
27
|
+
async function gitlabRequest(options, path, init = {}) {
|
|
28
|
+
const headers = new Headers(init.headers);
|
|
29
|
+
headers.set("Accept", "application/json");
|
|
30
|
+
if (options.token) headers.set(options.token.type === "job-token" ? "JOB-TOKEN" : "PRIVATE-TOKEN", options.token.value);
|
|
31
|
+
return fetch(`${gitlabApiUrl(options.apiUrl)}${path}`, {
|
|
32
|
+
...init,
|
|
33
|
+
headers
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function releaseExistsByTag(repo, tag, options = {}) {
|
|
37
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
38
|
+
const response = await gitlabRequest(options, `/projects/${encodedProjectPath}/releases/${encodeURIComponent(tag)}`, { method: "HEAD" });
|
|
39
|
+
if (response.status === 404) return false;
|
|
40
|
+
if (!response.ok) throw new Error(`Failed to get GitLab release for ${tag}.`);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
async function createRelease(repo, options) {
|
|
44
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
45
|
+
if (!(await gitlabRequest(options, `/projects/${encodedProjectPath}/releases`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: { "Content-Type": "application/json" },
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
tag_name: options.tag,
|
|
50
|
+
name: options.title,
|
|
51
|
+
description: options.notes
|
|
52
|
+
})
|
|
53
|
+
})).ok) throw new Error(`Failed to create GitLab release for ${options.tag}.`);
|
|
54
|
+
}
|
|
55
|
+
async function findOpenMergeRequest(repo, options) {
|
|
56
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
57
|
+
const params = new URLSearchParams({
|
|
58
|
+
source_branch: options.head,
|
|
59
|
+
state: "opened"
|
|
60
|
+
});
|
|
61
|
+
if (options.base) params.set("target_branch", options.base);
|
|
62
|
+
const response = await gitlabRequest(options, `/projects/${encodedProjectPath}/merge_requests?${params}`);
|
|
63
|
+
if (!response.ok) throw new Error("Failed to check for an existing version merge request.");
|
|
64
|
+
return (await response.json())[0]?.iid;
|
|
65
|
+
}
|
|
66
|
+
async function updateMergeRequest(repo, number, options) {
|
|
67
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
68
|
+
if (!(await gitlabRequest(options, `/projects/${encodedProjectPath}/merge_requests/${number}`, {
|
|
69
|
+
method: "PUT",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
title: options.title,
|
|
73
|
+
description: options.body,
|
|
74
|
+
target_branch: options.base
|
|
75
|
+
})
|
|
76
|
+
})).ok) throw new Error("Failed to update the version merge request.");
|
|
77
|
+
}
|
|
78
|
+
async function createMergeRequest(repo, options) {
|
|
79
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
80
|
+
if (!(await gitlabRequest(options, `/projects/${encodedProjectPath}/merge_requests`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
title: options.title,
|
|
85
|
+
description: options.body,
|
|
86
|
+
source_branch: options.head,
|
|
87
|
+
target_branch: options.base
|
|
88
|
+
})
|
|
89
|
+
})).ok) throw new Error("Failed to create the version merge request.");
|
|
90
|
+
}
|
|
91
|
+
async function getMergeRequest(repo, number, options = {}) {
|
|
92
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
93
|
+
const response = await gitlabRequest(options, `/projects/${encodedProjectPath}/merge_requests/${number}`);
|
|
94
|
+
if (!response.ok) throw new Error(`Failed to resolve merge request !${number}.`);
|
|
95
|
+
const data = await response.json();
|
|
96
|
+
let sourceProjectPath;
|
|
97
|
+
if (data.source_project_id !== void 0) {
|
|
98
|
+
const projectResponse = await gitlabRequest(options, `/projects/${data.source_project_id}`);
|
|
99
|
+
if (projectResponse.ok) sourceProjectPath = (await projectResponse.json()).path_with_namespace;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
sourceBranch: data.source_branch,
|
|
103
|
+
sourceProjectPath,
|
|
104
|
+
baseSha: data.diff_refs?.base_sha ?? "",
|
|
105
|
+
headSha: data.diff_refs?.head_sha ?? data.sha ?? ""
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function listMergeRequestsForCommit(repo, commitSha, options = {}) {
|
|
109
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
110
|
+
const response = await gitlabRequest(options, `/projects/${encodedProjectPath}/repository/commits/${commitSha}/merge_requests`);
|
|
111
|
+
if (!response.ok) throw new Error(`Failed to list merge requests for commit ${commitSha.slice(0, 7)}.`);
|
|
112
|
+
return (await response.json()).map((mergeRequest) => ({
|
|
113
|
+
number: mergeRequest.iid,
|
|
114
|
+
title: mergeRequest.title,
|
|
115
|
+
user: mergeRequest.author ? { login: mergeRequest.author.username } : void 0
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
async function findMergeRequestCommentByPrefix(repo, mergeRequestNumber, prefix, options = {}) {
|
|
119
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
120
|
+
let page = 1;
|
|
121
|
+
while (true) {
|
|
122
|
+
const response = await gitlabRequest(options, `/projects/${encodedProjectPath}/merge_requests/${mergeRequestNumber}/notes?per_page=100&page=${page}`);
|
|
123
|
+
if (!response.ok) throw new Error("Failed to list merge request comments.");
|
|
124
|
+
const batch = await response.json();
|
|
125
|
+
if (batch.length === 0) break;
|
|
126
|
+
const comment = batch.find((comment) => comment.body.startsWith(prefix));
|
|
127
|
+
if (comment) return comment.id;
|
|
128
|
+
if (batch.length < 100) break;
|
|
129
|
+
page += 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function updateMergeRequestComment(repo, mergeRequestNumber, commentId, body, options = {}) {
|
|
133
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
134
|
+
if (!(await gitlabRequest(options, `/projects/${encodedProjectPath}/merge_requests/${mergeRequestNumber}/notes/${commentId}`, {
|
|
135
|
+
method: "PUT",
|
|
136
|
+
headers: { "Content-Type": "application/json" },
|
|
137
|
+
body: JSON.stringify({ body })
|
|
138
|
+
})).ok) throw new Error("Failed to update merge request comment.");
|
|
139
|
+
}
|
|
140
|
+
async function createMergeRequestComment(repo, mergeRequestNumber, body, options = {}) {
|
|
141
|
+
const { encodedProjectPath } = parseGitLabRepo(repo);
|
|
142
|
+
if (!(await gitlabRequest(options, `/projects/${encodedProjectPath}/merge_requests/${mergeRequestNumber}/notes`, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: { "Content-Type": "application/json" },
|
|
145
|
+
body: JSON.stringify({ body })
|
|
146
|
+
})).ok) throw new Error("Failed to create merge request comment.");
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/plugins/gitlab/cli.ts
|
|
150
|
+
const COMMENT_MARKER = "<!-- tegami -->";
|
|
151
|
+
function registerMrCli(cli) {
|
|
152
|
+
cli.command("mr preview", { description: "show a merge request release preview and changelog guidance" }).option("artifact", {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "write preview markdown to a file"
|
|
155
|
+
}).option("number", {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "merge request iid"
|
|
158
|
+
}).action(async ({ context, values }) => {
|
|
159
|
+
const number = values.number ? parsePositiveInt(values.number, "--number") : void 0;
|
|
160
|
+
const artifact = values.artifact;
|
|
161
|
+
const body = await buildMrPreview(context, await createDraft(await readChangelogEntries(context), context), { number });
|
|
162
|
+
if (artifact) {
|
|
163
|
+
const artifactPath = resolve(context.cwd, artifact);
|
|
164
|
+
await writeFile(artifactPath, body);
|
|
165
|
+
if (!isCI()) {
|
|
166
|
+
note(relative(context.cwd, artifactPath) || artifact, "Release preview");
|
|
167
|
+
outro("Release preview ready.");
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (isCI()) {
|
|
172
|
+
process.stdout.write(`${body}\n`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
note(body, "Release preview");
|
|
176
|
+
outro("Release preview ready.");
|
|
177
|
+
});
|
|
178
|
+
cli.command("mr comment", { description: "post the merge request release preview as a comment" }).positional("artifact").option("number", {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "merge request iid"
|
|
181
|
+
}).action(async ({ context, values, positionals }) => {
|
|
182
|
+
await postMrComment(context, await readFile(positionals.artifact, "utf8"), { number: values.number ? parsePositiveInt(values.number, "--number") : void 0 });
|
|
183
|
+
outro("Merge request comment updated.");
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async function buildMrPreview(context, draft, options = {}) {
|
|
187
|
+
const mergeRequest = await resolveMergeRequest(context, options);
|
|
188
|
+
const tegamiCommandRaw = resolveCommand(context.npm?.client ?? "npm", "run", ["tegami"]);
|
|
189
|
+
const tegamiCommand = [tegamiCommandRaw.command, ...tegamiCommandRaw.args].join(" ");
|
|
190
|
+
const changelogFiles = await listMergeRequestChangelogFiles(context, mergeRequest.baseSha, mergeRequest.headSha);
|
|
191
|
+
const createLink = createChangelogUrl(context, mergeRequest.headRepo, mergeRequest.headRef, changelogFilename());
|
|
192
|
+
const pendingPackages = [];
|
|
193
|
+
const lines = [
|
|
194
|
+
"### Tegami",
|
|
195
|
+
"",
|
|
196
|
+
`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.`,
|
|
197
|
+
"",
|
|
198
|
+
`[**Create a changelog →**](${createLink}) · [Changelog format](https://tegami.fuma-nama.dev/changelog)`,
|
|
199
|
+
""
|
|
200
|
+
];
|
|
201
|
+
for (const pkg of context.graph.getPackages()) {
|
|
202
|
+
const plan = draft.getPackageDraft(pkg.id);
|
|
203
|
+
if (!plan) continue;
|
|
204
|
+
const bumped = plan.bumpVersion(pkg);
|
|
205
|
+
if (!bumped || !pkg.version || bumped === pkg.version) continue;
|
|
206
|
+
pendingPackages.push({
|
|
207
|
+
name: pkg.name,
|
|
208
|
+
type: plan.type ?? "—",
|
|
209
|
+
from: pkg.version,
|
|
210
|
+
to: bumped,
|
|
211
|
+
distTag: plan.npm?.distTag
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const requestChangelogs = draft.getChangelogs().filter((entry) => changelogFiles.has(entry.filename));
|
|
215
|
+
if (pendingPackages.length > 0) {
|
|
216
|
+
lines.push("#### Release preview", "", "| Package | Bump | Version |", "| --- | --- | --- |");
|
|
217
|
+
for (const { name, type, from, to, distTag } of pendingPackages) lines.push(`| \`${name}\` | ${type} | \`${from}\` → \`${to}\`${formatNpmDistTag(distTag)} |`);
|
|
218
|
+
lines.push("");
|
|
219
|
+
}
|
|
220
|
+
if (requestChangelogs.length > 0) {
|
|
221
|
+
lines.push("#### Changelogs in this MR", "", "| Changelog | Title |", "| --- | --- |");
|
|
222
|
+
for (const entry of requestChangelogs) for (const section of entry.sections) lines.push(`| \`${entry.filename}\` | ${section.title} |`);
|
|
223
|
+
lines.push("");
|
|
224
|
+
} else if (pendingPackages.length === 0) lines.push("#### No changelogs yet", "", "This MR has no pending changelog files. If your changes require a release, add a changelog before merging.", "");
|
|
225
|
+
else if (changelogFiles.size === 0) lines.push("This MR does not add changelog files. Pending changelogs from other branches are included in the preview above.", "");
|
|
226
|
+
lines.push(`Run \`${tegamiCommand}\` locally to create a changelog interactively.`, "", `<sub>Managed by [Tegami](https://tegami.fuma-nama.dev).</sub>`, "");
|
|
227
|
+
return lines.join("\n");
|
|
228
|
+
}
|
|
229
|
+
async function postMrComment(context, body, options = {}) {
|
|
230
|
+
const { repo } = context.gitlab ?? {};
|
|
231
|
+
if (!repo) {
|
|
232
|
+
outro("GITLAB_REPOSITORY or CI_PROJECT_PATH is required.");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const number = options.number ?? (process.env.CI_MERGE_REQUEST_IID ? parsePositiveInt(process.env.CI_MERGE_REQUEST_IID, "CI_MERGE_REQUEST_IID") : void 0);
|
|
236
|
+
if (!number) {
|
|
237
|
+
outro("CI_MERGE_REQUEST_IID or --number is required.");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const api = {
|
|
241
|
+
apiUrl: context.gitlab?.apiUrl,
|
|
242
|
+
token: context.gitlab?.token
|
|
243
|
+
};
|
|
244
|
+
const markedBody = `${COMMENT_MARKER}\n${body}`;
|
|
245
|
+
const existingId = await findMergeRequestCommentByPrefix(repo, number, COMMENT_MARKER, api);
|
|
246
|
+
if (existingId) {
|
|
247
|
+
await updateMergeRequestComment(repo, number, existingId, markedBody, api);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
await createMergeRequestComment(repo, number, markedBody, api);
|
|
251
|
+
}
|
|
252
|
+
async function resolveMergeRequest(context, options) {
|
|
253
|
+
const { repo } = context.gitlab ?? {};
|
|
254
|
+
if (!repo) throw new Error("GITLAB_REPOSITORY or CI_PROJECT_PATH is required.");
|
|
255
|
+
const api = {
|
|
256
|
+
apiUrl: context.gitlab?.apiUrl,
|
|
257
|
+
token: context.gitlab?.token
|
|
258
|
+
};
|
|
259
|
+
if (options.number !== void 0) {
|
|
260
|
+
const data = await getMergeRequest(repo, options.number, api);
|
|
261
|
+
if (!data.baseSha || !data.headSha) throw new Error(`Merge request !${options.number} does not include diff refs.`);
|
|
262
|
+
return {
|
|
263
|
+
headRepo: data.sourceProjectPath ?? repo,
|
|
264
|
+
headRef: data.sourceBranch,
|
|
265
|
+
baseSha: data.baseSha,
|
|
266
|
+
headSha: data.headSha
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const headRef = process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
|
|
270
|
+
const baseSha = process.env.CI_MERGE_REQUEST_DIFF_BASE_SHA;
|
|
271
|
+
const headSha = process.env.CI_COMMIT_SHA;
|
|
272
|
+
if (headRef && baseSha && headSha) return {
|
|
273
|
+
headRepo: process.env.CI_MERGE_REQUEST_SOURCE_PROJECT_PATH ?? repo,
|
|
274
|
+
headRef,
|
|
275
|
+
baseSha,
|
|
276
|
+
headSha
|
|
277
|
+
};
|
|
278
|
+
throw new Error("A merge request event or --number is required.");
|
|
279
|
+
}
|
|
280
|
+
async function listMergeRequestChangelogFiles(context, baseSha, headSha) {
|
|
281
|
+
const dir = relative(context.cwd, context.changelogDir);
|
|
282
|
+
const result = await x("git", [
|
|
283
|
+
"diff",
|
|
284
|
+
"--name-only",
|
|
285
|
+
"--diff-filter=ACMRD",
|
|
286
|
+
`${baseSha}...${headSha}`,
|
|
287
|
+
"--",
|
|
288
|
+
`${dir}/`
|
|
289
|
+
], { nodeOptions: { cwd: context.cwd } });
|
|
290
|
+
if (result.exitCode !== 0) throw execFailure("Failed to list merge request changelog files.", result);
|
|
291
|
+
const files = /* @__PURE__ */ new Set();
|
|
292
|
+
for (const line of result.stdout.split("\n")) {
|
|
293
|
+
const trimmed = line.trim();
|
|
294
|
+
if (trimmed.endsWith(".md")) files.add(basename(trimmed));
|
|
295
|
+
}
|
|
296
|
+
return files;
|
|
297
|
+
}
|
|
298
|
+
function createChangelogUrl(context, repo, branch, filename) {
|
|
299
|
+
const filePath = join(relative(context.cwd, context.changelogDir), filename).replaceAll("\\", "/");
|
|
300
|
+
const branchPath = branch.split("/").map(encodeURIComponent).join("/");
|
|
301
|
+
const params = new URLSearchParams({ file_name: filePath });
|
|
302
|
+
return `${gitlabWebUrl(context.gitlab?.webUrl)}/${repo}/-/new/${branchPath}?${params}`;
|
|
303
|
+
}
|
|
304
|
+
function parsePositiveInt(value, option) {
|
|
305
|
+
const number = Number(value);
|
|
306
|
+
if (!Number.isInteger(number) || number <= 0) throw new Error(`${option} must be a positive integer.`);
|
|
307
|
+
return number;
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/plugins/gitlab.ts
|
|
311
|
+
/** Create GitLab releases for successfully published packages after the whole plan succeeds. */
|
|
312
|
+
function gitlab(options = {}) {
|
|
313
|
+
const { release: releaseOptions = true } = options;
|
|
314
|
+
let renderer;
|
|
315
|
+
function getRenderer(context) {
|
|
316
|
+
return renderer ??= createChangelogRenderer(context);
|
|
317
|
+
}
|
|
318
|
+
const cliOriginalPackageVersions = /* @__PURE__ */ new Map();
|
|
319
|
+
return [git(options), {
|
|
320
|
+
name: "gitlab",
|
|
321
|
+
init() {
|
|
322
|
+
this.gitlab = {
|
|
323
|
+
repo: options.repo ?? process.env.GITLAB_REPOSITORY ?? process.env.CI_PROJECT_PATH,
|
|
324
|
+
token: resolveGitLabToken(options.token),
|
|
325
|
+
apiUrl: options.apiUrl ?? process.env.GITLAB_API_URL ?? process.env.CI_API_V4_URL,
|
|
326
|
+
webUrl: options.webUrl ?? process.env.GITLAB_SERVER_URL ?? process.env.CI_SERVER_URL
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
async resolvePlanStatus({ plan }) {
|
|
330
|
+
const { repo, token } = this.gitlab;
|
|
331
|
+
if (!repo || !token || releaseOptions === false) return;
|
|
332
|
+
const requiredTags = /* @__PURE__ */ new Set();
|
|
333
|
+
const api = gitLabApiOptions(this.gitlab);
|
|
334
|
+
for (const pkg of plan.packages.values()) if (pkg.preflight.shouldPublish && pkg.git?.tag) requiredTags.add(pkg.git.tag);
|
|
335
|
+
return Array.from(requiredTags, async (tag) => {
|
|
336
|
+
if (!await releaseExistsByTag(repo, tag, api)) return "pending";
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
async afterPublishAll({ plan }) {
|
|
340
|
+
const { repo, token } = this.gitlab;
|
|
341
|
+
if (!repo || !token || releaseOptions === false) return;
|
|
342
|
+
const api = gitLabApiOptions(this.gitlab);
|
|
343
|
+
const { eager = false, create, createGrouped } = releaseOptions === true ? {} : releaseOptions;
|
|
344
|
+
const groups = /* @__PURE__ */ new Map();
|
|
345
|
+
for (const [id, { preflight, publishResult, git }] of plan.packages) {
|
|
346
|
+
if (!eager && publishResult.type === "failed") return;
|
|
347
|
+
const tag = git?.tag;
|
|
348
|
+
if (!tag || !preflight.shouldPublish) continue;
|
|
349
|
+
const pkg = this.graph.get(id);
|
|
350
|
+
const group = groups.get(tag);
|
|
351
|
+
if (group) group.push(pkg);
|
|
352
|
+
else groups.set(tag, [pkg]);
|
|
353
|
+
}
|
|
354
|
+
await Promise.all(Array.from(groups, async ([tag, packages]) => {
|
|
355
|
+
for (const member of packages) if (plan.packages.get(member.id).publishResult.type === "failed") return;
|
|
356
|
+
if (await releaseExistsByTag(repo, tag, api)) return;
|
|
357
|
+
let release;
|
|
358
|
+
if (packages.length > 1) {
|
|
359
|
+
const overrides = await createGrouped?.call(this, {
|
|
360
|
+
tag,
|
|
361
|
+
packages,
|
|
362
|
+
plan
|
|
363
|
+
}) ?? {};
|
|
364
|
+
release = {
|
|
365
|
+
title: overrides.title ?? tag,
|
|
366
|
+
notes: overrides.notes ?? await defaultGroupedNotes(getRenderer(this), plan, packages)
|
|
367
|
+
};
|
|
368
|
+
} else {
|
|
369
|
+
const pkg = packages[0];
|
|
370
|
+
const overrides = await create?.call(this, {
|
|
371
|
+
tag,
|
|
372
|
+
pkg,
|
|
373
|
+
plan
|
|
374
|
+
}) ?? {};
|
|
375
|
+
const packagePlan = plan.packages.get(pkg.id);
|
|
376
|
+
release = {
|
|
377
|
+
title: overrides.title ?? formatPackageVersion(pkg.name, pkg.version, packagePlan?.npm?.distTag),
|
|
378
|
+
notes: overrides.notes ?? await defaultNotes(getRenderer(this), pkg, packagePlan)
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
await createRelease(repo, {
|
|
382
|
+
tag,
|
|
383
|
+
title: release.title,
|
|
384
|
+
notes: release.notes,
|
|
385
|
+
...api
|
|
386
|
+
});
|
|
387
|
+
}));
|
|
388
|
+
},
|
|
389
|
+
async initCli(cli) {
|
|
390
|
+
registerMrCli(cli);
|
|
391
|
+
if (!isCI()) return;
|
|
392
|
+
const { repo, token, webUrl } = this.gitlab ?? {};
|
|
393
|
+
if (!token || !repo) return;
|
|
394
|
+
const result = await x("git", [
|
|
395
|
+
"remote",
|
|
396
|
+
"set-url",
|
|
397
|
+
"origin",
|
|
398
|
+
gitlabRemoteUrl(repo, token, webUrl)
|
|
399
|
+
], { nodeOptions: { cwd: this.cwd } });
|
|
400
|
+
if (result.exitCode !== 0) throw execFailure("Failed to configure git remote for GitLab CI.", result);
|
|
401
|
+
},
|
|
402
|
+
initCliDraft() {
|
|
403
|
+
for (const pkg of this.graph.getPackages()) cliOriginalPackageVersions.set(pkg.id, pkg.version);
|
|
404
|
+
},
|
|
405
|
+
async applyCliDraft(draft) {
|
|
406
|
+
const config = options.versionMr ?? {};
|
|
407
|
+
if (config === false || !(config.forceCreate || isCI()) || !await hasGitChanges(this.cwd)) return;
|
|
408
|
+
const repo = this.gitlab?.repo;
|
|
409
|
+
const { branch = "tegami/version-packages", base = "main" } = config;
|
|
410
|
+
const baseMR = await config.create?.call(this, { draft });
|
|
411
|
+
const mr = {
|
|
412
|
+
title: baseMR?.title ?? "Version Packages",
|
|
413
|
+
body: baseMR?.body ?? createVersionRequestBody(draft, this, cliOriginalPackageVersions, "Merge this MR to publish the versioned packages.")
|
|
414
|
+
};
|
|
415
|
+
await commitVersionBranchChanges(this.cwd, branch, mr.title);
|
|
416
|
+
const api = gitLabApiOptions(this.gitlab);
|
|
417
|
+
if (!repo) return;
|
|
418
|
+
const openMr = await findOpenMergeRequest(repo, {
|
|
419
|
+
head: branch,
|
|
420
|
+
base,
|
|
421
|
+
...api
|
|
422
|
+
});
|
|
423
|
+
if (openMr !== void 0) {
|
|
424
|
+
await updateMergeRequest(repo, openMr, {
|
|
425
|
+
title: mr.title,
|
|
426
|
+
body: mr.body,
|
|
427
|
+
base,
|
|
428
|
+
...api
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
await createMergeRequest(repo, {
|
|
433
|
+
title: mr.title,
|
|
434
|
+
body: mr.body,
|
|
435
|
+
head: branch,
|
|
436
|
+
base,
|
|
437
|
+
...api
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}];
|
|
441
|
+
}
|
|
442
|
+
function createChangelogRenderer(context) {
|
|
443
|
+
const { repo, webUrl } = context.gitlab;
|
|
444
|
+
const api = gitLabApiOptions(context.gitlab);
|
|
445
|
+
const baseUrl = gitlabWebUrl(webUrl);
|
|
446
|
+
const resolveFileCommit = cached((filename) => filename, async (filename) => {
|
|
447
|
+
const result = await x("git", [
|
|
448
|
+
"log",
|
|
449
|
+
"--diff-filter=A",
|
|
450
|
+
"-1",
|
|
451
|
+
"--format=%H",
|
|
452
|
+
"--",
|
|
453
|
+
relative(context.cwd, join(context.changelogDir, filename))
|
|
454
|
+
], { nodeOptions: { cwd: context.cwd } });
|
|
455
|
+
if (result.exitCode !== 0) return;
|
|
456
|
+
return result.stdout.trim() || void 0;
|
|
457
|
+
});
|
|
458
|
+
const resolveEntryMeta = cached((entry) => entry.id, async (entry) => {
|
|
459
|
+
const commit = await resolveFileCommit(entry.filename);
|
|
460
|
+
if (!commit || !repo) return {
|
|
461
|
+
commit,
|
|
462
|
+
mergeRequests: []
|
|
463
|
+
};
|
|
464
|
+
return {
|
|
465
|
+
commit,
|
|
466
|
+
mergeRequests: await listMergeRequestsForCommit(repo, commit, api).catch(() => [])
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
function formatEntryDetails(meta) {
|
|
470
|
+
if (meta.mergeRequests.length === 0) return;
|
|
471
|
+
const lines = [];
|
|
472
|
+
for (const mr of meta.mergeRequests) {
|
|
473
|
+
let line = repo ? `- [!${mr.number} ${mr.title}](${baseUrl}/${repo}/-/merge_requests/${mr.number})` : `- #${mr.number} ${mr.title}`;
|
|
474
|
+
if (mr.user) line += ` by @${mr.user.login}`;
|
|
475
|
+
lines.push(line);
|
|
476
|
+
}
|
|
477
|
+
return [
|
|
478
|
+
"<details>",
|
|
479
|
+
"<summary>Merge request & contributors</summary>",
|
|
480
|
+
"",
|
|
481
|
+
...lines,
|
|
482
|
+
"",
|
|
483
|
+
"</details>"
|
|
484
|
+
].join("\n");
|
|
485
|
+
}
|
|
486
|
+
return async (entry) => {
|
|
487
|
+
const meta = await resolveEntryMeta(entry);
|
|
488
|
+
let commitSuffix = "";
|
|
489
|
+
if (meta.commit) {
|
|
490
|
+
const short = meta.commit.slice(0, 7);
|
|
491
|
+
const link = repo ? `[${short}](${baseUrl}/${repo}/-/commit/${meta.commit})` : `\`${short}\``;
|
|
492
|
+
commitSuffix += ` (${link})`;
|
|
493
|
+
}
|
|
494
|
+
const lines = [];
|
|
495
|
+
for (const section of entry.sections) {
|
|
496
|
+
lines.push(`### ${section.title}${commitSuffix}`, "");
|
|
497
|
+
if (section.content) lines.push(section.content, "");
|
|
498
|
+
}
|
|
499
|
+
const details = formatEntryDetails(meta);
|
|
500
|
+
if (details) lines.push(details);
|
|
501
|
+
return lines.join("\n").trim();
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
async function defaultNotes(renderer, pkg, packagePlan) {
|
|
505
|
+
if (packagePlan && packagePlan.changelogs.length > 0) return (await Promise.all(packagePlan.changelogs.map(renderer))).join("\n\n");
|
|
506
|
+
return `Published ${formatPackageVersion(pkg.name, pkg.version, packagePlan?.npm?.distTag)}.`;
|
|
507
|
+
}
|
|
508
|
+
async function defaultGroupedNotes(renderer, plan, packages) {
|
|
509
|
+
const changelogs = /* @__PURE__ */ new Map();
|
|
510
|
+
for (const pkg of packages) {
|
|
511
|
+
const packagePlan = plan.packages.get(pkg.id);
|
|
512
|
+
if (!packagePlan) continue;
|
|
513
|
+
for (const entry of packagePlan.changelogs) changelogs.set(entry.id, entry);
|
|
514
|
+
}
|
|
515
|
+
const sections = [packages.map((pkg) => {
|
|
516
|
+
const packagePlan = plan.packages.get(pkg.id);
|
|
517
|
+
return `- ${formatPackageVersion(pkg.name, pkg.version, packagePlan?.npm?.distTag)}`;
|
|
518
|
+
}).join("\n")];
|
|
519
|
+
if (changelogs.size > 0) {
|
|
520
|
+
const notes = await Promise.all(Array.from(changelogs.values(), renderer));
|
|
521
|
+
sections.push("", notes.join("\n\n"));
|
|
522
|
+
}
|
|
523
|
+
return sections.join("\n");
|
|
524
|
+
}
|
|
525
|
+
function gitLabApiOptions(gitlab) {
|
|
526
|
+
const options = {};
|
|
527
|
+
if (gitlab?.apiUrl) options.apiUrl = gitlab.apiUrl;
|
|
528
|
+
if (gitlab?.token) options.token = gitlab.token;
|
|
529
|
+
return options;
|
|
530
|
+
}
|
|
531
|
+
function resolveGitLabToken(optionToken) {
|
|
532
|
+
if (optionToken) return {
|
|
533
|
+
value: optionToken,
|
|
534
|
+
type: "private-token"
|
|
535
|
+
};
|
|
536
|
+
if (process.env.GITLAB_TOKEN) return {
|
|
537
|
+
value: process.env.GITLAB_TOKEN,
|
|
538
|
+
type: "private-token"
|
|
539
|
+
};
|
|
540
|
+
if (process.env.GL_TOKEN) return {
|
|
541
|
+
value: process.env.GL_TOKEN,
|
|
542
|
+
type: "private-token"
|
|
543
|
+
};
|
|
544
|
+
if (process.env.CI_JOB_TOKEN) return {
|
|
545
|
+
value: process.env.CI_JOB_TOKEN,
|
|
546
|
+
type: "job-token"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function gitlabRemoteUrl(repo, token, webUrl) {
|
|
550
|
+
const username = token.type === "job-token" ? "gitlab-ci-token" : "oauth2";
|
|
551
|
+
return `${gitlabWebUrl(webUrl).replace(/^https?:\/\//, `https://${username}:${token.value}@`)}/${repo}.git`;
|
|
552
|
+
}
|
|
553
|
+
//#endregion
|
|
554
|
+
export { gitlab };
|
package/dist/plugins/go.d.ts
CHANGED
package/dist/plugins/go.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as isNodeError, n as execFailure } from "../error-
|
|
1
|
+
import { i as isNodeError, n as execFailure } from "../error-We7chQVJ.js";
|
|
2
2
|
import { n as WorkspacePackage } from "../graph-gThXu8Cz.js";
|
|
3
3
|
import { relative, resolve } from "node:path";
|
|
4
4
|
import { x } from "tinyexec";
|
|
@@ -135,6 +135,7 @@ function go({ updateLockFile = true, bumpDep: getBumpDepType } = {}) {
|
|
|
135
135
|
},
|
|
136
136
|
async publishPreflight({ pkg }) {
|
|
137
137
|
if (!(pkg instanceof GoPackage)) return;
|
|
138
|
+
const shouldPublish = pkg.getPackageOptions().go?.publish ?? this.graph.getPackageGroup(pkg.id)?.options?.go?.publish ?? true;
|
|
138
139
|
const wait = [];
|
|
139
140
|
for (const [moduleName] of pkg.mod.requires) {
|
|
140
141
|
const linked = this.graph.get(`go:${moduleName}`);
|
|
@@ -147,7 +148,7 @@ function go({ updateLockFile = true, bumpDep: getBumpDepType } = {}) {
|
|
|
147
148
|
wait.push(linked.id);
|
|
148
149
|
}
|
|
149
150
|
return {
|
|
150
|
-
shouldPublish
|
|
151
|
+
shouldPublish,
|
|
151
152
|
wait
|
|
152
153
|
};
|
|
153
154
|
},
|
|
@@ -163,7 +164,7 @@ function go({ updateLockFile = true, bumpDep: getBumpDepType } = {}) {
|
|
|
163
164
|
if (!(pkg instanceof GoPackage)) return;
|
|
164
165
|
return { type: await isModulePublished(pkg.name, pkg.version) ? "skipped" : "published" };
|
|
165
166
|
},
|
|
166
|
-
|
|
167
|
+
async applyCliDraft() {
|
|
167
168
|
if (!active || !updateLockFile) return;
|
|
168
169
|
if (await listGoWorkUsePaths(this.cwd)) {
|
|
169
170
|
const result = await x("go", ["work", "sync"], { nodeOptions: { cwd: this.cwd } });
|
|
@@ -175,7 +176,7 @@ function go({ updateLockFile = true, bumpDep: getBumpDepType } = {}) {
|
|
|
175
176
|
const result = await x("go", ["mod", "tidy"], { nodeOptions: { cwd: pkg.path } });
|
|
176
177
|
if (result.exitCode !== 0) throw execFailure(`Failed to run \`go mod tidy\` in ${pkg.path}.`, result);
|
|
177
178
|
}));
|
|
178
|
-
}
|
|
179
|
+
}
|
|
179
180
|
};
|
|
180
181
|
}
|
|
181
182
|
function depsPolicy({ graph }, getBumpDepType = () => "patch") {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as cargo, v as CargoPackage, y as CargoPluginOptions } from "../types-DKZsadC8.js";
|
|
2
2
|
export { CargoPackage, CargoPluginOptions, cargo };
|
package/dist/providers/cargo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as isNodeError, n as execFailure } from "../error-
|
|
1
|
+
import { i as isNodeError, n as execFailure } from "../error-We7chQVJ.js";
|
|
2
2
|
import { n as WorkspacePackage } from "../graph-gThXu8Cz.js";
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join, normalize } from "node:path";
|
|
@@ -129,11 +129,11 @@ function cargo({ updateLockFile = true, bumpDep: getBumpDepType } = {}) {
|
|
|
129
129
|
}
|
|
130
130
|
await Promise.all(writes);
|
|
131
131
|
},
|
|
132
|
-
|
|
132
|
+
async applyCliDraft() {
|
|
133
133
|
if (!active || !updateLockFile) return;
|
|
134
134
|
const result = await x("cargo", ["update", "--workspace"], { nodeOptions: { cwd: this.cwd } });
|
|
135
135
|
if (result.exitCode !== 0) throw execFailure("Failed to update Cargo lock file", result);
|
|
136
|
-
}
|
|
136
|
+
}
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
function depsPolicy({ graph }, getBumpDepType = ({ kind }) => {
|
package/dist/providers/npm.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as npm, S as NpmPluginOptions, x as NpmPackage } from "../types-DKZsadC8.js";
|
|
2
2
|
export { NpmPackage, NpmPluginOptions, npm };
|
package/dist/providers/npm.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as npm, t as NpmPackage } from "../npm-
|
|
1
|
+
import { n as npm, t as NpmPackage } from "../npm-CMOyacwf.js";
|
|
2
2
|
export { NpmPackage, npm };
|