tegami 0.1.1 → 0.1.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,277 @@
1
+ import { a as maxBump, t as bumpDepth } from "./semver-C4vJ4SK8.js";
2
+ import { n as execFailure, r as handlePluginError } from "./error-DNy8R5ue.js";
3
+ import { n as jsonCodec, t as bumpTypeSchema } from "./schemas-CurBAaW5.js";
4
+ import { mkdir, writeFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { x } from "tinyexec";
7
+ import * as semver from "semver";
8
+ import z$1 from "zod";
9
+ import { dump } from "js-yaml";
10
+ import { readFile as readFile$1 } from "fs/promises";
11
+ //#region src/utils/conventional-commit.ts
12
+ /**
13
+ * Conventional commit header per conventionalcommits.org and semantic-release defaults.
14
+ * @see https://www.conventionalcommits.org/en/v1.0.0/
15
+ */
16
+ const CONVENTIONAL_COMMIT_HEADER = /^(?<type>\w+)(?:\((?<scope>[^)]*)\))?(?<breaking>!)?: (?<title>.+)$/;
17
+ const BREAKING_CHANGE_FOOTER = /^BREAKING[ -]CHANGE:/m;
18
+ /** Parse conventional commits and resolve scopes against the workspace graph. */
19
+ function createConventionalCommitParser(graph) {
20
+ const byShortName = /* @__PURE__ */ new Map();
21
+ for (const pkg of graph.getPackages()) {
22
+ const slash = pkg.name.lastIndexOf("/");
23
+ const short = slash >= 0 ? pkg.name.slice(slash + 1) : pkg.name;
24
+ const names = byShortName.get(short);
25
+ if (names) names.push(pkg.name);
26
+ else byShortName.set(short, [pkg.name]);
27
+ }
28
+ function resolvePackages(scope) {
29
+ if (!scope) return [];
30
+ const packages = /* @__PURE__ */ new Set();
31
+ for (const item of scope.split(",")) {
32
+ const name = item.trim();
33
+ if (!name) continue;
34
+ const direct = graph.getByName(name);
35
+ if (direct.length > 0) {
36
+ for (const pkg of direct) packages.add(pkg.name);
37
+ continue;
38
+ }
39
+ const byShort = byShortName.get(name);
40
+ if (byShort) {
41
+ for (const pkgName of byShort) packages.add(pkgName);
42
+ continue;
43
+ }
44
+ packages.add(name);
45
+ }
46
+ return Array.from(packages);
47
+ }
48
+ return function parseConventionalCommit(subject, body = "") {
49
+ const match = CONVENTIONAL_COMMIT_HEADER.exec(subject.trim());
50
+ if (!match?.groups) return;
51
+ const { type, scope, breaking, title } = match.groups;
52
+ if (!type || !title) return;
53
+ const trimmedTitle = title.trim();
54
+ if (!trimmedTitle) return;
55
+ const trimmedScope = scope?.trim();
56
+ return {
57
+ type: type.toLowerCase(),
58
+ packages: resolvePackages(trimmedScope || void 0),
59
+ breaking: Boolean(breaking) || BREAKING_CHANGE_FOOTER.test(body),
60
+ title: trimmedTitle
61
+ };
62
+ };
63
+ }
64
+ /** Map releasable conventional commit types to semver bumps (semantic-release defaults). */
65
+ function conventionalCommitToBump(type, breaking) {
66
+ if (breaking) return "major";
67
+ switch (type) {
68
+ case "feat": return "minor";
69
+ case "fix":
70
+ case "perf":
71
+ case "revert": return "patch";
72
+ default: return;
73
+ }
74
+ }
75
+ //#endregion
76
+ //#region src/changelog/shared.ts
77
+ const changelogPackageConfigSchema = z$1.object({
78
+ type: bumpTypeSchema.optional(),
79
+ replay: z$1.array(z$1.string().min(1)).optional()
80
+ });
81
+ const changelogFrontmatterSchema = z$1.object({
82
+ subject: z$1.string().optional(),
83
+ packages: z$1.union([z$1.array(z$1.string()), z$1.record(z$1.string(), z$1.union([
84
+ bumpTypeSchema,
85
+ z$1.null(),
86
+ changelogPackageConfigSchema
87
+ ]))]).optional()
88
+ });
89
+ function renderChangelog(frontmatter, body) {
90
+ return [
91
+ "---",
92
+ dump(frontmatter).trim(),
93
+ "---",
94
+ "",
95
+ body.trim(),
96
+ ""
97
+ ].join("\n");
98
+ }
99
+ //#endregion
100
+ //#region src/changelog/generate.ts
101
+ async function generateChangelog(context, options = {}) {
102
+ const write = options.write ?? true;
103
+ const commits = await readConventionalCommits(context, options);
104
+ const groups = /* @__PURE__ */ new Map();
105
+ for (const commit of commits) {
106
+ const key = commit.packages.join("\0");
107
+ const group = groups.get(key);
108
+ if (group) group.push(commit);
109
+ else groups.set(key, [commit]);
110
+ }
111
+ const changelogs = Array.from(groups, ([key, changes], index) => {
112
+ const packageNames = key ? key.split("\0") : [];
113
+ let bumpType = "patch";
114
+ for (const change of changes) bumpType = maxBump(change.type, bumpType);
115
+ const packageBumpMap = Object.fromEntries(packageNames.map((name) => [name, bumpType]));
116
+ const packages = generateReplays(context.graph, packageBumpMap);
117
+ const content = renderChangelog({ packages }, changes.map((change) => {
118
+ const heading = "#".repeat(bumpDepth(change.type));
119
+ if (!change.body) return `${heading} ${change.title}`;
120
+ return `${heading} ${change.title}\n\n${change.body}`;
121
+ }).join("\n\n"));
122
+ return {
123
+ filename: changelogFilename(index),
124
+ content,
125
+ packages,
126
+ changes
127
+ };
128
+ });
129
+ if (write && changelogs.length > 0) {
130
+ await mkdir(context.changelogDir, { recursive: true });
131
+ await Promise.all(changelogs.map((entry) => writeFile(join(context.changelogDir, entry.filename), entry.content)));
132
+ }
133
+ return changelogs;
134
+ }
135
+ async function readConventionalCommits(context, options) {
136
+ const to = options.to ?? "HEAD";
137
+ const from = options.from ?? await latestTag(context.cwd);
138
+ const args = [
139
+ "log",
140
+ "--no-merges",
141
+ "--format=%H%x1f%s%x1f%b%x1e"
142
+ ];
143
+ if (from) args.push(`${from}..${to}`);
144
+ else if (to !== "HEAD") args.push(to);
145
+ const result = await x("git", args, { nodeOptions: { cwd: context.cwd } });
146
+ if (result.exitCode !== 0) throw execFailure(`Unable to read git commits from ${from ?? "start"} to ${to}`, result);
147
+ const parseCommit = createConventionalCommitParser(context.graph);
148
+ const changes = [];
149
+ for (const record of result.stdout.split("")) {
150
+ const [hash, subject, body = ""] = record.replace(/^\n+|\n+$/g, "").split("");
151
+ if (!hash || !subject) continue;
152
+ const parsed = parseCommit(subject, body);
153
+ if (!parsed) continue;
154
+ const bump = conventionalCommitToBump(parsed.type, parsed.breaking);
155
+ if (!bump) continue;
156
+ changes.push({
157
+ hash,
158
+ subject,
159
+ body: body.trim(),
160
+ packages: parsed.packages,
161
+ type: bump,
162
+ title: parsed.title.charAt(0).toUpperCase() + parsed.title.slice(1)
163
+ });
164
+ }
165
+ return changes;
166
+ }
167
+ async function latestTag(cwd) {
168
+ const result = await x("git", [
169
+ "describe",
170
+ "--tags",
171
+ "--abbrev=0"
172
+ ], { nodeOptions: { cwd } });
173
+ if (result.exitCode !== 0) return;
174
+ return result.stdout.trim() || void 0;
175
+ }
176
+ function generateReplays(graph, base) {
177
+ const packages = { ...base };
178
+ for (const [ref, type] of Object.entries(base)) {
179
+ const resolved = graph.getByName(ref);
180
+ for (const pkg of resolved) {
181
+ const plan = pkg.initPlan();
182
+ pkg.configurePlan(plan, graph.getPackageGroup(pkg.id));
183
+ const prerelease = semver.prerelease(pkg.version)?.[0];
184
+ const targetPrerelease = plan.prerelease;
185
+ if (targetPrerelease && !prerelease || targetPrerelease && prerelease && targetPrerelease === prerelease) packages[pkg.id] = {
186
+ type,
187
+ replay: [`exit prerelease: ${pkg.name}`]
188
+ };
189
+ }
190
+ }
191
+ return packages;
192
+ }
193
+ function changelogFilename(disambiguator = 0) {
194
+ const date = /* @__PURE__ */ new Date();
195
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}-${(Date.now() + disambiguator).toString(36)}.md`;
196
+ }
197
+ //#endregion
198
+ //#region src/plans/store.ts
199
+ const packagePlanStoreSchema = z$1.object({
200
+ type: bumpTypeSchema.optional(),
201
+ changelogIds: z$1.array(z$1.string()).optional(),
202
+ bumpReasons: z$1.array(z$1.string()).optional(),
203
+ npm: z$1.object({ distTag: z$1.string().optional() }).optional(),
204
+ publish: z$1.boolean()
205
+ });
206
+ /** the persisted plan data for actual publishing */
207
+ const planStoreSchema = jsonCodec(z$1.object({
208
+ id: z$1.string(),
209
+ createdAt: z$1.iso.datetime(),
210
+ version: z$1.literal("0.0.0").default("0.0.0"),
211
+ /** release note entries */
212
+ changelogs: z$1.record(z$1.string(), z$1.object({
213
+ filename: z$1.string(),
214
+ content: z$1.string()
215
+ })),
216
+ /** package id -> package info */
217
+ packages: z$1.record(z$1.string(), packagePlanStoreSchema)
218
+ }));
219
+ function createPlanStore(draft, context) {
220
+ const store = {
221
+ version: "0.0.0",
222
+ id: `tegami-${Date.now().toString(36)}`,
223
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
224
+ changelogs: {},
225
+ packages: {}
226
+ };
227
+ for (const entry of draft.getChangelogs()) store.changelogs[entry.id] = {
228
+ filename: entry.filename,
229
+ content: entry.getRawContent()
230
+ };
231
+ for (const pkg of context.graph.getPackages()) {
232
+ const plan = draft.getPackagePlan(pkg.id);
233
+ if (plan) store.packages[pkg.id] = {
234
+ publish: plan.publish ?? false,
235
+ type: plan.type,
236
+ npm: plan.npm,
237
+ changelogIds: plan.changelogs?.map((entry) => entry.id),
238
+ bumpReasons: plan.bumpReasons ? Array.from(plan.bumpReasons) : void 0
239
+ };
240
+ }
241
+ return planStoreSchema.encode(store);
242
+ }
243
+ async function readPlanStore(context) {
244
+ try {
245
+ return planStoreSchema.decode(await readFile$1(context.planPath, "utf8"));
246
+ } catch {}
247
+ }
248
+ //#endregion
249
+ //#region src/plans/checks.ts
250
+ async function publishPlanStatus(store, context) {
251
+ async function defaultStatus() {
252
+ try {
253
+ await Promise.all(Object.entries(store.packages).map(async ([id, plan]) => {
254
+ const pkg = context.graph.get(id);
255
+ if (!pkg || !plan.publish) return;
256
+ if (!await context.getRegistryClient(pkg).isPackagePublished(pkg)) throw "pending";
257
+ }));
258
+ return { state: "success" };
259
+ } catch (err) {
260
+ if (err === "pending") return { state: "pending" };
261
+ throw err;
262
+ }
263
+ }
264
+ let status = await defaultStatus();
265
+ for (const plugin of context.plugins) {
266
+ const resolved = await handlePluginError(plugin, "resolvePlanStatus", () => plugin.resolvePlanStatus?.call(context, status, { plan: store }));
267
+ if (resolved) status = resolved;
268
+ }
269
+ return status;
270
+ }
271
+ async function assertPublishPlanFinished(context) {
272
+ const store = await readPlanStore(context);
273
+ if (!store) return;
274
+ 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.`);
275
+ }
276
+ //#endregion
277
+ export { changelogFilename as a, changelogFrontmatterSchema as c, readPlanStore as i, renderChangelog as l, publishPlanStatus as n, generateChangelog as o, createPlanStore as r, generateReplays as s, assertPublishPlanFinished as t };
@@ -1,4 +1,4 @@
1
- import { k as DraftPlan, t as Awaitable, v as Tegami, w as PublishResult } from "../types-Diwnh8Tn.js";
1
+ import { k as DraftPlan, t as Awaitable, v as Tegami, w as PublishResult } from "../types-BXk2fMqa.js";
2
2
  import { Command } from "commander";
3
3
 
4
4
  //#region src/cli/index.d.ts