@icebreakers/changelog-github 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,8 +35,8 @@ Run `pnpm changeset` as usual—generated changelogs will now use the enhanced f
35
35
  ## Key Differences from `@changesets/changelog-github`
36
36
 
37
37
  - **Summary tags** – recognises leading `pr:`, `commit:`, and `author:` / `user:` directives (case-insensitive) to override GitHub metadata while keeping the rest of the summary intact.
38
- - **Short commit links** – when you supply a `commit:` tag, the formatter renders the shortened SHA as a hyperlink rather than just the full URL.
39
- - **Release type callout** – appends a `> MAJOR release` style block for non-`none` releases so changelog readers can scan the impact quickly.
38
+ - **Short commit links** – when you supply a `commit:` tag, the formatter renders the shortened SHA as a hyperlink (and shows it alongside the PR link when both are provided).
39
+ - **Optimized formatting** – uses a clean, multi-line layout with essential metadata only. Removes redundant separators and labels for better readability on GitHub.
40
40
  - **Dotenv bootstrapping** – automatically loads `.env` before hitting the GitHub API, matching local development to CI without extra wiring.
41
41
  - **Dependency section** – lists dependency bumps with consistent Markdown indentation and links any backing commits for extra context.
42
42
 
@@ -60,10 +60,9 @@ Add optional `tailwind-config` flag to the lint preset
60
60
  The rendered changelog entry becomes:
61
61
 
62
62
  ```
63
- - Add optional `tailwind-config` flag to the lint preset (sonofmagic/dev-configs#42 · `1234567` · Thanks [@octocat](https://github.com/octocat)!)
63
+ - ✨ **Add optional `tailwind-config` flag** [#42] [`1234567`](https://github.com/org/repo/commit/1234567890abcdef1234567890abcdef12345678) by @octocat
64
64
  - accepts a relative path to `tailwind.config.ts`
65
65
  - falls back to the workspace root when unset
66
- > MINOR release
67
66
  ```
68
67
 
69
68
  ## GitHub Authentication
package/README.zh.md CHANGED
@@ -33,8 +33,8 @@ pnpm add -D @changesets/cli @icebreakers/changelog-github
33
33
  ## 与 `@changesets/changelog-github` 的差异
34
34
 
35
35
  - **摘要指令**:额外识别位于摘要首部的 `pr:`、`commit:`、`author:` / `user:`(不区分大小写),用于覆盖 GitHub 拉取的元数据,同时保留正文内容。
36
- - **短哈希链接**:当提供 `commit:` 指令时,会以短 SHA 生成链接,而不是仅展示完整 URL,阅读体验更好。
37
- - **版本类型提示**:对非 `none` 版本自动附加 `> MAJOR release`、`> MINOR release` 等提示,方便读者快速掌握影响范围。
36
+ - **短哈希链接**:当提供 `commit:` 指令时,会以短 SHA 生成链接(若同时提供 `pr:`,会与 PR 链接并列展示),阅读体验更好。
37
+ - **优化的格式**:采用简洁的多行布局,仅保留核心元数据,移除冗余的分隔符和标签,提升 GitHub 上的可读性。
38
38
  - **自动加载 dotenv**:在访问 GitHub API 前调用 `dotenv`,本地 CLI 与 CI 行为保持一致,无需额外手动引入。
39
39
  - **依赖更新板块**:列出依赖升级时,会根据变更集补全提交链接,并统一 Markdown 缩进,便于对齐阅读。
40
40
 
@@ -58,10 +58,9 @@ author: @octocat
58
58
  最终 changelog 会渲染为:
59
59
 
60
60
  ```
61
- - 为 lint preset 新增可选的 `tailwind-config` 配置项 (sonofmagic/dev-configs#42 · `1234567` · Thanks [@octocat](https://github.com/octocat)!)
61
+ - ✨ **为 lint preset 新增可选的 `tailwind-config` 配置项** [#42] [`1234567`](https://github.com/org/repo/commit/1234567890abcdef1234567890abcdef12345678) by @octocat
62
62
  - 接受相对路径指向 `tailwind.config.ts`
63
63
  - 未设置时默认读取工作区根目录
64
- > MINOR release
65
64
  ```
66
65
 
67
66
  ## GitHub 鉴权
package/dist/index.cjs CHANGED
@@ -37,38 +37,93 @@ async function collectDependencyReferences(changesets, repo) {
37
37
  return references.filter((link) => Boolean(link));
38
38
  }
39
39
  function formatDependencyLine(references, dependenciesUpdated) {
40
- const dependencySummaries = dependenciesUpdated.map(
41
- (dependency) => `\`${dependency.name}\` @ ${dependency.newVersion}`
42
- );
43
- const segments = [];
44
- if (dependencySummaries.length > 0) {
45
- segments.push(dependencySummaries.join(", "));
40
+ const count = dependenciesUpdated.length;
41
+ const firstCommit = references[0] || "";
42
+ if (count === 0) {
43
+ return firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
46
44
  }
47
- if (references.length > 0) {
48
- segments.push(references.join(" \xB7 "));
45
+ if (count <= 3) {
46
+ const dependencySummaries = dependenciesUpdated.map(
47
+ (dependency) => `\`${dependency.name}@${dependency.newVersion}\``
48
+ );
49
+ const header = firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
50
+ const details = ` \u2192 ${dependencySummaries.join(", ")}`;
51
+ return `${header}
52
+ ${details}`;
49
53
  }
50
- const details = segments.join(" \xB7 ");
51
- return details.length > 0 ? `- \u{1F4E6} Updated dependencies: ${details}` : "- \u{1F4E6} Updated dependencies";
54
+ return firstCommit ? `- \u{1F4E6} Updated ${count} dependencies ${firstCommit}` : `- \u{1F4E6} Updated ${count} dependencies`;
52
55
  }
53
- function parseSummary(summary) {
56
+ function normalizeUserMentions(users) {
57
+ const normalized = users.map((user) => user.trim()).filter(Boolean).map((user) => user.startsWith("@") ? user : `@${user}`);
58
+ if (normalized.length === 0) {
59
+ return "";
60
+ }
61
+ if (normalized.length === 1) {
62
+ return normalized[0];
63
+ }
64
+ if (normalized.length === 2) {
65
+ return `${normalized[0]} and ${normalized[1]}`;
66
+ }
67
+ return `${normalized.slice(0, -1).join(", ")}, and ${normalized.at(-1)}`;
68
+ }
69
+ function extractSummaryMeta(summary) {
54
70
  let prNumber;
55
71
  let commitRef;
56
72
  const users = [];
57
- const cleanedSummary = summary.replace(/^\s*(?:pr|pull|pull\s+request):\s*#?(\d+)/im, (_, pr) => {
58
- const num = Number(pr);
59
- if (!Number.isNaN(num)) {
60
- prNumber = num;
73
+ const contentLines = summary.split(/\r?\n/).filter((line) => {
74
+ const trimmed = line.trim();
75
+ const withoutListMarker = trimmed.replace(/^[-*+]\s+/, "");
76
+ const prMatch = withoutListMarker.match(
77
+ /^(?:pr|pull|pull\s*request):\s*#?(\d+)\s*$/i
78
+ );
79
+ if (prMatch) {
80
+ const num = Number(prMatch[1]);
81
+ if (!Number.isNaN(num)) {
82
+ prNumber = num;
83
+ }
84
+ return false;
61
85
  }
62
- return "";
63
- }).replace(/^\s*commit:\s*(\S+)/im, (_, commit) => {
64
- commitRef = commit;
65
- return "";
66
- }).replace(/^\s*(?:author|user):\s*@?(\S+)/gim, (_, user) => {
67
- users.push(user);
68
- return "";
69
- }).trim();
70
- const [headline = "", ...restLines] = cleanedSummary.split("\n").map((line) => line.trimRight());
71
- const detailLines = restLines.map((line) => line.trim()).map((line) => line.replace(/^\s*[-*]\s*/, "")).filter((line) => line.length > 0);
86
+ const commitMatch = withoutListMarker.match(/^commit:\s*(\S+)\s*$/i);
87
+ if (commitMatch) {
88
+ commitRef = commitMatch[1];
89
+ return false;
90
+ }
91
+ const userMatch = withoutListMarker.match(/^(?:author|user):\s*(\S+)\s*$/i);
92
+ if (userMatch) {
93
+ users.push(userMatch[1].replace(/^@/, ""));
94
+ return false;
95
+ }
96
+ return true;
97
+ }).map((line) => line.trimRight());
98
+ return {
99
+ prNumber,
100
+ commitRef,
101
+ users,
102
+ contentLines
103
+ };
104
+ }
105
+ function parseSummary(summary) {
106
+ const {
107
+ prNumber,
108
+ commitRef,
109
+ users,
110
+ contentLines
111
+ } = extractSummaryMeta(summary);
112
+ const nonEmptyIndex = contentLines.findIndex((line) => line.trim().length > 0);
113
+ if (nonEmptyIndex === -1) {
114
+ return {
115
+ headline: "",
116
+ detailLines: [],
117
+ prNumber,
118
+ commitRef,
119
+ users
120
+ };
121
+ }
122
+ const firstContentLine = contentLines[nonEmptyIndex].trim();
123
+ const startsWithList = /^(?:[-*+]\s+|\d+[.)]\s+)/.test(firstContentLine);
124
+ const headline = startsWithList ? "" : firstContentLine;
125
+ const restLines = startsWithList ? contentLines.slice(nonEmptyIndex) : contentLines.slice(nonEmptyIndex + 1);
126
+ const detailLines = restLines;
72
127
  return {
73
128
  headline,
74
129
  detailLines,
@@ -105,9 +160,35 @@ async function resolveLinks(repo, parsed, changesetCommit) {
105
160
  }
106
161
  function buildUserMentions(users, fallbackUser) {
107
162
  if (users.length > 0) {
108
- return users.map((username) => `[@${username}](https://github.com/${username})`).join(", ");
163
+ return normalizeUserMentions(users);
109
164
  }
110
- return _nullishCoalesce(fallbackUser, () => ( ""));
165
+ if (fallbackUser) {
166
+ const match = fallbackUser.match(/@([^\]]+)/);
167
+ return match ? normalizeUserMentions([match[1]]) : "";
168
+ }
169
+ return "";
170
+ }
171
+ function indentMarkdownBlock(lines, spaces) {
172
+ const prefix = " ".repeat(spaces);
173
+ return lines.map((line) => {
174
+ if (line.trim().length === 0) {
175
+ return "";
176
+ }
177
+ return `${prefix}${line}`;
178
+ }).join("\n");
179
+ }
180
+ function formatDetailBlock(detailLines) {
181
+ const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimRight());
182
+ const hasFence = trimmed.some((line) => /^\s*```/.test(line));
183
+ const isListLike = trimmed.some((line) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(line));
184
+ if (trimmed.length === 0) {
185
+ return "";
186
+ }
187
+ if (hasFence || isListLike) {
188
+ return indentMarkdownBlock(trimmed, 2);
189
+ }
190
+ const paragraphs = trimmed.join("\n").split(/\n\s*\n/).map((chunk) => chunk.replace(/\s+/g, " ").trim()).filter(Boolean);
191
+ return paragraphs.map((text) => ` - ${text}`).join("\n");
111
192
  }
112
193
  var changelogFunctions = {
113
194
  async getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
@@ -137,28 +218,29 @@ var changelogFunctions = {
137
218
  links.user
138
219
  );
139
220
  const headlineText = parsedSummary.headline || "Miscellaneous improvements";
140
- const detailText = parsedSummary.detailLines.join(" \xB7 ");
141
- const metaSegments = [];
221
+ const mainLineParts = [
222
+ `- ${releaseTypeMap[resolvedType].icon}`,
223
+ `**${headlineText}**`
224
+ ];
142
225
  if (links.pull) {
143
- metaSegments.push(links.pull);
226
+ mainLineParts.push(links.pull);
227
+ } else if (links.commit) {
228
+ mainLineParts.push(links.commit);
144
229
  }
145
- if (links.commit) {
146
- metaSegments.push(links.commit);
230
+ if (links.pull && parsedSummary.commitRef && links.commit) {
231
+ mainLineParts.push(links.commit);
147
232
  }
148
233
  if (userMentions) {
149
- metaSegments.push(`Thanks ${userMentions}`);
234
+ mainLineParts.push(`by ${userMentions}`);
150
235
  }
151
- if (resolvedType !== "none") {
152
- metaSegments.push(`${releaseTypeMap[resolvedType].label} release`);
153
- }
154
- const trailingSegments = [
155
- detailText,
156
- metaSegments.join(" \xB7 ")
157
- ].filter((segment) => segment.length > 0);
158
- const suffix = trailingSegments.length ? ` \u2014 ${trailingSegments.join(" \xB7 ")}` : "";
159
- return `
236
+ const mainLine = mainLineParts.join(" ");
237
+ const detailBlock = formatDetailBlock(parsedSummary.detailLines);
238
+ return detailBlock.length > 0 ? `
239
+
240
+ ${mainLine}
241
+ ${detailBlock}` : `
160
242
 
161
- - ${releaseTypeMap[resolvedType].icon} **${headlineText}**${suffix}`;
243
+ ${mainLine}`;
162
244
  }
163
245
  };
164
246
  var index_default = changelogFunctions;
package/dist/index.mjs CHANGED
@@ -37,38 +37,93 @@ async function collectDependencyReferences(changesets, repo) {
37
37
  return references.filter((link) => Boolean(link));
38
38
  }
39
39
  function formatDependencyLine(references, dependenciesUpdated) {
40
- const dependencySummaries = dependenciesUpdated.map(
41
- (dependency) => `\`${dependency.name}\` @ ${dependency.newVersion}`
42
- );
43
- const segments = [];
44
- if (dependencySummaries.length > 0) {
45
- segments.push(dependencySummaries.join(", "));
40
+ const count = dependenciesUpdated.length;
41
+ const firstCommit = references[0] || "";
42
+ if (count === 0) {
43
+ return firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
46
44
  }
47
- if (references.length > 0) {
48
- segments.push(references.join(" \xB7 "));
45
+ if (count <= 3) {
46
+ const dependencySummaries = dependenciesUpdated.map(
47
+ (dependency) => `\`${dependency.name}@${dependency.newVersion}\``
48
+ );
49
+ const header = firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
50
+ const details = ` \u2192 ${dependencySummaries.join(", ")}`;
51
+ return `${header}
52
+ ${details}`;
49
53
  }
50
- const details = segments.join(" \xB7 ");
51
- return details.length > 0 ? `- \u{1F4E6} Updated dependencies: ${details}` : "- \u{1F4E6} Updated dependencies";
54
+ return firstCommit ? `- \u{1F4E6} Updated ${count} dependencies ${firstCommit}` : `- \u{1F4E6} Updated ${count} dependencies`;
52
55
  }
53
- function parseSummary(summary) {
56
+ function normalizeUserMentions(users) {
57
+ const normalized = users.map((user) => user.trim()).filter(Boolean).map((user) => user.startsWith("@") ? user : `@${user}`);
58
+ if (normalized.length === 0) {
59
+ return "";
60
+ }
61
+ if (normalized.length === 1) {
62
+ return normalized[0];
63
+ }
64
+ if (normalized.length === 2) {
65
+ return `${normalized[0]} and ${normalized[1]}`;
66
+ }
67
+ return `${normalized.slice(0, -1).join(", ")}, and ${normalized.at(-1)}`;
68
+ }
69
+ function extractSummaryMeta(summary) {
54
70
  let prNumber;
55
71
  let commitRef;
56
72
  const users = [];
57
- const cleanedSummary = summary.replace(/^\s*(?:pr|pull|pull\s+request):\s*#?(\d+)/im, (_, pr) => {
58
- const num = Number(pr);
59
- if (!Number.isNaN(num)) {
60
- prNumber = num;
73
+ const contentLines = summary.split(/\r?\n/).filter((line) => {
74
+ const trimmed = line.trim();
75
+ const withoutListMarker = trimmed.replace(/^[-*+]\s+/, "");
76
+ const prMatch = withoutListMarker.match(
77
+ /^(?:pr|pull|pull\s*request):\s*#?(\d+)\s*$/i
78
+ );
79
+ if (prMatch) {
80
+ const num = Number(prMatch[1]);
81
+ if (!Number.isNaN(num)) {
82
+ prNumber = num;
83
+ }
84
+ return false;
61
85
  }
62
- return "";
63
- }).replace(/^\s*commit:\s*(\S+)/im, (_, commit) => {
64
- commitRef = commit;
65
- return "";
66
- }).replace(/^\s*(?:author|user):\s*@?(\S+)/gim, (_, user) => {
67
- users.push(user);
68
- return "";
69
- }).trim();
70
- const [headline = "", ...restLines] = cleanedSummary.split("\n").map((line) => line.trimRight());
71
- const detailLines = restLines.map((line) => line.trim()).map((line) => line.replace(/^\s*[-*]\s*/, "")).filter((line) => line.length > 0);
86
+ const commitMatch = withoutListMarker.match(/^commit:\s*(\S+)\s*$/i);
87
+ if (commitMatch) {
88
+ commitRef = commitMatch[1];
89
+ return false;
90
+ }
91
+ const userMatch = withoutListMarker.match(/^(?:author|user):\s*(\S+)\s*$/i);
92
+ if (userMatch) {
93
+ users.push(userMatch[1].replace(/^@/, ""));
94
+ return false;
95
+ }
96
+ return true;
97
+ }).map((line) => line.trimRight());
98
+ return {
99
+ prNumber,
100
+ commitRef,
101
+ users,
102
+ contentLines
103
+ };
104
+ }
105
+ function parseSummary(summary) {
106
+ const {
107
+ prNumber,
108
+ commitRef,
109
+ users,
110
+ contentLines
111
+ } = extractSummaryMeta(summary);
112
+ const nonEmptyIndex = contentLines.findIndex((line) => line.trim().length > 0);
113
+ if (nonEmptyIndex === -1) {
114
+ return {
115
+ headline: "",
116
+ detailLines: [],
117
+ prNumber,
118
+ commitRef,
119
+ users
120
+ };
121
+ }
122
+ const firstContentLine = contentLines[nonEmptyIndex].trim();
123
+ const startsWithList = /^(?:[-*+]\s+|\d+[.)]\s+)/.test(firstContentLine);
124
+ const headline = startsWithList ? "" : firstContentLine;
125
+ const restLines = startsWithList ? contentLines.slice(nonEmptyIndex) : contentLines.slice(nonEmptyIndex + 1);
126
+ const detailLines = restLines;
72
127
  return {
73
128
  headline,
74
129
  detailLines,
@@ -105,9 +160,35 @@ async function resolveLinks(repo, parsed, changesetCommit) {
105
160
  }
106
161
  function buildUserMentions(users, fallbackUser) {
107
162
  if (users.length > 0) {
108
- return users.map((username) => `[@${username}](https://github.com/${username})`).join(", ");
163
+ return normalizeUserMentions(users);
109
164
  }
110
- return fallbackUser ?? "";
165
+ if (fallbackUser) {
166
+ const match = fallbackUser.match(/@([^\]]+)/);
167
+ return match ? normalizeUserMentions([match[1]]) : "";
168
+ }
169
+ return "";
170
+ }
171
+ function indentMarkdownBlock(lines, spaces) {
172
+ const prefix = " ".repeat(spaces);
173
+ return lines.map((line) => {
174
+ if (line.trim().length === 0) {
175
+ return "";
176
+ }
177
+ return `${prefix}${line}`;
178
+ }).join("\n");
179
+ }
180
+ function formatDetailBlock(detailLines) {
181
+ const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimRight());
182
+ const hasFence = trimmed.some((line) => /^\s*```/.test(line));
183
+ const isListLike = trimmed.some((line) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(line));
184
+ if (trimmed.length === 0) {
185
+ return "";
186
+ }
187
+ if (hasFence || isListLike) {
188
+ return indentMarkdownBlock(trimmed, 2);
189
+ }
190
+ const paragraphs = trimmed.join("\n").split(/\n\s*\n/).map((chunk) => chunk.replace(/\s+/g, " ").trim()).filter(Boolean);
191
+ return paragraphs.map((text) => ` - ${text}`).join("\n");
111
192
  }
112
193
  var changelogFunctions = {
113
194
  async getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
@@ -137,28 +218,29 @@ var changelogFunctions = {
137
218
  links.user
138
219
  );
139
220
  const headlineText = parsedSummary.headline || "Miscellaneous improvements";
140
- const detailText = parsedSummary.detailLines.join(" \xB7 ");
141
- const metaSegments = [];
221
+ const mainLineParts = [
222
+ `- ${releaseTypeMap[resolvedType].icon}`,
223
+ `**${headlineText}**`
224
+ ];
142
225
  if (links.pull) {
143
- metaSegments.push(links.pull);
226
+ mainLineParts.push(links.pull);
227
+ } else if (links.commit) {
228
+ mainLineParts.push(links.commit);
144
229
  }
145
- if (links.commit) {
146
- metaSegments.push(links.commit);
230
+ if (links.pull && parsedSummary.commitRef && links.commit) {
231
+ mainLineParts.push(links.commit);
147
232
  }
148
233
  if (userMentions) {
149
- metaSegments.push(`Thanks ${userMentions}`);
234
+ mainLineParts.push(`by ${userMentions}`);
150
235
  }
151
- if (resolvedType !== "none") {
152
- metaSegments.push(`${releaseTypeMap[resolvedType].label} release`);
153
- }
154
- const trailingSegments = [
155
- detailText,
156
- metaSegments.join(" \xB7 ")
157
- ].filter((segment) => segment.length > 0);
158
- const suffix = trailingSegments.length ? ` \u2014 ${trailingSegments.join(" \xB7 ")}` : "";
159
- return `
236
+ const mainLine = mainLineParts.join(" ");
237
+ const detailBlock = formatDetailBlock(parsedSummary.detailLines);
238
+ return detailBlock.length > 0 ? `
239
+
240
+ ${mainLine}
241
+ ${detailBlock}` : `
160
242
 
161
- - ${releaseTypeMap[resolvedType].icon} **${headlineText}**${suffix}`;
243
+ ${mainLine}`;
162
244
  }
163
245
  };
164
246
  var index_default = changelogFunctions;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@icebreakers/changelog-github",
3
3
  "type": "module",
4
- "version": "0.0.2",
4
+ "version": "0.1.0",
5
5
  "description": "Changesets GitHub changelog formatter for Icebreaker's dev-configs",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",