tegami 1.0.0-beta.1 → 1.0.0-beta.3

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.
@@ -70,6 +70,12 @@ async function createPullRequest(repo, options) {
70
70
  })
71
71
  })).ok) throw new Error("Failed to create the version pull request.");
72
72
  }
73
+ async function listPullRequestsForCommit(repo, commitSha, token) {
74
+ const { owner, repo: name } = parseGitHubRepo(repo);
75
+ const response = await githubRequest(`/repos/${owner}/${name}/commits/${commitSha}/pulls`, token);
76
+ if (!response.ok) throw new Error(`Failed to list pull requests for commit ${commitSha.slice(0, 7)}.`);
77
+ return await response.json();
78
+ }
73
79
  async function getPullRequest(repo, number, token) {
74
80
  const { owner, repo: name } = parseGitHubRepo(repo);
75
81
  const response = await githubRequest(`/repos/${owner}/${name}/pulls/${number}`, token);
@@ -113,4 +119,4 @@ async function createIssueComment(repo, issueNumber, body, token) {
113
119
  })).ok) throw new Error("Failed to create pull request comment.");
114
120
  }
115
121
  //#endregion
116
- export { findOpenPullRequest as a, updateIssueComment as c, findIssueCommentByPrefix as i, updatePullRequest as l, createPullRequest as n, getPullRequest as o, createRelease as r, releaseExistsByTag as s, createIssueComment as t };
122
+ export { findOpenPullRequest as a, releaseExistsByTag as c, findIssueCommentByPrefix as i, updateIssueComment as l, createPullRequest as n, getPullRequest as o, createRelease as r, listPullRequestsForCommit as s, createIssueComment as t, updatePullRequest as u };
@@ -1,5 +1,5 @@
1
- import { p as PublishPlan, t as Awaitable, w as Draft } from "../types-BbDtPn8I.js";
2
- import { n as Tegami } from "../index-CH40idD8.js";
1
+ import { p as PublishPlan, t as Awaitable, w as Draft } from "../types-BHT_9k9p.js";
2
+ import { n as Tegami } from "../index-syNgzQTi.js";
3
3
  import { Command } from "commander";
4
4
 
5
5
  //#region src/cli/index.d.ts
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { r as formatNpmDistTag } from "../semver-EKJ8yK5U.js";
2
- import { i as renderChangelog, n as generateFromCommits, t as changelogFilename } from "../generate-BqkwhjRy.js";
3
- import { a as isCI, n as execFailure, r as handlePluginError, t as CancelledError } from "../error-DNy8R5ue.js";
4
- import { c as updateIssueComment, i as findIssueCommentByPrefix, o as getPullRequest, t as createIssueComment } from "../api-D-jf_8xY.js";
2
+ import { i as renderChangelog, n as generateFromCommits, t as changelogFilename } from "../generate-BfYdNTFi.js";
3
+ import { n as execFailure, o as isCI, r as handlePluginError, t as CancelledError } from "../error-We7chQVJ.js";
4
+ import { i as findIssueCommentByPrefix, l as updateIssueComment, o as getPullRequest, t as createIssueComment } from "../api-CGfXmmEM.js";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
6
  import path, { basename, join, relative } from "node:path";
7
7
  import { x } from "tinyexec";
@@ -543,7 +543,7 @@ async function publishPackages(tegami, options) {
543
543
  const lines = [];
544
544
  let hasFailed = false;
545
545
  for (const [id, packagePlan] of plan.packages) {
546
- if (!packagePlan.updated) continue;
546
+ if (!packagePlan.preflight.shouldPublish) continue;
547
547
  const result = packagePlan.publishResult;
548
548
  const pkg = context.graph.get(id);
549
549
  if (result.type === "failed") hasFailed = true;
@@ -1,5 +1,36 @@
1
- //#region src/utils/constants.ts
1
+ //#region src/utils/common.ts
2
2
  const isCI = () => Boolean(process.env.CI);
3
+ async function somePromise(promises, fn) {
4
+ return new Promise((res, reject) => {
5
+ const n = promises.length;
6
+ if (n === 0) res(false);
7
+ let i = 0;
8
+ for (const promise of promises) {
9
+ if (promise instanceof Promise) {
10
+ promise.then((v) => {
11
+ if (fn(v)) return res(true);
12
+ i++;
13
+ if (i === n) res(false);
14
+ }).catch(reject);
15
+ continue;
16
+ }
17
+ if (fn(promise)) return res(true);
18
+ i++;
19
+ if (i === n) res(false);
20
+ }
21
+ });
22
+ }
23
+ function cached(cacheKey, fn, cacheMap = /* @__PURE__ */ new Map()) {
24
+ return (...args) => {
25
+ const key = cacheKey(...args);
26
+ let out = cacheMap.get(key);
27
+ if (!out) {
28
+ out = fn(...args);
29
+ cacheMap.set(key, out);
30
+ }
31
+ return out;
32
+ };
33
+ }
3
34
  //#endregion
4
35
  //#region src/utils/error.ts
5
36
  var CancelledError = class extends Error {
@@ -42,4 +73,4 @@ async function handlePluginError(plugin, hookName, callback) {
42
73
  }
43
74
  }
44
75
  //#endregion
45
- export { isCI as a, isNodeError as i, execFailure as n, handlePluginError as r, CancelledError as t };
76
+ export { cached as a, isNodeError as i, execFailure as n, isCI as o, handlePluginError as r, somePromise as s, CancelledError as t };
@@ -1,5 +1,5 @@
1
1
  import { a as maxBump, t as bumpDepth } from "./semver-EKJ8yK5U.js";
2
- import { n as execFailure } from "./error-DNy8R5ue.js";
2
+ import { n as execFailure } from "./error-We7chQVJ.js";
3
3
  import { x } from "tinyexec";
4
4
  import z from "zod";
5
5
  import { dump } from "js-yaml";
@@ -1,4 +1,4 @@
1
- import { r as LogGenerator } from "../types-BbDtPn8I.js";
1
+ import { r as LogGenerator } from "../types-BHT_9k9p.js";
2
2
 
3
3
  //#region src/generators/simple.d.ts
4
4
  declare function simpleGenerator(): LogGenerator;
@@ -1,4 +1,4 @@
1
- import { D as ChangelogPackageConfig, O as BumpType, b as TegamiContext, f as PublishOptions, o as TegamiOptions, p as PublishPlan, w as Draft, x as PackageGraph } from "./types-BbDtPn8I.js";
1
+ import { D as ChangelogPackageConfig, O as BumpType, b as TegamiContext, f as PublishOptions, o as TegamiOptions, p as PublishPlan, w as Draft, x as PackageGraph } from "./types-BHT_9k9p.js";
2
2
 
3
3
  //#region src/changelog/generate.d.ts
4
4
  interface GenerateFromCommitsOptions {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { C as WorkspacePackage, E as PackageDraft, S as PackageGroup, T as DraftPolicy, a as PublishPreflight, c as TegamiPluginOption, d as PackagePublishResult, f as PublishOptions, i as PackageOptions, l as PublishLock, n as GroupOptions, o as TegamiOptions, p as PublishPlan, r as LogGenerator, s as TegamiPlugin, u as PackagePublishPlan, w as Draft, x as PackageGraph } from "./types-BbDtPn8I.js";
2
- import { i as CommitChangelog, n as Tegami, r as tegami, t as GenerateChangelogOptions } from "./index-CH40idD8.js";
1
+ import { C as WorkspacePackage, E as PackageDraft, S as PackageGroup, T as DraftPolicy, a as PublishPreflight, c as TegamiPluginOption, d as PackagePublishResult, f as PublishOptions, i as PackageOptions, l as PublishLock, n as GroupOptions, o as TegamiOptions, p as PublishPlan, r as LogGenerator, s as TegamiPlugin, u as PackagePublishPlan, w as Draft, x as PackageGraph } from "./types-BHT_9k9p.js";
2
+ import { i as CommitChangelog, n as Tegami, r as tegami, t as GenerateChangelogOptions } from "./index-syNgzQTi.js";
3
3
  export { type CommitChangelog, type Draft, type DraftPolicy, GenerateChangelogOptions, type GroupOptions, type LogGenerator, type PackageDraft, type PackageGraph, type PackageGroup, type PackageOptions, type PackagePublishPlan, type PackagePublishResult, type PublishLock, type PublishOptions, type PublishPlan, type PublishPreflight, Tegami, type TegamiOptions, type TegamiPlugin, type TegamiPluginOption, type WorkspacePackage, tegami };
package/dist/index.js CHANGED
@@ -1,17 +1,15 @@
1
1
  import { a as maxBump } from "./semver-EKJ8yK5U.js";
2
- import { n as generateFromCommits, r as changelogFrontmatterSchema } from "./generate-BqkwhjRy.js";
3
- import { r as handlePluginError } from "./error-DNy8R5ue.js";
2
+ import { n as generateFromCommits, r as changelogFrontmatterSchema } from "./generate-BfYdNTFi.js";
3
+ import { r as handlePluginError, s as somePromise } from "./error-We7chQVJ.js";
4
4
  import { t as PackageGraph } from "./graph-gThXu8Cz.js";
5
5
  import { cargo } from "./providers/cargo.js";
6
- import { n as npm } from "./npm-DqGyAsdo.js";
6
+ import { n as npm } from "./npm-BzhclYfR.js";
7
7
  import { simpleGenerator } from "./generators/simple.js";
8
8
  import fs, { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
9
9
  import path, { basename, dirname, join } from "node:path";
10
10
  import * as semver$1 from "semver";
11
11
  import z from "zod";
12
12
  import { dump, load, visit } from "js-yaml";
13
- import { fromMarkdown } from "mdast-util-from-markdown";
14
- import { toMarkdown } from "mdast-util-to-markdown";
15
13
  //#region src/context.ts
16
14
  async function createTegamiContext(options = {}) {
17
15
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
@@ -103,17 +101,16 @@ function parseChangelogFile(filename, content) {
103
101
  const parsed = frontmatter(content);
104
102
  const { success, data } = changelogFrontmatterSchema.safeParse(parsed.data);
105
103
  if (!success || !data.packages) return;
106
- const tree = fromMarkdown(parsed.content);
107
104
  let headingBump;
108
105
  const packages = /* @__PURE__ */ new Map();
109
106
  const sections = [];
110
- for (const section of getHeadingSections(tree)) {
111
- const sectionBumpType = headingToBump(section.heading.depth);
107
+ for (const section of parseMarkdownSections(parsed.content)) {
108
+ const sectionBumpType = headingToBump(section.depth);
112
109
  if (sectionBumpType) headingBump = headingBump ? maxBump(headingBump, sectionBumpType) : sectionBumpType;
113
110
  sections.push({
114
- depth: section.heading.depth,
115
- title: sectionToMarkdown(section.heading.children),
116
- content: sectionToMarkdown(section.children)
111
+ depth: section.depth,
112
+ title: section.title,
113
+ content: section.content
117
114
  });
118
115
  }
119
116
  if (sections.length === 0) return;
@@ -164,31 +161,51 @@ function parseReplayCondition(condition) {
164
161
  version: condition.slice(idx + 1)
165
162
  };
166
163
  }
167
- function getHeadingSections(tree) {
164
+ function parseMarkdownSections(markdown) {
168
165
  const sections = [];
166
+ const lines = markdown.split(/\r\n|\r|\n/);
169
167
  let current;
170
- for (const child of tree.children) {
171
- if (child.type === "heading") {
172
- current = {
173
- heading: child,
174
- children: []
175
- };
176
- sections.push(current);
177
- continue;
168
+ let fence;
169
+ for (const line of lines) {
170
+ const fenceMarker = line.match(/^ {0,3}(`{3,}|~{3,})/);
171
+ if (fenceMarker) {
172
+ const marker = fenceMarker[1];
173
+ if (!fence) fence = marker;
174
+ else if (marker[0] === fence[0] && marker.length >= fence.length) fence = void 0;
175
+ }
176
+ if (!fence) {
177
+ const heading = parseHeading(line);
178
+ if (heading) {
179
+ if (current) sections.push(toMarkdownSection(current));
180
+ current = {
181
+ ...heading,
182
+ contentLines: []
183
+ };
184
+ continue;
185
+ }
178
186
  }
179
- current?.children.push(child);
187
+ current?.contentLines.push(line);
180
188
  }
189
+ if (current) sections.push(toMarkdownSection(current));
181
190
  return sections;
182
191
  }
183
- function sectionToMarkdown(children) {
184
- if (children.length === 0) return "";
185
- return toMarkdown({
186
- type: "root",
187
- children
188
- }, {
189
- bullet: "-",
190
- fence: "`"
191
- }).trim();
192
+ function parseHeading(line) {
193
+ const match = line.match(/^ {0,3}(#{1,6})(?:[ \t]+|$)(.*)$/);
194
+ if (!match) return;
195
+ return {
196
+ depth: match[1].length,
197
+ title: stripClosingHeadingSequence(match[2]).trim()
198
+ };
199
+ }
200
+ function stripClosingHeadingSequence(value) {
201
+ return value.replace(/[ \t]+#{1,}[ \t]*$/, "");
202
+ }
203
+ function toMarkdownSection(section) {
204
+ return {
205
+ depth: section.depth,
206
+ title: section.title,
207
+ content: section.contentLines.join("\n").trim()
208
+ };
192
209
  }
193
210
  function headingToBump(depth) {
194
211
  switch (depth) {
@@ -543,7 +560,7 @@ function resolvePublishTargets(plan) {
543
560
  const ordered = [];
544
561
  function scan(id, stack = /* @__PURE__ */ new Set()) {
545
562
  const preflight = plan.packages.get(id)?.preflight;
546
- if (!preflight || !preflight.publish) return;
563
+ if (!preflight || !preflight.shouldPublish) return;
547
564
  if (stack.has(id)) throw new Error(`circular reference of deps: ${[...stack, id].join(" -> ")}`);
548
565
  if (ordered.includes(id)) return;
549
566
  if (preflight.wait) {
@@ -596,25 +613,19 @@ async function runPublishPlan(context, plan) {
596
613
  async function runPreflights(context, plan) {
597
614
  const promises = [];
598
615
  const runPreflight = async (pkg) => {
599
- const out = { publish: true };
600
616
  for (const plugin of context.plugins) {
601
617
  const res = await handlePluginError(plugin, "publishPreflight", () => plugin.publishPreflight?.call(context, {
602
618
  pkg,
603
619
  plan
604
620
  }));
605
- if (res === void 0) continue;
606
- out.publish = res.publish;
607
- if (res.wait) {
608
- out.wait ??= [];
609
- out.wait.push(...res.wait);
610
- }
621
+ if (res) return res;
611
622
  }
612
- return out;
623
+ return { shouldPublish: false };
613
624
  };
614
625
  for (const [id, packagePlan] of plan.packages) {
615
626
  const pkg = context.graph.get(id);
616
627
  if (!packagePlan.updated) {
617
- packagePlan.preflight = { publish: false };
628
+ packagePlan.preflight = { shouldPublish: false };
618
629
  continue;
619
630
  }
620
631
  promises.push(runPreflight(pkg).then((preflight) => {
@@ -624,19 +635,12 @@ async function runPreflights(context, plan) {
624
635
  await Promise.all(promises);
625
636
  }
626
637
  async function publishPlanStatus(plan, context) {
627
- for (const pkg of plan.packages.values()) {
628
- if (!pkg.preflight) throw new Error("Should perform preflight before checking plan status.");
629
- if (pkg.preflight.publish) return "pending";
630
- }
631
- try {
632
- await Promise.all(context.plugins.map(async (plugin) => {
633
- if (await handlePluginError(plugin, "resolvePlanStatus", () => plugin.resolvePlanStatus?.call(context, { plan })) === "pending") throw "pending";
634
- }));
635
- return "success";
636
- } catch (e) {
637
- if (e === "pending") return "pending";
638
- throw e;
638
+ for (const plugin of context.plugins) {
639
+ const status = await handlePluginError(plugin, "resolvePlanStatus", () => plugin.resolvePlanStatus?.call(context, { plan }));
640
+ if (Array.isArray(status) && await somePromise(status, (v) => v === "pending")) return "pending";
641
+ if (status === "pending") return "pending";
639
642
  }
643
+ return "success";
640
644
  }
641
645
  //#endregion
642
646
  //#region src/index.ts
@@ -1,4 +1,4 @@
1
- import { i as isNodeError, n as execFailure } from "./error-DNy8R5ue.js";
1
+ import { i as isNodeError, n as execFailure } from "./error-We7chQVJ.js";
2
2
  import { n as WorkspacePackage } from "./graph-gThXu8Cz.js";
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
@@ -139,7 +139,15 @@ function npm({ client: defaultClient, onBreakPeerDep = "set", updateLockFile = t
139
139
  },
140
140
  async publishPreflight({ pkg }) {
141
141
  if (!(pkg instanceof NpmPackage)) return;
142
- return { publish: pkg.version !== void 0 && pkg.manifest.private !== true && !await isPackagePublished(pkg.name, pkg.version, pkg.manifest.publishConfig?.registry) };
142
+ return { shouldPublish: pkg.version !== void 0 && pkg.manifest.private !== true };
143
+ },
144
+ resolvePlanStatus({ plan }) {
145
+ return Array.from(plan.packages, async ([id, { preflight }]) => {
146
+ if (!preflight.shouldPublish) return;
147
+ const pkg = this.graph.get(id);
148
+ if (!(pkg instanceof NpmPackage) || !pkg.version) return;
149
+ if (!await isPackagePublished(pkg.name, pkg.version, pkg.manifest.publishConfig?.registry)) return "pending";
150
+ });
143
151
  },
144
152
  initPublishLock({ lock, draft }) {
145
153
  for (const [id, pkg] of draft.getPackageDrafts()) {
@@ -190,7 +198,7 @@ function npm({ client: defaultClient, onBreakPeerDep = "set", updateLockFile = t
190
198
  let updatedRange;
191
199
  const isPeer = field === "peerDependencies";
192
200
  if (isPeer && onBreakPeerDep === "ignore") continue;
193
- else if (isPeer && onBreakPeerDep === "set") updatedRange = spec.linked.version;
201
+ if (isPeer && onBreakPeerDep === "set") updatedRange = spec.linked.version;
194
202
  else if (isPeer && onBreakPeerDep === "error") throw new Error(`[Tegami] the version of "${spec.linked.name}" is beyond its peer dependency constraint "${v}" in package "${pkg.name}", please update the constraint to satisfy.`);
195
203
  else if (spec.range.startsWith("^")) updatedRange = `^${spec.linked.version}`;
196
204
  else if (spec.range.startsWith("~")) updatedRange = `~${spec.linked.version}`;
@@ -264,6 +272,7 @@ function depsPolicy(context, getBumpDepType) {
264
272
  };
265
273
  }
266
274
  async function publish(client, pkg, distTag) {
275
+ if (!pkg.version || await isPackagePublished(pkg.name, pkg.version, pkg.manifest.publishConfig?.registry)) return { type: "skipped" };
267
276
  if (client === "bun") {
268
277
  for (const script of [
269
278
  "prepublishOnly",
@@ -1,4 +1,4 @@
1
- import { s as TegamiPlugin } from "../types-BbDtPn8I.js";
1
+ import { s as TegamiPlugin } from "../types-BHT_9k9p.js";
2
2
 
3
3
  //#region src/plugins/git.d.ts
4
4
  interface GitPluginOptions {
@@ -1,4 +1,4 @@
1
- import { a as isCI, n as execFailure } from "../error-DNy8R5ue.js";
1
+ import { n as execFailure, o as isCI } from "../error-We7chQVJ.js";
2
2
  import { x } from "tinyexec";
3
3
  //#region src/plugins/git.ts
4
4
  /**
@@ -9,11 +9,12 @@ import { x } from "tinyexec";
9
9
  */
10
10
  function git(options = {}) {
11
11
  const { createTags = true, pushTags = isCI() } = options;
12
- function getPendingTags(plan, filterPackage) {
12
+ function getPendingTags(plan) {
13
13
  const pendingTags = /* @__PURE__ */ new Set();
14
14
  if ((plan.options.dryRun ?? false) || !createTags) return pendingTags;
15
15
  for (const pkg of plan.packages.values()) {
16
- if (filterPackage && !filterPackage(pkg)) continue;
16
+ if (!pkg.preflight.shouldPublish) continue;
17
+ if (pkg.publishResult && pkg.publishResult.type === "failed") continue;
17
18
  const tag = pkg.git?.tag;
18
19
  if (tag) pendingTags.add(tag);
19
20
  }
@@ -49,26 +50,22 @@ function git(options = {}) {
49
50
  }
50
51
  },
51
52
  async resolvePlanStatus({ plan }) {
52
- const pendingTags = getPendingTags(plan, (pkg) => pkg.preflight.publish);
53
- if (pendingTags.size === 0) return;
54
- try {
55
- await Promise.all(Array.from(pendingTags, async (tag) => {
56
- if (!await gitTagExists(this.cwd, tag)) throw "pending";
57
- }));
58
- } catch (e) {
59
- if (e === "pending") return "pending";
60
- throw e;
61
- }
53
+ const pendingTags = getPendingTags(plan);
54
+ return Array.from(pendingTags, async (tag) => {
55
+ if (!await gitTagExists(this.cwd, tag)) return "pending";
56
+ });
62
57
  },
63
58
  async afterPublishAll({ plan }) {
64
59
  const { cwd } = this;
65
60
  const createdTags = [];
66
- const pendingTags = getPendingTags(plan, (pkg) => pkg.publishResult.type === "published");
61
+ const pendingTags = getPendingTags(plan);
67
62
  if (pendingTags.size === 0) return;
68
63
  await Promise.all(Array.from(pendingTags, async (tag) => {
69
- if (await gitTagExists(cwd, tag)) return;
70
64
  const gitOut = await x("git", ["tag", tag], { nodeOptions: { cwd } });
71
- if (gitOut.exitCode !== 0) throw execFailure(`Failed to create Git tag "${tag}" for release`, gitOut);
65
+ if (gitOut.exitCode !== 0) {
66
+ if (/already exists/i.test(`${gitOut.stdout}\n${gitOut.stderr}`)) return;
67
+ throw execFailure(`Failed to create Git tag "${tag}" for release`, gitOut);
68
+ }
72
69
  createdTags.push(tag);
73
70
  }));
74
71
  if (pushTags && createdTags.length > 0) {
@@ -1,4 +1,4 @@
1
- import { C as WorkspacePackage, b as TegamiContext, p as PublishPlan, s as TegamiPlugin, t as Awaitable, w as Draft } from "../types-BbDtPn8I.js";
1
+ import { C as WorkspacePackage, b as TegamiContext, p as PublishPlan, s as TegamiPlugin, t as Awaitable, w as Draft } from "../types-BHT_9k9p.js";
2
2
  import { GitPluginOptions } from "./git.js";
3
3
 
4
4
  //#region src/plugins/github.d.ts
@@ -1,7 +1,8 @@
1
- import { i as formatPackageVersion, r as formatNpmDistTag } from "../semver-EKJ8yK5U.js";
2
- import { a as isCI, n as execFailure } from "../error-DNy8R5ue.js";
3
- import { a as findOpenPullRequest, l as updatePullRequest, n as createPullRequest, r as createRelease, s as releaseExistsByTag } from "../api-D-jf_8xY.js";
1
+ import { i as formatPackageVersion } from "../semver-EKJ8yK5U.js";
2
+ import { a as cached, n as execFailure, o as isCI } from "../error-We7chQVJ.js";
3
+ import { a as findOpenPullRequest, c as releaseExistsByTag, n as createPullRequest, r as createRelease, s as listPullRequestsForCommit, u as updatePullRequest } from "../api-CGfXmmEM.js";
4
4
  import { git } from "./git.js";
5
+ import { n as createVersionRequestBody, r as hasGitChanges, t as commitVersionBranchChanges } from "../version-request-cFS0Suzd.js";
5
6
  import { join, relative } from "node:path";
6
7
  import { x } from "tinyexec";
7
8
  import semver from "semver";
@@ -13,48 +14,7 @@ function github(options = {}) {
13
14
  function getRenderer(context) {
14
15
  return renderer ??= createChangelogRenderer(context);
15
16
  }
16
- function resolvePROptions() {
17
- const config = options.versionPr ?? {};
18
- if (config === false) return [false];
19
- if (config.forceCreate || isCI()) return [true, config];
20
- return [false];
21
- }
22
- function defaultVersionPRBody(draft, context) {
23
- const packageLines = [];
24
- const changesets = /* @__PURE__ */ new Map();
25
- for (const [id, packageDraft] of draft.getPackageDrafts()) {
26
- const pkg = context.graph.get(id);
27
- if (!pkg) continue;
28
- for (const entry of packageDraft.changelogs ?? []) {
29
- const list = changesets.get(entry);
30
- if (list) list.push(pkg);
31
- else changesets.set(entry, [pkg]);
32
- }
33
- const originalVersion = cliOriginalPackageVersions.get(pkg.id);
34
- if (!originalVersion || originalVersion === pkg.version) continue;
35
- packageLines.push(`| \`${pkg.name}\` | \`${originalVersion}\` | \`${pkg.version}\`${formatNpmDistTag(packageDraft.npm?.distTag)} |`);
36
- }
37
- const changelogLines = [];
38
- for (const [entry, linkedPackages] of changesets) {
39
- changelogLines.push(`### ${entry.subject ?? `\`${entry.filename}\``}`, "");
40
- changelogLines.push("<details>", `<summary>Show Bumped Packages (${linkedPackages.length})</summary>`, "", ...linkedPackages.map((pkg) => `- \`${pkg.id}\``), "", "</details>", "");
41
- for (const section of entry.sections) {
42
- changelogLines.push(`#### ${section.title}`, "");
43
- if (section.content) changelogLines.push(section.content);
44
- }
45
- }
46
- const sections = [
47
- "## Summary",
48
- "",
49
- "Merge this PR to publish the versioned packages.",
50
- ""
51
- ];
52
- if (packageLines.length > 0) sections.push("| Package | From | To |", "| --- | --- | --- |", ...packageLines);
53
- if (changelogLines.length > 0) sections.push("", "## Changelogs", ...changelogLines);
54
- sections.push("");
55
- return sections.join("\n");
56
- }
57
- const cliOriginalPackageVersions = /* @__PURE__ */ new Map();
17
+ const cliSnapshots = /* @__PURE__ */ new Map();
58
18
  return [git(options), {
59
19
  name: "github",
60
20
  init() {
@@ -67,42 +27,27 @@ function github(options = {}) {
67
27
  const { repo, token } = this.github;
68
28
  if (!repo || !token || releaseOptions === false) return;
69
29
  const requiredTags = /* @__PURE__ */ new Set();
70
- for (const pkg of plan.packages.values()) if (pkg.preflight.publish && pkg.git?.tag) requiredTags.add(pkg.git.tag);
71
- try {
72
- await Promise.all(Array.from(requiredTags, async (tag) => {
73
- if (!await releaseExistsByTag(repo, tag, token)) throw "pending";
74
- }));
75
- } catch (e) {
76
- if (e === "pending") return "pending";
77
- throw e;
78
- }
30
+ for (const pkg of plan.packages.values()) if (pkg.preflight.shouldPublish && pkg.git?.tag) requiredTags.add(pkg.git.tag);
31
+ return Array.from(requiredTags, async (tag) => {
32
+ if (!await releaseExistsByTag(repo, tag, token)) return "pending";
33
+ });
79
34
  },
80
35
  async afterPublishAll({ plan }) {
81
36
  const { repo, token } = this.github;
82
37
  if (!repo || !token || releaseOptions === false) return;
83
38
  const { eager = false, create, createGrouped } = releaseOptions === true ? {} : releaseOptions;
84
39
  const groups = /* @__PURE__ */ new Map();
85
- for (const [id, packagePlan] of plan.packages) {
40
+ for (const [id, { preflight, publishResult, git }] of plan.packages) {
41
+ if (!eager && publishResult.type === "failed") return;
42
+ const tag = git?.tag;
43
+ if (!tag || !preflight.shouldPublish) continue;
86
44
  const pkg = this.graph.get(id);
87
- if (!eager && packagePlan.publishResult.type === "failed") return;
88
- const tag = packagePlan.git?.tag;
89
- if (!tag) continue;
90
45
  const group = groups.get(tag);
91
46
  if (group) group.push(pkg);
92
47
  else groups.set(tag, [pkg]);
93
48
  }
94
49
  await Promise.all(Array.from(groups, async ([tag, packages]) => {
95
- let hasFailed = false;
96
- let hasPublished = false;
97
- for (const member of packages) switch (plan.packages.get(member.id).publishResult.type) {
98
- case "published":
99
- hasPublished = true;
100
- break;
101
- case "failed":
102
- hasFailed = true;
103
- break;
104
- }
105
- if (hasFailed || !hasPublished) return;
50
+ for (const member of packages) if (plan.packages.get(member.id).publishResult.type === "failed") return;
106
51
  if (await releaseExistsByTag(repo, tag, token)) return;
107
52
  let release;
108
53
  if (packages.length > 1) {
@@ -153,42 +98,19 @@ function github(options = {}) {
153
98
  if (result.exitCode !== 0) throw execFailure("Failed to configure git remote for GitHub Actions.", result);
154
99
  },
155
100
  draftCreated() {
156
- for (const pkg of this.graph.getPackages()) cliOriginalPackageVersions.set(pkg.id, pkg.version);
101
+ for (const pkg of this.graph.getPackages()) cliSnapshots.set(pkg.id, pkg.version);
157
102
  },
158
103
  async draftApplied(draft) {
159
- const { cwd } = this;
160
- const [enabled, config] = resolvePROptions();
161
- if (!enabled || !await hasGitChanges(cwd)) return;
104
+ const config = options.versionPr ?? {};
105
+ if (config === false || !(config.forceCreate || isCI()) || !await hasGitChanges(this.cwd)) return;
162
106
  const repo = this.github?.repo;
163
107
  const { branch = "tegami/version-packages", base = "main" } = config;
164
108
  const basePR = await config.create?.call(this, { draft });
165
109
  const pr = {
166
110
  title: basePR?.title ?? "Version Packages",
167
- body: basePR?.body ?? defaultVersionPRBody(draft, this)
111
+ body: basePR?.body ?? createVersionRequestBody(draft, this, cliSnapshots, "Merge this PR to publish the versioned packages.")
168
112
  };
169
- const gitOptions = { nodeOptions: { cwd } };
170
- let result = await x("git", [
171
- "checkout",
172
- "-B",
173
- branch
174
- ], gitOptions);
175
- if (result.exitCode !== 0) throw execFailure("Failed to create the version pull request branch.", result);
176
- result = await x("git", ["add", "-A"], gitOptions);
177
- if (result.exitCode !== 0) throw execFailure("Failed to stage version changes.", result);
178
- result = await x("git", [
179
- "commit",
180
- "-m",
181
- pr.title
182
- ], gitOptions);
183
- if (result.exitCode !== 0) throw execFailure("Failed to commit version changes.", result);
184
- result = await x("git", [
185
- "push",
186
- "--force",
187
- "-u",
188
- "origin",
189
- branch
190
- ], gitOptions);
191
- if (result.exitCode !== 0) throw execFailure("Failed to push the version branch to origin. Ensure `origin` is configured and you have push access.", result);
113
+ await commitVersionBranchChanges(this.cwd, branch, pr.title);
192
114
  const token = this.github?.token;
193
115
  if (!repo) return;
194
116
  const openPr = await findOpenPullRequest(repo, branch, token);
@@ -211,12 +133,9 @@ function github(options = {}) {
211
133
  }
212
134
  }];
213
135
  }
214
- async function hasGitChanges(cwd) {
215
- return (await x("git", ["status", "--porcelain"], { nodeOptions: { cwd } })).stdout.trim().length > 0;
216
- }
217
136
  function createChangelogRenderer(context) {
218
- const cache = /* @__PURE__ */ new Map();
219
- async function resolveFileCommit(filename) {
137
+ const { repo, token } = context.github;
138
+ const resolveFileCommit = cached((filename) => filename, async (filename) => {
220
139
  const result = await x("git", [
221
140
  "log",
222
141
  "--diff-filter=A",
@@ -227,26 +146,50 @@ function createChangelogRenderer(context) {
227
146
  ], { nodeOptions: { cwd: context.cwd } });
228
147
  if (result.exitCode !== 0) return;
229
148
  return result.stdout.trim() || void 0;
230
- }
231
- function formatCommitLink(commit) {
232
- const short = commit.slice(0, 7);
233
- const repo = context.github?.repo;
234
- if (!repo) return `\`${short}\``;
235
- return `[${short}](https://github.com/${repo}/commit/${commit})`;
149
+ });
150
+ const resolveEntryMeta = cached((entry) => entry.id, async (entry) => {
151
+ const commit = await resolveFileCommit(entry.filename);
152
+ if (!commit || !repo) return {
153
+ commit,
154
+ pullRequests: []
155
+ };
156
+ return {
157
+ commit,
158
+ pullRequests: await listPullRequestsForCommit(repo, commit, token).catch(() => [])
159
+ };
160
+ });
161
+ function formatEntryDetails(meta) {
162
+ if (meta.pullRequests.length === 0) return;
163
+ const lines = [];
164
+ for (const pr of meta.pullRequests) {
165
+ let line = repo ? `- [#${pr.number} ${pr.title}](https://github.com/${repo}/pull/${pr.number})` : `- #${pr.number} ${pr.title}`;
166
+ if (pr.user) line += ` by @${pr.user.login}`;
167
+ lines.push(line);
168
+ }
169
+ return [
170
+ "<details>",
171
+ "<summary>Pull request & contributors</summary>",
172
+ "",
173
+ ...lines,
174
+ "",
175
+ "</details>"
176
+ ].join("\n");
236
177
  }
237
178
  return async (entry) => {
238
- let commitPromise = cache.get(entry.filename);
239
- if (!commitPromise) {
240
- commitPromise = resolveFileCommit(entry.filename);
241
- cache.set(entry.filename, commitPromise);
179
+ const meta = await resolveEntryMeta(entry);
180
+ let commitSuffix = "";
181
+ if (meta.commit) {
182
+ const short = meta.commit.slice(0, 7);
183
+ const link = repo ? `[${short}](https://github.com/${repo}/commit/${meta.commit})` : `\`${short}\``;
184
+ commitSuffix += ` (${link})`;
242
185
  }
243
- const commit = await commitPromise;
244
- const commitSuffix = commit ? ` (${formatCommitLink(commit)})` : "";
245
186
  const lines = [];
246
187
  for (const section of entry.sections) {
247
188
  lines.push(`### ${section.title}${commitSuffix}`, "");
248
189
  if (section.content) lines.push(section.content, "");
249
190
  }
191
+ const details = formatEntryDetails(meta);
192
+ if (details) lines.push(details);
250
193
  return lines.join("\n").trim();
251
194
  };
252
195
  }