@icebreakers/changelog-github 0.1.1 → 0.2.1

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
@@ -46,7 +46,7 @@ All other behaviours (tuple arguments, required `repo` option, rendered layout)
46
46
 
47
47
  Leverage the summary tags to control the metadata the formatter displays:
48
48
 
49
- ```
49
+ ```text
50
50
  pr: #42
51
51
  commit: 1234567890abcdef1234567890abcdef12345678
52
52
  author: @octocat
@@ -59,7 +59,7 @@ Add optional `tailwind-config` flag to the lint preset
59
59
 
60
60
  The rendered changelog entry becomes:
61
61
 
62
- ```
62
+ ```md
63
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
package/README.zh.md CHANGED
@@ -44,7 +44,7 @@ pnpm add -D @changesets/cli @icebreakers/changelog-github
44
44
 
45
45
  可以借助摘要指令精准控制展示的元数据:
46
46
 
47
- ```
47
+ ```text
48
48
  pr: #42
49
49
  commit: 1234567890abcdef1234567890abcdef12345678
50
50
  author: @octocat
@@ -57,7 +57,7 @@ author: @octocat
57
57
 
58
58
  最终 changelog 会渲染为:
59
59
 
60
- ```
60
+ ```md
61
61
  - ✨ **为 lint preset 新增可选的 `tailwind-config` 配置项** [#42] [`1234567`](https://github.com/org/repo/commit/1234567890abcdef1234567890abcdef12345678) by @octocat
62
62
  - 接受相对路径指向 `tailwind.config.ts`
63
63
  - 未设置时默认读取工作区根目录
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/index.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/index.ts
2
2
  var _getgithubinfo = require('@changesets/get-github-info');
3
3
  var _dotenv = require('dotenv');
4
4
  _dotenv.config.call(void 0, );
@@ -15,7 +15,7 @@ function resolveReleaseType(type) {
15
15
  return "none";
16
16
  }
17
17
  function assertRepo(options) {
18
- if (!options || !options.repo) {
18
+ if (!options || typeof options["repo"] !== "string" || options["repo"].trim().length === 0) {
19
19
  throw new Error(
20
20
  'Please provide a repo to this changelog generator like this:\n"changelog": ["@icebreakers/changelog-github", { "repo": "org/repo" }]'
21
21
  );
@@ -34,7 +34,7 @@ async function collectDependencyReferences(changesets, repo) {
34
34
  return links.commit;
35
35
  })
36
36
  );
37
- return references.filter((link) => Boolean(link));
37
+ return [...new Set(references.filter((link) => Boolean(link)))];
38
38
  }
39
39
  function formatDependencyLine(references, dependenciesUpdated) {
40
40
  const count = dependenciesUpdated.length;
@@ -42,16 +42,53 @@ function formatDependencyLine(references, dependenciesUpdated) {
42
42
  if (count === 0) {
43
43
  return firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
44
44
  }
45
+ const dependencySummaries = dependenciesUpdated.map(
46
+ (dependency) => `\`${dependency.name}@${dependency.newVersion}\``
47
+ );
45
48
  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**";
49
+ const header2 = firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
50
50
  const details = ` \u2192 ${dependencySummaries.join(", ")}`;
51
- return `${header}
51
+ return `${header2}
52
52
  ${details}`;
53
53
  }
54
- return firstCommit ? `- \u{1F4E6} Updated ${count} dependencies ${firstCommit}` : `- \u{1F4E6} Updated ${count} dependencies`;
54
+ const header = firstCommit ? `- \u{1F4E6} Updated ${count} dependencies ${firstCommit}` : `- \u{1F4E6} Updated ${count} dependencies`;
55
+ const detailBlock = [
56
+ " <details><summary>Details</summary>",
57
+ "",
58
+ ` ${dependencySummaries.join(", ")}`,
59
+ "",
60
+ " </details>"
61
+ ].join("\n");
62
+ return `${header}
63
+ ${detailBlock}`;
64
+ }
65
+ function parseConventionalHeadline(raw) {
66
+ const match = raw.match(
67
+ /^(\w+)(?:\(([^)]*)\))?(!?):\s(.+)$/
68
+ );
69
+ if (!match) {
70
+ return { breaking: false, description: raw };
71
+ }
72
+ const type = match[1];
73
+ const scope = match[2] || void 0;
74
+ const description = (_nullishCoalesce(match[4], () => ( raw))).trim();
75
+ return {
76
+ type,
77
+ scope,
78
+ breaking: match[3] === "!",
79
+ description
80
+ };
81
+ }
82
+ function formatHeadline(raw) {
83
+ const parsed = parseConventionalHeadline(raw);
84
+ if (!parsed.type) {
85
+ return { text: raw, breaking: parsed.breaking };
86
+ }
87
+ const scopeTag = parsed.scope ? `${parsed.scope}: ` : "";
88
+ return {
89
+ text: `${scopeTag}${parsed.description}`,
90
+ breaking: parsed.breaking
91
+ };
55
92
  }
56
93
  function normalizeUserMentions(users) {
57
94
  const normalized = users.map((user) => user.trim()).filter(Boolean).map((user) => user.startsWith("@") ? user : `@${user}`);
@@ -59,16 +96,17 @@ function normalizeUserMentions(users) {
59
96
  return "";
60
97
  }
61
98
  if (normalized.length === 1) {
62
- return normalized[0];
99
+ return _nullishCoalesce(normalized[0], () => ( ""));
63
100
  }
64
101
  if (normalized.length === 2) {
65
- return `${normalized[0]} and ${normalized[1]}`;
102
+ return `${_nullishCoalesce(normalized[0], () => ( ""))} and ${_nullishCoalesce(normalized[1], () => ( ""))}`;
66
103
  }
67
- return `${normalized.slice(0, -1).join(", ")}, and ${normalized.at(-1)}`;
104
+ return `${normalized.slice(0, -1).join(", ")}, and ${_nullishCoalesce(normalized.at(-1), () => ( ""))}`;
68
105
  }
69
106
  function extractSummaryMeta(summary) {
70
107
  let prNumber;
71
108
  let commitRef;
109
+ let breaking = false;
72
110
  const users = [];
73
111
  const contentLines = summary.split(/\r?\n/).filter((line) => {
74
112
  const trimmed = line.trim();
@@ -77,7 +115,7 @@ function extractSummaryMeta(summary) {
77
115
  /^(?:pr|pull|pull\s*request):\s*#?(\d+)\s*$/i
78
116
  );
79
117
  if (prMatch) {
80
- const num = Number(prMatch[1]);
118
+ const num = Number(_nullishCoalesce(prMatch[1], () => ( Number.NaN)));
81
119
  if (!Number.isNaN(num)) {
82
120
  prNumber = num;
83
121
  }
@@ -90,16 +128,23 @@ function extractSummaryMeta(summary) {
90
128
  }
91
129
  const userMatch = withoutListMarker.match(/^(?:author|user):\s*(\S+)\s*$/i);
92
130
  if (userMatch) {
93
- users.push(userMatch[1].replace(/^@/, ""));
131
+ const user = userMatch[1];
132
+ if (user) {
133
+ users.push(user.replace(/^@/, ""));
134
+ }
94
135
  return false;
95
136
  }
137
+ if (/^BREAKING[\s-]CHANGE:/i.test(withoutListMarker)) {
138
+ breaking = true;
139
+ }
96
140
  return true;
97
- }).map((line) => line.trimRight());
141
+ }).map((line) => line.trimEnd());
98
142
  return {
99
- prNumber,
100
- commitRef,
143
+ ...prNumber !== void 0 ? { prNumber } : {},
144
+ ...commitRef !== void 0 ? { commitRef } : {},
101
145
  users,
102
- contentLines
146
+ contentLines,
147
+ breaking
103
148
  };
104
149
  }
105
150
  function parseSummary(summary) {
@@ -107,29 +152,34 @@ function parseSummary(summary) {
107
152
  prNumber,
108
153
  commitRef,
109
154
  users,
110
- contentLines
155
+ contentLines,
156
+ breaking: bodyBreaking
111
157
  } = extractSummaryMeta(summary);
112
158
  const nonEmptyIndex = contentLines.findIndex((line) => line.trim().length > 0);
113
159
  if (nonEmptyIndex === -1) {
114
160
  return {
115
161
  headline: "",
116
162
  detailLines: [],
117
- prNumber,
118
- commitRef,
119
- users
163
+ ...prNumber !== void 0 ? { prNumber } : {},
164
+ ...commitRef !== void 0 ? { commitRef } : {},
165
+ users,
166
+ breaking: bodyBreaking
120
167
  };
121
168
  }
122
- const firstContentLine = contentLines[nonEmptyIndex].trim();
169
+ const firstContentLine = _nullishCoalesce(_optionalChain([contentLines, 'access', _ => _[nonEmptyIndex], 'optionalAccess', _2 => _2.trim, 'call', _3 => _3()]), () => ( ""));
123
170
  const startsWithList = /^(?:[-*+]\s+|\d+[.)]\s+)/.test(firstContentLine);
124
171
  const headline = startsWithList ? "" : firstContentLine;
125
172
  const restLines = startsWithList ? contentLines.slice(nonEmptyIndex) : contentLines.slice(nonEmptyIndex + 1);
126
173
  const detailLines = restLines;
174
+ const headlineParsed = headline ? parseConventionalHeadline(headline) : null;
175
+ const breaking = bodyBreaking || (_nullishCoalesce(_optionalChain([headlineParsed, 'optionalAccess', _4 => _4.breaking]), () => ( false)));
127
176
  return {
128
177
  headline,
129
178
  detailLines,
130
- prNumber,
131
- commitRef,
132
- users
179
+ ...prNumber !== void 0 ? { prNumber } : {},
180
+ ...commitRef !== void 0 ? { commitRef } : {},
181
+ users,
182
+ breaking
133
183
  };
134
184
  }
135
185
  async function resolveLinks(repo, parsed, changesetCommit) {
@@ -164,7 +214,8 @@ function buildUserMentions(users, fallbackUser) {
164
214
  }
165
215
  if (fallbackUser) {
166
216
  const match = fallbackUser.match(/@([^\]]+)/);
167
- return match ? normalizeUserMentions([match[1]]) : "";
217
+ const matchedUser = _optionalChain([match, 'optionalAccess', _5 => _5[1]]);
218
+ return matchedUser ? normalizeUserMentions([matchedUser]) : "";
168
219
  }
169
220
  return "";
170
221
  }
@@ -178,7 +229,7 @@ function indentMarkdownBlock(lines, spaces) {
178
229
  }).join("\n");
179
230
  }
180
231
  function formatDetailBlock(detailLines) {
181
- const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimRight());
232
+ const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimEnd());
182
233
  const hasFence = trimmed.some((line) => /^\s*```/.test(line));
183
234
  const isListLike = trimmed.some((line) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(line));
184
235
  if (trimmed.length === 0) {
@@ -187,13 +238,16 @@ function formatDetailBlock(detailLines) {
187
238
  if (hasFence || isListLike) {
188
239
  return indentMarkdownBlock(trimmed, 2);
189
240
  }
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");
241
+ const paragraphs = trimmed.join("\n").split(/\n\s*\n/).filter((chunk) => chunk.trim().length > 0);
242
+ const lines = paragraphs.flatMap(
243
+ (chunk) => chunk.split("\n").map((line) => line.trim()).filter(Boolean)
244
+ );
245
+ return lines.map((text) => ` - ${text}`).join("\n");
192
246
  }
193
247
  var changelogFunctions = {
194
248
  async getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
195
249
  assertRepo(options);
196
- const { repo } = options;
250
+ const repo = options.repo;
197
251
  if (dependenciesUpdated.length === 0) {
198
252
  return "";
199
253
  }
@@ -205,7 +259,7 @@ var changelogFunctions = {
205
259
  },
206
260
  async getReleaseLine(changeset, type, options) {
207
261
  assertRepo(options);
208
- const { repo } = options;
262
+ const repo = options.repo;
209
263
  const parsedSummary = parseSummary(changeset.summary);
210
264
  const resolvedType = resolveReleaseType(type);
211
265
  const links = await resolveLinks(
@@ -217,10 +271,13 @@ var changelogFunctions = {
217
271
  parsedSummary.users,
218
272
  links.user
219
273
  );
220
- const headlineText = parsedSummary.headline || "Miscellaneous improvements";
274
+ const { text: headlineText, breaking: headlineBreaking } = parsedSummary.headline ? formatHeadline(parsedSummary.headline) : { text: "Miscellaneous improvements", breaking: false };
275
+ const isBreaking = parsedSummary.breaking || headlineBreaking;
276
+ const breakingPrefix = isBreaking ? "\u26A0\uFE0F " : "";
277
+ const icon = isBreaking ? "\u{1F4A5}" : releaseTypeMap[resolvedType].icon;
221
278
  const mainLineParts = [
222
- `- ${releaseTypeMap[resolvedType].icon}`,
223
- `**${headlineText}**`
279
+ `- ${icon}`,
280
+ `${breakingPrefix}**${headlineText}**`
224
281
  ];
225
282
  if (links.pull) {
226
283
  mainLineParts.push(links.pull);
@@ -249,4 +306,6 @@ var { getDependencyReleaseLine, getReleaseLine } = changelogFunctions;
249
306
 
250
307
 
251
308
 
252
- exports.default = index_default; exports.getDependencyReleaseLine = getDependencyReleaseLine; exports.getReleaseLine = getReleaseLine;
309
+
310
+
311
+ exports.default = index_default; exports.formatHeadline = formatHeadline; exports.getDependencyReleaseLine = getDependencyReleaseLine; exports.getReleaseLine = getReleaseLine; exports.parseConventionalHeadline = parseConventionalHeadline;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,17 @@
1
1
  import * as _changesets_types from '@changesets/types';
2
2
  import { ChangelogFunctions } from '@changesets/types';
3
3
 
4
+ interface ConventionalHeadline {
5
+ type?: string | undefined;
6
+ scope?: string | undefined;
7
+ breaking: boolean;
8
+ description: string;
9
+ }
10
+ declare function parseConventionalHeadline(raw: string): ConventionalHeadline;
11
+ declare function formatHeadline(raw: string): {
12
+ text: string;
13
+ breaking: boolean;
14
+ };
4
15
  declare const changelogFunctions: ChangelogFunctions;
5
16
 
6
17
  declare const getDependencyReleaseLine: _changesets_types.GetDependencyReleaseLine;
@@ -8,4 +19,4 @@ declare const getReleaseLine: _changesets_types.GetReleaseLine;
8
19
 
9
20
  // @ts-ignore
10
21
  export = changelogFunctions;
11
- export { getDependencyReleaseLine, getReleaseLine };
22
+ export { formatHeadline, getDependencyReleaseLine, getReleaseLine, parseConventionalHeadline };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,20 @@
1
1
  import * as _changesets_types from '@changesets/types';
2
2
  import { ChangelogFunctions } from '@changesets/types';
3
3
 
4
+ interface ConventionalHeadline {
5
+ type?: string | undefined;
6
+ scope?: string | undefined;
7
+ breaking: boolean;
8
+ description: string;
9
+ }
10
+ declare function parseConventionalHeadline(raw: string): ConventionalHeadline;
11
+ declare function formatHeadline(raw: string): {
12
+ text: string;
13
+ breaking: boolean;
14
+ };
4
15
  declare const changelogFunctions: ChangelogFunctions;
5
16
 
6
17
  declare const getDependencyReleaseLine: _changesets_types.GetDependencyReleaseLine;
7
18
  declare const getReleaseLine: _changesets_types.GetReleaseLine;
8
19
 
9
- export { changelogFunctions as default, getDependencyReleaseLine, getReleaseLine };
20
+ export { changelogFunctions as default, formatHeadline, getDependencyReleaseLine, getReleaseLine, parseConventionalHeadline };
package/dist/index.mjs CHANGED
@@ -15,7 +15,7 @@ function resolveReleaseType(type) {
15
15
  return "none";
16
16
  }
17
17
  function assertRepo(options) {
18
- if (!options || !options.repo) {
18
+ if (!options || typeof options["repo"] !== "string" || options["repo"].trim().length === 0) {
19
19
  throw new Error(
20
20
  'Please provide a repo to this changelog generator like this:\n"changelog": ["@icebreakers/changelog-github", { "repo": "org/repo" }]'
21
21
  );
@@ -34,7 +34,7 @@ async function collectDependencyReferences(changesets, repo) {
34
34
  return links.commit;
35
35
  })
36
36
  );
37
- return references.filter((link) => Boolean(link));
37
+ return [...new Set(references.filter((link) => Boolean(link)))];
38
38
  }
39
39
  function formatDependencyLine(references, dependenciesUpdated) {
40
40
  const count = dependenciesUpdated.length;
@@ -42,16 +42,53 @@ function formatDependencyLine(references, dependenciesUpdated) {
42
42
  if (count === 0) {
43
43
  return firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
44
44
  }
45
+ const dependencySummaries = dependenciesUpdated.map(
46
+ (dependency) => `\`${dependency.name}@${dependency.newVersion}\``
47
+ );
45
48
  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**";
49
+ const header2 = firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
50
50
  const details = ` \u2192 ${dependencySummaries.join(", ")}`;
51
- return `${header}
51
+ return `${header2}
52
52
  ${details}`;
53
53
  }
54
- return firstCommit ? `- \u{1F4E6} Updated ${count} dependencies ${firstCommit}` : `- \u{1F4E6} Updated ${count} dependencies`;
54
+ const header = firstCommit ? `- \u{1F4E6} Updated ${count} dependencies ${firstCommit}` : `- \u{1F4E6} Updated ${count} dependencies`;
55
+ const detailBlock = [
56
+ " <details><summary>Details</summary>",
57
+ "",
58
+ ` ${dependencySummaries.join(", ")}`,
59
+ "",
60
+ " </details>"
61
+ ].join("\n");
62
+ return `${header}
63
+ ${detailBlock}`;
64
+ }
65
+ function parseConventionalHeadline(raw) {
66
+ const match = raw.match(
67
+ /^(\w+)(?:\(([^)]*)\))?(!?):\s(.+)$/
68
+ );
69
+ if (!match) {
70
+ return { breaking: false, description: raw };
71
+ }
72
+ const type = match[1];
73
+ const scope = match[2] || void 0;
74
+ const description = (match[4] ?? raw).trim();
75
+ return {
76
+ type,
77
+ scope,
78
+ breaking: match[3] === "!",
79
+ description
80
+ };
81
+ }
82
+ function formatHeadline(raw) {
83
+ const parsed = parseConventionalHeadline(raw);
84
+ if (!parsed.type) {
85
+ return { text: raw, breaking: parsed.breaking };
86
+ }
87
+ const scopeTag = parsed.scope ? `${parsed.scope}: ` : "";
88
+ return {
89
+ text: `${scopeTag}${parsed.description}`,
90
+ breaking: parsed.breaking
91
+ };
55
92
  }
56
93
  function normalizeUserMentions(users) {
57
94
  const normalized = users.map((user) => user.trim()).filter(Boolean).map((user) => user.startsWith("@") ? user : `@${user}`);
@@ -59,16 +96,17 @@ function normalizeUserMentions(users) {
59
96
  return "";
60
97
  }
61
98
  if (normalized.length === 1) {
62
- return normalized[0];
99
+ return normalized[0] ?? "";
63
100
  }
64
101
  if (normalized.length === 2) {
65
- return `${normalized[0]} and ${normalized[1]}`;
102
+ return `${normalized[0] ?? ""} and ${normalized[1] ?? ""}`;
66
103
  }
67
- return `${normalized.slice(0, -1).join(", ")}, and ${normalized.at(-1)}`;
104
+ return `${normalized.slice(0, -1).join(", ")}, and ${normalized.at(-1) ?? ""}`;
68
105
  }
69
106
  function extractSummaryMeta(summary) {
70
107
  let prNumber;
71
108
  let commitRef;
109
+ let breaking = false;
72
110
  const users = [];
73
111
  const contentLines = summary.split(/\r?\n/).filter((line) => {
74
112
  const trimmed = line.trim();
@@ -77,7 +115,7 @@ function extractSummaryMeta(summary) {
77
115
  /^(?:pr|pull|pull\s*request):\s*#?(\d+)\s*$/i
78
116
  );
79
117
  if (prMatch) {
80
- const num = Number(prMatch[1]);
118
+ const num = Number(prMatch[1] ?? Number.NaN);
81
119
  if (!Number.isNaN(num)) {
82
120
  prNumber = num;
83
121
  }
@@ -90,16 +128,23 @@ function extractSummaryMeta(summary) {
90
128
  }
91
129
  const userMatch = withoutListMarker.match(/^(?:author|user):\s*(\S+)\s*$/i);
92
130
  if (userMatch) {
93
- users.push(userMatch[1].replace(/^@/, ""));
131
+ const user = userMatch[1];
132
+ if (user) {
133
+ users.push(user.replace(/^@/, ""));
134
+ }
94
135
  return false;
95
136
  }
137
+ if (/^BREAKING[\s-]CHANGE:/i.test(withoutListMarker)) {
138
+ breaking = true;
139
+ }
96
140
  return true;
97
- }).map((line) => line.trimRight());
141
+ }).map((line) => line.trimEnd());
98
142
  return {
99
- prNumber,
100
- commitRef,
143
+ ...prNumber !== void 0 ? { prNumber } : {},
144
+ ...commitRef !== void 0 ? { commitRef } : {},
101
145
  users,
102
- contentLines
146
+ contentLines,
147
+ breaking
103
148
  };
104
149
  }
105
150
  function parseSummary(summary) {
@@ -107,29 +152,34 @@ function parseSummary(summary) {
107
152
  prNumber,
108
153
  commitRef,
109
154
  users,
110
- contentLines
155
+ contentLines,
156
+ breaking: bodyBreaking
111
157
  } = extractSummaryMeta(summary);
112
158
  const nonEmptyIndex = contentLines.findIndex((line) => line.trim().length > 0);
113
159
  if (nonEmptyIndex === -1) {
114
160
  return {
115
161
  headline: "",
116
162
  detailLines: [],
117
- prNumber,
118
- commitRef,
119
- users
163
+ ...prNumber !== void 0 ? { prNumber } : {},
164
+ ...commitRef !== void 0 ? { commitRef } : {},
165
+ users,
166
+ breaking: bodyBreaking
120
167
  };
121
168
  }
122
- const firstContentLine = contentLines[nonEmptyIndex].trim();
169
+ const firstContentLine = contentLines[nonEmptyIndex]?.trim() ?? "";
123
170
  const startsWithList = /^(?:[-*+]\s+|\d+[.)]\s+)/.test(firstContentLine);
124
171
  const headline = startsWithList ? "" : firstContentLine;
125
172
  const restLines = startsWithList ? contentLines.slice(nonEmptyIndex) : contentLines.slice(nonEmptyIndex + 1);
126
173
  const detailLines = restLines;
174
+ const headlineParsed = headline ? parseConventionalHeadline(headline) : null;
175
+ const breaking = bodyBreaking || (headlineParsed?.breaking ?? false);
127
176
  return {
128
177
  headline,
129
178
  detailLines,
130
- prNumber,
131
- commitRef,
132
- users
179
+ ...prNumber !== void 0 ? { prNumber } : {},
180
+ ...commitRef !== void 0 ? { commitRef } : {},
181
+ users,
182
+ breaking
133
183
  };
134
184
  }
135
185
  async function resolveLinks(repo, parsed, changesetCommit) {
@@ -164,7 +214,8 @@ function buildUserMentions(users, fallbackUser) {
164
214
  }
165
215
  if (fallbackUser) {
166
216
  const match = fallbackUser.match(/@([^\]]+)/);
167
- return match ? normalizeUserMentions([match[1]]) : "";
217
+ const matchedUser = match?.[1];
218
+ return matchedUser ? normalizeUserMentions([matchedUser]) : "";
168
219
  }
169
220
  return "";
170
221
  }
@@ -178,7 +229,7 @@ function indentMarkdownBlock(lines, spaces) {
178
229
  }).join("\n");
179
230
  }
180
231
  function formatDetailBlock(detailLines) {
181
- const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimRight());
232
+ const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimEnd());
182
233
  const hasFence = trimmed.some((line) => /^\s*```/.test(line));
183
234
  const isListLike = trimmed.some((line) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(line));
184
235
  if (trimmed.length === 0) {
@@ -187,13 +238,16 @@ function formatDetailBlock(detailLines) {
187
238
  if (hasFence || isListLike) {
188
239
  return indentMarkdownBlock(trimmed, 2);
189
240
  }
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");
241
+ const paragraphs = trimmed.join("\n").split(/\n\s*\n/).filter((chunk) => chunk.trim().length > 0);
242
+ const lines = paragraphs.flatMap(
243
+ (chunk) => chunk.split("\n").map((line) => line.trim()).filter(Boolean)
244
+ );
245
+ return lines.map((text) => ` - ${text}`).join("\n");
192
246
  }
193
247
  var changelogFunctions = {
194
248
  async getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
195
249
  assertRepo(options);
196
- const { repo } = options;
250
+ const repo = options.repo;
197
251
  if (dependenciesUpdated.length === 0) {
198
252
  return "";
199
253
  }
@@ -205,7 +259,7 @@ var changelogFunctions = {
205
259
  },
206
260
  async getReleaseLine(changeset, type, options) {
207
261
  assertRepo(options);
208
- const { repo } = options;
262
+ const repo = options.repo;
209
263
  const parsedSummary = parseSummary(changeset.summary);
210
264
  const resolvedType = resolveReleaseType(type);
211
265
  const links = await resolveLinks(
@@ -217,10 +271,13 @@ var changelogFunctions = {
217
271
  parsedSummary.users,
218
272
  links.user
219
273
  );
220
- const headlineText = parsedSummary.headline || "Miscellaneous improvements";
274
+ const { text: headlineText, breaking: headlineBreaking } = parsedSummary.headline ? formatHeadline(parsedSummary.headline) : { text: "Miscellaneous improvements", breaking: false };
275
+ const isBreaking = parsedSummary.breaking || headlineBreaking;
276
+ const breakingPrefix = isBreaking ? "\u26A0\uFE0F " : "";
277
+ const icon = isBreaking ? "\u{1F4A5}" : releaseTypeMap[resolvedType].icon;
221
278
  const mainLineParts = [
222
- `- ${releaseTypeMap[resolvedType].icon}`,
223
- `**${headlineText}**`
279
+ `- ${icon}`,
280
+ `${breakingPrefix}**${headlineText}**`
224
281
  ];
225
282
  if (links.pull) {
226
283
  mainLineParts.push(links.pull);
@@ -247,6 +304,8 @@ var index_default = changelogFunctions;
247
304
  var { getDependencyReleaseLine, getReleaseLine } = changelogFunctions;
248
305
  export {
249
306
  index_default as default,
307
+ formatHeadline,
250
308
  getDependencyReleaseLine,
251
- getReleaseLine
309
+ getReleaseLine,
310
+ parseConventionalHeadline
252
311
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@icebreakers/changelog-github",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.2.1",
5
5
  "description": "Changesets GitHub changelog formatter for Icebreaker's dev-configs",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",
@@ -34,12 +34,12 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@changesets/get-github-info": "^0.7.0",
37
+ "@changesets/get-github-info": "^0.8.0",
38
38
  "@changesets/types": "^6.1.0",
39
- "dotenv": "^17.2.4"
39
+ "dotenv": "^17.3.1"
40
40
  },
41
41
  "devDependencies": {
42
- "@changesets/parse": "^0.4.2"
42
+ "@changesets/parse": "^0.4.3"
43
43
  },
44
44
  "scripts": {
45
45
  "stub": "unbuild --stub",
@@ -49,6 +49,7 @@
49
49
  "test:dev": "vitest",
50
50
  "release": "pnpm publish",
51
51
  "lint": "eslint .",
52
- "lint:fix": "eslint . --fix"
52
+ "lint:fix": "eslint . --fix",
53
+ "typecheck": "tsc --noEmit"
53
54
  }
54
55
  }