@icebreakers/changelog-github 0.2.1 → 1.0.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/dist/index.cjs +220 -283
- package/dist/index.d.cts +12 -15
- package/dist/index.d.ts +12 -13
- package/dist/index.js +241 -0
- package/package.json +16 -7
- package/dist/index.mjs +0 -311
package/dist/index.cjs
CHANGED
|
@@ -1,311 +1,248 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
let _changesets_get_github_info = require("@changesets/get-github-info");
|
|
6
|
+
//#region src/index.ts
|
|
7
|
+
(0, require("dotenv").config)();
|
|
8
|
+
const releaseTypeMap = {
|
|
9
|
+
major: {
|
|
10
|
+
icon: "🚀",
|
|
11
|
+
label: "Major"
|
|
12
|
+
},
|
|
13
|
+
minor: {
|
|
14
|
+
icon: "✨",
|
|
15
|
+
label: "Minor"
|
|
16
|
+
},
|
|
17
|
+
patch: {
|
|
18
|
+
icon: "🐛",
|
|
19
|
+
label: "Patch"
|
|
20
|
+
},
|
|
21
|
+
none: {
|
|
22
|
+
icon: "📝",
|
|
23
|
+
label: "Update"
|
|
24
|
+
}
|
|
10
25
|
};
|
|
26
|
+
const CONVENTIONAL_HEADLINE_RE = /^(\w+)(?:\(([^)]*)\))?(!?):\s(.+)$/;
|
|
27
|
+
const NEWLINE_RE = /\r?\n/;
|
|
28
|
+
const LIST_MARKER_RE = /^[-*+]\s+/;
|
|
29
|
+
const PR_META_RE = /^(?:pr|pull|pull\s*request):\s*#?(\d+)\s*$/i;
|
|
30
|
+
const COMMIT_META_RE = /^commit:\s*(\S+)\s*$/i;
|
|
31
|
+
const USER_META_RE = /^(?:author|user):\s*(\S+)\s*$/i;
|
|
32
|
+
const LEADING_AT_RE = /^@/;
|
|
33
|
+
const BREAKING_CHANGE_RE = /^BREAKING[\s-]CHANGE:/i;
|
|
34
|
+
const LIST_LINE_RE = /^(?:[-*+]\s+|\d+[.)]\s+)/;
|
|
35
|
+
const FALLBACK_USER_RE = /@([^\]]+)/;
|
|
36
|
+
const CODE_FENCE_RE = /^\s*```/;
|
|
37
|
+
const LIST_LIKE_RE = /^\s*(?:[-*+]\s+|\d+[.)]\s+)/;
|
|
38
|
+
const PARAGRAPH_BREAK_RE = /\n\s*\n/;
|
|
11
39
|
function resolveReleaseType(type) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
return "none";
|
|
40
|
+
if (type && type in releaseTypeMap) return type;
|
|
41
|
+
return "none";
|
|
16
42
|
}
|
|
17
43
|
function assertRepo(options) {
|
|
18
|
-
|
|
19
|
-
throw new Error(
|
|
20
|
-
'Please provide a repo to this changelog generator like this:\n"changelog": ["@icebreakers/changelog-github", { "repo": "org/repo" }]'
|
|
21
|
-
);
|
|
22
|
-
}
|
|
44
|
+
if (!options || typeof options["repo"] !== "string" || options["repo"].trim().length === 0) throw new Error("Please provide a repo to this changelog generator like this:\n\"changelog\": [\"@icebreakers/changelog-github\", { \"repo\": \"org/repo\" }]");
|
|
23
45
|
}
|
|
24
46
|
async function collectDependencyReferences(changesets, repo) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return links.commit;
|
|
35
|
-
})
|
|
36
|
-
);
|
|
37
|
-
return [...new Set(references.filter((link) => Boolean(link)))];
|
|
47
|
+
const references = await Promise.all(changesets.map(async (cs) => {
|
|
48
|
+
if (!cs.commit) return null;
|
|
49
|
+
const { links } = await (0, _changesets_get_github_info.getInfo)({
|
|
50
|
+
repo,
|
|
51
|
+
commit: cs.commit
|
|
52
|
+
});
|
|
53
|
+
return links.commit;
|
|
54
|
+
}));
|
|
55
|
+
return [...new Set(references.filter((link) => Boolean(link)))];
|
|
38
56
|
}
|
|
39
57
|
function formatDependencyLine(references, dependenciesUpdated) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
${details}`;
|
|
53
|
-
}
|
|
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}`;
|
|
58
|
+
const count = dependenciesUpdated.length;
|
|
59
|
+
const firstCommit = references[0] || "";
|
|
60
|
+
if (count === 0) return firstCommit ? `- 📦 **Dependencies** ${firstCommit}` : "- 📦 **Dependencies**";
|
|
61
|
+
const dependencySummaries = dependenciesUpdated.map((dependency) => `\`${dependency.name}@${dependency.newVersion}\``);
|
|
62
|
+
if (count <= 3) return `${firstCommit ? `- 📦 **Dependencies** ${firstCommit}` : "- 📦 **Dependencies**"}\n${` → ${dependencySummaries.join(", ")}`}`;
|
|
63
|
+
return `${firstCommit ? `- 📦 Updated ${count} dependencies ${firstCommit}` : `- 📦 Updated ${count} dependencies`}\n${[
|
|
64
|
+
" <details><summary>Details</summary>",
|
|
65
|
+
"",
|
|
66
|
+
` ${dependencySummaries.join(", ")}`,
|
|
67
|
+
"",
|
|
68
|
+
" </details>"
|
|
69
|
+
].join("\n")}`;
|
|
64
70
|
}
|
|
65
71
|
function parseConventionalHeadline(raw) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
};
|
|
72
|
+
const match = raw.match(CONVENTIONAL_HEADLINE_RE);
|
|
73
|
+
if (!match) return {
|
|
74
|
+
breaking: false,
|
|
75
|
+
description: raw
|
|
76
|
+
};
|
|
77
|
+
const type = match[1];
|
|
78
|
+
const scope = match[2] || void 0;
|
|
79
|
+
const description = (match[4] ?? raw).trim();
|
|
80
|
+
return {
|
|
81
|
+
type,
|
|
82
|
+
scope,
|
|
83
|
+
breaking: match[3] === "!",
|
|
84
|
+
description
|
|
85
|
+
};
|
|
81
86
|
}
|
|
82
87
|
function formatHeadline(raw) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
const parsed = parseConventionalHeadline(raw);
|
|
89
|
+
if (!parsed.type) return {
|
|
90
|
+
text: raw,
|
|
91
|
+
breaking: parsed.breaking
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
text: `${parsed.scope ? `${parsed.scope}: ` : ""}${parsed.description}`,
|
|
95
|
+
breaking: parsed.breaking
|
|
96
|
+
};
|
|
92
97
|
}
|
|
93
98
|
function normalizeUserMentions(users) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return _nullishCoalesce(normalized[0], () => ( ""));
|
|
100
|
-
}
|
|
101
|
-
if (normalized.length === 2) {
|
|
102
|
-
return `${_nullishCoalesce(normalized[0], () => ( ""))} and ${_nullishCoalesce(normalized[1], () => ( ""))}`;
|
|
103
|
-
}
|
|
104
|
-
return `${normalized.slice(0, -1).join(", ")}, and ${_nullishCoalesce(normalized.at(-1), () => ( ""))}`;
|
|
99
|
+
const normalized = users.map((user) => user.trim()).filter(Boolean).map((user) => user.startsWith("@") ? user : `@${user}`);
|
|
100
|
+
if (normalized.length === 0) return "";
|
|
101
|
+
if (normalized.length === 1) return normalized[0] ?? "";
|
|
102
|
+
if (normalized.length === 2) return `${normalized[0] ?? ""} and ${normalized[1] ?? ""}`;
|
|
103
|
+
return `${normalized.slice(0, -1).join(", ")}, and ${normalized.at(-1) ?? ""}`;
|
|
105
104
|
}
|
|
106
105
|
function extractSummaryMeta(summary) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return true;
|
|
141
|
-
}).map((line) => line.trimEnd());
|
|
142
|
-
return {
|
|
143
|
-
...prNumber !== void 0 ? { prNumber } : {},
|
|
144
|
-
...commitRef !== void 0 ? { commitRef } : {},
|
|
145
|
-
users,
|
|
146
|
-
contentLines,
|
|
147
|
-
breaking
|
|
148
|
-
};
|
|
106
|
+
let prNumber;
|
|
107
|
+
let commitRef;
|
|
108
|
+
let breaking = false;
|
|
109
|
+
const users = [];
|
|
110
|
+
const contentLines = summary.split(NEWLINE_RE).filter((line) => {
|
|
111
|
+
const withoutListMarker = line.trim().replace(LIST_MARKER_RE, "");
|
|
112
|
+
const prMatch = withoutListMarker.match(PR_META_RE);
|
|
113
|
+
if (prMatch) {
|
|
114
|
+
const num = Number(prMatch[1] ?? NaN);
|
|
115
|
+
if (!Number.isNaN(num)) prNumber = num;
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const commitMatch = withoutListMarker.match(COMMIT_META_RE);
|
|
119
|
+
if (commitMatch) {
|
|
120
|
+
commitRef = commitMatch[1];
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const userMatch = withoutListMarker.match(USER_META_RE);
|
|
124
|
+
if (userMatch) {
|
|
125
|
+
const user = userMatch[1];
|
|
126
|
+
if (user) users.push(user.replace(LEADING_AT_RE, ""));
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (BREAKING_CHANGE_RE.test(withoutListMarker)) breaking = true;
|
|
130
|
+
return true;
|
|
131
|
+
}).map((line) => line.trimEnd());
|
|
132
|
+
return {
|
|
133
|
+
...prNumber !== void 0 ? { prNumber } : {},
|
|
134
|
+
...commitRef !== void 0 ? { commitRef } : {},
|
|
135
|
+
users,
|
|
136
|
+
contentLines,
|
|
137
|
+
breaking
|
|
138
|
+
};
|
|
149
139
|
}
|
|
150
140
|
function parseSummary(summary) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const breaking = bodyBreaking || (_nullishCoalesce(_optionalChain([headlineParsed, 'optionalAccess', _4 => _4.breaking]), () => ( false)));
|
|
176
|
-
return {
|
|
177
|
-
headline,
|
|
178
|
-
detailLines,
|
|
179
|
-
...prNumber !== void 0 ? { prNumber } : {},
|
|
180
|
-
...commitRef !== void 0 ? { commitRef } : {},
|
|
181
|
-
users,
|
|
182
|
-
breaking
|
|
183
|
-
};
|
|
141
|
+
const { prNumber, commitRef, users, contentLines, breaking: bodyBreaking } = extractSummaryMeta(summary);
|
|
142
|
+
const nonEmptyIndex = contentLines.findIndex((line) => line.trim().length > 0);
|
|
143
|
+
if (nonEmptyIndex === -1) return {
|
|
144
|
+
headline: "",
|
|
145
|
+
detailLines: [],
|
|
146
|
+
...prNumber !== void 0 ? { prNumber } : {},
|
|
147
|
+
...commitRef !== void 0 ? { commitRef } : {},
|
|
148
|
+
users,
|
|
149
|
+
breaking: bodyBreaking
|
|
150
|
+
};
|
|
151
|
+
const firstContentLine = contentLines[nonEmptyIndex]?.trim() ?? "";
|
|
152
|
+
const startsWithList = LIST_LINE_RE.test(firstContentLine);
|
|
153
|
+
const headline = startsWithList ? "" : firstContentLine;
|
|
154
|
+
const detailLines = startsWithList ? contentLines.slice(nonEmptyIndex) : contentLines.slice(nonEmptyIndex + 1);
|
|
155
|
+
const headlineParsed = headline ? parseConventionalHeadline(headline) : null;
|
|
156
|
+
const breaking = bodyBreaking || (headlineParsed?.breaking ?? false);
|
|
157
|
+
return {
|
|
158
|
+
headline,
|
|
159
|
+
detailLines,
|
|
160
|
+
...prNumber !== void 0 ? { prNumber } : {},
|
|
161
|
+
...commitRef !== void 0 ? { commitRef } : {},
|
|
162
|
+
users,
|
|
163
|
+
breaking
|
|
164
|
+
};
|
|
184
165
|
}
|
|
185
166
|
async function resolveLinks(repo, parsed, changesetCommit) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
pull: null,
|
|
208
|
-
user: null
|
|
209
|
-
};
|
|
167
|
+
if (parsed.prNumber !== void 0) {
|
|
168
|
+
const { links } = await (0, _changesets_get_github_info.getInfoFromPullRequest)({
|
|
169
|
+
repo,
|
|
170
|
+
pull: parsed.prNumber
|
|
171
|
+
});
|
|
172
|
+
if (parsed.commitRef) links.commit = `[\`${parsed.commitRef.slice(0, 7)}\`](https://github.com/${repo}/commit/${parsed.commitRef})`;
|
|
173
|
+
return links;
|
|
174
|
+
}
|
|
175
|
+
const commitToUse = parsed.commitRef ?? changesetCommit;
|
|
176
|
+
if (commitToUse) {
|
|
177
|
+
const { links } = await (0, _changesets_get_github_info.getInfo)({
|
|
178
|
+
repo,
|
|
179
|
+
commit: commitToUse
|
|
180
|
+
});
|
|
181
|
+
return links;
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
commit: null,
|
|
185
|
+
pull: null,
|
|
186
|
+
user: null
|
|
187
|
+
};
|
|
210
188
|
}
|
|
211
189
|
function buildUserMentions(users, fallbackUser) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return matchedUser ? normalizeUserMentions([matchedUser]) : "";
|
|
219
|
-
}
|
|
220
|
-
return "";
|
|
190
|
+
if (users.length > 0) return normalizeUserMentions(users);
|
|
191
|
+
if (fallbackUser) {
|
|
192
|
+
const matchedUser = fallbackUser.match(FALLBACK_USER_RE)?.[1];
|
|
193
|
+
return matchedUser ? normalizeUserMentions([matchedUser]) : "";
|
|
194
|
+
}
|
|
195
|
+
return "";
|
|
221
196
|
}
|
|
222
197
|
function indentMarkdownBlock(lines, spaces) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return `${prefix}${line}`;
|
|
229
|
-
}).join("\n");
|
|
198
|
+
const prefix = " ".repeat(spaces);
|
|
199
|
+
return lines.map((line) => {
|
|
200
|
+
if (line.trim().length === 0) return "";
|
|
201
|
+
return `${prefix}${line}`;
|
|
202
|
+
}).join("\n");
|
|
230
203
|
}
|
|
231
204
|
function formatDetailBlock(detailLines) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
if (hasFence || isListLike) {
|
|
239
|
-
return indentMarkdownBlock(trimmed, 2);
|
|
240
|
-
}
|
|
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");
|
|
205
|
+
const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimEnd());
|
|
206
|
+
const hasFence = trimmed.some((line) => CODE_FENCE_RE.test(line));
|
|
207
|
+
const isListLike = trimmed.some((line) => LIST_LIKE_RE.test(line));
|
|
208
|
+
if (trimmed.length === 0) return "";
|
|
209
|
+
if (hasFence || isListLike) return indentMarkdownBlock(trimmed, 2);
|
|
210
|
+
return trimmed.join("\n").split(PARAGRAPH_BREAK_RE).filter((chunk) => chunk.trim().length > 0).flatMap((chunk) => chunk.split("\n").map((line) => line.trim()).filter(Boolean)).map((text) => ` - ${text}`).join("\n");
|
|
246
211
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const breakingPrefix = isBreaking ? "\u26A0\uFE0F " : "";
|
|
277
|
-
const icon = isBreaking ? "\u{1F4A5}" : releaseTypeMap[resolvedType].icon;
|
|
278
|
-
const mainLineParts = [
|
|
279
|
-
`- ${icon}`,
|
|
280
|
-
`${breakingPrefix}**${headlineText}**`
|
|
281
|
-
];
|
|
282
|
-
if (links.pull) {
|
|
283
|
-
mainLineParts.push(links.pull);
|
|
284
|
-
} else if (links.commit) {
|
|
285
|
-
mainLineParts.push(links.commit);
|
|
286
|
-
}
|
|
287
|
-
if (links.pull && parsedSummary.commitRef && links.commit) {
|
|
288
|
-
mainLineParts.push(links.commit);
|
|
289
|
-
}
|
|
290
|
-
if (userMentions) {
|
|
291
|
-
mainLineParts.push(`by ${userMentions}`);
|
|
292
|
-
}
|
|
293
|
-
const mainLine = mainLineParts.join(" ");
|
|
294
|
-
const detailBlock = formatDetailBlock(parsedSummary.detailLines);
|
|
295
|
-
return detailBlock.length > 0 ? `
|
|
296
|
-
|
|
297
|
-
${mainLine}
|
|
298
|
-
${detailBlock}` : `
|
|
299
|
-
|
|
300
|
-
${mainLine}`;
|
|
301
|
-
}
|
|
212
|
+
const changelogFunctions = {
|
|
213
|
+
async getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
|
|
214
|
+
assertRepo(options);
|
|
215
|
+
const repo = options.repo;
|
|
216
|
+
if (dependenciesUpdated.length === 0) return "";
|
|
217
|
+
return formatDependencyLine(await collectDependencyReferences(changesets, repo), dependenciesUpdated);
|
|
218
|
+
},
|
|
219
|
+
async getReleaseLine(changeset, type, options) {
|
|
220
|
+
assertRepo(options);
|
|
221
|
+
const repo = options.repo;
|
|
222
|
+
const parsedSummary = parseSummary(changeset.summary);
|
|
223
|
+
const resolvedType = resolveReleaseType(type);
|
|
224
|
+
const links = await resolveLinks(repo, parsedSummary, changeset.commit);
|
|
225
|
+
const userMentions = buildUserMentions(parsedSummary.users, links.user);
|
|
226
|
+
const { text: headlineText, breaking: headlineBreaking } = parsedSummary.headline ? formatHeadline(parsedSummary.headline) : {
|
|
227
|
+
text: "Miscellaneous improvements",
|
|
228
|
+
breaking: false
|
|
229
|
+
};
|
|
230
|
+
const isBreaking = parsedSummary.breaking || headlineBreaking;
|
|
231
|
+
const breakingPrefix = isBreaking ? "⚠️ " : "";
|
|
232
|
+
const mainLineParts = [`- ${isBreaking ? "💥" : releaseTypeMap[resolvedType].icon}`, `${breakingPrefix}**${headlineText}**`];
|
|
233
|
+
if (links.pull) mainLineParts.push(links.pull);
|
|
234
|
+
else if (links.commit) mainLineParts.push(links.commit);
|
|
235
|
+
if (links.pull && parsedSummary.commitRef && links.commit) mainLineParts.push(links.commit);
|
|
236
|
+
if (userMentions) mainLineParts.push(`by ${userMentions}`);
|
|
237
|
+
const mainLine = mainLineParts.join(" ");
|
|
238
|
+
const detailBlock = formatDetailBlock(parsedSummary.detailLines);
|
|
239
|
+
return detailBlock.length > 0 ? `\n\n${mainLine}\n${detailBlock}` : `\n\n${mainLine}`;
|
|
240
|
+
}
|
|
302
241
|
};
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
exports.default = index_default; exports.formatHeadline = formatHeadline; exports.getDependencyReleaseLine = getDependencyReleaseLine; exports.getReleaseLine = getReleaseLine; exports.parseConventionalHeadline = parseConventionalHeadline;
|
|
242
|
+
const { getDependencyReleaseLine, getReleaseLine } = changelogFunctions;
|
|
243
|
+
//#endregion
|
|
244
|
+
exports.default = changelogFunctions;
|
|
245
|
+
exports.formatHeadline = formatHeadline;
|
|
246
|
+
exports.getDependencyReleaseLine = getDependencyReleaseLine;
|
|
247
|
+
exports.getReleaseLine = getReleaseLine;
|
|
248
|
+
exports.parseConventionalHeadline = parseConventionalHeadline;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { ChangelogFunctions } from
|
|
1
|
+
import * as _$_changesets_types0 from "@changesets/types";
|
|
2
|
+
import { ChangelogFunctions } from "@changesets/types";
|
|
3
3
|
|
|
4
|
+
//#region src/index.d.ts
|
|
4
5
|
interface ConventionalHeadline {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
type?: string | undefined;
|
|
7
|
+
scope?: string | undefined;
|
|
8
|
+
breaking: boolean;
|
|
9
|
+
description: string;
|
|
9
10
|
}
|
|
10
11
|
declare function parseConventionalHeadline(raw: string): ConventionalHeadline;
|
|
11
12
|
declare function formatHeadline(raw: string): {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
text: string;
|
|
14
|
+
breaking: boolean;
|
|
14
15
|
};
|
|
15
16
|
declare const changelogFunctions: ChangelogFunctions;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// @ts-ignore
|
|
21
|
-
export = changelogFunctions;
|
|
22
|
-
export { formatHeadline, getDependencyReleaseLine, getReleaseLine, parseConventionalHeadline };
|
|
17
|
+
declare const getDependencyReleaseLine: _$_changesets_types0.GetDependencyReleaseLine, getReleaseLine: _$_changesets_types0.GetReleaseLine;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { changelogFunctions as default, formatHeadline, getDependencyReleaseLine, getReleaseLine, parseConventionalHeadline };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { ChangelogFunctions } from
|
|
1
|
+
import * as _$_changesets_types0 from "@changesets/types";
|
|
2
|
+
import { ChangelogFunctions } from "@changesets/types";
|
|
3
3
|
|
|
4
|
+
//#region src/index.d.ts
|
|
4
5
|
interface ConventionalHeadline {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
type?: string | undefined;
|
|
7
|
+
scope?: string | undefined;
|
|
8
|
+
breaking: boolean;
|
|
9
|
+
description: string;
|
|
9
10
|
}
|
|
10
11
|
declare function parseConventionalHeadline(raw: string): ConventionalHeadline;
|
|
11
12
|
declare function formatHeadline(raw: string): {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
text: string;
|
|
14
|
+
breaking: boolean;
|
|
14
15
|
};
|
|
15
16
|
declare const changelogFunctions: ChangelogFunctions;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export { changelogFunctions as default, formatHeadline, getDependencyReleaseLine, getReleaseLine, parseConventionalHeadline };
|
|
17
|
+
declare const getDependencyReleaseLine: _$_changesets_types0.GetDependencyReleaseLine, getReleaseLine: _$_changesets_types0.GetReleaseLine;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { changelogFunctions as default, formatHeadline, getDependencyReleaseLine, getReleaseLine, parseConventionalHeadline };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { getInfo, getInfoFromPullRequest } from "@changesets/get-github-info";
|
|
2
|
+
import { config } from "dotenv";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
config();
|
|
5
|
+
const releaseTypeMap = {
|
|
6
|
+
major: {
|
|
7
|
+
icon: "🚀",
|
|
8
|
+
label: "Major"
|
|
9
|
+
},
|
|
10
|
+
minor: {
|
|
11
|
+
icon: "✨",
|
|
12
|
+
label: "Minor"
|
|
13
|
+
},
|
|
14
|
+
patch: {
|
|
15
|
+
icon: "🐛",
|
|
16
|
+
label: "Patch"
|
|
17
|
+
},
|
|
18
|
+
none: {
|
|
19
|
+
icon: "📝",
|
|
20
|
+
label: "Update"
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const CONVENTIONAL_HEADLINE_RE = /^(\w+)(?:\(([^)]*)\))?(!?):\s(.+)$/;
|
|
24
|
+
const NEWLINE_RE = /\r?\n/;
|
|
25
|
+
const LIST_MARKER_RE = /^[-*+]\s+/;
|
|
26
|
+
const PR_META_RE = /^(?:pr|pull|pull\s*request):\s*#?(\d+)\s*$/i;
|
|
27
|
+
const COMMIT_META_RE = /^commit:\s*(\S+)\s*$/i;
|
|
28
|
+
const USER_META_RE = /^(?:author|user):\s*(\S+)\s*$/i;
|
|
29
|
+
const LEADING_AT_RE = /^@/;
|
|
30
|
+
const BREAKING_CHANGE_RE = /^BREAKING[\s-]CHANGE:/i;
|
|
31
|
+
const LIST_LINE_RE = /^(?:[-*+]\s+|\d+[.)]\s+)/;
|
|
32
|
+
const FALLBACK_USER_RE = /@([^\]]+)/;
|
|
33
|
+
const CODE_FENCE_RE = /^\s*```/;
|
|
34
|
+
const LIST_LIKE_RE = /^\s*(?:[-*+]\s+|\d+[.)]\s+)/;
|
|
35
|
+
const PARAGRAPH_BREAK_RE = /\n\s*\n/;
|
|
36
|
+
function resolveReleaseType(type) {
|
|
37
|
+
if (type && type in releaseTypeMap) return type;
|
|
38
|
+
return "none";
|
|
39
|
+
}
|
|
40
|
+
function assertRepo(options) {
|
|
41
|
+
if (!options || typeof options["repo"] !== "string" || options["repo"].trim().length === 0) throw new Error("Please provide a repo to this changelog generator like this:\n\"changelog\": [\"@icebreakers/changelog-github\", { \"repo\": \"org/repo\" }]");
|
|
42
|
+
}
|
|
43
|
+
async function collectDependencyReferences(changesets, repo) {
|
|
44
|
+
const references = await Promise.all(changesets.map(async (cs) => {
|
|
45
|
+
if (!cs.commit) return null;
|
|
46
|
+
const { links } = await getInfo({
|
|
47
|
+
repo,
|
|
48
|
+
commit: cs.commit
|
|
49
|
+
});
|
|
50
|
+
return links.commit;
|
|
51
|
+
}));
|
|
52
|
+
return [...new Set(references.filter((link) => Boolean(link)))];
|
|
53
|
+
}
|
|
54
|
+
function formatDependencyLine(references, dependenciesUpdated) {
|
|
55
|
+
const count = dependenciesUpdated.length;
|
|
56
|
+
const firstCommit = references[0] || "";
|
|
57
|
+
if (count === 0) return firstCommit ? `- 📦 **Dependencies** ${firstCommit}` : "- 📦 **Dependencies**";
|
|
58
|
+
const dependencySummaries = dependenciesUpdated.map((dependency) => `\`${dependency.name}@${dependency.newVersion}\``);
|
|
59
|
+
if (count <= 3) return `${firstCommit ? `- 📦 **Dependencies** ${firstCommit}` : "- 📦 **Dependencies**"}\n${` → ${dependencySummaries.join(", ")}`}`;
|
|
60
|
+
return `${firstCommit ? `- 📦 Updated ${count} dependencies ${firstCommit}` : `- 📦 Updated ${count} dependencies`}\n${[
|
|
61
|
+
" <details><summary>Details</summary>",
|
|
62
|
+
"",
|
|
63
|
+
` ${dependencySummaries.join(", ")}`,
|
|
64
|
+
"",
|
|
65
|
+
" </details>"
|
|
66
|
+
].join("\n")}`;
|
|
67
|
+
}
|
|
68
|
+
function parseConventionalHeadline(raw) {
|
|
69
|
+
const match = raw.match(CONVENTIONAL_HEADLINE_RE);
|
|
70
|
+
if (!match) return {
|
|
71
|
+
breaking: false,
|
|
72
|
+
description: raw
|
|
73
|
+
};
|
|
74
|
+
const type = match[1];
|
|
75
|
+
const scope = match[2] || void 0;
|
|
76
|
+
const description = (match[4] ?? raw).trim();
|
|
77
|
+
return {
|
|
78
|
+
type,
|
|
79
|
+
scope,
|
|
80
|
+
breaking: match[3] === "!",
|
|
81
|
+
description
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function formatHeadline(raw) {
|
|
85
|
+
const parsed = parseConventionalHeadline(raw);
|
|
86
|
+
if (!parsed.type) return {
|
|
87
|
+
text: raw,
|
|
88
|
+
breaking: parsed.breaking
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
text: `${parsed.scope ? `${parsed.scope}: ` : ""}${parsed.description}`,
|
|
92
|
+
breaking: parsed.breaking
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function normalizeUserMentions(users) {
|
|
96
|
+
const normalized = users.map((user) => user.trim()).filter(Boolean).map((user) => user.startsWith("@") ? user : `@${user}`);
|
|
97
|
+
if (normalized.length === 0) return "";
|
|
98
|
+
if (normalized.length === 1) return normalized[0] ?? "";
|
|
99
|
+
if (normalized.length === 2) return `${normalized[0] ?? ""} and ${normalized[1] ?? ""}`;
|
|
100
|
+
return `${normalized.slice(0, -1).join(", ")}, and ${normalized.at(-1) ?? ""}`;
|
|
101
|
+
}
|
|
102
|
+
function extractSummaryMeta(summary) {
|
|
103
|
+
let prNumber;
|
|
104
|
+
let commitRef;
|
|
105
|
+
let breaking = false;
|
|
106
|
+
const users = [];
|
|
107
|
+
const contentLines = summary.split(NEWLINE_RE).filter((line) => {
|
|
108
|
+
const withoutListMarker = line.trim().replace(LIST_MARKER_RE, "");
|
|
109
|
+
const prMatch = withoutListMarker.match(PR_META_RE);
|
|
110
|
+
if (prMatch) {
|
|
111
|
+
const num = Number(prMatch[1] ?? NaN);
|
|
112
|
+
if (!Number.isNaN(num)) prNumber = num;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const commitMatch = withoutListMarker.match(COMMIT_META_RE);
|
|
116
|
+
if (commitMatch) {
|
|
117
|
+
commitRef = commitMatch[1];
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const userMatch = withoutListMarker.match(USER_META_RE);
|
|
121
|
+
if (userMatch) {
|
|
122
|
+
const user = userMatch[1];
|
|
123
|
+
if (user) users.push(user.replace(LEADING_AT_RE, ""));
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (BREAKING_CHANGE_RE.test(withoutListMarker)) breaking = true;
|
|
127
|
+
return true;
|
|
128
|
+
}).map((line) => line.trimEnd());
|
|
129
|
+
return {
|
|
130
|
+
...prNumber !== void 0 ? { prNumber } : {},
|
|
131
|
+
...commitRef !== void 0 ? { commitRef } : {},
|
|
132
|
+
users,
|
|
133
|
+
contentLines,
|
|
134
|
+
breaking
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function parseSummary(summary) {
|
|
138
|
+
const { prNumber, commitRef, users, contentLines, breaking: bodyBreaking } = extractSummaryMeta(summary);
|
|
139
|
+
const nonEmptyIndex = contentLines.findIndex((line) => line.trim().length > 0);
|
|
140
|
+
if (nonEmptyIndex === -1) return {
|
|
141
|
+
headline: "",
|
|
142
|
+
detailLines: [],
|
|
143
|
+
...prNumber !== void 0 ? { prNumber } : {},
|
|
144
|
+
...commitRef !== void 0 ? { commitRef } : {},
|
|
145
|
+
users,
|
|
146
|
+
breaking: bodyBreaking
|
|
147
|
+
};
|
|
148
|
+
const firstContentLine = contentLines[nonEmptyIndex]?.trim() ?? "";
|
|
149
|
+
const startsWithList = LIST_LINE_RE.test(firstContentLine);
|
|
150
|
+
const headline = startsWithList ? "" : firstContentLine;
|
|
151
|
+
const detailLines = startsWithList ? contentLines.slice(nonEmptyIndex) : contentLines.slice(nonEmptyIndex + 1);
|
|
152
|
+
const headlineParsed = headline ? parseConventionalHeadline(headline) : null;
|
|
153
|
+
const breaking = bodyBreaking || (headlineParsed?.breaking ?? false);
|
|
154
|
+
return {
|
|
155
|
+
headline,
|
|
156
|
+
detailLines,
|
|
157
|
+
...prNumber !== void 0 ? { prNumber } : {},
|
|
158
|
+
...commitRef !== void 0 ? { commitRef } : {},
|
|
159
|
+
users,
|
|
160
|
+
breaking
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function resolveLinks(repo, parsed, changesetCommit) {
|
|
164
|
+
if (parsed.prNumber !== void 0) {
|
|
165
|
+
const { links } = await getInfoFromPullRequest({
|
|
166
|
+
repo,
|
|
167
|
+
pull: parsed.prNumber
|
|
168
|
+
});
|
|
169
|
+
if (parsed.commitRef) links.commit = `[\`${parsed.commitRef.slice(0, 7)}\`](https://github.com/${repo}/commit/${parsed.commitRef})`;
|
|
170
|
+
return links;
|
|
171
|
+
}
|
|
172
|
+
const commitToUse = parsed.commitRef ?? changesetCommit;
|
|
173
|
+
if (commitToUse) {
|
|
174
|
+
const { links } = await getInfo({
|
|
175
|
+
repo,
|
|
176
|
+
commit: commitToUse
|
|
177
|
+
});
|
|
178
|
+
return links;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
commit: null,
|
|
182
|
+
pull: null,
|
|
183
|
+
user: null
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function buildUserMentions(users, fallbackUser) {
|
|
187
|
+
if (users.length > 0) return normalizeUserMentions(users);
|
|
188
|
+
if (fallbackUser) {
|
|
189
|
+
const matchedUser = fallbackUser.match(FALLBACK_USER_RE)?.[1];
|
|
190
|
+
return matchedUser ? normalizeUserMentions([matchedUser]) : "";
|
|
191
|
+
}
|
|
192
|
+
return "";
|
|
193
|
+
}
|
|
194
|
+
function indentMarkdownBlock(lines, spaces) {
|
|
195
|
+
const prefix = " ".repeat(spaces);
|
|
196
|
+
return lines.map((line) => {
|
|
197
|
+
if (line.trim().length === 0) return "";
|
|
198
|
+
return `${prefix}${line}`;
|
|
199
|
+
}).join("\n");
|
|
200
|
+
}
|
|
201
|
+
function formatDetailBlock(detailLines) {
|
|
202
|
+
const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimEnd());
|
|
203
|
+
const hasFence = trimmed.some((line) => CODE_FENCE_RE.test(line));
|
|
204
|
+
const isListLike = trimmed.some((line) => LIST_LIKE_RE.test(line));
|
|
205
|
+
if (trimmed.length === 0) return "";
|
|
206
|
+
if (hasFence || isListLike) return indentMarkdownBlock(trimmed, 2);
|
|
207
|
+
return trimmed.join("\n").split(PARAGRAPH_BREAK_RE).filter((chunk) => chunk.trim().length > 0).flatMap((chunk) => chunk.split("\n").map((line) => line.trim()).filter(Boolean)).map((text) => ` - ${text}`).join("\n");
|
|
208
|
+
}
|
|
209
|
+
const changelogFunctions = {
|
|
210
|
+
async getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
|
|
211
|
+
assertRepo(options);
|
|
212
|
+
const repo = options.repo;
|
|
213
|
+
if (dependenciesUpdated.length === 0) return "";
|
|
214
|
+
return formatDependencyLine(await collectDependencyReferences(changesets, repo), dependenciesUpdated);
|
|
215
|
+
},
|
|
216
|
+
async getReleaseLine(changeset, type, options) {
|
|
217
|
+
assertRepo(options);
|
|
218
|
+
const repo = options.repo;
|
|
219
|
+
const parsedSummary = parseSummary(changeset.summary);
|
|
220
|
+
const resolvedType = resolveReleaseType(type);
|
|
221
|
+
const links = await resolveLinks(repo, parsedSummary, changeset.commit);
|
|
222
|
+
const userMentions = buildUserMentions(parsedSummary.users, links.user);
|
|
223
|
+
const { text: headlineText, breaking: headlineBreaking } = parsedSummary.headline ? formatHeadline(parsedSummary.headline) : {
|
|
224
|
+
text: "Miscellaneous improvements",
|
|
225
|
+
breaking: false
|
|
226
|
+
};
|
|
227
|
+
const isBreaking = parsedSummary.breaking || headlineBreaking;
|
|
228
|
+
const breakingPrefix = isBreaking ? "⚠️ " : "";
|
|
229
|
+
const mainLineParts = [`- ${isBreaking ? "💥" : releaseTypeMap[resolvedType].icon}`, `${breakingPrefix}**${headlineText}**`];
|
|
230
|
+
if (links.pull) mainLineParts.push(links.pull);
|
|
231
|
+
else if (links.commit) mainLineParts.push(links.commit);
|
|
232
|
+
if (links.pull && parsedSummary.commitRef && links.commit) mainLineParts.push(links.commit);
|
|
233
|
+
if (userMentions) mainLineParts.push(`by ${userMentions}`);
|
|
234
|
+
const mainLine = mainLineParts.join(" ");
|
|
235
|
+
const detailBlock = formatDetailBlock(parsedSummary.detailLines);
|
|
236
|
+
return detailBlock.length > 0 ? `\n\n${mainLine}\n${detailBlock}` : `\n\n${mainLine}`;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const { getDependencyReleaseLine, getReleaseLine } = changelogFunctions;
|
|
240
|
+
//#endregion
|
|
241
|
+
export { changelogFunctions as default, formatHeadline, getDependencyReleaseLine, getReleaseLine, parseConventionalHeadline };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icebreakers/changelog-github",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "1.0.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",
|
|
@@ -23,29 +23,38 @@
|
|
|
23
23
|
"exports": {
|
|
24
24
|
".": {
|
|
25
25
|
"types": "./dist/index.d.ts",
|
|
26
|
-
"import": "./dist/index.
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
27
|
"require": "./dist/index.cjs"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"main": "./dist/index.cjs",
|
|
31
|
-
"module": "./dist/index.
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
32
|
"types": "./dist/index.d.ts",
|
|
33
33
|
"files": [
|
|
34
34
|
"dist"
|
|
35
35
|
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
38
|
+
},
|
|
39
|
+
"tsd": {
|
|
40
|
+
"directory": "test-d",
|
|
41
|
+
"compilerOptions": {
|
|
42
|
+
"skipLibCheck": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
36
45
|
"dependencies": {
|
|
37
46
|
"@changesets/get-github-info": "^0.8.0",
|
|
38
47
|
"@changesets/types": "^6.1.0",
|
|
39
|
-
"dotenv": "^17.
|
|
48
|
+
"dotenv": "^17.4.0"
|
|
40
49
|
},
|
|
41
50
|
"devDependencies": {
|
|
42
51
|
"@changesets/parse": "^0.4.3"
|
|
43
52
|
},
|
|
44
53
|
"scripts": {
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"build": "tsup",
|
|
54
|
+
"dev": "tsdown --watch --sourcemap",
|
|
55
|
+
"build": "tsdown",
|
|
48
56
|
"test": "vitest run",
|
|
57
|
+
"test:types": "tsd",
|
|
49
58
|
"test:dev": "vitest",
|
|
50
59
|
"release": "pnpm publish",
|
|
51
60
|
"lint": "eslint .",
|
package/dist/index.mjs
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { getInfo, getInfoFromPullRequest } from "@changesets/get-github-info";
|
|
3
|
-
import { config as loadEnv } from "dotenv";
|
|
4
|
-
loadEnv();
|
|
5
|
-
var releaseTypeMap = {
|
|
6
|
-
major: { icon: "\u{1F680}", label: "Major" },
|
|
7
|
-
minor: { icon: "\u2728", label: "Minor" },
|
|
8
|
-
patch: { icon: "\u{1F41B}", label: "Patch" },
|
|
9
|
-
none: { icon: "\u{1F4DD}", label: "Update" }
|
|
10
|
-
};
|
|
11
|
-
function resolveReleaseType(type) {
|
|
12
|
-
if (type && type in releaseTypeMap) {
|
|
13
|
-
return type;
|
|
14
|
-
}
|
|
15
|
-
return "none";
|
|
16
|
-
}
|
|
17
|
-
function assertRepo(options) {
|
|
18
|
-
if (!options || typeof options["repo"] !== "string" || options["repo"].trim().length === 0) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
'Please provide a repo to this changelog generator like this:\n"changelog": ["@icebreakers/changelog-github", { "repo": "org/repo" }]'
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
async function collectDependencyReferences(changesets, repo) {
|
|
25
|
-
const references = await Promise.all(
|
|
26
|
-
changesets.map(async (cs) => {
|
|
27
|
-
if (!cs.commit) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
const { links } = await getInfo({
|
|
31
|
-
repo,
|
|
32
|
-
commit: cs.commit
|
|
33
|
-
});
|
|
34
|
-
return links.commit;
|
|
35
|
-
})
|
|
36
|
-
);
|
|
37
|
-
return [...new Set(references.filter((link) => Boolean(link)))];
|
|
38
|
-
}
|
|
39
|
-
function formatDependencyLine(references, dependenciesUpdated) {
|
|
40
|
-
const count = dependenciesUpdated.length;
|
|
41
|
-
const firstCommit = references[0] || "";
|
|
42
|
-
if (count === 0) {
|
|
43
|
-
return firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
|
|
44
|
-
}
|
|
45
|
-
const dependencySummaries = dependenciesUpdated.map(
|
|
46
|
-
(dependency) => `\`${dependency.name}@${dependency.newVersion}\``
|
|
47
|
-
);
|
|
48
|
-
if (count <= 3) {
|
|
49
|
-
const header2 = firstCommit ? `- \u{1F4E6} **Dependencies** ${firstCommit}` : "- \u{1F4E6} **Dependencies**";
|
|
50
|
-
const details = ` \u2192 ${dependencySummaries.join(", ")}`;
|
|
51
|
-
return `${header2}
|
|
52
|
-
${details}`;
|
|
53
|
-
}
|
|
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
|
-
};
|
|
92
|
-
}
|
|
93
|
-
function normalizeUserMentions(users) {
|
|
94
|
-
const normalized = users.map((user) => user.trim()).filter(Boolean).map((user) => user.startsWith("@") ? user : `@${user}`);
|
|
95
|
-
if (normalized.length === 0) {
|
|
96
|
-
return "";
|
|
97
|
-
}
|
|
98
|
-
if (normalized.length === 1) {
|
|
99
|
-
return normalized[0] ?? "";
|
|
100
|
-
}
|
|
101
|
-
if (normalized.length === 2) {
|
|
102
|
-
return `${normalized[0] ?? ""} and ${normalized[1] ?? ""}`;
|
|
103
|
-
}
|
|
104
|
-
return `${normalized.slice(0, -1).join(", ")}, and ${normalized.at(-1) ?? ""}`;
|
|
105
|
-
}
|
|
106
|
-
function extractSummaryMeta(summary) {
|
|
107
|
-
let prNumber;
|
|
108
|
-
let commitRef;
|
|
109
|
-
let breaking = false;
|
|
110
|
-
const users = [];
|
|
111
|
-
const contentLines = summary.split(/\r?\n/).filter((line) => {
|
|
112
|
-
const trimmed = line.trim();
|
|
113
|
-
const withoutListMarker = trimmed.replace(/^[-*+]\s+/, "");
|
|
114
|
-
const prMatch = withoutListMarker.match(
|
|
115
|
-
/^(?:pr|pull|pull\s*request):\s*#?(\d+)\s*$/i
|
|
116
|
-
);
|
|
117
|
-
if (prMatch) {
|
|
118
|
-
const num = Number(prMatch[1] ?? Number.NaN);
|
|
119
|
-
if (!Number.isNaN(num)) {
|
|
120
|
-
prNumber = num;
|
|
121
|
-
}
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
const commitMatch = withoutListMarker.match(/^commit:\s*(\S+)\s*$/i);
|
|
125
|
-
if (commitMatch) {
|
|
126
|
-
commitRef = commitMatch[1];
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
const userMatch = withoutListMarker.match(/^(?:author|user):\s*(\S+)\s*$/i);
|
|
130
|
-
if (userMatch) {
|
|
131
|
-
const user = userMatch[1];
|
|
132
|
-
if (user) {
|
|
133
|
-
users.push(user.replace(/^@/, ""));
|
|
134
|
-
}
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
if (/^BREAKING[\s-]CHANGE:/i.test(withoutListMarker)) {
|
|
138
|
-
breaking = true;
|
|
139
|
-
}
|
|
140
|
-
return true;
|
|
141
|
-
}).map((line) => line.trimEnd());
|
|
142
|
-
return {
|
|
143
|
-
...prNumber !== void 0 ? { prNumber } : {},
|
|
144
|
-
...commitRef !== void 0 ? { commitRef } : {},
|
|
145
|
-
users,
|
|
146
|
-
contentLines,
|
|
147
|
-
breaking
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function parseSummary(summary) {
|
|
151
|
-
const {
|
|
152
|
-
prNumber,
|
|
153
|
-
commitRef,
|
|
154
|
-
users,
|
|
155
|
-
contentLines,
|
|
156
|
-
breaking: bodyBreaking
|
|
157
|
-
} = extractSummaryMeta(summary);
|
|
158
|
-
const nonEmptyIndex = contentLines.findIndex((line) => line.trim().length > 0);
|
|
159
|
-
if (nonEmptyIndex === -1) {
|
|
160
|
-
return {
|
|
161
|
-
headline: "",
|
|
162
|
-
detailLines: [],
|
|
163
|
-
...prNumber !== void 0 ? { prNumber } : {},
|
|
164
|
-
...commitRef !== void 0 ? { commitRef } : {},
|
|
165
|
-
users,
|
|
166
|
-
breaking: bodyBreaking
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
const firstContentLine = contentLines[nonEmptyIndex]?.trim() ?? "";
|
|
170
|
-
const startsWithList = /^(?:[-*+]\s+|\d+[.)]\s+)/.test(firstContentLine);
|
|
171
|
-
const headline = startsWithList ? "" : firstContentLine;
|
|
172
|
-
const restLines = startsWithList ? contentLines.slice(nonEmptyIndex) : contentLines.slice(nonEmptyIndex + 1);
|
|
173
|
-
const detailLines = restLines;
|
|
174
|
-
const headlineParsed = headline ? parseConventionalHeadline(headline) : null;
|
|
175
|
-
const breaking = bodyBreaking || (headlineParsed?.breaking ?? false);
|
|
176
|
-
return {
|
|
177
|
-
headline,
|
|
178
|
-
detailLines,
|
|
179
|
-
...prNumber !== void 0 ? { prNumber } : {},
|
|
180
|
-
...commitRef !== void 0 ? { commitRef } : {},
|
|
181
|
-
users,
|
|
182
|
-
breaking
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
async function resolveLinks(repo, parsed, changesetCommit) {
|
|
186
|
-
if (parsed.prNumber !== void 0) {
|
|
187
|
-
const { links } = await getInfoFromPullRequest({
|
|
188
|
-
repo,
|
|
189
|
-
pull: parsed.prNumber
|
|
190
|
-
});
|
|
191
|
-
if (parsed.commitRef) {
|
|
192
|
-
const shortCommitId = parsed.commitRef.slice(0, 7);
|
|
193
|
-
links.commit = `[\`${shortCommitId}\`](https://github.com/${repo}/commit/${parsed.commitRef})`;
|
|
194
|
-
}
|
|
195
|
-
return links;
|
|
196
|
-
}
|
|
197
|
-
const commitToUse = parsed.commitRef ?? changesetCommit;
|
|
198
|
-
if (commitToUse) {
|
|
199
|
-
const { links } = await getInfo({
|
|
200
|
-
repo,
|
|
201
|
-
commit: commitToUse
|
|
202
|
-
});
|
|
203
|
-
return links;
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
commit: null,
|
|
207
|
-
pull: null,
|
|
208
|
-
user: null
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function buildUserMentions(users, fallbackUser) {
|
|
212
|
-
if (users.length > 0) {
|
|
213
|
-
return normalizeUserMentions(users);
|
|
214
|
-
}
|
|
215
|
-
if (fallbackUser) {
|
|
216
|
-
const match = fallbackUser.match(/@([^\]]+)/);
|
|
217
|
-
const matchedUser = match?.[1];
|
|
218
|
-
return matchedUser ? normalizeUserMentions([matchedUser]) : "";
|
|
219
|
-
}
|
|
220
|
-
return "";
|
|
221
|
-
}
|
|
222
|
-
function indentMarkdownBlock(lines, spaces) {
|
|
223
|
-
const prefix = " ".repeat(spaces);
|
|
224
|
-
return lines.map((line) => {
|
|
225
|
-
if (line.trim().length === 0) {
|
|
226
|
-
return "";
|
|
227
|
-
}
|
|
228
|
-
return `${prefix}${line}`;
|
|
229
|
-
}).join("\n");
|
|
230
|
-
}
|
|
231
|
-
function formatDetailBlock(detailLines) {
|
|
232
|
-
const trimmed = detailLines.join("\n").trim().split("\n").map((line) => line.trimEnd());
|
|
233
|
-
const hasFence = trimmed.some((line) => /^\s*```/.test(line));
|
|
234
|
-
const isListLike = trimmed.some((line) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(line));
|
|
235
|
-
if (trimmed.length === 0) {
|
|
236
|
-
return "";
|
|
237
|
-
}
|
|
238
|
-
if (hasFence || isListLike) {
|
|
239
|
-
return indentMarkdownBlock(trimmed, 2);
|
|
240
|
-
}
|
|
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");
|
|
246
|
-
}
|
|
247
|
-
var changelogFunctions = {
|
|
248
|
-
async getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
|
|
249
|
-
assertRepo(options);
|
|
250
|
-
const repo = options.repo;
|
|
251
|
-
if (dependenciesUpdated.length === 0) {
|
|
252
|
-
return "";
|
|
253
|
-
}
|
|
254
|
-
const references = await collectDependencyReferences(
|
|
255
|
-
changesets,
|
|
256
|
-
repo
|
|
257
|
-
);
|
|
258
|
-
return formatDependencyLine(references, dependenciesUpdated);
|
|
259
|
-
},
|
|
260
|
-
async getReleaseLine(changeset, type, options) {
|
|
261
|
-
assertRepo(options);
|
|
262
|
-
const repo = options.repo;
|
|
263
|
-
const parsedSummary = parseSummary(changeset.summary);
|
|
264
|
-
const resolvedType = resolveReleaseType(type);
|
|
265
|
-
const links = await resolveLinks(
|
|
266
|
-
repo,
|
|
267
|
-
parsedSummary,
|
|
268
|
-
changeset.commit
|
|
269
|
-
);
|
|
270
|
-
const userMentions = buildUserMentions(
|
|
271
|
-
parsedSummary.users,
|
|
272
|
-
links.user
|
|
273
|
-
);
|
|
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;
|
|
278
|
-
const mainLineParts = [
|
|
279
|
-
`- ${icon}`,
|
|
280
|
-
`${breakingPrefix}**${headlineText}**`
|
|
281
|
-
];
|
|
282
|
-
if (links.pull) {
|
|
283
|
-
mainLineParts.push(links.pull);
|
|
284
|
-
} else if (links.commit) {
|
|
285
|
-
mainLineParts.push(links.commit);
|
|
286
|
-
}
|
|
287
|
-
if (links.pull && parsedSummary.commitRef && links.commit) {
|
|
288
|
-
mainLineParts.push(links.commit);
|
|
289
|
-
}
|
|
290
|
-
if (userMentions) {
|
|
291
|
-
mainLineParts.push(`by ${userMentions}`);
|
|
292
|
-
}
|
|
293
|
-
const mainLine = mainLineParts.join(" ");
|
|
294
|
-
const detailBlock = formatDetailBlock(parsedSummary.detailLines);
|
|
295
|
-
return detailBlock.length > 0 ? `
|
|
296
|
-
|
|
297
|
-
${mainLine}
|
|
298
|
-
${detailBlock}` : `
|
|
299
|
-
|
|
300
|
-
${mainLine}`;
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
var index_default = changelogFunctions;
|
|
304
|
-
var { getDependencyReleaseLine, getReleaseLine } = changelogFunctions;
|
|
305
|
-
export {
|
|
306
|
-
index_default as default,
|
|
307
|
-
formatHeadline,
|
|
308
|
-
getDependencyReleaseLine,
|
|
309
|
-
getReleaseLine,
|
|
310
|
-
parseConventionalHeadline
|
|
311
|
-
};
|