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.
@@ -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 };
@@ -1,4 +1,4 @@
1
- import { C as WorkspacePackage, O as BumpType, s as TegamiPlugin } from "../types-DKeF_CDv.js";
1
+ import { D as WorkspacePackage, j as BumpType, s as TegamiPlugin } from "../types-DKZsadC8.js";
2
2
 
3
3
  //#region src/plugins/go.d.ts
4
4
  interface GoModFile {
@@ -1,4 +1,4 @@
1
- import { i as isNodeError, n as execFailure } from "../error-D3dwQnwR.js";
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: true,
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
- cli: { async draftApplied() {
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 { g as cargo, h as CargoPluginOptions, m as CargoPackage } from "../types-DKeF_CDv.js";
1
+ import { b as cargo, v as CargoPackage, y as CargoPluginOptions } from "../types-DKZsadC8.js";
2
2
  export { CargoPackage, CargoPluginOptions, cargo };
@@ -1,4 +1,4 @@
1
- import { i as isNodeError, n as execFailure } from "../error-D3dwQnwR.js";
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
- cli: { async draftApplied() {
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 }) => {
@@ -1,2 +1,2 @@
1
- import { _ as NpmPackage, v as NpmPluginOptions, y as npm } from "../types-DKeF_CDv.js";
1
+ import { C as npm, S as NpmPluginOptions, x as NpmPackage } from "../types-DKZsadC8.js";
2
2
  export { NpmPackage, NpmPluginOptions, npm };
@@ -1,2 +1,2 @@
1
- import { n as npm, t as NpmPackage } from "../npm-B_43doHi.js";
1
+ import { n as npm, t as NpmPackage } from "../npm-CMOyacwf.js";
2
2
  export { NpmPackage, npm };