tegami 0.1.6 → 0.2.0
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/api-D-jf_8xY.js +116 -0
- package/dist/cli/index.d.ts +4 -4
- package/dist/cli/index.js +94 -126
- package/dist/{generate-B3bcuKLY.js → generate-BMlrn-2e.js} +36 -18
- package/dist/generators/simple.d.ts +1 -1
- package/dist/generators/simple.js +3 -3
- package/dist/{graph-DLKPUXSD.js → graph-DrzluXw8.js} +17 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +478 -105
- package/dist/plugins/git.d.ts +1 -1
- package/dist/plugins/git.js +39 -35
- package/dist/plugins/github.d.ts +28 -15
- package/dist/plugins/github.js +158 -158
- package/dist/providers/cargo.d.ts +2 -2
- package/dist/providers/cargo.js +86 -100
- package/dist/providers/npm.d.ts +2 -2
- package/dist/providers/npm.js +132 -129
- package/dist/schemas-B7N6EE2k.js +26 -0
- package/dist/{types-DexeJdl7.d.ts → types-CurHqnWl.d.ts} +199 -229
- package/package.json +6 -7
- package/dist/checks-azjuIFt7.js +0 -109
- package/dist/publish-DkRT0yh6.js +0 -282
- package/dist/schemas-CurBAaW5.js +0 -42
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//#region src/plugins/github/api.ts
|
|
2
|
+
function parseGitHubRepo(repo) {
|
|
3
|
+
const [owner, name] = repo.split("/", 2);
|
|
4
|
+
if (!owner || !name) throw new Error(`Invalid GitHub repository: ${repo}`);
|
|
5
|
+
return {
|
|
6
|
+
owner,
|
|
7
|
+
repo: name
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
async function githubRequest(path, token, init = {}) {
|
|
11
|
+
const headers = new Headers(init.headers);
|
|
12
|
+
headers.set("Accept", "application/vnd.github+json");
|
|
13
|
+
headers.set("X-GitHub-Api-Version", "2022-11-28");
|
|
14
|
+
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
15
|
+
return fetch(`https://api.github.com${path}`, {
|
|
16
|
+
...init,
|
|
17
|
+
headers
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async function releaseExistsByTag(repo, tag, token) {
|
|
21
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
22
|
+
const response = await githubRequest(`/repos/${owner}/${name}/releases/tags/${encodeURIComponent(tag)}`, token, { method: "HEAD" });
|
|
23
|
+
if (response.status === 404) return false;
|
|
24
|
+
if (!response.ok) throw new Error(`Failed to get GitHub release for ${tag}.`);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
async function createRelease(repo, options) {
|
|
28
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
29
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/releases`, options.token, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
tag_name: options.tag,
|
|
34
|
+
name: options.title,
|
|
35
|
+
body: options.notes,
|
|
36
|
+
prerelease: options.prerelease ?? false
|
|
37
|
+
})
|
|
38
|
+
})).ok) throw new Error(`Failed to create GitHub release for ${options.tag}.`);
|
|
39
|
+
}
|
|
40
|
+
async function findOpenPullRequest(repo, headBranch, token) {
|
|
41
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
42
|
+
const response = await githubRequest(`/repos/${owner}/${name}/pulls?${new URLSearchParams({
|
|
43
|
+
head: `${owner}:${headBranch}`,
|
|
44
|
+
state: "open"
|
|
45
|
+
})}`, token);
|
|
46
|
+
if (!response.ok) throw new Error("Failed to check for an existing version pull request.");
|
|
47
|
+
return (await response.json())[0]?.number;
|
|
48
|
+
}
|
|
49
|
+
async function updatePullRequest(repo, number, options) {
|
|
50
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
51
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/pulls/${number}`, options.token, {
|
|
52
|
+
method: "PATCH",
|
|
53
|
+
headers: { "Content-Type": "application/json" },
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
title: options.title,
|
|
56
|
+
body: options.body
|
|
57
|
+
})
|
|
58
|
+
})).ok) throw new Error("Failed to update the version pull request.");
|
|
59
|
+
}
|
|
60
|
+
async function createPullRequest(repo, options) {
|
|
61
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
62
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/pulls`, options.token, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
title: options.title,
|
|
67
|
+
body: options.body,
|
|
68
|
+
head: options.head,
|
|
69
|
+
base: options.base
|
|
70
|
+
})
|
|
71
|
+
})).ok) throw new Error("Failed to create the version pull request.");
|
|
72
|
+
}
|
|
73
|
+
async function getPullRequest(repo, number, token) {
|
|
74
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
75
|
+
const response = await githubRequest(`/repos/${owner}/${name}/pulls/${number}`, token);
|
|
76
|
+
if (!response.ok) throw new Error(`Failed to resolve pull request #${number}.`);
|
|
77
|
+
const data = await response.json();
|
|
78
|
+
return {
|
|
79
|
+
headRefName: data.head.ref,
|
|
80
|
+
baseRefOid: data.base.sha,
|
|
81
|
+
headRefOid: data.head.sha,
|
|
82
|
+
headRepository: data.head.repo
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function findIssueCommentByPrefix(repo, issueNumber, prefix, token) {
|
|
86
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
87
|
+
let page = 1;
|
|
88
|
+
while (true) {
|
|
89
|
+
const response = await githubRequest(`/repos/${owner}/${name}/issues/${issueNumber}/comments?per_page=100&page=${page}`, token);
|
|
90
|
+
if (!response.ok) throw new Error("Failed to list pull request comments.");
|
|
91
|
+
const batch = await response.json();
|
|
92
|
+
if (batch.length === 0) break;
|
|
93
|
+
const comment = batch.find((comment) => comment.body.startsWith(prefix));
|
|
94
|
+
if (comment) return comment.id;
|
|
95
|
+
if (batch.length < 100) break;
|
|
96
|
+
page += 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function updateIssueComment(repo, commentId, body, token) {
|
|
100
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
101
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/issues/comments/${commentId}`, token, {
|
|
102
|
+
method: "PATCH",
|
|
103
|
+
headers: { "Content-Type": "application/json" },
|
|
104
|
+
body: JSON.stringify({ body })
|
|
105
|
+
})).ok) throw new Error("Failed to update pull request comment.");
|
|
106
|
+
}
|
|
107
|
+
async function createIssueComment(repo, issueNumber, body, token) {
|
|
108
|
+
const { owner, repo: name } = parseGitHubRepo(repo);
|
|
109
|
+
if (!(await githubRequest(`/repos/${owner}/${name}/issues/${issueNumber}/comments`, token, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/json" },
|
|
112
|
+
body: JSON.stringify({ body })
|
|
113
|
+
})).ok) throw new Error("Failed to create pull request comment.");
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { findOpenPullRequest as a, updateIssueComment as c, findIssueCommentByPrefix as i, updatePullRequest as l, createPullRequest as n, getPullRequest as o, createRelease as r, releaseExistsByTag as s, createIssueComment as t };
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as Draft, S as PublishPlan, g as Tegami, t as Awaitable } from "../types-CurHqnWl.js";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
|
|
4
4
|
//#region src/cli/index.d.ts
|
|
5
5
|
interface TegamiCLIOptions {
|
|
6
|
-
/** create a custom draft
|
|
7
|
-
version?: () => Awaitable<
|
|
8
|
-
publish?: () => Awaitable<
|
|
6
|
+
/** create a custom draft, it must not be applied */
|
|
7
|
+
version?: () => Awaitable<Draft>;
|
|
8
|
+
publish?: () => Awaitable<PublishPlan>;
|
|
9
9
|
}
|
|
10
10
|
declare function createCli(tegami: Tegami, options?: TegamiCLIOptions): Command;
|
|
11
11
|
//#endregion
|
package/dist/cli/index.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { r as formatNpmDistTag } from "../semver-C4vJ4SK8.js";
|
|
2
|
-
import { r as generateReplays, t as changelogFilename } from "../generate-
|
|
2
|
+
import { a as renderChangelog, n as generateFromCommits, r as generateReplays, t as changelogFilename } from "../generate-BMlrn-2e.js";
|
|
3
3
|
import { a as isCI, n as execFailure, r as handlePluginError, t as CancelledError } from "../error-DNy8R5ue.js";
|
|
4
|
-
import { o as
|
|
5
|
-
import { mkdir,
|
|
4
|
+
import { c as updateIssueComment, i as findIssueCommentByPrefix, o as getPullRequest, t as createIssueComment } from "../api-D-jf_8xY.js";
|
|
5
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import path, { basename, isAbsolute, join, normalize, relative } from "node:path";
|
|
7
7
|
import { x } from "tinyexec";
|
|
8
|
-
import z
|
|
8
|
+
import z from "zod";
|
|
9
9
|
import { resolveCommand } from "package-manager-detector";
|
|
10
10
|
import { autocompleteMultiselect, confirm, intro, isCancel, multiline, note, outro, select, spinner } from "@clack/prompts";
|
|
11
11
|
import { Command, InvalidArgumentError } from "commander";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
12
|
//#region src/utils/git-changes.ts
|
|
14
13
|
async function getChangedPackages(graph, cwd) {
|
|
15
14
|
return resolveChangedPackages(graph, await getChangedFilePaths(cwd), cwd);
|
|
@@ -49,7 +48,7 @@ async function addGitOutput(files, cwd, args) {
|
|
|
49
48
|
//#region src/cli/changelog.ts
|
|
50
49
|
async function runChangelogTui(tegami) {
|
|
51
50
|
const context = await tegami._internal.context();
|
|
52
|
-
await
|
|
51
|
+
if (await tegami.publishStatus() === "pending") throw new Error(`Publish lock at ${context.lockPath} is still pending. Publish it before applying a new draft.`);
|
|
53
52
|
intro("Create changelogs");
|
|
54
53
|
let selectedPackages = [];
|
|
55
54
|
if (!isCI()) selectedPackages = await promptPackageSelection(context.graph, context.cwd);
|
|
@@ -65,7 +64,7 @@ async function runChangelogTui(tegami) {
|
|
|
65
64
|
return;
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
|
-
await persistChangelogs(context, (await
|
|
67
|
+
await persistChangelogs(context, (await generateFromCommits(context)).map(({ filename, content, packages }) => ({
|
|
69
68
|
filename,
|
|
70
69
|
content,
|
|
71
70
|
packages
|
|
@@ -124,6 +123,7 @@ async function promptPackageSelection(graph, cwd) {
|
|
|
124
123
|
return useShortname.get(pkg.name) ? pkg.name : pkg.id;
|
|
125
124
|
};
|
|
126
125
|
const changedPackages = new Set(await getChangedPackages(graph, cwd));
|
|
126
|
+
const initialValues = /* @__PURE__ */ new Set();
|
|
127
127
|
const selectOptions = [];
|
|
128
128
|
const groups = [];
|
|
129
129
|
for (const group of graph.getGroups()) {
|
|
@@ -132,23 +132,27 @@ async function promptPackageSelection(graph, cwd) {
|
|
|
132
132
|
}
|
|
133
133
|
groups.sort((a, b) => (a[1] ? 0 : 1) - (b[1] ? 0 : 1));
|
|
134
134
|
for (const [group, changed] of groups) {
|
|
135
|
-
|
|
135
|
+
if (changed) initialValues.add(`group:${group.name}`);
|
|
136
136
|
selectOptions.push({
|
|
137
|
-
label: `(Group) ${group.name}
|
|
137
|
+
label: `(Group) ${group.name}` + (changed ? "*" : ""),
|
|
138
138
|
value: `group:${group.name}`,
|
|
139
|
-
hint:
|
|
139
|
+
hint: group.packages.map(getPackageLabel).join(", ")
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
142
|
const packages = graph.getPackages().toSorted((a, b) => (changedPackages.has(a) ? 0 : 1) - (changedPackages.has(b) ? 0 : 1));
|
|
143
|
-
for (const pkg of packages)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
for (const pkg of packages) {
|
|
144
|
+
const changed = changedPackages.has(pkg);
|
|
145
|
+
if (changed) initialValues.add(pkg.id);
|
|
146
|
+
selectOptions.push({
|
|
147
|
+
label: getPackageLabel(pkg) + (changed ? "*" : ""),
|
|
148
|
+
value: pkg.id
|
|
149
|
+
});
|
|
150
|
+
}
|
|
148
151
|
const selected = await autocompleteMultiselect({
|
|
149
152
|
message: "Select packages (leave empty to auto-generate from commits)",
|
|
150
153
|
required: false,
|
|
151
|
-
options: selectOptions
|
|
154
|
+
options: selectOptions,
|
|
155
|
+
initialValues: Array.from(initialValues)
|
|
152
156
|
});
|
|
153
157
|
if (isCancel(selected)) throw new CancelledError();
|
|
154
158
|
return selected;
|
|
@@ -223,7 +227,7 @@ async function initAgent(context, options = {}) {
|
|
|
223
227
|
}
|
|
224
228
|
function renderAgentsMd(context) {
|
|
225
229
|
const changelogDir = path.relative(context.cwd, context.changelogDir) || "project root";
|
|
226
|
-
const
|
|
230
|
+
const lockPath = path.relative(context.cwd, context.lockPath) || "project root";
|
|
227
231
|
return [
|
|
228
232
|
"# Release workflow",
|
|
229
233
|
"",
|
|
@@ -261,7 +265,7 @@ function renderAgentsMd(context) {
|
|
|
261
265
|
"- Include YAML frontmatter with `packages`",
|
|
262
266
|
"- Include at least one `#`, `##`, or `###` heading in the body",
|
|
263
267
|
"- Write user-facing release notes under each heading",
|
|
264
|
-
`- Do not edit \`${
|
|
268
|
+
`- Do not edit the publish lock file (\`${lockPath}\`) or package \`CHANGELOG.md\` files directly`,
|
|
265
269
|
""
|
|
266
270
|
].join("\n");
|
|
267
271
|
}
|
|
@@ -286,24 +290,20 @@ async function buildPrPreview(context, draft, options = {}) {
|
|
|
286
290
|
];
|
|
287
291
|
const pendingPackages = [];
|
|
288
292
|
for (const pkg of context.graph.getPackages()) {
|
|
289
|
-
const plan = draft.
|
|
293
|
+
const plan = draft.getPackageDraft(pkg.id);
|
|
290
294
|
if (!plan || plan.bumpVersion(pkg) === pkg.version) continue;
|
|
291
295
|
pendingPackages.push({
|
|
292
296
|
name: pkg.name,
|
|
293
297
|
type: plan.type ?? "—",
|
|
294
298
|
from: pkg.version,
|
|
295
299
|
to: plan.bumpVersion(pkg),
|
|
296
|
-
distTag: plan.npm?.distTag
|
|
297
|
-
publish: plan.publish ?? false
|
|
300
|
+
distTag: plan.npm?.distTag
|
|
298
301
|
});
|
|
299
302
|
}
|
|
300
303
|
const prChangelogs = draft.getChangelogs().filter((entry) => prChangelogFiles.has(entry.filename));
|
|
301
304
|
if (pendingPackages.length > 0) {
|
|
302
305
|
lines.push("#### Release preview", "", "| Package | Bump | Version |", "| --- | --- | --- |");
|
|
303
|
-
for (const { name, type, from, to, distTag
|
|
304
|
-
const publishNote = publish ? "" : " (no publish)";
|
|
305
|
-
lines.push(`| \`${name}\` | ${type} | \`${from}\` → \`${to}\`${formatNpmDistTag(distTag)}${publishNote} |`);
|
|
306
|
-
}
|
|
306
|
+
for (const { name, type, from, to, distTag } of pendingPackages) lines.push(`| \`${name}\` | ${type} | \`${from}\` → \`${to}\`${formatNpmDistTag(distTag)} |`);
|
|
307
307
|
lines.push("");
|
|
308
308
|
}
|
|
309
309
|
if (prChangelogs.length > 0) {
|
|
@@ -330,52 +330,18 @@ async function postPrComment(body) {
|
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
332
|
const markedBody = `${COMMENT_MARKER}\n${body}`;
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
`repos/${repo}/issues/${pr.number}/comments`,
|
|
336
|
-
"--paginate",
|
|
337
|
-
"--jq",
|
|
338
|
-
`[.[] | select(.body | startswith("${COMMENT_MARKER}")) | .id][0] // empty`
|
|
339
|
-
]);
|
|
340
|
-
if (listResult.exitCode !== 0) throw execFailure("Failed to list pull request comments.", listResult);
|
|
341
|
-
const existingId = listResult.stdout.trim();
|
|
333
|
+
const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
334
|
+
const existingId = await findIssueCommentByPrefix(repo, pr.number, COMMENT_MARKER, token);
|
|
342
335
|
if (existingId) {
|
|
343
|
-
|
|
344
|
-
const inputPath = join(dir, "body.json");
|
|
345
|
-
try {
|
|
346
|
-
await writeFile(inputPath, JSON.stringify({ body: markedBody }));
|
|
347
|
-
const updateResult = await x("gh", [
|
|
348
|
-
"api",
|
|
349
|
-
"-X",
|
|
350
|
-
"PATCH",
|
|
351
|
-
`repos/${repo}/issues/comments/${existingId}`,
|
|
352
|
-
"--input",
|
|
353
|
-
inputPath
|
|
354
|
-
]);
|
|
355
|
-
if (updateResult.exitCode !== 0) throw execFailure("Failed to update pull request comment.", updateResult);
|
|
356
|
-
} finally {
|
|
357
|
-
await rm(dir, {
|
|
358
|
-
recursive: true,
|
|
359
|
-
force: true
|
|
360
|
-
});
|
|
361
|
-
}
|
|
336
|
+
await updateIssueComment(repo, existingId, markedBody, token);
|
|
362
337
|
return;
|
|
363
338
|
}
|
|
364
|
-
|
|
365
|
-
"pr",
|
|
366
|
-
"comment",
|
|
367
|
-
String(pr.number),
|
|
368
|
-
"--body",
|
|
369
|
-
markedBody,
|
|
370
|
-
"--repo",
|
|
371
|
-
repo
|
|
372
|
-
]);
|
|
373
|
-
if (createResult.exitCode !== 0) throw execFailure("Failed to create pull request comment.", createResult);
|
|
339
|
+
await createIssueComment(repo, pr.number, markedBody, token);
|
|
374
340
|
}
|
|
375
|
-
const eventSchema = z
|
|
376
|
-
event: z
|
|
377
|
-
conclusion: z
|
|
378
|
-
pull_requests: z
|
|
341
|
+
const eventSchema = z.looseObject({ workflow_run: z.looseObject({
|
|
342
|
+
event: z.literal("pull_request", { error: "The preview workflow was not triggered by a pull request." }),
|
|
343
|
+
conclusion: z.literal("success", { error: "The preview workflow did not complete successfully." }),
|
|
344
|
+
pull_requests: z.array(z.looseObject({ number: z.int() }), { error: "The preview workflow is not associated with a pull request." }).min(1)
|
|
379
345
|
}, { error: "A workflow_run event is required." }) });
|
|
380
346
|
async function readPullRequestFromWorkflowRunEvent() {
|
|
381
347
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
@@ -395,7 +361,7 @@ async function readPullRequestFromWorkflowRunEvent() {
|
|
|
395
361
|
const { error, data } = eventSchema.safeParse(raw);
|
|
396
362
|
if (error) return {
|
|
397
363
|
found: false,
|
|
398
|
-
reason: z
|
|
364
|
+
reason: z.prettifyError(error)
|
|
399
365
|
};
|
|
400
366
|
return {
|
|
401
367
|
found: true,
|
|
@@ -405,9 +371,18 @@ async function readPullRequestFromWorkflowRunEvent() {
|
|
|
405
371
|
async function resolvePullRequest(context, options) {
|
|
406
372
|
const repo = context.github?.repo ?? process.env.GITHUB_REPOSITORY;
|
|
407
373
|
if (!repo) throw new Error("GITHUB_REPOSITORY is required.");
|
|
374
|
+
const token = context.github?.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
375
|
+
if (!repo) throw new Error("GITHUB_TOKEN is required.");
|
|
408
376
|
if (options.number !== void 0) {
|
|
409
377
|
if (!Number.isInteger(options.number) || options.number <= 0) throw new Error("--number must be a positive integer.");
|
|
410
|
-
|
|
378
|
+
const data = await getPullRequest(repo, options.number, token);
|
|
379
|
+
return {
|
|
380
|
+
repo,
|
|
381
|
+
headRepo: data.headRepository ? `${data.headRepository.owner.login}/${data.headRepository.name}` : repo,
|
|
382
|
+
headRef: data.headRefName,
|
|
383
|
+
baseSha: data.baseRefOid,
|
|
384
|
+
headSha: data.headRefOid
|
|
385
|
+
};
|
|
411
386
|
}
|
|
412
387
|
const event = await readPullRequestEvent();
|
|
413
388
|
if (event) return {
|
|
@@ -416,26 +391,6 @@ async function resolvePullRequest(context, options) {
|
|
|
416
391
|
};
|
|
417
392
|
throw new Error("A pull request event or --number is required.");
|
|
418
393
|
}
|
|
419
|
-
async function readPullRequestFromGh(repo, number) {
|
|
420
|
-
const result = await x("gh", [
|
|
421
|
-
"pr",
|
|
422
|
-
"view",
|
|
423
|
-
String(number),
|
|
424
|
-
"--repo",
|
|
425
|
-
repo,
|
|
426
|
-
"--json",
|
|
427
|
-
"headRefName,baseRefOid,headRefOid,headRepository"
|
|
428
|
-
]);
|
|
429
|
-
if (result.exitCode !== 0) throw execFailure(`Failed to resolve pull request #${number}.`, result);
|
|
430
|
-
const data = JSON.parse(result.stdout);
|
|
431
|
-
return {
|
|
432
|
-
repo,
|
|
433
|
-
headRepo: data.headRepository ? `${data.headRepository.owner.login}/${data.headRepository.name}` : repo,
|
|
434
|
-
headRef: data.headRefName,
|
|
435
|
-
baseSha: data.baseRefOid,
|
|
436
|
-
headSha: data.headRefOid
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
394
|
async function readPullRequestEvent() {
|
|
440
395
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
441
396
|
if (!eventPath) return;
|
|
@@ -481,13 +436,17 @@ function createChangelogUrl(context, repo, branch, filename) {
|
|
|
481
436
|
function createCli(tegami, options = {}) {
|
|
482
437
|
const program = new Command();
|
|
483
438
|
program.name("tegami").description("create changelogs").action(() => runAction(tegami, () => runChangelogTui(tegami)));
|
|
484
|
-
program.command("version").description("draft and
|
|
439
|
+
program.command("version").description("draft version changes and write the publish lock").action(() => runAction(tegami, async () => {
|
|
485
440
|
await versionPackages(tegami, { cli: options });
|
|
486
441
|
}));
|
|
487
442
|
program.command("ci").description("version and publish packages").action(() => runAction(tegami, async () => {
|
|
488
443
|
if (await versionPackages(tegami, { cli: options })) return;
|
|
489
444
|
await publishPackages(tegami, { cli: options });
|
|
490
445
|
}));
|
|
446
|
+
program.command("check-publish").description("exit with code 1 if no publishing needed, otherwise 0").action(() => runAction(tegami, async () => {
|
|
447
|
+
const status = await tegami.publishStatus();
|
|
448
|
+
process.exit(status === "pending" ? 0 : 1);
|
|
449
|
+
}));
|
|
491
450
|
const programPr = program.command("pr");
|
|
492
451
|
programPr.command("preview").description("(should be executed in a GitHub action) show a pull request release preview and changelog guidance").option("--artifact <path>", "write preview markdown to a file").option("--number <number>", "pull request number", (value) => {
|
|
493
452
|
const number = Number(value);
|
|
@@ -522,13 +481,13 @@ function createCli(tegami, options = {}) {
|
|
|
522
481
|
process.exit(1);
|
|
523
482
|
}
|
|
524
483
|
});
|
|
525
|
-
program.command("publish").description("publish packages from the
|
|
484
|
+
program.command("publish").description("publish packages from the publish lock").option("--dry-run", "validate the publish lock without publishing packages").action((commandOptions) => runAction(tegami, async () => {
|
|
526
485
|
await publishPackages(tegami, {
|
|
527
486
|
...commandOptions,
|
|
528
487
|
cli: options
|
|
529
488
|
});
|
|
530
489
|
}));
|
|
531
|
-
program.command("cleanup").description("remove the publish
|
|
490
|
+
program.command("cleanup").description("remove the publish lock after all packages have been published").action(() => runAction(tegami, () => runCleanup(tegami)));
|
|
532
491
|
program.command("init-agent").description("write AGENTS.md with changelog instructions for AI agents").option("-o, --output <path>", "output path", "AGENTS.md").action((commandOptions) => runAction(tegami, () => runInitAgent(tegami, commandOptions)));
|
|
533
492
|
return program;
|
|
534
493
|
}
|
|
@@ -537,60 +496,69 @@ async function versionPackages(tegami, options) {
|
|
|
537
496
|
const { version: customVersion } = options.cli;
|
|
538
497
|
const context = await tegami._internal.context();
|
|
539
498
|
const draft = customVersion ? await customVersion() : await tegami.draft();
|
|
540
|
-
if (!draft.canApply()) throw new Error(`The draft
|
|
541
|
-
for (const plugin of context.plugins) await handlePluginError(plugin, "cli.
|
|
499
|
+
if (!draft.canApply()) throw new Error(`The draft from custom "version" hook must not be applied`);
|
|
500
|
+
for (const plugin of context.plugins) await handlePluginError(plugin, "cli.draftCreated", () => plugin.cli?.draftCreated?.call(context, draft));
|
|
542
501
|
if (!draft.hasPending()) {
|
|
543
502
|
note("No pending version changes matched workspace packages.", "Nothing to version");
|
|
544
503
|
outro("No versions changed.");
|
|
545
504
|
return false;
|
|
546
505
|
}
|
|
547
|
-
|
|
506
|
+
if (await tegami.publishStatus() === "pending") throw new Error(`Publish lock at ${context.lockPath} is still pending. Publish it before applying a new draft.`);
|
|
507
|
+
const lines = [];
|
|
548
508
|
for (const pkg of context.graph.getPackages()) {
|
|
549
|
-
const plan = draft.
|
|
509
|
+
const plan = draft.getPackageDraft(pkg.id);
|
|
550
510
|
if (!plan || plan.bumpVersion(pkg) === pkg.version) continue;
|
|
551
|
-
|
|
552
|
-
if (plan.bumpReasons) for (const reason of plan.bumpReasons)
|
|
511
|
+
lines.push(`${pkg.id}: ${pkg.version} → ${plan.bumpVersion(pkg)} (${plan.changelogs?.length ?? 0} changelogs)`);
|
|
512
|
+
if (plan.bumpReasons) for (const reason of plan.bumpReasons) lines.push(` - ${reason}`);
|
|
553
513
|
}
|
|
554
|
-
note(
|
|
514
|
+
note(lines.join("\n"), "Release plan");
|
|
555
515
|
const s = spinner();
|
|
556
516
|
s.start("Updating package versions");
|
|
557
517
|
try {
|
|
558
|
-
await draft.
|
|
518
|
+
await draft.apply();
|
|
559
519
|
} catch (error) {
|
|
560
|
-
s.stop("Failed to apply
|
|
520
|
+
s.stop("Failed to apply draft");
|
|
561
521
|
throw error;
|
|
562
522
|
}
|
|
563
523
|
s.stop("Package versions updated");
|
|
564
|
-
for (const plugin of context.plugins) await handlePluginError(plugin, "cli.
|
|
565
|
-
outro("Publish
|
|
524
|
+
for (const plugin of context.plugins) await handlePluginError(plugin, "cli.draftApplied", () => plugin.cli?.draftApplied?.call(context, draft));
|
|
525
|
+
outro("Publish lock written.");
|
|
566
526
|
return true;
|
|
567
527
|
}
|
|
568
528
|
async function publishPackages(tegami, options) {
|
|
569
529
|
const dryRun = options.dryRun ?? false;
|
|
530
|
+
const context = await tegami._internal.context();
|
|
570
531
|
const { publish: customPublish } = options.cli;
|
|
571
532
|
intro(dryRun ? "Publish packages (dry run)" : "Publish packages");
|
|
572
533
|
const s = spinner();
|
|
573
|
-
s.start(dryRun ? "Validating publish
|
|
574
|
-
const
|
|
575
|
-
if (
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
outro(`No publishable packages were found in ${planPath}.`);
|
|
534
|
+
s.start(dryRun ? "Validating publish lock" : "Publishing packages");
|
|
535
|
+
const plan = customPublish ? await customPublish() : await tegami.publish({ dryRun });
|
|
536
|
+
if (plan === "skipped") {
|
|
537
|
+
s.stop(dryRun ? "No publish lock to validate" : "Nothing to publish");
|
|
538
|
+
outro(`No publishable packages were found in ${context.lockPath}.`);
|
|
579
539
|
return false;
|
|
580
540
|
}
|
|
581
|
-
s.stop(dryRun ? "Publish
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
541
|
+
s.stop(dryRun ? "Publish lock validated" : "Publish complete");
|
|
542
|
+
const lines = [];
|
|
543
|
+
let hasFailed = false;
|
|
544
|
+
for (const [id, packagePlan] of plan.packages) {
|
|
545
|
+
if (!packagePlan.updated) continue;
|
|
546
|
+
const result = packagePlan.publishResult;
|
|
547
|
+
const pkg = context.graph.get(id);
|
|
548
|
+
if (result.type === "failed") hasFailed = true;
|
|
549
|
+
let message = `${result.type} ${pkg.id} - ${pkg.version}`;
|
|
550
|
+
const distTag = packagePlan.npm?.distTag;
|
|
551
|
+
if (distTag) message += ` (npm dist-tag: ${distTag})`;
|
|
552
|
+
if (result.type === "failed" && result.error) message += `: ${result.error}`;
|
|
553
|
+
lines.push(message);
|
|
554
|
+
}
|
|
555
|
+
note(lines.join("\n"), dryRun ? "Publish dry run" : "Publish result");
|
|
556
|
+
if (hasFailed) {
|
|
588
557
|
process.exitCode = 1;
|
|
589
|
-
if (result.error) note(result.error, "Error when publishing");
|
|
590
558
|
outro("Failed to publish.");
|
|
591
559
|
return false;
|
|
592
560
|
}
|
|
593
|
-
outro(dryRun ? "Publish
|
|
561
|
+
outro(dryRun ? "Publish lock is valid." : "Packages published.");
|
|
594
562
|
return true;
|
|
595
563
|
}
|
|
596
564
|
async function runInitAgent(tegami, options) {
|
|
@@ -604,21 +572,21 @@ async function runInitAgent(tegami, options) {
|
|
|
604
572
|
outro("Agents can follow AGENTS.md to write changelogs.");
|
|
605
573
|
}
|
|
606
574
|
async function runCleanup(tegami) {
|
|
607
|
-
intro("Cleanup publish
|
|
575
|
+
intro("Cleanup publish lock");
|
|
608
576
|
const s = spinner();
|
|
609
|
-
s.start("Checking publish
|
|
577
|
+
s.start("Checking publish lock status");
|
|
610
578
|
const result = await tegami.cleanup();
|
|
611
|
-
const {
|
|
612
|
-
s.stop(result.state === "removed" ? "Publish
|
|
579
|
+
const { lockPath } = await tegami._internal.context();
|
|
580
|
+
s.stop(result.state === "removed" ? "Publish lock removed" : "Publish lock kept");
|
|
613
581
|
if (result.state === "removed") {
|
|
614
|
-
outro(`Removed ${
|
|
582
|
+
outro(`Removed ${lockPath}.`);
|
|
615
583
|
return;
|
|
616
584
|
}
|
|
617
|
-
if (result.reason === "
|
|
618
|
-
outro(`No publish
|
|
585
|
+
if (result.reason === "no-plan") {
|
|
586
|
+
outro(`No publish lock found at ${lockPath}.`);
|
|
619
587
|
return;
|
|
620
588
|
}
|
|
621
|
-
outro(`Publish
|
|
589
|
+
outro(`Publish lock at ${lockPath} is still pending. Publish it before cleanup.`);
|
|
622
590
|
}
|
|
623
591
|
async function runAction(tegami, action) {
|
|
624
592
|
try {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { a as maxBump, t as bumpDepth } from "./semver-C4vJ4SK8.js";
|
|
2
2
|
import { n as execFailure } from "./error-DNy8R5ue.js";
|
|
3
|
-
import {
|
|
4
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
-
import { join } from "node:path";
|
|
3
|
+
import { t as bumpTypeSchema } from "./schemas-B7N6EE2k.js";
|
|
6
4
|
import { x } from "tinyexec";
|
|
7
5
|
import * as semver from "semver";
|
|
6
|
+
import z from "zod";
|
|
7
|
+
import { dump } from "js-yaml";
|
|
8
8
|
//#region src/utils/conventional-commit.ts
|
|
9
9
|
/**
|
|
10
10
|
* Conventional commit header per conventionalcommits.org and semantic-release defaults.
|
|
@@ -70,10 +70,33 @@ function conventionalCommitToBump(type, breaking) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
//#endregion
|
|
73
|
+
//#region src/changelog/shared.ts
|
|
74
|
+
const changelogPackageConfigSchema = z.object({
|
|
75
|
+
type: bumpTypeSchema.optional(),
|
|
76
|
+
replay: z.array(z.string().min(1)).optional()
|
|
77
|
+
});
|
|
78
|
+
const changelogFrontmatterSchema = z.object({
|
|
79
|
+
subject: z.string().optional(),
|
|
80
|
+
packages: z.union([z.array(z.string()), z.record(z.string(), z.union([
|
|
81
|
+
bumpTypeSchema,
|
|
82
|
+
z.null(),
|
|
83
|
+
changelogPackageConfigSchema
|
|
84
|
+
]))]).optional()
|
|
85
|
+
});
|
|
86
|
+
function renderChangelog(frontmatter, body) {
|
|
87
|
+
return [
|
|
88
|
+
"---",
|
|
89
|
+
dump(frontmatter).trim(),
|
|
90
|
+
"---",
|
|
91
|
+
"",
|
|
92
|
+
body.trim(),
|
|
93
|
+
""
|
|
94
|
+
].join("\n");
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
73
97
|
//#region src/changelog/generate.ts
|
|
74
|
-
async function
|
|
75
|
-
const
|
|
76
|
-
const commits = await readConventionalCommits(context, options);
|
|
98
|
+
async function generateFromCommits(context, { from, to } = {}) {
|
|
99
|
+
const commits = await readConventionalCommits(context, from, to);
|
|
77
100
|
const groups = /* @__PURE__ */ new Map();
|
|
78
101
|
for (const commit of commits) {
|
|
79
102
|
const key = commit.packages.join("\0");
|
|
@@ -81,7 +104,7 @@ async function generateChangelog(context, options = {}) {
|
|
|
81
104
|
if (group) group.push(commit);
|
|
82
105
|
else groups.set(key, [commit]);
|
|
83
106
|
}
|
|
84
|
-
|
|
107
|
+
return Array.from(groups, ([key, changes], index) => {
|
|
85
108
|
const packageNames = key ? key.split("\0") : [];
|
|
86
109
|
let bumpType = "patch";
|
|
87
110
|
for (const change of changes) bumpType = maxBump(change.type, bumpType);
|
|
@@ -99,15 +122,10 @@ async function generateChangelog(context, options = {}) {
|
|
|
99
122
|
changes
|
|
100
123
|
};
|
|
101
124
|
});
|
|
102
|
-
if (write && changelogs.length > 0) {
|
|
103
|
-
await mkdir(context.changelogDir, { recursive: true });
|
|
104
|
-
await Promise.all(changelogs.map((entry) => writeFile(join(context.changelogDir, entry.filename), entry.content)));
|
|
105
|
-
}
|
|
106
|
-
return changelogs;
|
|
107
125
|
}
|
|
108
|
-
async function readConventionalCommits(context,
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
async function readConventionalCommits(context, from, to) {
|
|
127
|
+
from ??= await latestTag(context.cwd);
|
|
128
|
+
to ??= "HEAD";
|
|
111
129
|
const args = [
|
|
112
130
|
"log",
|
|
113
131
|
"--no-merges",
|
|
@@ -151,8 +169,8 @@ function generateReplays(graph, base) {
|
|
|
151
169
|
for (const [ref, type] of Object.entries(base)) {
|
|
152
170
|
const resolved = graph.getByName(ref);
|
|
153
171
|
for (const pkg of resolved) {
|
|
154
|
-
const plan = pkg.
|
|
155
|
-
pkg.
|
|
172
|
+
const plan = pkg.initDraft();
|
|
173
|
+
pkg.configureDraft(plan, graph.getPackageGroup(pkg.id));
|
|
156
174
|
const prerelease = semver.prerelease(pkg.version)?.[0];
|
|
157
175
|
const targetPrerelease = plan.prerelease;
|
|
158
176
|
if (targetPrerelease && !prerelease || targetPrerelease && prerelease && targetPrerelease === prerelease) packages[pkg.id] = {
|
|
@@ -168,4 +186,4 @@ function changelogFilename(disambiguator = 0) {
|
|
|
168
186
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}-${(Date.now() + disambiguator).toString(36)}.md`;
|
|
169
187
|
}
|
|
170
188
|
//#endregion
|
|
171
|
-
export {
|
|
189
|
+
export { renderChangelog as a, changelogFrontmatterSchema as i, generateFromCommits as n, generateReplays as r, changelogFilename as t };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { i as formatPackageVersion } from "../semver-C4vJ4SK8.js";
|
|
2
2
|
//#region src/generators/simple.ts
|
|
3
3
|
function simpleGenerator() {
|
|
4
|
-
return { generate({
|
|
5
|
-
const lines = [`## ${formatPackageVersion(
|
|
6
|
-
for (const entry of changelogs) {
|
|
4
|
+
return { generate({ pkg, packageDraft }) {
|
|
5
|
+
const lines = [`## ${formatPackageVersion(pkg.name, pkg.version, packageDraft.npm?.distTag)}`, ""];
|
|
6
|
+
for (const entry of packageDraft.changelogs ?? []) {
|
|
7
7
|
let sectionDepth = 4;
|
|
8
8
|
if (entry.subject) lines.push(`### ${entry.subject}`, "");
|
|
9
9
|
else sectionDepth--;
|