tegami 0.1.0-beta.2 → 0.1.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/{checks-Fz8-N09y.js → checks-BQISvt_o.js} +8 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +373 -17
- package/dist/generators/simple.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +139 -87
- package/dist/plugins/git.d.ts +1 -1
- package/dist/plugins/git.js +1 -1
- package/dist/plugins/github.d.ts +7 -1
- package/dist/plugins/github.js +9 -5
- package/dist/providers/cargo.d.ts +1 -1
- package/dist/providers/npm.d.ts +1 -1
- package/dist/providers/npm.js +24 -11
- package/dist/{types-BNhcu4-X.d.ts → types-Cx-uvEWw.d.ts} +19 -5
- package/package.json +1 -1
|
@@ -2,6 +2,12 @@ import { n as handlePluginError } from "./error-DBK-9uBa.js";
|
|
|
2
2
|
import { n as jsonCodec, t as bumpTypeSchema } from "./schemas-CurBAaW5.js";
|
|
3
3
|
import z$1 from "zod";
|
|
4
4
|
import { readFile } from "fs/promises";
|
|
5
|
+
//#region src/utils/changelog.ts
|
|
6
|
+
function changelogFilename(disambiguator = 0) {
|
|
7
|
+
const date = /* @__PURE__ */ new Date();
|
|
8
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}-${(Date.now() + disambiguator).toString(36)}.md`;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
5
11
|
//#region src/plans/store.ts
|
|
6
12
|
const packagePlanStoreSchema = z$1.object({
|
|
7
13
|
type: bumpTypeSchema.optional(),
|
|
@@ -21,6 +27,7 @@ const planStoreSchema = jsonCodec(z$1.object({
|
|
|
21
27
|
subject: z$1.string().optional(),
|
|
22
28
|
packages: z$1.record(z$1.string(), bumpTypeSchema),
|
|
23
29
|
sections: z$1.array(z$1.object({
|
|
30
|
+
depth: z$1.number(),
|
|
24
31
|
title: z$1.string(),
|
|
25
32
|
content: z$1.string()
|
|
26
33
|
}))
|
|
@@ -88,4 +95,4 @@ async function assertPublishPlanFinished(context) {
|
|
|
88
95
|
if ((await publishPlanStatus(store, context)).state === "pending") throw new Error(`Publish plan already exists at ${context.planPath} and is pending. Publish it before applying a new plan.`);
|
|
89
96
|
}
|
|
90
97
|
//#endregion
|
|
91
|
-
export { readPlanStore as i, publishPlanStatus as n, createPlanStore as r, assertPublishPlanFinished as t };
|
|
98
|
+
export { changelogFilename as a, readPlanStore as i, publishPlanStatus as n, createPlanStore as r, assertPublishPlanFinished as t };
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as PublishResult, O as DraftPlan, _ as Tegami, t as Awaitable } from "../types-
|
|
1
|
+
import { C as PublishResult, O as DraftPlan, _ as Tegami, t as Awaitable } from "../types-Cx-uvEWw.js";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
|
|
4
4
|
//#region src/cli/index.d.ts
|
package/dist/cli/index.js
CHANGED
|
@@ -1,12 +1,316 @@
|
|
|
1
|
+
import { a as changelogFilename, t as assertPublishPlanFinished } from "../checks-BQISvt_o.js";
|
|
1
2
|
import { n as handlePluginError } from "../error-DBK-9uBa.js";
|
|
2
|
-
import { t as bumpDepth } from "../semver-CPtl0XNq.js";
|
|
3
|
-
import { t as assertPublishPlanFinished } from "../checks-Fz8-N09y.js";
|
|
3
|
+
import { r as formatNpmDistTag, t as bumpDepth } from "../semver-CPtl0XNq.js";
|
|
4
4
|
import { t as isCI } from "../constants-B9qjNfvr.js";
|
|
5
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
|
-
import { join } from "node:path";
|
|
5
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import path, { basename, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
7
|
+
import { x } from "tinyexec";
|
|
7
8
|
import { dump } from "js-yaml";
|
|
9
|
+
import { detect, resolveCommand } from "package-manager-detector";
|
|
8
10
|
import { autocompleteMultiselect, confirm, intro, isCancel, multiline, note, outro, select, spinner } from "@clack/prompts";
|
|
9
|
-
import { Command } from "commander";
|
|
11
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
//#region src/cli/init-agent.ts
|
|
14
|
+
const CHANGELOG_DOCS_URL$1 = "https://tegami.fuma-nama.dev/changelog";
|
|
15
|
+
async function initAgent(context, options = {}) {
|
|
16
|
+
const output = path.resolve(context.cwd, options.output ?? "AGENTS.md");
|
|
17
|
+
const content = renderAgentsMd(context);
|
|
18
|
+
try {
|
|
19
|
+
await writeFile(output, (await readFile(output, "utf8")).trimEnd() + "\n\n" + content);
|
|
20
|
+
return {
|
|
21
|
+
path: output,
|
|
22
|
+
created: false
|
|
23
|
+
};
|
|
24
|
+
} catch {
|
|
25
|
+
await writeFile(output, content);
|
|
26
|
+
return {
|
|
27
|
+
path: output,
|
|
28
|
+
created: true
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function renderAgentsMd(context) {
|
|
33
|
+
const changelogDir = path.relative(context.cwd, context.changelogDir) || "project root";
|
|
34
|
+
const planPath = path.relative(context.cwd, context.planPath) || "project root";
|
|
35
|
+
return [
|
|
36
|
+
"# Release workflow",
|
|
37
|
+
"",
|
|
38
|
+
"This repository uses [Tegami](https://tegami.fuma-nama.dev) for versioning and publishing.",
|
|
39
|
+
"",
|
|
40
|
+
"## Write changelog files",
|
|
41
|
+
"",
|
|
42
|
+
`Create pending changelog files under \`${changelogDir}/\` as \`YYYY-MM-DD-{hash}.md\`.`,
|
|
43
|
+
"",
|
|
44
|
+
`See the [changelog format docs](${CHANGELOG_DOCS_URL$1}) for details.`,
|
|
45
|
+
"",
|
|
46
|
+
"### Example",
|
|
47
|
+
"",
|
|
48
|
+
"```md",
|
|
49
|
+
"---",
|
|
50
|
+
"packages:",
|
|
51
|
+
` "npm:@acme/ui": patch`,
|
|
52
|
+
"---",
|
|
53
|
+
"",
|
|
54
|
+
"### Fix button hover state",
|
|
55
|
+
"",
|
|
56
|
+
"The hover color now matches the design system.",
|
|
57
|
+
"```",
|
|
58
|
+
"",
|
|
59
|
+
"### Package references",
|
|
60
|
+
"",
|
|
61
|
+
"Use package names, ids, or groups in frontmatter. For example:",
|
|
62
|
+
"",
|
|
63
|
+
"- `\"@acme/ui\"` — package name",
|
|
64
|
+
"- `\"npm:@acme/ui\"` — package id",
|
|
65
|
+
"- `\"group:acme\"` — every package in a group",
|
|
66
|
+
"",
|
|
67
|
+
"Rules:",
|
|
68
|
+
"",
|
|
69
|
+
"- Include YAML frontmatter with `packages`",
|
|
70
|
+
"- Include at least one `#`, `##`, or `###` heading in the body",
|
|
71
|
+
"- Write user-facing release notes under each heading",
|
|
72
|
+
`- Do not edit \`${planPath}\` or package \`CHANGELOG.md\` files directly`,
|
|
73
|
+
""
|
|
74
|
+
].join("\n");
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/utils/package-manager.ts
|
|
78
|
+
async function formatRunScriptCommand(cwd, script, agent) {
|
|
79
|
+
const resolved = resolveCommand(agent ?? (await detect({ cwd }))?.agent ?? "npm", "run", [script]);
|
|
80
|
+
if (!resolved) return `npm run ${script}`;
|
|
81
|
+
return [resolved.command, ...resolved.args].join(" ");
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/cli/pr.ts
|
|
85
|
+
const TEGAMI_DOCS_URL = "https://tegami.fuma-nama.dev";
|
|
86
|
+
const CHANGELOG_DOCS_URL = `${TEGAMI_DOCS_URL}/changelog`;
|
|
87
|
+
const COMMENT_MARKER = "<!-- tegami -->";
|
|
88
|
+
async function buildPrPreview(context, draft, options = {}) {
|
|
89
|
+
const pullRequest = await resolvePullRequest(context, options);
|
|
90
|
+
const tegamiCommand = await formatRunScriptCommand(context.cwd, "tegami", context.options.npm?.client);
|
|
91
|
+
const prChangelogFiles = await listPullRequestChangelogFiles(context, pullRequest.baseSha, pullRequest.headSha);
|
|
92
|
+
const changelogDir = relative(context.cwd, context.changelogDir) || ".tegami";
|
|
93
|
+
const createLink = createChangelogUrl(pullRequest.headRepo, pullRequest.headRef, changelogDir, changelogFilename());
|
|
94
|
+
const lines = [
|
|
95
|
+
"### Tegami",
|
|
96
|
+
"",
|
|
97
|
+
`This repository uses [Tegami](${TEGAMI_DOCS_URL}) to manage releases. When your changes affect published packages, add a changelog file under \`.tegami/\` before merging.`,
|
|
98
|
+
"",
|
|
99
|
+
`[**Create a changelog →**](${createLink}) · [Changelog format](${CHANGELOG_DOCS_URL})`,
|
|
100
|
+
""
|
|
101
|
+
];
|
|
102
|
+
const pendingPackages = [];
|
|
103
|
+
for (const pkg of context.graph.getPackages()) {
|
|
104
|
+
const plan = draft.getPackagePlan(pkg.id);
|
|
105
|
+
if (!plan?.type) continue;
|
|
106
|
+
pendingPackages.push({
|
|
107
|
+
name: pkg.name,
|
|
108
|
+
type: plan.type,
|
|
109
|
+
from: pkg.version,
|
|
110
|
+
to: plan.bumpVersion(pkg),
|
|
111
|
+
distTag: plan.npm?.distTag,
|
|
112
|
+
publish: plan.publish ?? false
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const prChangelogs = draft.getChangelogs().filter((entry) => prChangelogFiles.has(entry.filename));
|
|
116
|
+
if (pendingPackages.length > 0) {
|
|
117
|
+
lines.push("#### Release preview", "", "| Package | Bump | Version |", "| --- | --- | --- |");
|
|
118
|
+
for (const { name, type, from, to, distTag, publish } of pendingPackages) {
|
|
119
|
+
const publishNote = publish ? "" : " (no publish)";
|
|
120
|
+
lines.push(`| \`${name}\` | ${type} | \`${from}\` → \`${to}\`${formatNpmDistTag(distTag)}${publishNote} |`);
|
|
121
|
+
}
|
|
122
|
+
lines.push("");
|
|
123
|
+
}
|
|
124
|
+
if (prChangelogs.length > 0) {
|
|
125
|
+
lines.push("#### Changelogs in this PR", "");
|
|
126
|
+
for (const entry of prChangelogs) {
|
|
127
|
+
const title = entry.sections[0]?.title ?? entry.filename;
|
|
128
|
+
lines.push(`- \`${entry.filename}\` — ${title}`);
|
|
129
|
+
}
|
|
130
|
+
lines.push("");
|
|
131
|
+
} else if (pendingPackages.length === 0) lines.push("#### No changelogs yet", "", "This PR has no pending changelog files. If your changes require a release, add a changelog before merging.", "");
|
|
132
|
+
else if (prChangelogFiles.size === 0) lines.push("This PR does not add changelog files. Pending changelogs from other branches are included in the preview above.", "");
|
|
133
|
+
lines.push(`Run \`${tegamiCommand}\` locally to create a changelog interactively.`, "", `<sub>Managed by [Tegami](${TEGAMI_DOCS_URL}).</sub>`, "");
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
|
136
|
+
async function postPrComment(body) {
|
|
137
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
138
|
+
if (!repo) throw new Error("GITHUB_REPOSITORY is required.");
|
|
139
|
+
const number = await readPullRequestNumberFromWorkflowRunEvent();
|
|
140
|
+
const markedBody = `${COMMENT_MARKER}\n${body}`;
|
|
141
|
+
const listResult = await x("gh", [
|
|
142
|
+
"api",
|
|
143
|
+
`repos/${repo}/issues/${number}/comments`,
|
|
144
|
+
"--paginate",
|
|
145
|
+
"--jq",
|
|
146
|
+
`[.[] | select(.body | startswith("${COMMENT_MARKER}")) | .id][0] // empty`
|
|
147
|
+
]);
|
|
148
|
+
if (listResult.exitCode !== 0) throw new Error(listResult.stderr || "Failed to list pull request comments.");
|
|
149
|
+
const existingId = listResult.stdout.trim();
|
|
150
|
+
if (existingId) {
|
|
151
|
+
const dir = await mkdtemp(join(tmpdir(), "tegami-pr-comment-"));
|
|
152
|
+
const inputPath = join(dir, "body.json");
|
|
153
|
+
try {
|
|
154
|
+
await writeFile(inputPath, JSON.stringify({ body: markedBody }));
|
|
155
|
+
const updateResult = await x("gh", [
|
|
156
|
+
"api",
|
|
157
|
+
"-X",
|
|
158
|
+
"PATCH",
|
|
159
|
+
`repos/${repo}/issues/comments/${existingId}`,
|
|
160
|
+
"--input",
|
|
161
|
+
inputPath
|
|
162
|
+
]);
|
|
163
|
+
if (updateResult.exitCode !== 0) throw new Error(updateResult.stderr || "Failed to update pull request comment.");
|
|
164
|
+
} finally {
|
|
165
|
+
await rm(dir, {
|
|
166
|
+
recursive: true,
|
|
167
|
+
force: true
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const createResult = await x("gh", [
|
|
173
|
+
"pr",
|
|
174
|
+
"comment",
|
|
175
|
+
String(number),
|
|
176
|
+
"--body",
|
|
177
|
+
markedBody,
|
|
178
|
+
"--repo",
|
|
179
|
+
repo
|
|
180
|
+
]);
|
|
181
|
+
if (createResult.exitCode !== 0) throw new Error(createResult.stderr || "Failed to create pull request comment.");
|
|
182
|
+
}
|
|
183
|
+
async function readPullRequestNumberFromWorkflowRunEvent() {
|
|
184
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
185
|
+
if (!eventPath) throw new Error("GITHUB_EVENT_PATH is required.");
|
|
186
|
+
let event;
|
|
187
|
+
try {
|
|
188
|
+
event = JSON.parse(await readFile(eventPath, "utf8"));
|
|
189
|
+
} catch {
|
|
190
|
+
throw new Error("Failed to read workflow_run event.");
|
|
191
|
+
}
|
|
192
|
+
const workflowRun = event.workflow_run;
|
|
193
|
+
if (!workflowRun) throw new Error("A workflow_run event is required.");
|
|
194
|
+
if (workflowRun.event !== "pull_request") throw new Error("The preview workflow was not triggered by a pull request.");
|
|
195
|
+
if (workflowRun.conclusion !== "success") throw new Error("The preview workflow did not complete successfully.");
|
|
196
|
+
const number = workflowRun.pull_requests?.[0]?.number;
|
|
197
|
+
if (!Number.isInteger(number) || !number || number <= 0) throw new Error("The preview workflow is not associated with a pull request.");
|
|
198
|
+
return number;
|
|
199
|
+
}
|
|
200
|
+
async function resolvePullRequest(context, options) {
|
|
201
|
+
const repo = context.github?.repo ?? process.env.GITHUB_REPOSITORY;
|
|
202
|
+
if (!repo) throw new Error("GITHUB_REPOSITORY is required.");
|
|
203
|
+
if (options.number !== void 0) {
|
|
204
|
+
if (!Number.isInteger(options.number) || options.number <= 0) throw new Error("--number must be a positive integer.");
|
|
205
|
+
return readPullRequestFromGh(repo, options.number);
|
|
206
|
+
}
|
|
207
|
+
const event = await readPullRequestEvent();
|
|
208
|
+
if (event) return {
|
|
209
|
+
repo,
|
|
210
|
+
...event
|
|
211
|
+
};
|
|
212
|
+
throw new Error("A pull request event or --number is required.");
|
|
213
|
+
}
|
|
214
|
+
async function readPullRequestFromGh(repo, number) {
|
|
215
|
+
const result = await x("gh", [
|
|
216
|
+
"pr",
|
|
217
|
+
"view",
|
|
218
|
+
String(number),
|
|
219
|
+
"--repo",
|
|
220
|
+
repo,
|
|
221
|
+
"--json",
|
|
222
|
+
"headRefName,baseRefOid,headRefOid,headRepository"
|
|
223
|
+
]);
|
|
224
|
+
if (result.exitCode !== 0) throw new Error(result.stderr || `Failed to resolve pull request #${number}.`);
|
|
225
|
+
const data = JSON.parse(result.stdout);
|
|
226
|
+
return {
|
|
227
|
+
repo,
|
|
228
|
+
headRepo: data.headRepository ? `${data.headRepository.owner.login}/${data.headRepository.name}` : repo,
|
|
229
|
+
headRef: data.headRefName,
|
|
230
|
+
baseSha: data.baseRefOid,
|
|
231
|
+
headSha: data.headRefOid
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
async function readPullRequestEvent() {
|
|
235
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
236
|
+
if (!eventPath) return;
|
|
237
|
+
let event;
|
|
238
|
+
try {
|
|
239
|
+
event = JSON.parse(await readFile(eventPath, "utf8"));
|
|
240
|
+
} catch {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const pullRequest = event.pull_request;
|
|
244
|
+
if (!pullRequest) return;
|
|
245
|
+
return {
|
|
246
|
+
headRepo: pullRequest.head.repo.full_name,
|
|
247
|
+
headRef: pullRequest.head.ref,
|
|
248
|
+
baseSha: pullRequest.base.sha,
|
|
249
|
+
headSha: pullRequest.head.sha
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async function listPullRequestChangelogFiles(context, baseSha, headSha) {
|
|
253
|
+
const dir = relative(context.cwd, context.changelogDir);
|
|
254
|
+
const result = await x("git", [
|
|
255
|
+
"diff",
|
|
256
|
+
"--name-only",
|
|
257
|
+
"--diff-filter=ACMRD",
|
|
258
|
+
`${baseSha}...${headSha}`,
|
|
259
|
+
"--",
|
|
260
|
+
`${dir}/`
|
|
261
|
+
], { nodeOptions: { cwd: context.cwd } });
|
|
262
|
+
if (result.exitCode !== 0) {
|
|
263
|
+
const detail = result.stderr.trim();
|
|
264
|
+
throw new Error(detail ? `Failed to list pull request changelog files: ${detail}` : "Failed to list pull request changelog files.");
|
|
265
|
+
}
|
|
266
|
+
const files = /* @__PURE__ */ new Set();
|
|
267
|
+
for (const line of result.stdout.split("\n")) {
|
|
268
|
+
const trimmed = line.trim();
|
|
269
|
+
if (trimmed.endsWith(".md")) files.add(basename(trimmed));
|
|
270
|
+
}
|
|
271
|
+
return files;
|
|
272
|
+
}
|
|
273
|
+
function createChangelogUrl(repo, branch, changelogDir, filename) {
|
|
274
|
+
const segments = [...changelogDir.split("/").filter(Boolean), filename].map((part) => encodeURIComponent(part));
|
|
275
|
+
return `https://github.com/${repo}/new/${branch.split("/").map(encodeURIComponent).join("/")}/${segments.join("/")}`;
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/utils/git-changes.ts
|
|
279
|
+
async function getChangedPackages(graph, cwd) {
|
|
280
|
+
return resolveChangedPackages(graph, await getChangedFilePaths(cwd), cwd);
|
|
281
|
+
}
|
|
282
|
+
async function getChangedFilePaths(cwd) {
|
|
283
|
+
const files = /* @__PURE__ */ new Set();
|
|
284
|
+
for (const args of [["diff", "--name-only"], [
|
|
285
|
+
"diff",
|
|
286
|
+
"--cached",
|
|
287
|
+
"--name-only"
|
|
288
|
+
]]) await addGitOutput(files, cwd, args);
|
|
289
|
+
return Array.from(files);
|
|
290
|
+
}
|
|
291
|
+
function resolveChangedPackages(graph, files, cwd) {
|
|
292
|
+
const packages = [...graph.getPackages()].sort((a, b) => b.path.length - a.path.length);
|
|
293
|
+
const matched = /* @__PURE__ */ new Map();
|
|
294
|
+
for (const file of files) for (const pkg of packages) if (isUnderDir(file, pkg.path, cwd)) {
|
|
295
|
+
matched.set(pkg.id, pkg);
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
return [...matched.values()];
|
|
299
|
+
}
|
|
300
|
+
function isUnderDir(file, dir, cwd) {
|
|
301
|
+
const absolute = join(cwd, file);
|
|
302
|
+
const rel = relative(normalize(dir), absolute);
|
|
303
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
304
|
+
}
|
|
305
|
+
async function addGitOutput(files, cwd, args) {
|
|
306
|
+
const result = await x("git", args, { nodeOptions: { cwd } });
|
|
307
|
+
if (result.exitCode !== 0) return;
|
|
308
|
+
for (const line of result.stdout.split("\n")) {
|
|
309
|
+
const trimmed = line.trim();
|
|
310
|
+
if (trimmed) files.add(trimmed);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
10
314
|
//#region src/cli/index.ts
|
|
11
315
|
var CancelledError = class extends Error {
|
|
12
316
|
constructor() {
|
|
@@ -29,6 +333,40 @@ function createCli(tegami, options = {}) {
|
|
|
29
333
|
if (await versionPackages(tegami, { cli: options })) return;
|
|
30
334
|
await publishPackages(tegami, { cli: options });
|
|
31
335
|
}));
|
|
336
|
+
const programPr = program.command("pr");
|
|
337
|
+
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) => {
|
|
338
|
+
const number = Number(value);
|
|
339
|
+
if (!Number.isInteger(number) || number <= 0) throw new InvalidArgumentError("--number must be a positive integer.");
|
|
340
|
+
return number;
|
|
341
|
+
}).action((commandOptions) => runAction(tegami, async () => {
|
|
342
|
+
const context = await tegami._internal.context();
|
|
343
|
+
const body = await buildPrPreview(context, await tegami.draft(), commandOptions);
|
|
344
|
+
if (commandOptions.artifact) {
|
|
345
|
+
const artifactPath = resolve(context.cwd, commandOptions.artifact);
|
|
346
|
+
await writeFile(artifactPath, body);
|
|
347
|
+
if (!isCI()) {
|
|
348
|
+
note(relative(context.cwd, artifactPath) || commandOptions.artifact, "Release preview");
|
|
349
|
+
outro("Release preview ready.");
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (isCI()) {
|
|
354
|
+
process.stdout.write(`${body}\n`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
note(body, "Release preview");
|
|
358
|
+
outro("Release preview ready.");
|
|
359
|
+
}));
|
|
360
|
+
programPr.command("comment").description("(should be used with 'pr preview') post the pull request release preview as a comment").argument("<artifact>", "the file path of GitHub artifact").action(async (artifact) => {
|
|
361
|
+
try {
|
|
362
|
+
await postPrComment(await readFile(artifact, "utf8"));
|
|
363
|
+
outro("Pull request comment updated.");
|
|
364
|
+
} catch (error) {
|
|
365
|
+
note(error instanceof Error ? error.message : String(error), "Error");
|
|
366
|
+
outro("Command failed.");
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
32
370
|
program.command("publish").description("publish packages from the applied publish plan").option("--dry-run", "validate the publish plan without publishing packages").action((commandOptions) => runAction(tegami, async () => {
|
|
33
371
|
await publishPackages(tegami, {
|
|
34
372
|
...commandOptions,
|
|
@@ -36,6 +374,7 @@ function createCli(tegami, options = {}) {
|
|
|
36
374
|
});
|
|
37
375
|
}));
|
|
38
376
|
program.command("cleanup").description("remove the publish plan after all packages have been published").action(() => runAction(tegami, () => runCleanup(tegami)));
|
|
377
|
+
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)));
|
|
39
378
|
return program;
|
|
40
379
|
}
|
|
41
380
|
async function createChangelogs(tegami, _options) {
|
|
@@ -44,22 +383,33 @@ async function createChangelogs(tegami, _options) {
|
|
|
44
383
|
intro("Create changelogs");
|
|
45
384
|
let selectedPackages = [];
|
|
46
385
|
if (!isCI()) {
|
|
47
|
-
const packages = context.graph.getPackages();
|
|
48
386
|
const useShortname = /* @__PURE__ */ new Map();
|
|
49
|
-
for (const pkg of
|
|
387
|
+
for (const pkg of context.graph.getPackages()) if (useShortname.has(pkg.name)) useShortname.set(pkg.name, false);
|
|
50
388
|
else useShortname.set(pkg.name, true);
|
|
51
389
|
const getPackageLabel = (pkg) => {
|
|
52
390
|
return useShortname.get(pkg.name) ? pkg.name : pkg.id;
|
|
53
391
|
};
|
|
392
|
+
const changedPackages = new Set(await getChangedPackages(context.graph, context.cwd));
|
|
54
393
|
const selectOptions = [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
394
|
+
const groups = [];
|
|
395
|
+
for (const group of context.graph.getGroups()) {
|
|
396
|
+
const changed = group.packages.some((pkg) => changedPackages.has(pkg));
|
|
397
|
+
groups.push([group, changed]);
|
|
398
|
+
}
|
|
399
|
+
groups.sort((a, b) => (a[1] ? 0 : 1) - (b[1] ? 0 : 1));
|
|
400
|
+
for (const [group, changed] of groups) {
|
|
401
|
+
const members = group.packages.map(getPackageLabel).join(", ");
|
|
402
|
+
selectOptions.push({
|
|
403
|
+
label: `Group ${group.name}`,
|
|
404
|
+
value: `group:${group.name}`,
|
|
405
|
+
hint: changed ? `changed · ${members}` : members
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const packages = context.graph.getPackages().toSorted((a, b) => (changedPackages.has(a) ? 0 : 1) - (changedPackages.has(b) ? 0 : 1));
|
|
60
409
|
for (const pkg of packages) selectOptions.push({
|
|
61
410
|
label: getPackageLabel(pkg),
|
|
62
|
-
value: pkg.id
|
|
411
|
+
value: pkg.id,
|
|
412
|
+
hint: changedPackages.has(pkg) ? "changed" : void 0
|
|
63
413
|
});
|
|
64
414
|
const selected = await autocompleteMultiselect({
|
|
65
415
|
message: "Select packages (leave empty to auto-generate from commits)",
|
|
@@ -186,6 +536,16 @@ async function publishPackages(tegami, options) {
|
|
|
186
536
|
outro(dryRun ? "Publish plan is valid." : "Packages published.");
|
|
187
537
|
return true;
|
|
188
538
|
}
|
|
539
|
+
async function runInitAgent(tegami, options) {
|
|
540
|
+
intro("Init agent instructions");
|
|
541
|
+
const context = await tegami._internal.context();
|
|
542
|
+
const s = spinner();
|
|
543
|
+
s.start("Writing AGENTS.md");
|
|
544
|
+
const result = await initAgent(context, options);
|
|
545
|
+
s.stop(result.created ? "Created AGENTS.md" : "Appended to AGENTS.md");
|
|
546
|
+
note(relative(context.cwd, result.path) || "AGENTS.md", "Agent instructions");
|
|
547
|
+
outro("Agents can follow AGENTS.md to write changelogs.");
|
|
548
|
+
}
|
|
189
549
|
async function runCleanup(tegami) {
|
|
190
550
|
intro("Cleanup publish plan");
|
|
191
551
|
const s = spinner();
|
|
@@ -216,10 +576,6 @@ function renderManualChangelog(packages, type, message) {
|
|
|
216
576
|
""
|
|
217
577
|
].join("\n");
|
|
218
578
|
}
|
|
219
|
-
function changelogFilename() {
|
|
220
|
-
const date = /* @__PURE__ */ new Date();
|
|
221
|
-
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}-${Date.now().toString(36)}.md`;
|
|
222
|
-
}
|
|
223
579
|
async function runAction(tegami, action) {
|
|
224
580
|
try {
|
|
225
581
|
const context = await tegami._internal.context();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as PublishResult, D as WorkspacePackage, E as PackageGroup, O as DraftPlan, S as PublishOptions, T as PackageGraph, _ as Tegami, a as RegistryClient, b as CreatedChangelog, c as TegamiPluginOption, i as PackageOptions, k as PackagePlan, n as GroupOptions, o as TegamiOptions, r as LogGenerator, s as TegamiPlugin, v as tegami, x as PackagePublishResult, y as CreateChangelogOptions } from "./types-
|
|
1
|
+
import { C as PublishResult, D as WorkspacePackage, E as PackageGroup, O as DraftPlan, S as PublishOptions, T as PackageGraph, _ as Tegami, a as RegistryClient, b as CreatedChangelog, c as TegamiPluginOption, i as PackageOptions, k as PackagePlan, n as GroupOptions, o as TegamiOptions, r as LogGenerator, s as TegamiPlugin, v as tegami, x as PackagePublishResult, y as CreateChangelogOptions } from "./types-Cx-uvEWw.js";
|
|
2
2
|
export { type CreateChangelogOptions, type CreatedChangelog, type DraftPlan, type GroupOptions, type LogGenerator, type PackageGraph, type PackageGroup, type PackageOptions, type PackagePlan, type PackagePublishResult, type PublishOptions, type PublishResult, type RegistryClient, Tegami, type TegamiOptions, type TegamiPlugin, type TegamiPluginOption, type WorkspacePackage, tegami };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { n as
|
|
1
|
+
import { a as changelogFilename, i as readPlanStore, n as publishPlanStatus, r as createPlanStore, t as assertPublishPlanFinished } from "./checks-BQISvt_o.js";
|
|
2
|
+
import { n as handlePluginError, t as execFailure } from "./error-DBK-9uBa.js";
|
|
2
3
|
import { a as maxBump } from "./semver-CPtl0XNq.js";
|
|
3
4
|
import { t as PackageGraph } from "./graph-B22NBRUG.js";
|
|
4
5
|
import { cargo } from "./providers/cargo.js";
|
|
5
6
|
import { t as bumpTypeSchema } from "./schemas-CurBAaW5.js";
|
|
6
7
|
import { npm } from "./providers/npm.js";
|
|
7
8
|
import { simpleGenerator } from "./generators/simple.js";
|
|
8
|
-
import { i as readPlanStore, n as publishPlanStatus, r as createPlanStore, t as assertPublishPlanFinished } from "./checks-Fz8-N09y.js";
|
|
9
9
|
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
10
10
|
import path, { basename, dirname, join } from "node:path";
|
|
11
11
|
import { x } from "tinyexec";
|
|
@@ -13,8 +13,73 @@ import { load } from "js-yaml";
|
|
|
13
13
|
import z$1 from "zod";
|
|
14
14
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
15
15
|
import { toMarkdown } from "mdast-util-to-markdown";
|
|
16
|
-
//#region src/
|
|
17
|
-
|
|
16
|
+
//#region src/utils/conventional-commit.ts
|
|
17
|
+
/**
|
|
18
|
+
* Conventional commit header per conventionalcommits.org and semantic-release defaults.
|
|
19
|
+
* @see https://www.conventionalcommits.org/en/v1.0.0/
|
|
20
|
+
*/
|
|
21
|
+
const CONVENTIONAL_COMMIT_HEADER = /^(?<type>\w+)(?:\((?<scope>[^)]*)\))?(?<breaking>!)?: (?<title>.+)$/;
|
|
22
|
+
const BREAKING_CHANGE_FOOTER = /^BREAKING[ -]CHANGE:/m;
|
|
23
|
+
/** Parse conventional commits and resolve scopes against the workspace graph. */
|
|
24
|
+
function createConventionalCommitParser(graph) {
|
|
25
|
+
const byShortName = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const pkg of graph.getPackages()) {
|
|
27
|
+
const slash = pkg.name.lastIndexOf("/");
|
|
28
|
+
const short = slash >= 0 ? pkg.name.slice(slash + 1) : pkg.name;
|
|
29
|
+
const names = byShortName.get(short);
|
|
30
|
+
if (names) names.push(pkg.name);
|
|
31
|
+
else byShortName.set(short, [pkg.name]);
|
|
32
|
+
}
|
|
33
|
+
function resolvePackages(scope) {
|
|
34
|
+
if (!scope) return [];
|
|
35
|
+
const packages = /* @__PURE__ */ new Set();
|
|
36
|
+
for (const item of scope.split(",")) {
|
|
37
|
+
const name = item.trim();
|
|
38
|
+
if (!name) continue;
|
|
39
|
+
const direct = graph.getByName(name);
|
|
40
|
+
if (direct.length > 0) {
|
|
41
|
+
for (const pkg of direct) packages.add(pkg.name);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const byShort = byShortName.get(name);
|
|
45
|
+
if (byShort) {
|
|
46
|
+
for (const pkgName of byShort) packages.add(pkgName);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
packages.add(name);
|
|
50
|
+
}
|
|
51
|
+
return Array.from(packages);
|
|
52
|
+
}
|
|
53
|
+
return function parseConventionalCommit(subject, body = "") {
|
|
54
|
+
const match = CONVENTIONAL_COMMIT_HEADER.exec(subject.trim());
|
|
55
|
+
if (!match?.groups) return;
|
|
56
|
+
const { type, scope, breaking, title } = match.groups;
|
|
57
|
+
if (!type || !title) return;
|
|
58
|
+
const trimmedTitle = title.trim();
|
|
59
|
+
if (!trimmedTitle) return;
|
|
60
|
+
const trimmedScope = scope?.trim();
|
|
61
|
+
return {
|
|
62
|
+
type: type.toLowerCase(),
|
|
63
|
+
packages: resolvePackages(trimmedScope || void 0),
|
|
64
|
+
breaking: Boolean(breaking) || BREAKING_CHANGE_FOOTER.test(body),
|
|
65
|
+
title: trimmedTitle
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Map releasable conventional commit types to semver bumps (semantic-release defaults). */
|
|
70
|
+
function conventionalCommitToBump(type, breaking) {
|
|
71
|
+
if (breaking) return "major";
|
|
72
|
+
switch (type) {
|
|
73
|
+
case "feat": return "minor";
|
|
74
|
+
case "fix":
|
|
75
|
+
case "perf":
|
|
76
|
+
case "revert": return "patch";
|
|
77
|
+
default: return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/changelog/generate.ts
|
|
82
|
+
async function generateChangelog(context, options = {}) {
|
|
18
83
|
const commits = await readConventionalCommits(context, options);
|
|
19
84
|
const groups = /* @__PURE__ */ new Map();
|
|
20
85
|
for (const commit of commits) {
|
|
@@ -23,23 +88,19 @@ async function createChangelog(context, options = {}) {
|
|
|
23
88
|
if (group) group.push(commit);
|
|
24
89
|
else groups.set(key, [commit]);
|
|
25
90
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const created = [];
|
|
29
|
-
const stamp = Date.now().toString(36);
|
|
30
|
-
for (const [key, changes] of groups) {
|
|
91
|
+
await mkdir(context.changelogDir, { recursive: true });
|
|
92
|
+
return Promise.all(Array.from(groups, async ([key, changes], index) => {
|
|
31
93
|
const packages = key ? key.split("\0") : [];
|
|
32
|
-
const filename =
|
|
33
|
-
const path = join(
|
|
94
|
+
const filename = changelogFilename(index);
|
|
95
|
+
const path = join(context.changelogDir, filename);
|
|
34
96
|
await writeFile(path, renderChangelog(packages, changes));
|
|
35
|
-
|
|
97
|
+
return {
|
|
36
98
|
filename,
|
|
37
99
|
path,
|
|
38
100
|
packages,
|
|
39
101
|
changes: changes.length
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return created;
|
|
102
|
+
};
|
|
103
|
+
}));
|
|
43
104
|
}
|
|
44
105
|
async function readConventionalCommits(context, options) {
|
|
45
106
|
const to = options.to ?? "HEAD";
|
|
@@ -52,13 +113,24 @@ async function readConventionalCommits(context, options) {
|
|
|
52
113
|
if (from) args.push(`${from}..${to}`);
|
|
53
114
|
else if (to !== "HEAD") args.push(to);
|
|
54
115
|
const result = await x("git", args, { nodeOptions: { cwd: context.cwd } });
|
|
55
|
-
if (result.exitCode !== 0) throw
|
|
116
|
+
if (result.exitCode !== 0) throw execFailure(`Unable to read git commits from ${from ?? "start"} to ${to}`, result);
|
|
117
|
+
const parseCommit = createConventionalCommitParser(context.graph);
|
|
56
118
|
const changes = [];
|
|
57
119
|
for (const record of result.stdout.split("")) {
|
|
58
120
|
const [hash, subject, body = ""] = record.replace(/^\n+|\n+$/g, "").split("");
|
|
59
121
|
if (!hash || !subject) continue;
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
122
|
+
const parsed = parseCommit(subject, body);
|
|
123
|
+
if (!parsed) continue;
|
|
124
|
+
const bump = conventionalCommitToBump(parsed.type, parsed.breaking);
|
|
125
|
+
if (!bump) continue;
|
|
126
|
+
changes.push({
|
|
127
|
+
hash,
|
|
128
|
+
subject,
|
|
129
|
+
body: body.trim(),
|
|
130
|
+
packages: parsed.packages,
|
|
131
|
+
type: bump,
|
|
132
|
+
title: titleCase(parsed.title)
|
|
133
|
+
});
|
|
62
134
|
}
|
|
63
135
|
return changes;
|
|
64
136
|
}
|
|
@@ -68,47 +140,9 @@ async function latestTag(cwd) {
|
|
|
68
140
|
"--tags",
|
|
69
141
|
"--abbrev=0"
|
|
70
142
|
], { nodeOptions: { cwd } });
|
|
71
|
-
if (result.exitCode !== 0) return
|
|
143
|
+
if (result.exitCode !== 0) return;
|
|
72
144
|
return result.stdout.trim() || void 0;
|
|
73
145
|
}
|
|
74
|
-
function parseConventionalCommit(context, hash, subject, body) {
|
|
75
|
-
const match = /^(?<type>[a-zA-Z]+)(?:\((?<scope>[^)]+)\))?(?<breaking>!)?:\s*(?<title>.+)$/.exec(subject);
|
|
76
|
-
if (!match?.groups) return void 0;
|
|
77
|
-
const bump = commitTypeToBump(match.groups.type.toLowerCase(), Boolean(match.groups.breaking) || /^BREAKING(?:-| )CHANGE:/m.test(body));
|
|
78
|
-
if (!bump) return void 0;
|
|
79
|
-
return {
|
|
80
|
-
hash,
|
|
81
|
-
subject,
|
|
82
|
-
body: body.trim(),
|
|
83
|
-
packages: resolvePackages(context, match.groups.scope),
|
|
84
|
-
type: bump,
|
|
85
|
-
title: titleCase(match.groups.title)
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function commitTypeToBump(type, breaking) {
|
|
89
|
-
if (breaking) return "major";
|
|
90
|
-
if (type === "feat") return "minor";
|
|
91
|
-
if (type === "fix" || type === "perf") return "patch";
|
|
92
|
-
}
|
|
93
|
-
function resolvePackages(context, scope) {
|
|
94
|
-
if (!scope) return [];
|
|
95
|
-
const packages = /* @__PURE__ */ new Set();
|
|
96
|
-
for (const item of scope.split(",")) {
|
|
97
|
-
const name = item.trim();
|
|
98
|
-
if (!name) continue;
|
|
99
|
-
if (context.graph.getByName(name).length > 0) {
|
|
100
|
-
packages.add(name);
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
const byShortName = context.graph.getPackages().filter((pkg) => pkg.name.split("/").at(-1) === name);
|
|
104
|
-
if (byShortName.length > 0) {
|
|
105
|
-
for (const pkg of byShortName) packages.add(pkg.name);
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
packages.add(name);
|
|
109
|
-
}
|
|
110
|
-
return Array.from(packages).sort();
|
|
111
|
-
}
|
|
112
146
|
function renderChangelog(packages, changes) {
|
|
113
147
|
return [
|
|
114
148
|
"---",
|
|
@@ -127,12 +161,6 @@ function renderChange(change) {
|
|
|
127
161
|
function titleCase(value) {
|
|
128
162
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
129
163
|
}
|
|
130
|
-
function slugify(value) {
|
|
131
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
132
|
-
}
|
|
133
|
-
function commandOutput(result) {
|
|
134
|
-
return [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
135
|
-
}
|
|
136
164
|
//#endregion
|
|
137
165
|
//#region src/context.ts
|
|
138
166
|
async function createTegamiContext(options = {}) {
|
|
@@ -412,7 +440,7 @@ function parseChangelogFile(file, content) {
|
|
|
412
440
|
if (sectionBumpType) bumpType = bumpType ? maxBump(bumpType, sectionBumpType) : sectionBumpType;
|
|
413
441
|
sections.push({
|
|
414
442
|
depth: section.heading.depth,
|
|
415
|
-
title:
|
|
443
|
+
title: sectionToMarkdown(section.heading.children),
|
|
416
444
|
content: sectionToMarkdown(section.children)
|
|
417
445
|
});
|
|
418
446
|
}
|
|
@@ -443,14 +471,6 @@ function getHeadingSections(tree) {
|
|
|
443
471
|
}
|
|
444
472
|
return sections;
|
|
445
473
|
}
|
|
446
|
-
function headingText(heading) {
|
|
447
|
-
return heading.children.map((child) => nodeText(child)).join("").trim();
|
|
448
|
-
}
|
|
449
|
-
function nodeText(node) {
|
|
450
|
-
if ("value" in node && typeof node.value === "string") return node.value;
|
|
451
|
-
if ("children" in node && Array.isArray(node.children)) return node.children.map((child) => nodeText(child)).join("");
|
|
452
|
-
return "";
|
|
453
|
-
}
|
|
454
474
|
function sectionToMarkdown(children) {
|
|
455
475
|
if (children.length === 0) return "";
|
|
456
476
|
return toMarkdown({
|
|
@@ -462,9 +482,11 @@ function sectionToMarkdown(children) {
|
|
|
462
482
|
}).trim();
|
|
463
483
|
}
|
|
464
484
|
function headingToBump(depth) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
485
|
+
switch (depth) {
|
|
486
|
+
case 1: return "major";
|
|
487
|
+
case 2: return "minor";
|
|
488
|
+
case 3: return "patch";
|
|
489
|
+
}
|
|
468
490
|
}
|
|
469
491
|
//#endregion
|
|
470
492
|
//#region src/publish.ts
|
|
@@ -476,6 +498,7 @@ async function publishFromPlan(context, store, options) {
|
|
|
476
498
|
if (!plan.publish) continue;
|
|
477
499
|
const pkg = context.graph.get(id);
|
|
478
500
|
if (!pkg) continue;
|
|
501
|
+
const registryClient = context.getRegistryClient(pkg);
|
|
479
502
|
const changelogs = [];
|
|
480
503
|
for (const id of plan.changelogIds ?? []) {
|
|
481
504
|
const entry = store.changelogs[id];
|
|
@@ -486,7 +509,7 @@ async function publishFromPlan(context, store, options) {
|
|
|
486
509
|
});
|
|
487
510
|
}
|
|
488
511
|
if (!dryRun) {
|
|
489
|
-
if (await
|
|
512
|
+
if (await registryClient.isPackagePublished(pkg)) {
|
|
490
513
|
packages.push({
|
|
491
514
|
id: pkg.id,
|
|
492
515
|
name: pkg.name,
|
|
@@ -497,15 +520,7 @@ async function publishFromPlan(context, store, options) {
|
|
|
497
520
|
});
|
|
498
521
|
continue;
|
|
499
522
|
}
|
|
500
|
-
}
|
|
501
|
-
try {
|
|
502
|
-
if (!dryRun) {
|
|
503
|
-
for (const plugin of context.plugins) await handlePluginError(plugin, "willPublish", () => plugin.willPublish?.call(context, { pkg }));
|
|
504
|
-
await context.getRegistryClient(pkg).publish(pkg, {
|
|
505
|
-
packageStore: plan,
|
|
506
|
-
store
|
|
507
|
-
});
|
|
508
|
-
}
|
|
523
|
+
} else {
|
|
509
524
|
packages.push({
|
|
510
525
|
id: pkg.id,
|
|
511
526
|
name: pkg.name,
|
|
@@ -514,6 +529,40 @@ async function publishFromPlan(context, store, options) {
|
|
|
514
529
|
state: "success",
|
|
515
530
|
changelogs
|
|
516
531
|
});
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
let result;
|
|
536
|
+
for (const plugin of context.plugins) {
|
|
537
|
+
const next = await handlePluginError(plugin, "willPublish", () => plugin.willPublish?.call(context, { pkg }));
|
|
538
|
+
if (next !== void 0) {
|
|
539
|
+
result = next;
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (result === void 0) {
|
|
544
|
+
await registryClient.publish(pkg, {
|
|
545
|
+
packageStore: plan,
|
|
546
|
+
store
|
|
547
|
+
});
|
|
548
|
+
result = {
|
|
549
|
+
id: pkg.id,
|
|
550
|
+
name: pkg.name,
|
|
551
|
+
version: pkg.version,
|
|
552
|
+
npm: plan.npm,
|
|
553
|
+
state: "success",
|
|
554
|
+
changelogs
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
if (result === false) continue;
|
|
558
|
+
for (const plugin of context.plugins) {
|
|
559
|
+
const next = await handlePluginError(plugin, "afterPublish", () => plugin.afterPublish?.call(context, {
|
|
560
|
+
pkg,
|
|
561
|
+
result
|
|
562
|
+
}));
|
|
563
|
+
if (next) result = next;
|
|
564
|
+
}
|
|
565
|
+
packages.push(result);
|
|
517
566
|
} catch (error) {
|
|
518
567
|
packages.push({
|
|
519
568
|
id: pkg.id,
|
|
@@ -543,7 +592,7 @@ function tegami(options = {}) {
|
|
|
543
592
|
}
|
|
544
593
|
return {
|
|
545
594
|
async generateChangelog(createOptions = {}) {
|
|
546
|
-
return
|
|
595
|
+
return generateChangelog(await $context, createOptions);
|
|
547
596
|
},
|
|
548
597
|
_internal: {
|
|
549
598
|
options,
|
|
@@ -568,7 +617,10 @@ function tegami(options = {}) {
|
|
|
568
617
|
...context,
|
|
569
618
|
publishOptions
|
|
570
619
|
};
|
|
571
|
-
for (const plugin of context.plugins)
|
|
620
|
+
for (const plugin of context.plugins) {
|
|
621
|
+
const next = await handlePluginError(plugin, "afterPublishAll", () => plugin.afterPublishAll?.call(publishCtx, result));
|
|
622
|
+
if (next) result = next;
|
|
623
|
+
}
|
|
572
624
|
return result;
|
|
573
625
|
},
|
|
574
626
|
async cleanup() {
|
package/dist/plugins/git.d.ts
CHANGED
package/dist/plugins/git.js
CHANGED
|
@@ -37,7 +37,7 @@ function git(options = {}) {
|
|
|
37
37
|
if (result.exitCode !== 0) throw execFailure("Failed to configure git user for GitHub Actions.", result);
|
|
38
38
|
}
|
|
39
39
|
} },
|
|
40
|
-
async
|
|
40
|
+
async afterPublishAll(result) {
|
|
41
41
|
const { cwd, publishOptions: { dryRun = false } } = this;
|
|
42
42
|
if (dryRun || !createTags || result.state !== "created") return result;
|
|
43
43
|
const pendingTags = /* @__PURE__ */ new Set();
|
package/dist/plugins/github.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { O as DraftPlan, s as TegamiPlugin, t as Awaitable, w as TegamiContext, x as PackagePublishResult } from "../types-
|
|
1
|
+
import { O as DraftPlan, s as TegamiPlugin, t as Awaitable, w as TegamiContext, x as PackagePublishResult } from "../types-Cx-uvEWw.js";
|
|
2
2
|
import { GitPluginOptions } from "./git.js";
|
|
3
3
|
|
|
4
4
|
//#region src/plugins/github.d.ts
|
|
@@ -17,6 +17,12 @@ interface VersionPullRequest {
|
|
|
17
17
|
body?: string;
|
|
18
18
|
}
|
|
19
19
|
interface VersionPullRequestOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Create the PR even outside of CI.
|
|
22
|
+
*
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
forceCreate?: boolean;
|
|
20
26
|
/**
|
|
21
27
|
* Pull request branch.
|
|
22
28
|
*
|
package/dist/plugins/github.js
CHANGED
|
@@ -39,23 +39,27 @@ function github(options = {}) {
|
|
|
39
39
|
function resolvePROptions() {
|
|
40
40
|
const setting = options.cli?.versionPr ?? isCI();
|
|
41
41
|
if (setting === false) return [false];
|
|
42
|
-
|
|
42
|
+
if (setting === true) return [true, {}];
|
|
43
|
+
if (setting.forceCreate || isCI()) return [true, setting];
|
|
44
|
+
return [false];
|
|
43
45
|
}
|
|
44
46
|
function defaultVersionPRBody(draft, context) {
|
|
45
47
|
const packageLines = [];
|
|
46
48
|
for (const pkg of context.graph.getPackages()) {
|
|
47
49
|
const packagePlan = draft.getPackagePlan(pkg.id);
|
|
48
|
-
if (!packagePlan
|
|
50
|
+
if (!packagePlan) continue;
|
|
51
|
+
const originalVersion = cliOriginalPackageVersions.get(pkg.id) ?? pkg.version;
|
|
52
|
+
if (originalVersion === pkg.version) continue;
|
|
49
53
|
const publishTxt = packagePlan.publish ? "" : " (no publish)";
|
|
50
54
|
const distTagTxt = formatNpmDistTag(packagePlan.npm?.distTag);
|
|
51
|
-
packageLines.push(`- ${pkg.name}@${
|
|
55
|
+
packageLines.push(`- ${pkg.name}@${originalVersion} → ${pkg.name}@${pkg.version}${distTagTxt}${publishTxt}`);
|
|
52
56
|
}
|
|
53
57
|
const changelogLines = [];
|
|
54
58
|
for (const entry of draft.getChangelogs()) {
|
|
55
59
|
changelogLines.push(`### ${entry.subject ?? `\`${entry.filename}\``}`, "");
|
|
56
60
|
for (const section of entry.sections) {
|
|
57
61
|
changelogLines.push(`#### ${section.title}`, "");
|
|
58
|
-
changelogLines.push(section.content);
|
|
62
|
+
if (section.content) changelogLines.push(section.content);
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
const sections = [
|
|
@@ -164,7 +168,7 @@ function github(options = {}) {
|
|
|
164
168
|
if (prResult.exitCode !== 0) throw execFailure("Failed to create the version pull request.", prResult);
|
|
165
169
|
}
|
|
166
170
|
},
|
|
167
|
-
async
|
|
171
|
+
async afterPublishAll(result) {
|
|
168
172
|
if (result.state !== "created") return;
|
|
169
173
|
for (const packages of groupPackagesByGitTag(result.packages).values()) if (packages.length > 1) await createGroupedRelease(this, packages);
|
|
170
174
|
else await createRelease(this, packages[0]);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { d as CargoRegistryClient, f as cargo, l as CargoPackage, u as CargoPluginOptions } from "../types-
|
|
1
|
+
import { d as CargoRegistryClient, f as cargo, l as CargoPackage, u as CargoPluginOptions } from "../types-Cx-uvEWw.js";
|
|
2
2
|
export { CargoPackage, CargoPluginOptions, CargoRegistryClient, cargo };
|
package/dist/providers/npm.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { g as npm, h as NpmRegistryClient, m as NpmPluginOptions, p as NpmPackage } from "../types-
|
|
1
|
+
import { g as npm, h as NpmRegistryClient, m as NpmPluginOptions, p as NpmPackage } from "../types-Cx-uvEWw.js";
|
|
2
2
|
export { NpmPackage, NpmPluginOptions, NpmRegistryClient, npm };
|
package/dist/providers/npm.js
CHANGED
|
@@ -2,7 +2,7 @@ import { r as isNodeError, t as execFailure } from "../error-DBK-9uBa.js";
|
|
|
2
2
|
import { n as WorkspacePackage } from "../graph-B22NBRUG.js";
|
|
3
3
|
import { i as pnpmWorkspaceSchema, r as packageManifestSchema } from "../schemas-CurBAaW5.js";
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import path from "node:path";
|
|
6
6
|
import { x } from "tinyexec";
|
|
7
7
|
import * as semver from "semver";
|
|
8
8
|
import { glob } from "tinyglobby";
|
|
@@ -31,7 +31,7 @@ var NpmPackage = class extends WorkspacePackage {
|
|
|
31
31
|
return this.manifest.version ?? "0.0.0";
|
|
32
32
|
}
|
|
33
33
|
async write() {
|
|
34
|
-
await writeFile(join(this.path, "package.json"), `${JSON.stringify(this.manifest, null, 2)}\n`);
|
|
34
|
+
await writeFile(path.join(this.path, "package.json"), `${JSON.stringify(this.manifest, null, 2)}\n`);
|
|
35
35
|
}
|
|
36
36
|
onPlan(context) {
|
|
37
37
|
const defaults = super.onPlan(context);
|
|
@@ -96,13 +96,22 @@ function isMissingRegistryEntry(output) {
|
|
|
96
96
|
const normalized = output.toLowerCase();
|
|
97
97
|
return normalized.includes("e404") || normalized.includes("404") || normalized.includes("no match") || normalized.includes("no matching version") || normalized.includes("not found");
|
|
98
98
|
}
|
|
99
|
-
function parseDependencySpec(context, name, range) {
|
|
99
|
+
function parseDependencySpec(context, dependent, name, range) {
|
|
100
100
|
const { graph } = context;
|
|
101
101
|
if (range.startsWith("workspace:")) return {
|
|
102
102
|
range: range.slice(10),
|
|
103
103
|
linked: graph.get(`npm:${name}`),
|
|
104
104
|
protocol: "workspace"
|
|
105
105
|
};
|
|
106
|
+
if (range.startsWith("file:")) {
|
|
107
|
+
let target = path.resolve(dependent.path, range.slice(5));
|
|
108
|
+
if (path.basename(target) === "package.json") target = path.dirname(target);
|
|
109
|
+
return {
|
|
110
|
+
protocol: "file",
|
|
111
|
+
raw: range,
|
|
112
|
+
linked: graph.getPackages().find((pkg) => pkg instanceof NpmPackage && pkg.path === target)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
106
115
|
if (range.startsWith("npm:")) {
|
|
107
116
|
const spec = range.slice(4);
|
|
108
117
|
const separator = spec.lastIndexOf("@");
|
|
@@ -122,6 +131,7 @@ function parseDependencySpec(context, name, range) {
|
|
|
122
131
|
}
|
|
123
132
|
function formatDependencySpec(spec) {
|
|
124
133
|
if (spec.protocol === "workspace") return `workspace:${spec.range}`;
|
|
134
|
+
if (spec.protocol === "file") return spec.raw;
|
|
125
135
|
if (spec.protocol === "npm") return `npm:${spec.alias}@${spec.range}`;
|
|
126
136
|
return spec.range;
|
|
127
137
|
}
|
|
@@ -148,8 +158,9 @@ function npm({ client: defaultClient, onBreakPeerDep = "set", updateLockFile = f
|
|
|
148
158
|
case "^":
|
|
149
159
|
case "~": return !semver.satisfies(target, `${spec.range}${spec.linked.version}`);
|
|
150
160
|
}
|
|
161
|
+
if (spec.protocol === "file") return true;
|
|
151
162
|
}
|
|
152
|
-
if (!semver.validRange(spec.range)) return false;
|
|
163
|
+
if (spec.protocol === "file" || !semver.validRange(spec.range)) return false;
|
|
153
164
|
return !semver.satisfies(target, spec.range);
|
|
154
165
|
}
|
|
155
166
|
return {
|
|
@@ -162,7 +173,7 @@ function npm({ client: defaultClient, onBreakPeerDep = "set", updateLockFile = f
|
|
|
162
173
|
const dependencies = dependent.manifest[field];
|
|
163
174
|
if (!dependencies) continue;
|
|
164
175
|
for (const [k, v] of Object.entries(dependencies)) {
|
|
165
|
-
const spec = parseDependencySpec(context, k, v);
|
|
176
|
+
const spec = parseDependencySpec(context, dependent, k, v);
|
|
166
177
|
if (!spec || spec.linked !== pkg) continue;
|
|
167
178
|
if (!needsUpdate(dependent, spec, plan.bumpVersion(pkg))) continue;
|
|
168
179
|
const bumpType = getBumpDepType({
|
|
@@ -215,9 +226,9 @@ function npm({ client: defaultClient, onBreakPeerDep = "set", updateLockFile = f
|
|
|
215
226
|
const dependencies = pkg.manifest[field];
|
|
216
227
|
if (!dependencies) continue;
|
|
217
228
|
for (const [k, v] of Object.entries(dependencies)) {
|
|
218
|
-
const spec = parseDependencySpec(this, k, v);
|
|
219
|
-
if (!spec ||
|
|
220
|
-
if (!semver.validRange(spec.range)
|
|
229
|
+
const spec = parseDependencySpec(this, pkg, k, v);
|
|
230
|
+
if (!spec?.linked || spec.protocol === "workspace" || spec.protocol === "file") continue;
|
|
231
|
+
if (!semver.validRange(spec.range)) continue;
|
|
221
232
|
if (semver.satisfies(spec.linked.version, spec.range)) continue;
|
|
222
233
|
let updatedRange;
|
|
223
234
|
const isPeer = field === "peerDependencies";
|
|
@@ -251,7 +262,7 @@ function npm({ client: defaultClient, onBreakPeerDep = "set", updateLockFile = f
|
|
|
251
262
|
async function discoverNpmPackages(cwd, add) {
|
|
252
263
|
let patterns;
|
|
253
264
|
const rootManifest = await readManifest(cwd).catch(() => void 0);
|
|
254
|
-
const pnpmPatterns = await readFile(join(cwd, "pnpm-workspace.yaml"), "utf8").then((content) => pnpmWorkspaceSchema.parse(load(content) ?? {})).catch((error) => {
|
|
265
|
+
const pnpmPatterns = await readFile(path.join(cwd, "pnpm-workspace.yaml"), "utf8").then((content) => pnpmWorkspaceSchema.parse(load(content) ?? {})).catch((error) => {
|
|
255
266
|
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
256
267
|
throw error;
|
|
257
268
|
});
|
|
@@ -270,16 +281,18 @@ async function discoverNpmPackages(cwd, add) {
|
|
|
270
281
|
}
|
|
271
282
|
async function expandWorkspacePatterns(cwd, patterns) {
|
|
272
283
|
if (patterns.length === 0) return [];
|
|
273
|
-
return glob(patterns, {
|
|
284
|
+
return (await glob(patterns, {
|
|
274
285
|
absolute: true,
|
|
275
286
|
cwd,
|
|
276
287
|
ignore: ["**/node_modules/**", "**/dist/**"],
|
|
277
288
|
onlyDirectories: true,
|
|
278
289
|
onlyFiles: false
|
|
290
|
+
})).map((item) => {
|
|
291
|
+
return item.endsWith(path.sep) ? item.slice(0, -1) : item;
|
|
279
292
|
});
|
|
280
293
|
}
|
|
281
294
|
async function readManifest(packagePath) {
|
|
282
|
-
const content = await readFile(join(packagePath, "package.json"), "utf8");
|
|
295
|
+
const content = await readFile(path.join(packagePath, "package.json"), "utf8");
|
|
283
296
|
const parsed = JSON.parse(content);
|
|
284
297
|
packageManifestSchema.parse(parsed);
|
|
285
298
|
return parsed;
|
|
@@ -168,6 +168,7 @@ declare const planStoreSchema: z$1.ZodCodec<z$1.ZodString, z$1.ZodObject<{
|
|
|
168
168
|
patch: "patch";
|
|
169
169
|
}>>;
|
|
170
170
|
sections: z$1.ZodArray<z$1.ZodObject<{
|
|
171
|
+
depth: z$1.ZodNumber;
|
|
171
172
|
title: z$1.ZodString;
|
|
172
173
|
content: z$1.ZodString;
|
|
173
174
|
}, z$1.core.$strip>>;
|
|
@@ -222,7 +223,7 @@ type PackagePublishResult = ({
|
|
|
222
223
|
changelogs: ChangelogEntry[];
|
|
223
224
|
};
|
|
224
225
|
//#endregion
|
|
225
|
-
//#region src/changelog/
|
|
226
|
+
//#region src/changelog/generate.d.ts
|
|
226
227
|
interface CreateChangelogOptions {
|
|
227
228
|
/** Start revision. Defaults to the latest reachable git tag, or all history if none exists. */
|
|
228
229
|
from?: string;
|
|
@@ -310,9 +311,17 @@ type DependencySpec = {
|
|
|
310
311
|
range: string;
|
|
311
312
|
linked?: WorkspacePackage;
|
|
312
313
|
} | {
|
|
313
|
-
protocol
|
|
314
|
+
protocol: "workspace";
|
|
314
315
|
range: string;
|
|
315
316
|
linked?: WorkspacePackage;
|
|
317
|
+
} | {
|
|
318
|
+
protocol: "file";
|
|
319
|
+
raw: string;
|
|
320
|
+
linked?: WorkspacePackage;
|
|
321
|
+
} | {
|
|
322
|
+
range: string;
|
|
323
|
+
linked?: WorkspacePackage;
|
|
324
|
+
protocol?: undefined;
|
|
316
325
|
};
|
|
317
326
|
interface NpmPluginOptions {
|
|
318
327
|
/** Package manager command used for npm registry operations. */
|
|
@@ -447,12 +456,17 @@ interface TegamiPlugin {
|
|
|
447
456
|
resolvePlanStatus?(this: TegamiContext, status: PublishPlanStatus, env: {
|
|
448
457
|
plan: PlanStore;
|
|
449
458
|
}): Awaitable<PublishPlanStatus>;
|
|
450
|
-
/** Called before a package will be published. */
|
|
459
|
+
/** Called before a package will be published, return `false` to prevent from publishing. */
|
|
451
460
|
willPublish?(this: TegamiContext, opts: {
|
|
452
461
|
pkg: WorkspacePackage;
|
|
453
|
-
}): Awaitable<
|
|
462
|
+
}): Awaitable<PackagePublishResult | false | void | undefined>;
|
|
463
|
+
/** Called after a package is published. */
|
|
464
|
+
afterPublish?(this: TegamiContext, opts: {
|
|
465
|
+
pkg: WorkspacePackage;
|
|
466
|
+
result: PackagePublishResult;
|
|
467
|
+
}): Awaitable<PackagePublishResult | void | undefined>;
|
|
454
468
|
/** Called after publishing finishes. */
|
|
455
|
-
|
|
469
|
+
afterPublishAll?(this: TegamiContext & {
|
|
456
470
|
publishOptions: PublishOptions;
|
|
457
471
|
}, result: PublishResult): Awaitable<PublishResult | void | undefined>;
|
|
458
472
|
/** CLI lifecycle hooks. */
|