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.
package/dist/index.js CHANGED
@@ -1,167 +1,16 @@
1
- import { a as changelogFilename, i as readPlanStore, n as publishPlanStatus, r as createPlanStore, t as assertPublishPlanFinished } from "./checks-Bz3Rf2OX.js";
2
- import { n as handlePluginError, t as execFailure } from "./error-BaOQJtvf.js";
3
- import { i as maxBump } from "./semver-mWK2Khi2.js";
4
- import { t as PackageGraph } from "./graph-CUgwuRW5.js";
1
+ import { a as maxBump } from "./semver-C4vJ4SK8.js";
2
+ import { c as changelogFrontmatterSchema, i as readPlanStore, n as publishPlanStatus, o as generateChangelog, r as createPlanStore, t as assertPublishPlanFinished } from "./checks-Cwz-Ezkq.js";
3
+ import { r as handlePluginError } from "./error-DNy8R5ue.js";
4
+ import { t as PackageGraph } from "./graph-DLKPUXSD.js";
5
5
  import { cargo } from "./providers/cargo.js";
6
- import { t as bumpTypeSchema } from "./schemas-CurBAaW5.js";
7
6
  import { npm } from "./providers/npm.js";
8
7
  import { simpleGenerator } from "./generators/simple.js";
9
8
  import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
10
9
  import path, { basename, dirname, join } from "node:path";
11
- import { x } from "tinyexec";
12
- import { load } from "js-yaml";
13
- import z$1 from "zod";
10
+ import * as semver from "semver";
11
+ import { dump, load } from "js-yaml";
14
12
  import { fromMarkdown } from "mdast-util-from-markdown";
15
13
  import { toMarkdown } from "mdast-util-to-markdown";
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 = {}) {
83
- const commits = await readConventionalCommits(context, options);
84
- const groups = /* @__PURE__ */ new Map();
85
- for (const commit of commits) {
86
- const key = commit.packages.join("\0");
87
- const group = groups.get(key);
88
- if (group) group.push(commit);
89
- else groups.set(key, [commit]);
90
- }
91
- await mkdir(context.changelogDir, { recursive: true });
92
- return Promise.all(Array.from(groups, async ([key, changes], index) => {
93
- const packages = key ? key.split("\0") : [];
94
- const filename = changelogFilename(index);
95
- const path = join(context.changelogDir, filename);
96
- await writeFile(path, renderChangelog(packages, changes));
97
- return {
98
- filename,
99
- path,
100
- packages,
101
- changes: changes.length
102
- };
103
- }));
104
- }
105
- async function readConventionalCommits(context, options) {
106
- const to = options.to ?? "HEAD";
107
- const from = options.from ?? await latestTag(context.cwd);
108
- const args = [
109
- "log",
110
- "--no-merges",
111
- "--format=%H%x1f%s%x1f%b%x1e"
112
- ];
113
- if (from) args.push(`${from}..${to}`);
114
- else if (to !== "HEAD") args.push(to);
115
- const result = await x("git", args, { nodeOptions: { cwd: context.cwd } });
116
- if (result.exitCode !== 0) throw execFailure(`Unable to read git commits from ${from ?? "start"} to ${to}`, result);
117
- const parseCommit = createConventionalCommitParser(context.graph);
118
- const changes = [];
119
- for (const record of result.stdout.split("")) {
120
- const [hash, subject, body = ""] = record.replace(/^\n+|\n+$/g, "").split("");
121
- if (!hash || !subject) continue;
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
- });
134
- }
135
- return changes;
136
- }
137
- async function latestTag(cwd) {
138
- const result = await x("git", [
139
- "describe",
140
- "--tags",
141
- "--abbrev=0"
142
- ], { nodeOptions: { cwd } });
143
- if (result.exitCode !== 0) return;
144
- return result.stdout.trim() || void 0;
145
- }
146
- function renderChangelog(packages, changes) {
147
- return [
148
- "---",
149
- `packages: ${JSON.stringify(packages)}`,
150
- "---",
151
- "",
152
- changes.map(renderChange).join("\n\n"),
153
- ""
154
- ].join("\n");
155
- }
156
- function renderChange(change) {
157
- const heading = "#".repeat(change.type === "major" ? 1 : change.type === "minor" ? 2 : 3);
158
- if (!change.body) return `${heading} ${change.title}`;
159
- return `${heading} ${change.title}\n\n${change.body}`;
160
- }
161
- function titleCase(value) {
162
- return value.charAt(0).toUpperCase() + value.slice(1);
163
- }
164
- //#endregion
165
14
  //#region src/context.ts
166
15
  async function createTegamiContext(options = {}) {
167
16
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
@@ -226,6 +75,141 @@ function resolvePlugins(plugins = []) {
226
75
  return plugins.flat(Infinity).sort((a, b) => PLUGIN_ORDER[a.enforce ?? "default"] - PLUGIN_ORDER[b.enforce ?? "default"]);
227
76
  }
228
77
  //#endregion
78
+ //#region src/utils/frontmatter.ts
79
+ /**
80
+ * Inspired by https://github.com/jonschlinkert/gray-matter
81
+ */
82
+ const regex = /^---\r?\n(.+?)\r?\n---\r?\n?/s;
83
+ /**
84
+ * parse frontmatter, it supports only yaml format
85
+ */
86
+ function frontmatter(input) {
87
+ const output = {
88
+ matter: "",
89
+ data: {},
90
+ content: input
91
+ };
92
+ const match = regex.exec(input);
93
+ if (!match) return output;
94
+ output.matter = match[0];
95
+ output.content = input.slice(match[0].length);
96
+ output.data = load(match[1]) ?? {};
97
+ return output;
98
+ }
99
+ //#endregion
100
+ //#region src/changelog/parse.ts
101
+ async function getChangelogFiles(context) {
102
+ return (await readdir(context.changelogDir).catch(() => [])).filter((file) => file.endsWith(".md"));
103
+ }
104
+ async function readChangelogEntries(context) {
105
+ const dir = context.changelogDir;
106
+ const files = await getChangelogFiles(context);
107
+ return (await Promise.all(files.map(async (file) => {
108
+ const filePath = join(dir, file);
109
+ const content = await readFile(filePath, "utf8");
110
+ return parseChangelogFile(basename(filePath), content);
111
+ }))).filter((v) => v !== void 0);
112
+ }
113
+ /** Parse one changelog markdown file into release entries. */
114
+ function parseChangelogFile(filename, content) {
115
+ const parsed = frontmatter(content);
116
+ const { success, data } = changelogFrontmatterSchema.safeParse(parsed.data);
117
+ if (!success || !data.packages) return;
118
+ const tree = fromMarkdown(parsed.content);
119
+ let headingBump;
120
+ const packages = /* @__PURE__ */ new Map();
121
+ const sections = [];
122
+ for (const section of getHeadingSections(tree)) {
123
+ const sectionBumpType = headingToBump(section.heading.depth);
124
+ if (sectionBumpType) headingBump = headingBump ? maxBump(headingBump, sectionBumpType) : sectionBumpType;
125
+ sections.push({
126
+ depth: section.heading.depth,
127
+ title: sectionToMarkdown(section.heading.children),
128
+ content: sectionToMarkdown(section.children)
129
+ });
130
+ }
131
+ if (sections.length === 0) return;
132
+ if (Array.isArray(data.packages)) {
133
+ if (!headingBump) return;
134
+ for (const pkg of data.packages) packages.set(pkg, { type: headingBump });
135
+ } else for (const [k, v] of Object.entries(data.packages)) {
136
+ let config;
137
+ if (typeof v === "string") config = { type: v };
138
+ else if (v === null) config = { type: headingBump };
139
+ else config = v;
140
+ if (config.type || config.replay?.length) packages.set(k, config);
141
+ }
142
+ if (packages.size === 0) return;
143
+ const entry = {
144
+ id: filename,
145
+ filename,
146
+ subject: data.subject,
147
+ packages,
148
+ sections,
149
+ _raw_body: parsed.content,
150
+ getRawContent() {
151
+ return [
152
+ "---",
153
+ dump({
154
+ subject: this.subject,
155
+ packages: Object.fromEntries(this.packages.entries())
156
+ }).trim(),
157
+ "---",
158
+ "",
159
+ entry._raw_body.trim(),
160
+ ""
161
+ ].join("\n");
162
+ }
163
+ };
164
+ return entry;
165
+ }
166
+ function parseReplayCondition(condition) {
167
+ if (condition.startsWith("exit prerelease:")) return {
168
+ type: "on-exit-prerelease",
169
+ name: condition.slice(16).trimStart()
170
+ };
171
+ const idx = condition.lastIndexOf("@");
172
+ if (idx <= 0) return null;
173
+ return {
174
+ type: "on-version",
175
+ name: condition.slice(0, idx),
176
+ version: condition.slice(idx + 1)
177
+ };
178
+ }
179
+ function getHeadingSections(tree) {
180
+ const sections = [];
181
+ let current;
182
+ for (const child of tree.children) {
183
+ if (child.type === "heading") {
184
+ current = {
185
+ heading: child,
186
+ children: []
187
+ };
188
+ sections.push(current);
189
+ continue;
190
+ }
191
+ current?.children.push(child);
192
+ }
193
+ return sections;
194
+ }
195
+ function sectionToMarkdown(children) {
196
+ if (children.length === 0) return "";
197
+ return toMarkdown({
198
+ type: "root",
199
+ children
200
+ }, {
201
+ bullet: "-",
202
+ fence: "`"
203
+ }).trim();
204
+ }
205
+ function headingToBump(depth) {
206
+ switch (depth) {
207
+ case 1: return "major";
208
+ case 2: return "minor";
209
+ case 3: return "patch";
210
+ }
211
+ }
212
+ //#endregion
229
213
  //#region src/plans/policy.ts
230
214
  function groupPolicy({ graph }) {
231
215
  return {
@@ -263,23 +247,35 @@ var DraftPlan = class {
263
247
  return this.packages.get(id);
264
248
  }
265
249
  bumpPackage(pkg, { type, reason }) {
266
- let plan = this.packages.get(pkg.id);
267
- if (!plan) {
268
- plan = this.initPackagePlan(pkg);
269
- this.packages.set(pkg.id, plan);
270
- }
250
+ return this.dispatchPackage(pkg, (plan) => {
251
+ plan.type = plan.type ? maxBump(plan.type, type) : type;
252
+ if (reason) {
253
+ plan.bumpReasons ??= /* @__PURE__ */ new Set();
254
+ plan.bumpReasons.add(reason);
255
+ }
256
+ });
257
+ }
258
+ dispatchPackage(pkg, dispatch, onUpdate) {
259
+ const plan = this.getOrInitPackage(pkg);
271
260
  const prevVersion = plan.bumpVersion(pkg);
272
- plan.type = plan.type ? maxBump(plan.type, type) : type;
273
- if (reason) {
274
- plan.bumpReasons ??= /* @__PURE__ */ new Set();
275
- plan.bumpReasons.add(reason);
261
+ dispatch(plan);
262
+ if (prevVersion !== plan.bumpVersion(pkg)) {
263
+ onUpdate?.(plan);
264
+ for (const policy of this.policies) policy.onUpdate?.call(this, {
265
+ plan,
266
+ pkg
267
+ });
276
268
  }
277
- if (prevVersion !== plan.bumpVersion(pkg)) for (const policy of this.policies) policy.onUpdate?.call(this, {
278
- plan,
279
- pkg
280
- });
281
269
  return plan;
282
270
  }
271
+ getOrInitPackage(pkg) {
272
+ const existing = this.packages.get(pkg.id);
273
+ if (existing) return existing;
274
+ this.packages.set(pkg.id, pkg.initPlan());
275
+ return this.dispatchPackage(pkg, (plan) => pkg.configurePlan(plan, this.context.graph.getPackageGroup(pkg.id)), (plan) => {
276
+ (plan.bumpReasons ??= /* @__PURE__ */ new Set()).add("align with script-level configs");
277
+ });
278
+ }
283
279
  hasPending() {
284
280
  const { graph } = this.context;
285
281
  for (const [id, plan] of this.packages) {
@@ -298,37 +294,36 @@ var DraftPlan = class {
298
294
  this.changelogs.set(entry.id, entry);
299
295
  const { graph } = this.context;
300
296
  const groupPackages = /* @__PURE__ */ new Map();
301
- for (const [name, bumpType] of entry.packages) for (const pkg of graph.getByName(name)) groupPackages.set(pkg, bumpType);
302
- for (const [pkg, bumpType] of groupPackages) {
303
- const plan = this.bumpPackage(pkg, { type: bumpType });
304
- plan.changelogs ??= [];
305
- plan.changelogs.push(entry);
297
+ for (const [name, config] of entry.packages) {
298
+ if (!config.type) continue;
299
+ for (const pkg of graph.getByName(name)) groupPackages.set(pkg, config.type);
306
300
  }
301
+ for (const [pkg, bumpType] of groupPackages) attachChangelog(this.bumpPackage(pkg, { type: bumpType }), entry);
307
302
  }
308
303
  deleteChangelog(id) {
309
304
  return this.changelogs.delete(id);
310
305
  }
311
- initPackagePlan(pkg) {
312
- const context = this.context;
313
- const plan = pkg.onPlan(context);
314
- const group = context.graph.getPackageGroup(pkg.id);
315
- if (group) plan.prerelease ??= group.options.prerelease;
316
- return plan;
317
- }
318
306
  /** Apply the publish plan: update package versions, write the plan file, and consume changelog files. */
319
307
  async applyPlan() {
320
308
  if (this.#applied) throw new Error("This draft has already applied a publish plan.");
321
309
  await assertPublishPlanFinished(this.context);
310
+ const { graph } = this.context;
322
311
  this.#applied = true;
312
+ const snapshots = /* @__PURE__ */ new Map();
313
+ for (const pkg of graph.getPackages()) snapshots.set(pkg.id, { version: pkg.version });
323
314
  for (const plugin of this.context.plugins) await handlePluginError(plugin, "applyPlan", () => plugin.applyPlan?.call(this.context, this));
324
- const { graph } = this.context;
315
+ const updatedChangelogs = this.applyReplays(snapshots);
325
316
  const writes = [];
326
317
  for (const [id, packagePlan] of this.packages) {
327
318
  const pkg = graph.get(id);
328
319
  if (!pkg) continue;
329
320
  writes.push(this.appendChangelog(pkg, packagePlan));
330
321
  }
331
- for (const entry of this.changelogs.values()) writes.push(rm(path.join(this.context.changelogDir, entry.filename), { force: true }));
322
+ for (const entry of this.changelogs.values()) {
323
+ const updated = updatedChangelogs.get(entry.id);
324
+ const filePath = path.join(this.context.changelogDir, entry.filename);
325
+ writes.push(updated ? writeFile(filePath, updated.getRawContent()) : rm(filePath, { force: true }));
326
+ }
332
327
  await Promise.all(writes);
333
328
  await mkdir(dirname(this.context.planPath), { recursive: true });
334
329
  await writeFile(this.context.planPath, createPlanStore(this, this.context));
@@ -343,6 +338,46 @@ var DraftPlan = class {
343
338
  canApply() {
344
339
  return !this.#applied;
345
340
  }
341
+ /** Attach replaying changelog entries to packages (already bumped), and return the updated changelog entries. */
342
+ applyReplays(snapshots) {
343
+ const updated = /* @__PURE__ */ new Map();
344
+ const { graph } = this.context;
345
+ const isMatch = (condition) => {
346
+ if (condition.type === "on-exit-prerelease") return graph.getByName(condition.name).some((pkg) => {
347
+ const previous = snapshots.get(pkg.id);
348
+ return previous && semver.inc(previous.version, "release") === pkg.version;
349
+ });
350
+ return graph.getByName(condition.name).some((pkg) => pkg.version === condition.version);
351
+ };
352
+ for (const entry of this.changelogs.values()) {
353
+ const updatedPackages = /* @__PURE__ */ new Map();
354
+ const matchedNames = /* @__PURE__ */ new Set();
355
+ for (const [name, config] of entry.packages) {
356
+ if (!config.replay || config.replay.length === 0) continue;
357
+ const replay = config.replay.filter((item) => {
358
+ const condition = parseReplayCondition(item);
359
+ if (condition && isMatch(condition)) {
360
+ matchedNames.add(name);
361
+ return false;
362
+ }
363
+ return true;
364
+ });
365
+ if (replay.length === 0) continue;
366
+ const updatedConfig = {
367
+ ...config,
368
+ replay
369
+ };
370
+ delete updatedConfig.type;
371
+ updatedPackages.set(name, updatedConfig);
372
+ }
373
+ if (updatedPackages.size > 0) updated.set(entry.id, {
374
+ ...entry,
375
+ packages: updatedPackages
376
+ });
377
+ for (const name of matchedNames) for (const pkg of graph.getByName(name)) attachChangelog(this.getOrInitPackage(pkg), entry);
378
+ }
379
+ return updated;
380
+ }
346
381
  async appendChangelog(pkg, plan) {
347
382
  if (!plan.changelogs || plan.changelogs.length === 0) return;
348
383
  const { generator = simpleGenerator() } = this.context.options;
@@ -350,10 +385,9 @@ var DraftPlan = class {
350
385
  packageId: pkg.id,
351
386
  packageName: pkg.name,
352
387
  version: pkg.version,
353
- npm: plan.npm,
354
388
  plan,
355
389
  changelogs: plan.changelogs,
356
- _draft: this
390
+ unstable_draft: this
357
391
  });
358
392
  const path = join(pkg.path, "CHANGELOG.md");
359
393
  const existing = await readFile(path, "utf8").catch(() => "");
@@ -383,110 +417,14 @@ async function createDraftPlan(changelogs, context) {
383
417
  const result = await handlePluginError(plugin, "initPlan", () => plugin.initPlan?.call(context, draft));
384
418
  if (result) draft = result;
385
419
  }
420
+ for (const pkg of context.graph.getPackages()) draft.getOrInitPackage(pkg);
386
421
  for (const entry of changelogs) draft.addChangelog(entry);
387
422
  return draft;
388
423
  }
389
- //#endregion
390
- //#region src/utils/frontmatter.ts
391
- /**
392
- * Inspired by https://github.com/jonschlinkert/gray-matter
393
- */
394
- const regex = /^---\r?\n(.+?)\r?\n---\r?\n?/s;
395
- /**
396
- * parse frontmatter, it supports only yaml format
397
- */
398
- function frontmatter(input) {
399
- const output = {
400
- matter: "",
401
- data: {},
402
- content: input
403
- };
404
- const match = regex.exec(input);
405
- if (!match) return output;
406
- output.matter = match[0];
407
- output.content = input.slice(match[0].length);
408
- output.data = load(match[1]) ?? {};
409
- return output;
410
- }
411
- //#endregion
412
- //#region src/changelog/parse.ts
413
- const changelogFrontmatterSchema = z$1.object({
414
- subject: z$1.string().optional(),
415
- packages: z$1.union([z$1.array(z$1.string()), z$1.record(z$1.string(), bumpTypeSchema.or(z$1.null()))]).optional()
416
- });
417
- async function getChangelogFiles(context) {
418
- return (await readdir(context.changelogDir).catch(() => [])).filter((file) => file.endsWith(".md"));
419
- }
420
- async function readChangelogEntries(context) {
421
- const dir = context.changelogDir;
422
- const files = await getChangelogFiles(context);
423
- return (await Promise.all(files.map(async (file) => {
424
- const filePath = join(dir, file);
425
- return parseChangelogFile(filePath, await readFile(filePath, "utf8"));
426
- }))).filter((v) => v !== void 0);
427
- }
428
- /** Parse one changelog markdown file into release entries. */
429
- function parseChangelogFile(file, content) {
430
- const parsed = frontmatter(content);
431
- const data = changelogFrontmatterSchema.parse(parsed.data);
432
- if (!data.packages) return;
433
- const tree = fromMarkdown(parsed.content);
434
- let bumpType;
435
- const packages = /* @__PURE__ */ new Map();
436
- const sections = [];
437
- const filename = basename(file);
438
- for (const section of getHeadingSections(tree)) {
439
- const sectionBumpType = headingToBump(section.heading.depth);
440
- if (sectionBumpType) bumpType = bumpType ? maxBump(bumpType, sectionBumpType) : sectionBumpType;
441
- sections.push({
442
- depth: section.heading.depth,
443
- title: sectionToMarkdown(section.heading.children),
444
- content: sectionToMarkdown(section.children)
445
- });
446
- }
447
- if (!bumpType) return;
448
- if (Array.isArray(data.packages)) for (const pkg of data.packages) packages.set(pkg, bumpType);
449
- else for (const [k, v] of Object.entries(data.packages)) packages.set(k, v ?? bumpType);
450
- return {
451
- id: filename,
452
- filename,
453
- subject: data.subject,
454
- packages,
455
- sections
456
- };
457
- }
458
- function getHeadingSections(tree) {
459
- const sections = [];
460
- let current;
461
- for (const child of tree.children) {
462
- if (child.type === "heading") {
463
- current = {
464
- heading: child,
465
- children: []
466
- };
467
- sections.push(current);
468
- continue;
469
- }
470
- current?.children.push(child);
471
- }
472
- return sections;
473
- }
474
- function sectionToMarkdown(children) {
475
- if (children.length === 0) return "";
476
- return toMarkdown({
477
- type: "root",
478
- children
479
- }, {
480
- bullet: "-",
481
- fence: "`"
482
- }).trim();
483
- }
484
- function headingToBump(depth) {
485
- switch (depth) {
486
- case 1: return "major";
487
- case 2: return "minor";
488
- case 3: return "patch";
489
- }
424
+ function attachChangelog(plan, entry) {
425
+ if (plan.changelogs?.some((item) => item.id === entry.id)) return;
426
+ plan.changelogs ??= [];
427
+ plan.changelogs.push(entry);
490
428
  }
491
429
  //#endregion
492
430
  //#region src/publish.ts
@@ -529,18 +467,20 @@ async function publishFromPlan(context, store, options) {
529
467
  const packages = [];
530
468
  if ((await publishPlanStatus(store, context)).state !== "pending") return { state: "skipped" };
531
469
  const orderedIds = await resolvePublishTargets(context, store);
470
+ const parsedChangelogs = /* @__PURE__ */ new Map();
471
+ for (const [id, entry] of Object.entries(store.changelogs)) {
472
+ const parsed = parseChangelogFile(entry.filename, entry.content);
473
+ if (!parsed) continue;
474
+ parsedChangelogs.set(id, parsed);
475
+ }
532
476
  for (const id of orderedIds) {
533
477
  const plan = store.packages[id];
534
478
  const pkg = context.graph.get(id);
535
479
  const registryClient = context.getRegistryClient(pkg);
536
480
  const changelogs = [];
537
481
  for (const id of plan.changelogIds ?? []) {
538
- const entry = store.changelogs[id];
539
- if (entry) changelogs.push({
540
- ...entry,
541
- packages: new Map(Object.entries(entry.packages)),
542
- id
543
- });
482
+ const entry = parsedChangelogs.get(id);
483
+ if (entry) changelogs.push(entry);
544
484
  }
545
485
  if (!dryRun) {
546
486
  if (await registryClient.isPackagePublished(pkg)) {
@@ -643,7 +583,6 @@ function tegami(options = {}) {
643
583
  },
644
584
  async publish(publishOptions = {}) {
645
585
  const context = await $context;
646
- if ((await getChangelogFiles(context)).length > 0) return { state: "skipped" };
647
586
  const parsed = await readPlanStore(context);
648
587
  if (!parsed) return { state: "skipped" };
649
588
  let result = await publishFromPlan(context, parsed, publishOptions);
@@ -1,4 +1,4 @@
1
- import { c as TegamiPlugin } from "../types-Diwnh8Tn.js";
1
+ import { c as TegamiPlugin } from "../types-BXk2fMqa.js";
2
2
 
3
3
  //#region src/plugins/git.d.ts
4
4
  interface GitPluginOptions {
@@ -1,5 +1,4 @@
1
- import { t as execFailure } from "../error-BaOQJtvf.js";
2
- import { t as isCI } from "../constants-B9qjNfvr.js";
1
+ import { a as isCI, n as execFailure } from "../error-DNy8R5ue.js";
3
2
  import { x } from "tinyexec";
4
3
  //#region src/plugins/git.ts
5
4
  /**
@@ -13,8 +12,7 @@ function git(options = {}) {
13
12
  function resolveGitTag(context, result) {
14
13
  const { graph } = context;
15
14
  const pkg = graph.get(result.id);
16
- if (!pkg) return `${result.name}@${result.version}`;
17
- const group = graph.getPackageGroup(pkg.id);
15
+ const group = pkg && graph.getPackageGroup(pkg.id);
18
16
  if (group?.options.syncGitTag) return `${group.name}@${result.version}`;
19
17
  return `${result.name}@${result.version}`;
20
18
  }
@@ -39,9 +37,10 @@ function git(options = {}) {
39
37
  } },
40
38
  async afterPublishAll(result) {
41
39
  const { cwd, publishOptions: { dryRun = false } } = this;
42
- if (dryRun || !createTags || result.state !== "created") return result;
40
+ if (dryRun || !createTags || result.state === "skipped") return result;
43
41
  const pendingTags = /* @__PURE__ */ new Set();
44
42
  for (const pkg of result.packages) {
43
+ if (pkg.state !== "success") continue;
45
44
  pkg.gitTag = resolveGitTag(this, pkg);
46
45
  pendingTags.add(pkg.gitTag);
47
46
  }
@@ -1,4 +1,4 @@
1
- import { S as PackagePublishResult, T as TegamiContext, c as TegamiPlugin, k as DraftPlan, t as Awaitable } from "../types-Diwnh8Tn.js";
1
+ import { S as PackagePublishResult, T as TegamiContext, c as TegamiPlugin, k as DraftPlan, t as Awaitable } from "../types-BXk2fMqa.js";
2
2
  import { GitPluginOptions } from "./git.js";
3
3
 
4
4
  //#region src/plugins/github.d.ts
@@ -42,6 +42,12 @@ interface GitHubPluginOptions extends GitPluginOptions {
42
42
  repo?: string;
43
43
  /** Optional GitHub token for Git & GitHub operations. */
44
44
  token?: string;
45
+ /**
46
+ * Create GitHub release immediately after successful publish, without waiting for others.
47
+ *
48
+ * @default false
49
+ */
50
+ eagerRelease?: boolean;
45
51
  /** Override release details for a single package, return `false` to skip. */
46
52
  onCreateRelease?: (this: TegamiContext, result: PackagePublishResult) => Awaitable<GithubRelease | false>;
47
53
  /** Override release details when multiple packages share a git tag, return `false` to skip. */