@kb-labs/release-manager-changelog 0.6.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/LICENSE +23 -0
- package/README.md +195 -0
- package/dist/index.d.ts +337 -0
- package/dist/index.js +1429 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/builtin/compact.d.ts +17 -0
- package/dist/templates/builtin/compact.js +40 -0
- package/dist/templates/builtin/compact.js.map +1 -0
- package/dist/templates/builtin/corporate-ai.d.ts +16 -0
- package/dist/templates/builtin/corporate-ai.js +164 -0
- package/dist/templates/builtin/corporate-ai.js.map +1 -0
- package/dist/templates/builtin/corporate.d.ts +17 -0
- package/dist/templates/builtin/corporate.js +98 -0
- package/dist/templates/builtin/corporate.js.map +1 -0
- package/dist/templates/builtin/technical.d.ts +18 -0
- package/dist/templates/builtin/technical.js +88 -0
- package/dist/templates/builtin/technical.js.map +1 -0
- package/dist/types-DdtOg4s3.d.ts +213 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1429 @@
|
|
|
1
|
+
import simpleGit from 'simple-git';
|
|
2
|
+
import { readFile, mkdir, writeFile, access } from 'fs/promises';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { dirname, join, isAbsolute } from 'path';
|
|
5
|
+
import semver from 'semver';
|
|
6
|
+
import { createHash } from 'crypto';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
8
|
+
|
|
9
|
+
// src/parser.ts
|
|
10
|
+
async function parseCommits(options) {
|
|
11
|
+
const {
|
|
12
|
+
cwd,
|
|
13
|
+
from,
|
|
14
|
+
to = "HEAD",
|
|
15
|
+
packagePath,
|
|
16
|
+
ignoreAuthors = [],
|
|
17
|
+
includeTypes,
|
|
18
|
+
excludeTypes,
|
|
19
|
+
collapseMerges = true,
|
|
20
|
+
collapseReverts = true,
|
|
21
|
+
preferMergeSummary = true
|
|
22
|
+
} = options;
|
|
23
|
+
const git = simpleGit(cwd);
|
|
24
|
+
const format = "%H%x00%an%x00%ae%x00%ai%x00%s%x00%b%n--COMMIT_FOOTER--";
|
|
25
|
+
const useRoot = from.endsWith("^");
|
|
26
|
+
const logArgs = useRoot ? ["log", "--no-merges", "--name-status", "--root", `--format=${format}`, to] : ["log", "--no-merges", "--name-status", `--format=${format}`, `${from}..${to}`];
|
|
27
|
+
if (packagePath) {
|
|
28
|
+
logArgs.push("--", packagePath);
|
|
29
|
+
}
|
|
30
|
+
const logOutput = await git.raw(logArgs);
|
|
31
|
+
return parseGitLogOutput(logOutput, {
|
|
32
|
+
ignoreAuthors,
|
|
33
|
+
includeTypes,
|
|
34
|
+
excludeTypes});
|
|
35
|
+
}
|
|
36
|
+
function parseGitLogOutput(output, options) {
|
|
37
|
+
const changes = [];
|
|
38
|
+
const commits = output.split("--COMMIT_FOOTER--").filter(Boolean);
|
|
39
|
+
for (let i = 0; i < commits.length; i++) {
|
|
40
|
+
const commitBlock = commits[i];
|
|
41
|
+
if (!commitBlock) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const lines = commitBlock.split("\n");
|
|
45
|
+
let header = "";
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
if (line.trim() && line.includes("\0")) {
|
|
48
|
+
header = line;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!header) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const fileLines = [];
|
|
56
|
+
if (i + 1 < commits.length) {
|
|
57
|
+
const nextBlock = commits[i + 1];
|
|
58
|
+
if (!nextBlock) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const nextLines = nextBlock.split("\n");
|
|
62
|
+
for (const line of nextLines) {
|
|
63
|
+
if (line.trim() && line.includes("\0")) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
if (line.trim()) {
|
|
67
|
+
fileLines.push(line);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const [sha, authorName, authorEmail, authorDate, subject, body = ""] = header.split("\0");
|
|
72
|
+
if (!sha || sha.length !== 40 || !authorName || !authorEmail || !authorDate || !subject) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (options.ignoreAuthors.some((pattern) => matchesGlob(authorName, pattern))) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const convention = parseConventionalCommit(`${subject}
|
|
79
|
+
|
|
80
|
+
${body || ""}`);
|
|
81
|
+
if (shouldSkipCommit(convention.type, options.includeTypes, options.excludeTypes)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const filesChanged = parseFileChanges(fileLines);
|
|
85
|
+
const packages = extractPackagesFromFiles(filesChanged);
|
|
86
|
+
const change = {
|
|
87
|
+
sha,
|
|
88
|
+
type: convention.type,
|
|
89
|
+
scope: convention.scope,
|
|
90
|
+
subject: convention.subject,
|
|
91
|
+
body: convention.body,
|
|
92
|
+
breaking: convention.breaking,
|
|
93
|
+
refs: extractReferences(convention.footers),
|
|
94
|
+
author: {
|
|
95
|
+
name: authorName,
|
|
96
|
+
email: authorEmail
|
|
97
|
+
},
|
|
98
|
+
coAuthors: extractCoAuthors(convention.footers),
|
|
99
|
+
packages,
|
|
100
|
+
filesChanged: filesChanged.map((f) => f.path),
|
|
101
|
+
timestamp: authorDate,
|
|
102
|
+
isMerge: false,
|
|
103
|
+
isRevert: convention.type === "revert",
|
|
104
|
+
revertOf: extractRevertOf(convention.body),
|
|
105
|
+
cherryPickOf: extractCherryPickOf(convention.footers)
|
|
106
|
+
};
|
|
107
|
+
changes.push(change);
|
|
108
|
+
}
|
|
109
|
+
return changes;
|
|
110
|
+
}
|
|
111
|
+
function parseConventionalCommit(commitMessage) {
|
|
112
|
+
const lines = commitMessage.split("\n");
|
|
113
|
+
const header = lines[0] || "";
|
|
114
|
+
const bodyLines = lines.slice(2);
|
|
115
|
+
const body = bodyLines.join("\n").trim();
|
|
116
|
+
const headerMatch = header.match(/^(\w+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/);
|
|
117
|
+
let parsedType;
|
|
118
|
+
let parsedScope;
|
|
119
|
+
let parsedSubject = "";
|
|
120
|
+
let hasBreaking = false;
|
|
121
|
+
if (headerMatch) {
|
|
122
|
+
parsedType = headerMatch[1];
|
|
123
|
+
parsedScope = headerMatch[2];
|
|
124
|
+
hasBreaking = headerMatch[3] === "!";
|
|
125
|
+
parsedSubject = headerMatch[4] || "";
|
|
126
|
+
} else {
|
|
127
|
+
parsedType = void 0;
|
|
128
|
+
parsedSubject = header;
|
|
129
|
+
}
|
|
130
|
+
const type = normalizeType(parsedType);
|
|
131
|
+
const breaking = [];
|
|
132
|
+
if (hasBreaking) {
|
|
133
|
+
breaking.push({ summary: parsedSubject });
|
|
134
|
+
}
|
|
135
|
+
const footers = [];
|
|
136
|
+
const footerLines = body.split("\n");
|
|
137
|
+
for (const line of footerLines) {
|
|
138
|
+
if (/^[A-Z-]+:\s*/.test(line)) {
|
|
139
|
+
footers.push(line);
|
|
140
|
+
const breakingMatch = line.match(/^BREAKING CHANGE:\s*(.+)/);
|
|
141
|
+
if (breakingMatch && breakingMatch[1]) {
|
|
142
|
+
breaking.push({ summary: breakingMatch[1] });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
type,
|
|
148
|
+
scope: parsedScope,
|
|
149
|
+
subject: parsedSubject,
|
|
150
|
+
body: body || void 0,
|
|
151
|
+
breaking: breaking.length > 0 ? breaking : void 0,
|
|
152
|
+
footers
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function normalizeType(type) {
|
|
156
|
+
const normalized = (type || "chore").toLowerCase();
|
|
157
|
+
const validTypes = ["feat", "fix", "perf", "refactor", "docs", "build", "ci", "test", "chore", "revert", "style"];
|
|
158
|
+
return validTypes.includes(normalized) ? normalized : "chore";
|
|
159
|
+
}
|
|
160
|
+
function parseFileChanges(lines) {
|
|
161
|
+
const changes = [];
|
|
162
|
+
for (const line of lines) {
|
|
163
|
+
if (!line.trim() || line.trim().startsWith("diff --git")) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const match = line.match(/^([ADMRT])\t(.*?)$/);
|
|
167
|
+
if (!match) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const [, status, path] = match;
|
|
171
|
+
if (!path || !status) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (status === "R" || status === "C") {
|
|
175
|
+
const [oldPath, newPath] = path.split(" ");
|
|
176
|
+
if (newPath) {
|
|
177
|
+
changes.push({ path: newPath, status, oldPath });
|
|
178
|
+
}
|
|
179
|
+
if (oldPath) {
|
|
180
|
+
changes.push({ path: oldPath, status: "D" });
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
changes.push({ path, status });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return changes;
|
|
187
|
+
}
|
|
188
|
+
function extractPackagesFromFiles(files) {
|
|
189
|
+
const packages = /* @__PURE__ */ new Set();
|
|
190
|
+
for (const file of files) {
|
|
191
|
+
const match = file.path.match(/^packages\/([^/]+)/);
|
|
192
|
+
if (match) {
|
|
193
|
+
packages.add(`packages/${match[1]}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return Array.from(packages);
|
|
197
|
+
}
|
|
198
|
+
function extractReferences(footers) {
|
|
199
|
+
const refs = [];
|
|
200
|
+
for (const footer of footers) {
|
|
201
|
+
const match = footer.match(/(?:Closes|Fixes|Refs)\s+#(\d+)/i);
|
|
202
|
+
if (match && match[1]) {
|
|
203
|
+
refs.push({
|
|
204
|
+
type: footer.toLowerCase().includes("closes") || footer.toLowerCase().includes("fixes") ? "issue" : "pr",
|
|
205
|
+
id: match[1]
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return refs;
|
|
210
|
+
}
|
|
211
|
+
function extractCoAuthors(footers) {
|
|
212
|
+
const coAuthors = [];
|
|
213
|
+
for (const footer of footers) {
|
|
214
|
+
const match = footer.match(/^Co-authored-by:\s*(.+?)\s*<(.+?)>$/i);
|
|
215
|
+
if (match && match[1] && match[2]) {
|
|
216
|
+
coAuthors.push({
|
|
217
|
+
name: match[1].trim(),
|
|
218
|
+
email: match[2].trim()
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return coAuthors;
|
|
223
|
+
}
|
|
224
|
+
function extractRevertOf(body, footers) {
|
|
225
|
+
if (!body) {
|
|
226
|
+
return void 0;
|
|
227
|
+
}
|
|
228
|
+
const match = body.match(/revert (?:of\s+)?([0-9a-f]{40})/i);
|
|
229
|
+
return match ? match[1] : void 0;
|
|
230
|
+
}
|
|
231
|
+
function extractCherryPickOf(footers) {
|
|
232
|
+
for (const footer of footers) {
|
|
233
|
+
const match = footer.match(/cherry picked from (.+)/i);
|
|
234
|
+
if (match && match[1]) {
|
|
235
|
+
return match[1].trim();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return void 0;
|
|
239
|
+
}
|
|
240
|
+
function shouldSkipCommit(type, includeTypes, excludeTypes) {
|
|
241
|
+
if (includeTypes && !includeTypes.includes(type)) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
if (excludeTypes && excludeTypes.includes(type)) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
function matchesGlob(text, pattern) {
|
|
250
|
+
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
251
|
+
const regex = new RegExp(`^${regexPattern}$`, "i");
|
|
252
|
+
return regex.test(text);
|
|
253
|
+
}
|
|
254
|
+
var CACHE_FILE = "cache.json";
|
|
255
|
+
var GRAPH_FILE = "graph.json";
|
|
256
|
+
var LOCK_FILE = ".cache.lock";
|
|
257
|
+
async function loadCache(cacheDir) {
|
|
258
|
+
const cachePath = join(cacheDir, CACHE_FILE);
|
|
259
|
+
try {
|
|
260
|
+
if (!existsSync(cachePath)) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const content = await readFile(cachePath, "utf-8");
|
|
264
|
+
return JSON.parse(content);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function saveCache(cacheDir, cache) {
|
|
270
|
+
const cachePath = join(cacheDir, CACHE_FILE);
|
|
271
|
+
try {
|
|
272
|
+
await mkdir(cacheDir, { recursive: true });
|
|
273
|
+
await writeFile(cachePath, JSON.stringify(cache, null, 2), "utf-8");
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.warn(`Failed to save cache: ${error instanceof Error ? error.message : String(error)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function getCachedChange(cache, sha) {
|
|
279
|
+
if (!cache) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
return cache.commits[sha] || null;
|
|
283
|
+
}
|
|
284
|
+
function updateCache(cache, commits) {
|
|
285
|
+
if (!cache) {
|
|
286
|
+
cache = {
|
|
287
|
+
meta: {
|
|
288
|
+
graphHash: "",
|
|
289
|
+
HEAD: ""
|
|
290
|
+
},
|
|
291
|
+
commits: {},
|
|
292
|
+
lastTags: {}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
for (const commit of commits) {
|
|
296
|
+
cache.commits[commit.sha] = commit;
|
|
297
|
+
}
|
|
298
|
+
return cache;
|
|
299
|
+
}
|
|
300
|
+
async function saveGraphSnapshot(cacheDir, graphHash) {
|
|
301
|
+
const graphPath = join(cacheDir, GRAPH_FILE);
|
|
302
|
+
try {
|
|
303
|
+
await mkdir(cacheDir, { recursive: true });
|
|
304
|
+
const content = JSON.stringify({ hash: graphHash, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2);
|
|
305
|
+
await writeFile(graphPath, content, "utf-8");
|
|
306
|
+
const cache = await loadCache(cacheDir);
|
|
307
|
+
if (cache) {
|
|
308
|
+
cache.meta.graphHash = graphHash;
|
|
309
|
+
await saveCache(cacheDir, cache);
|
|
310
|
+
}
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.warn(`Failed to save graph snapshot: ${error instanceof Error ? error.message : String(error)}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function updateHead(cacheDir, head) {
|
|
316
|
+
const cache = await loadCache(cacheDir);
|
|
317
|
+
if (cache) {
|
|
318
|
+
cache.meta.HEAD = head;
|
|
319
|
+
await saveCache(cacheDir, cache);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function updateLastTag(cacheDir, packageName, tag, sha) {
|
|
323
|
+
const cache = await loadCache(cacheDir);
|
|
324
|
+
const updatedCache = updateCache(cache, []);
|
|
325
|
+
updatedCache.lastTags[packageName] = { tag, sha };
|
|
326
|
+
await saveCache(cacheDir, updatedCache);
|
|
327
|
+
}
|
|
328
|
+
function getLastTag(cache, packageName) {
|
|
329
|
+
if (!cache) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
return cache.lastTags[packageName] || null;
|
|
333
|
+
}
|
|
334
|
+
async function isCacheValid(cacheDir, from, to, currentGraphHash, currentHead) {
|
|
335
|
+
const cache = await loadCache(cacheDir);
|
|
336
|
+
if (!cache) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
if (currentGraphHash && cache.meta.graphHash && cache.meta.graphHash !== currentGraphHash) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
if (currentHead && cache.meta.HEAD && !isCommitValid(cache.meta.HEAD)) ;
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
function isCommitValid(commit, from, to) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
async function acquireLock(cacheDir) {
|
|
349
|
+
const lockPath = join(cacheDir, LOCK_FILE);
|
|
350
|
+
if (existsSync(lockPath)) {
|
|
351
|
+
throw new Error("Cache lock already acquired. Another process may be running.");
|
|
352
|
+
}
|
|
353
|
+
await mkdir(cacheDir, { recursive: true });
|
|
354
|
+
await writeFile(lockPath, JSON.stringify({
|
|
355
|
+
pid: process.pid,
|
|
356
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
357
|
+
}), "utf-8");
|
|
358
|
+
return async () => {
|
|
359
|
+
if (existsSync(lockPath)) {
|
|
360
|
+
try {
|
|
361
|
+
await writeFile(lockPath, "", "utf-8");
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
async function resolveGitRange(options) {
|
|
368
|
+
const { cwd, from, to = "HEAD", sinceTag, autoUnshallow, requireSignedTags } = options;
|
|
369
|
+
const git = simpleGit(cwd);
|
|
370
|
+
if (sinceTag || from) {
|
|
371
|
+
await ensureHistoryDepth(git, sinceTag || from || "HEAD", "HEAD", autoUnshallow);
|
|
372
|
+
}
|
|
373
|
+
const lastTag = await findLastTag(git, requireSignedTags);
|
|
374
|
+
const fromRef = sinceTag || from || lastTag || await findFirstCommit(git);
|
|
375
|
+
const toRef = to;
|
|
376
|
+
return { from: fromRef, to: toRef };
|
|
377
|
+
}
|
|
378
|
+
async function findFirstCommit(git) {
|
|
379
|
+
try {
|
|
380
|
+
const result = await git.raw(["rev-list", "--max-parents=0", "HEAD"]);
|
|
381
|
+
const firstCommit = result.trim();
|
|
382
|
+
return `${firstCommit}^`;
|
|
383
|
+
} catch (error) {
|
|
384
|
+
return "HEAD~1";
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
async function findLastTag(git, requireSigned) {
|
|
388
|
+
try {
|
|
389
|
+
const tags = await git.tags();
|
|
390
|
+
const candidateTags = requireSigned ? await filterSignedTags(git, tags.all) : tags.all;
|
|
391
|
+
const recentTag = candidateTags.sort().reverse()[0];
|
|
392
|
+
return recentTag || null;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async function findPackageTag(git, packageName, requireSigned) {
|
|
398
|
+
try {
|
|
399
|
+
const tags = await git.tags();
|
|
400
|
+
const packageRegex = new RegExp(`@${packageName.replace("@", "")}@v?\\d+\\.\\d+\\.\\d+`);
|
|
401
|
+
const packageTags = tags.all.filter((tag) => packageRegex.test(tag));
|
|
402
|
+
const candidateTags = requireSigned ? await filterSignedTags(git, packageTags) : packageTags;
|
|
403
|
+
return candidateTags.sort().reverse()[0] || null;
|
|
404
|
+
} catch {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function filterSignedTags(git, tags) {
|
|
409
|
+
const signed = [];
|
|
410
|
+
for (const tag of tags) {
|
|
411
|
+
try {
|
|
412
|
+
const content = await git.raw(["cat-file", "tag", tag]);
|
|
413
|
+
if (content.includes("-----BEGIN PGP SIGNATURE-----")) {
|
|
414
|
+
signed.push(tag);
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return signed;
|
|
420
|
+
}
|
|
421
|
+
async function ensureHistoryDepth(git, from, to, autoUnshallow) {
|
|
422
|
+
try {
|
|
423
|
+
await git.raw(["rev-list", "--count", `${from}..${to}`]);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (autoUnshallow) {
|
|
426
|
+
console.log("\u26A0\uFE0F Shallow clone detected. Fetching full history...");
|
|
427
|
+
try {
|
|
428
|
+
await git.fetch(["--prune", "--unshallow", "--tags"]);
|
|
429
|
+
} catch (fetchError) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
"Shallow clone detected. Full history fetch failed. Run manually: git fetch --unshallow --tags"
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
throw new Error(
|
|
436
|
+
"Shallow clone detected. Use --auto-unshallow flag or run: git fetch --unshallow --tags"
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function parseGitUrl(url) {
|
|
442
|
+
const match = url.match(/(?:https:\/\/([^/]+)|git@([^:]+):)(?:.*?\/)?([^/]+)\/([^/]+)(?:\.git)?$/);
|
|
443
|
+
if (!match) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
const host = match[1] || match[2] || "";
|
|
447
|
+
const owner = match[3] || "";
|
|
448
|
+
const repo = match[4] || "";
|
|
449
|
+
return { host, owner, repo };
|
|
450
|
+
}
|
|
451
|
+
function computeBump(changes) {
|
|
452
|
+
const hasBreaking = changes.some((c) => c.breaking && c.breaking.length > 0);
|
|
453
|
+
const hasFeature = changes.some((c) => c.type === "feat");
|
|
454
|
+
const hasFix = changes.some((c) => ["fix", "perf", "refactor"].includes(c.type));
|
|
455
|
+
if (hasBreaking) {
|
|
456
|
+
return "major";
|
|
457
|
+
}
|
|
458
|
+
if (hasFeature) {
|
|
459
|
+
return "minor";
|
|
460
|
+
}
|
|
461
|
+
if (hasFix) {
|
|
462
|
+
return "patch";
|
|
463
|
+
}
|
|
464
|
+
return "none";
|
|
465
|
+
}
|
|
466
|
+
function computeNextVersion(currentVersion, bump, preid) {
|
|
467
|
+
if (bump === "none") {
|
|
468
|
+
return currentVersion;
|
|
469
|
+
}
|
|
470
|
+
if (preid) {
|
|
471
|
+
const nextVersion = semver.inc(currentVersion, bump === "major" ? "major" : bump === "minor" ? "minor" : "patch");
|
|
472
|
+
if (!nextVersion) {
|
|
473
|
+
return currentVersion;
|
|
474
|
+
}
|
|
475
|
+
const prerelease = semver.prerelease(nextVersion);
|
|
476
|
+
if (prerelease && prerelease[0] === preid) {
|
|
477
|
+
prerelease[1];
|
|
478
|
+
return semver.inc(nextVersion, "prerelease", preid) || nextVersion;
|
|
479
|
+
}
|
|
480
|
+
return `${nextVersion}-${preid}.1`;
|
|
481
|
+
}
|
|
482
|
+
return semver.inc(currentVersion, bump) || currentVersion;
|
|
483
|
+
}
|
|
484
|
+
function getImpactReason(changes) {
|
|
485
|
+
if (changes.some((c) => c.breaking && c.breaking.length > 0)) {
|
|
486
|
+
return { reason: "breaking", details: "Contains breaking changes" };
|
|
487
|
+
}
|
|
488
|
+
if (changes.some((c) => c.type === "feat")) {
|
|
489
|
+
return { reason: "feat", details: "New features added" };
|
|
490
|
+
}
|
|
491
|
+
if (changes.some((c) => c.type === "fix")) {
|
|
492
|
+
return { reason: "fix", details: "Bug fixes included" };
|
|
493
|
+
}
|
|
494
|
+
if (changes.some((c) => c.type === "perf")) {
|
|
495
|
+
return { reason: "perf", details: "Performance improvements" };
|
|
496
|
+
}
|
|
497
|
+
return { reason: "manual", details: "Manual version bump" };
|
|
498
|
+
}
|
|
499
|
+
function getRipplePackages(packageName, dependencyGraph, visited = /* @__PURE__ */ new Set()) {
|
|
500
|
+
const ripple = [];
|
|
501
|
+
if (visited.has(packageName)) {
|
|
502
|
+
return ripple;
|
|
503
|
+
}
|
|
504
|
+
visited.add(packageName);
|
|
505
|
+
for (const [pkg, deps] of Object.entries(dependencyGraph)) {
|
|
506
|
+
if (deps.includes(packageName) && pkg !== packageName) {
|
|
507
|
+
if (!ripple.includes(pkg)) {
|
|
508
|
+
ripple.push(pkg);
|
|
509
|
+
}
|
|
510
|
+
const transitive = getRipplePackages(pkg, dependencyGraph, visited);
|
|
511
|
+
for (const t2 of transitive) {
|
|
512
|
+
if (!ripple.includes(t2)) {
|
|
513
|
+
ripple.push(t2);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return ripple;
|
|
519
|
+
}
|
|
520
|
+
function applyVersionPolicy(changes, affectedPackages, currentVersions, policy, dependencyGraph) {
|
|
521
|
+
const result = {};
|
|
522
|
+
const changesByPackage = {};
|
|
523
|
+
for (const change of changes) {
|
|
524
|
+
for (const pkg of change.packages) {
|
|
525
|
+
if (!changesByPackage[pkg]) {
|
|
526
|
+
changesByPackage[pkg] = [];
|
|
527
|
+
}
|
|
528
|
+
changesByPackage[pkg].push(change);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (policy === "independent") {
|
|
532
|
+
for (const pkg of affectedPackages) {
|
|
533
|
+
const pkgChanges = changesByPackage[pkg] || [];
|
|
534
|
+
const bump = computeBump(pkgChanges);
|
|
535
|
+
const reasonInfo = getImpactReason(pkgChanges);
|
|
536
|
+
const currentVersion = currentVersions[pkg] || "0.0.0";
|
|
537
|
+
const nextVersion = computeNextVersion(currentVersion, bump);
|
|
538
|
+
result[pkg] = {
|
|
539
|
+
nextVersion,
|
|
540
|
+
bump,
|
|
541
|
+
reason: reasonInfo.reason
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
} else if (policy === "ripple" && dependencyGraph) {
|
|
545
|
+
const ripplePackages = /* @__PURE__ */ new Set();
|
|
546
|
+
for (const pkg of affectedPackages) {
|
|
547
|
+
const pkgChanges = changesByPackage[pkg] || [];
|
|
548
|
+
const bump = computeBump(pkgChanges);
|
|
549
|
+
const reasonInfo = getImpactReason(pkgChanges);
|
|
550
|
+
const currentVersion = currentVersions[pkg] || "0.0.0";
|
|
551
|
+
const nextVersion = computeNextVersion(currentVersion, bump);
|
|
552
|
+
result[pkg] = {
|
|
553
|
+
nextVersion,
|
|
554
|
+
bump,
|
|
555
|
+
reason: reasonInfo.reason
|
|
556
|
+
};
|
|
557
|
+
const ripple = getRipplePackages(pkg, dependencyGraph);
|
|
558
|
+
for (const rpkg of ripple) {
|
|
559
|
+
ripplePackages.add(rpkg);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
for (const pkg of ripplePackages) {
|
|
563
|
+
if (!result[pkg]) {
|
|
564
|
+
const currentVersion = currentVersions[pkg] || "0.0.0";
|
|
565
|
+
const nextVersion = computeNextVersion(currentVersion, "patch");
|
|
566
|
+
result[pkg] = {
|
|
567
|
+
nextVersion,
|
|
568
|
+
bump: "patch",
|
|
569
|
+
reason: "ripple",
|
|
570
|
+
rippleFrom: Array.from(affectedPackages)
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
} else if (policy === "lockstep") {
|
|
575
|
+
const allChanges = Object.values(changesByPackage).flat();
|
|
576
|
+
const bump = computeBump(allChanges);
|
|
577
|
+
const reasonInfo = getImpactReason(allChanges);
|
|
578
|
+
let highestVersion = "0.0.0";
|
|
579
|
+
for (const version of Object.values(currentVersions)) {
|
|
580
|
+
if (semver.gt(version, highestVersion)) {
|
|
581
|
+
highestVersion = version;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
const nextVersion = computeNextVersion(highestVersion, bump);
|
|
585
|
+
for (const pkg of affectedPackages) {
|
|
586
|
+
result[pkg] = {
|
|
587
|
+
nextVersion,
|
|
588
|
+
bump,
|
|
589
|
+
reason: reasonInfo.reason
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
function getAffectedPackages(changes) {
|
|
596
|
+
const packages = /* @__PURE__ */ new Set();
|
|
597
|
+
for (const change of changes) {
|
|
598
|
+
for (const pkg of change.packages) {
|
|
599
|
+
packages.add(pkg);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return Array.from(packages);
|
|
603
|
+
}
|
|
604
|
+
async function detectProvider(cwd, baseUrl) {
|
|
605
|
+
if (baseUrl) {
|
|
606
|
+
return {
|
|
607
|
+
type: inferProviderType(baseUrl),
|
|
608
|
+
baseUrl
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
const git = simpleGit(cwd);
|
|
612
|
+
try {
|
|
613
|
+
const remotes = await git.getRemotes(true);
|
|
614
|
+
const url = remotes[0]?.refs?.fetch || remotes[0]?.refs?.push;
|
|
615
|
+
if (!url) {
|
|
616
|
+
return { type: "generic", baseUrl: null };
|
|
617
|
+
}
|
|
618
|
+
const parsed = parseGitUrl(url);
|
|
619
|
+
if (!parsed) {
|
|
620
|
+
return { type: "generic", baseUrl: null };
|
|
621
|
+
}
|
|
622
|
+
const providerUrl = `https://${parsed.host}/${parsed.owner}/${parsed.repo}`;
|
|
623
|
+
return {
|
|
624
|
+
type: inferProviderType(providerUrl),
|
|
625
|
+
baseUrl: providerUrl
|
|
626
|
+
};
|
|
627
|
+
} catch (error) {
|
|
628
|
+
return { type: "generic", baseUrl: null };
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function inferProviderType(url) {
|
|
632
|
+
if (url.includes("github.com") || url.includes("githubusercontent.com")) {
|
|
633
|
+
return "github";
|
|
634
|
+
}
|
|
635
|
+
if (url.includes("gitlab.com") || url.includes("gitlab")) {
|
|
636
|
+
return "gitlab";
|
|
637
|
+
}
|
|
638
|
+
return "generic";
|
|
639
|
+
}
|
|
640
|
+
function formatCommitLink(provider, sha) {
|
|
641
|
+
if (!provider.baseUrl) {
|
|
642
|
+
return void 0;
|
|
643
|
+
}
|
|
644
|
+
if (provider.type === "github") {
|
|
645
|
+
return `${provider.baseUrl}/commit/${sha}`;
|
|
646
|
+
}
|
|
647
|
+
if (provider.type === "gitlab") {
|
|
648
|
+
return `${provider.baseUrl}/-/commit/${sha}`;
|
|
649
|
+
}
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
function formatPrLink(provider, prNumber) {
|
|
653
|
+
if (!provider.baseUrl) {
|
|
654
|
+
return void 0;
|
|
655
|
+
}
|
|
656
|
+
if (provider.type === "github") {
|
|
657
|
+
return `${provider.baseUrl}/pull/${prNumber}`;
|
|
658
|
+
}
|
|
659
|
+
if (provider.type === "gitlab") {
|
|
660
|
+
return `${provider.baseUrl}/-/merge_requests/${prNumber}`;
|
|
661
|
+
}
|
|
662
|
+
return void 0;
|
|
663
|
+
}
|
|
664
|
+
function formatIssueLink(provider, issueNumber) {
|
|
665
|
+
if (!provider.baseUrl) {
|
|
666
|
+
return void 0;
|
|
667
|
+
}
|
|
668
|
+
if (provider.type === "github") {
|
|
669
|
+
return `${provider.baseUrl}/issues/${issueNumber}`;
|
|
670
|
+
}
|
|
671
|
+
if (provider.type === "gitlab") {
|
|
672
|
+
return `${provider.baseUrl}/-/issues/${issueNumber}`;
|
|
673
|
+
}
|
|
674
|
+
return void 0;
|
|
675
|
+
}
|
|
676
|
+
function enhanceChangeWithLinks(change, provider) {
|
|
677
|
+
const providerLinks = {
|
|
678
|
+
commit: formatCommitLink(provider, change.sha),
|
|
679
|
+
pr: [],
|
|
680
|
+
issues: []
|
|
681
|
+
};
|
|
682
|
+
for (const ref of change.refs) {
|
|
683
|
+
if (ref.type === "pr") {
|
|
684
|
+
const link = formatPrLink(provider, ref.id);
|
|
685
|
+
if (link) {
|
|
686
|
+
providerLinks.pr.push(link);
|
|
687
|
+
ref.url = link;
|
|
688
|
+
}
|
|
689
|
+
} else if (ref.type === "issue") {
|
|
690
|
+
const link = formatIssueLink(provider, ref.id);
|
|
691
|
+
if (link) {
|
|
692
|
+
providerLinks.issues.push(link);
|
|
693
|
+
ref.url = link;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (!providerLinks.pr.length) {
|
|
698
|
+
delete providerLinks.pr;
|
|
699
|
+
}
|
|
700
|
+
if (!providerLinks.issues.length) {
|
|
701
|
+
delete providerLinks.issues;
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
...change,
|
|
705
|
+
providerLinks
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function formatAsJson(manifest, additionalContent) {
|
|
709
|
+
const integrity = {};
|
|
710
|
+
for (const [key, content] of Object.entries(additionalContent || {})) {
|
|
711
|
+
integrity[key] = calculateSha256(content);
|
|
712
|
+
}
|
|
713
|
+
const manifestWithIntegrity = {
|
|
714
|
+
...manifest,
|
|
715
|
+
integrity: Object.keys(integrity).length > 0 ? integrity : void 0
|
|
716
|
+
};
|
|
717
|
+
return JSON.stringify(manifestWithIntegrity, null, 2);
|
|
718
|
+
}
|
|
719
|
+
function createReleaseManifest(range, packages, timestamp) {
|
|
720
|
+
const byType = {};
|
|
721
|
+
let breakingCount = 0;
|
|
722
|
+
for (const pkg of packages) {
|
|
723
|
+
breakingCount += pkg.breaking.length;
|
|
724
|
+
for (const change of pkg.changes) {
|
|
725
|
+
byType[change.type] = (byType[change.type] || 0) + 1;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
schemaVersion: "1.0",
|
|
730
|
+
range,
|
|
731
|
+
timestamp: timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
732
|
+
packages,
|
|
733
|
+
workspace: {
|
|
734
|
+
breakingCount,
|
|
735
|
+
byType
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function calculateSha256(content) {
|
|
740
|
+
return createHash("sha256").update(content).digest("hex");
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/formatters/markdown.ts
|
|
744
|
+
function formatPackageAsMarkdown(pkg, level = "standard", locale = "en") {
|
|
745
|
+
const lines = [];
|
|
746
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
747
|
+
lines.push(`## [${pkg.next}] - ${date}`);
|
|
748
|
+
lines.push("");
|
|
749
|
+
const reasonLabel = formatReasonLabel(pkg.reason, locale);
|
|
750
|
+
lines.push(`> **${pkg.name}** ${pkg.prev} \u2192 ${pkg.next} (${reasonLabel})`);
|
|
751
|
+
lines.push("");
|
|
752
|
+
if (pkg.breaking && pkg.breaking.length > 0) {
|
|
753
|
+
lines.push("### " + t("breaking_changes", locale));
|
|
754
|
+
lines.push("");
|
|
755
|
+
for (const breaking of pkg.breaking) {
|
|
756
|
+
lines.push(`- **${breaking.summary}**`);
|
|
757
|
+
if (breaking.notes) {
|
|
758
|
+
lines.push(` ${breaking.notes}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
lines.push("");
|
|
762
|
+
}
|
|
763
|
+
const groupedChanges = groupChangesByType(pkg.changes);
|
|
764
|
+
for (const [type, changes] of Object.entries(groupedChanges)) {
|
|
765
|
+
if (changes.length === 0) {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
const sectionTitle = formatSectionTitle(type, locale);
|
|
769
|
+
lines.push(`### ${sectionTitle}`);
|
|
770
|
+
lines.push("");
|
|
771
|
+
for (const change of changes) {
|
|
772
|
+
lines.push(formatChangeLine(change, level));
|
|
773
|
+
}
|
|
774
|
+
lines.push("");
|
|
775
|
+
}
|
|
776
|
+
return lines.join("\n").trimEnd();
|
|
777
|
+
}
|
|
778
|
+
function formatChangeLine(change, level) {
|
|
779
|
+
const bullet = "-";
|
|
780
|
+
let line = `${bullet} **${change.scope || "global"}**: ${change.subject}`;
|
|
781
|
+
if (level === "compact") {
|
|
782
|
+
return line;
|
|
783
|
+
}
|
|
784
|
+
const links = [];
|
|
785
|
+
if (change.providerLinks?.commit) {
|
|
786
|
+
links.push(`[${change.sha.substring(0, 7)}](${change.providerLinks.commit})`);
|
|
787
|
+
}
|
|
788
|
+
if (change.providerLinks?.pr && change.providerLinks.pr.length > 0) {
|
|
789
|
+
for (const prLink of change.providerLinks.pr) {
|
|
790
|
+
const match = prLink.match(/[^/]+$/);
|
|
791
|
+
links.push(`[${match && match[0] ? match[0] : "PR"}](${prLink})`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (links.length > 0) {
|
|
795
|
+
line += ` (${links.join(", ")})`;
|
|
796
|
+
}
|
|
797
|
+
if (level === "detailed") {
|
|
798
|
+
const details = [];
|
|
799
|
+
if (change.author) {
|
|
800
|
+
details.push(`@${change.author.name}`);
|
|
801
|
+
}
|
|
802
|
+
if (change.coAuthors && change.coAuthors.length > 0) {
|
|
803
|
+
details.push(...change.coAuthors.map((a) => `@${a.name}`));
|
|
804
|
+
}
|
|
805
|
+
if (change.filesChanged && change.filesChanged.length > 0) {
|
|
806
|
+
details.push(`${change.filesChanged.length} files`);
|
|
807
|
+
}
|
|
808
|
+
if (details.length > 0) {
|
|
809
|
+
line += ` \u2014 ${details.join(", ")}`;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return line;
|
|
813
|
+
}
|
|
814
|
+
function groupChangesByType(changes) {
|
|
815
|
+
const grouped = {};
|
|
816
|
+
for (const change of changes) {
|
|
817
|
+
const type = change.type;
|
|
818
|
+
if (!grouped[type]) {
|
|
819
|
+
grouped[type] = [];
|
|
820
|
+
}
|
|
821
|
+
grouped[type].push(change);
|
|
822
|
+
}
|
|
823
|
+
return grouped;
|
|
824
|
+
}
|
|
825
|
+
function formatSectionTitle(type, locale) {
|
|
826
|
+
const titles = {
|
|
827
|
+
feat: { en: "Features", ru: "\u041D\u043E\u0432\u044B\u0435 \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E\u0441\u0442\u0438" },
|
|
828
|
+
fix: { en: "Bug Fixes", ru: "\u0418\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F" },
|
|
829
|
+
perf: { en: "Performance", ru: "\u041F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C" },
|
|
830
|
+
refactor: { en: "Refactoring", ru: "\u0420\u0435\u0444\u0430\u043A\u0442\u043E\u0440\u0438\u043D\u0433" },
|
|
831
|
+
docs: { en: "Documentation", ru: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F" },
|
|
832
|
+
build: { en: "Build", ru: "\u0421\u0431\u043E\u0440\u043A\u0430" },
|
|
833
|
+
ci: { en: "CI/CD", ru: "CI/CD" },
|
|
834
|
+
test: { en: "Tests", ru: "\u0422\u0435\u0441\u0442\u044B" },
|
|
835
|
+
chore: { en: "Chores", ru: "\u041E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435" },
|
|
836
|
+
revert: { en: "Reverts", ru: "\u041E\u0442\u043A\u0430\u0442\u044B" }
|
|
837
|
+
};
|
|
838
|
+
return titles[type]?.[locale] || type;
|
|
839
|
+
}
|
|
840
|
+
function formatReasonLabel(reason, locale) {
|
|
841
|
+
const labels = {
|
|
842
|
+
breaking: { en: "major \u2014 breaking changes", ru: "major \u2014 \u043A\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F" },
|
|
843
|
+
feat: { en: "minor \u2014 new features", ru: "minor \u2014 \u043D\u043E\u0432\u0430\u044F \u0444\u0443\u043D\u043A\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u044C" },
|
|
844
|
+
fix: { en: "patch \u2014 bug fixes", ru: "patch \u2014 \u0438\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F" },
|
|
845
|
+
perf: { en: "patch \u2014 performance", ru: "patch \u2014 \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C" },
|
|
846
|
+
ripple: { en: "patch \u2014 dependency update", ru: "patch \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439" },
|
|
847
|
+
manual: { en: "manual", ru: "\u0440\u0443\u0447\u043D\u043E\u0435" }
|
|
848
|
+
};
|
|
849
|
+
return labels[reason]?.[locale] ?? reason;
|
|
850
|
+
}
|
|
851
|
+
function t(key, locale) {
|
|
852
|
+
const translations = {
|
|
853
|
+
breaking_changes: { en: "BREAKING CHANGES", ru: "\u041A\u0420\u0418\u0422\u0418\u0427\u0415\u0421\u041A\u0418\u0415 \u0418\u0417\u041C\u0415\u041D\u0415\u041D\u0418\u042F" }
|
|
854
|
+
};
|
|
855
|
+
return translations[key]?.[locale] ?? key;
|
|
856
|
+
}
|
|
857
|
+
function formatLockstepChangelog(packages, version, locale = "en") {
|
|
858
|
+
const lines = [];
|
|
859
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
860
|
+
lines.push(`## [${version}] - ${date}`);
|
|
861
|
+
lines.push("");
|
|
862
|
+
const pkgCount = packages.length;
|
|
863
|
+
const pkgWord = locale === "ru" ? "\u043F\u0430\u043A\u0435\u0442\u043E\u0432" : pkgCount === 1 ? "package" : "packages";
|
|
864
|
+
lines.push(`**${pkgCount} ${pkgWord}** bumped to v${version}`);
|
|
865
|
+
lines.push("");
|
|
866
|
+
const changed = packages.filter((p) => p.bump !== "none");
|
|
867
|
+
if (changed.length > 0) {
|
|
868
|
+
const colPkg = locale === "ru" ? "\u041F\u0430\u043A\u0435\u0442" : "Package";
|
|
869
|
+
const colPrev = locale === "ru" ? "\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0430\u044F" : "Previous";
|
|
870
|
+
const colBump = locale === "ru" ? "\u0422\u0438\u043F" : "Bump";
|
|
871
|
+
lines.push(`| ${colPkg} | ${colPrev} | ${colBump} |`);
|
|
872
|
+
lines.push(`|---------|----------|------|`);
|
|
873
|
+
for (const pkg of changed) {
|
|
874
|
+
lines.push(`| \`${pkg.name}\` | ${pkg.prev} | ${pkg.bump} |`);
|
|
875
|
+
}
|
|
876
|
+
lines.push("");
|
|
877
|
+
}
|
|
878
|
+
const allBreaking = deduplicateBySummary(
|
|
879
|
+
packages.flatMap((p) => (p.breaking || []).map((b) => ({ ...b, _pkg: p.name })))
|
|
880
|
+
);
|
|
881
|
+
if (allBreaking.length > 0) {
|
|
882
|
+
lines.push(`### ${t("breaking_changes", locale)}`);
|
|
883
|
+
lines.push("");
|
|
884
|
+
for (const b of allBreaking) {
|
|
885
|
+
lines.push(`- **${b.summary}**`);
|
|
886
|
+
if (b.notes) {
|
|
887
|
+
lines.push(` ${b.notes}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
lines.push("");
|
|
891
|
+
}
|
|
892
|
+
const seenShas = /* @__PURE__ */ new Set();
|
|
893
|
+
const allChanges = [];
|
|
894
|
+
for (const pkg of packages) {
|
|
895
|
+
for (const change of pkg.changes) {
|
|
896
|
+
if (seenShas.has(change.sha)) {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if (["chore", "build", "ci", "style", "test"].includes(change.type)) {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
seenShas.add(change.sha);
|
|
903
|
+
allChanges.push({ change, packageName: pkg.name });
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
907
|
+
for (const item of allChanges) {
|
|
908
|
+
const type = item.change.type;
|
|
909
|
+
if (!grouped.has(type)) {
|
|
910
|
+
grouped.set(type, []);
|
|
911
|
+
}
|
|
912
|
+
grouped.get(type).push(item);
|
|
913
|
+
}
|
|
914
|
+
const typeOrder = ["feat", "fix", "perf", "refactor", "docs", "revert"];
|
|
915
|
+
for (const type of typeOrder) {
|
|
916
|
+
const items = grouped.get(type);
|
|
917
|
+
if (!items || items.length === 0) {
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
lines.push(`### ${formatSectionTitle(type, locale)}`);
|
|
921
|
+
lines.push("");
|
|
922
|
+
for (const { change, packageName } of items) {
|
|
923
|
+
const scopePart = change.scope ? `**${change.scope}**` : "**global**";
|
|
924
|
+
const pkgHint = change.scope === packageName || allChanges.filter((i) => i.change.sha === change.sha).length === 1 ? "" : ` *(${packageName})*`;
|
|
925
|
+
lines.push(`- ${scopePart}: ${change.subject}${pkgHint}`);
|
|
926
|
+
}
|
|
927
|
+
lines.push("");
|
|
928
|
+
}
|
|
929
|
+
if (allChanges.length === 0) {
|
|
930
|
+
const noChanges = locale === "ru" ? "\u0411\u0435\u0437 \u0444\u0443\u043D\u043A\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u044B\u0445 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439." : "No functional changes.";
|
|
931
|
+
lines.push(`*${noChanges}*`);
|
|
932
|
+
lines.push("");
|
|
933
|
+
}
|
|
934
|
+
return lines.join("\n").trimEnd();
|
|
935
|
+
}
|
|
936
|
+
function deduplicateBySummary(items) {
|
|
937
|
+
const seen = /* @__PURE__ */ new Set();
|
|
938
|
+
return items.filter((item) => {
|
|
939
|
+
if (seen.has(item.summary)) {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
seen.add(item.summary);
|
|
943
|
+
return true;
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/formatters/llm-markdown.ts
|
|
948
|
+
async function formatPackageWithLLM(platform, pkg, locale = "en") {
|
|
949
|
+
const llmAvailable = !!platform?.llm;
|
|
950
|
+
if (!llmAvailable) {
|
|
951
|
+
return formatPackageAsMarkdown(pkg, "standard", locale);
|
|
952
|
+
}
|
|
953
|
+
try {
|
|
954
|
+
const llm = platform.llm;
|
|
955
|
+
const substantialChanges = pkg.changes.filter(
|
|
956
|
+
(c) => !["chore", "build", "ci", "style", "test"].includes(c.type)
|
|
957
|
+
);
|
|
958
|
+
if (substantialChanges.length === 0) {
|
|
959
|
+
return "";
|
|
960
|
+
}
|
|
961
|
+
const grouped = groupByType(substantialChanges);
|
|
962
|
+
const changesContext = Object.entries(grouped).map(([type, changes]) => {
|
|
963
|
+
const items = changes.map((c) => ` - ${c.scope ? c.scope + ": " : ""}${c.subject}`).join("\n");
|
|
964
|
+
return `${type}:
|
|
965
|
+
${items}`;
|
|
966
|
+
}).join("\n\n");
|
|
967
|
+
const prompt = buildCorporatePrompt(pkg, changesContext, locale);
|
|
968
|
+
const systemPrompt = locale === "ru" ? "\u0422\u044B \u0442\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u043F\u0438\u0441\u0430\u0442\u0435\u043B\u044C, \u0441\u043E\u0437\u0434\u0430\u044E\u0449\u0438\u0439 changelog \u0434\u043B\u044F \u043A\u0440\u0443\u043F\u043D\u043E\u0439 \u043A\u043E\u043C\u043F\u0430\u043D\u0438\u0438. \u041F\u0438\u0448\u0438 \u043F\u0440\u043E\u0444\u0435\u0441\u0441\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u043E, \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u0438 \u043F\u043E\u043D\u044F\u0442\u043D\u043E." : "You are a technical writer creating changelogs for a major company. Write professionally, structured, and clearly.";
|
|
969
|
+
const response = await llm.complete(prompt, {
|
|
970
|
+
systemPrompt,
|
|
971
|
+
temperature: 0.7,
|
|
972
|
+
maxTokens: 1500
|
|
973
|
+
});
|
|
974
|
+
const formatted = formatLLMResponse(pkg, response.content, locale);
|
|
975
|
+
const validationResult = validateLLMChangelog(formatted, substantialChanges);
|
|
976
|
+
if (!validationResult.valid) {
|
|
977
|
+
console.warn(`LLM changelog validation failed: ${validationResult.errors.join(", ")}. Falling back to conventional format.`);
|
|
978
|
+
return formatPackageAsMarkdown(pkg, "standard", locale);
|
|
979
|
+
}
|
|
980
|
+
return formatted;
|
|
981
|
+
} catch (error) {
|
|
982
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
983
|
+
console.warn(`LLM changelog failed, falling back to conventional: ${errorMessage}`);
|
|
984
|
+
return formatPackageAsMarkdown(pkg, "standard", locale);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
function buildCorporatePrompt(pkg, changes, locale) {
|
|
988
|
+
const lang = locale === "ru" ? "Russian" : "English";
|
|
989
|
+
return `Generate a professional changelog entry for package "${pkg.name}" version ${pkg.prev} \u2192 ${pkg.next}.
|
|
990
|
+
|
|
991
|
+
Changes made:
|
|
992
|
+
${changes}
|
|
993
|
+
|
|
994
|
+
Requirements:
|
|
995
|
+
- Write in ${lang}
|
|
996
|
+
- Professional corporate tone (like OpenAI, GitHub, Microsoft releases)
|
|
997
|
+
- Start with a brief summary paragraph explaining WHAT changed and WHY it matters to users
|
|
998
|
+
- Group changes by category (Features, Improvements, Bug Fixes, etc)
|
|
999
|
+
- Focus on USER IMPACT, not technical details
|
|
1000
|
+
- Explain WHY changes matter, not just WHAT changed
|
|
1001
|
+
- Use clear, non-technical language when possible
|
|
1002
|
+
- Include emoji for readability: \u2728 Features, \u{1F41B} Fixes, \u26A1 Performance, \u{1F4DD} Documentation
|
|
1003
|
+
|
|
1004
|
+
Format:
|
|
1005
|
+
## ${pkg.name} ${pkg.next}
|
|
1006
|
+
|
|
1007
|
+
[1-2 sentence summary paragraph]
|
|
1008
|
+
|
|
1009
|
+
### \u2728 New Features
|
|
1010
|
+
- Clear description of feature and why it's useful
|
|
1011
|
+
|
|
1012
|
+
### \u{1F41B} Bug Fixes
|
|
1013
|
+
- What was fixed and how it helps users
|
|
1014
|
+
|
|
1015
|
+
### \u26A1 Performance Improvements
|
|
1016
|
+
- What's faster and by how much
|
|
1017
|
+
|
|
1018
|
+
DO NOT:
|
|
1019
|
+
- List commit SHAs or PR numbers
|
|
1020
|
+
- Use technical jargon unnecessarily
|
|
1021
|
+
- Mention internal refactoring unless user-facing
|
|
1022
|
+
- Include chore/build/ci changes
|
|
1023
|
+
|
|
1024
|
+
Output ONLY the markdown changelog, no explanations.`;
|
|
1025
|
+
}
|
|
1026
|
+
function formatLLMResponse(pkg, llmContent, locale) {
|
|
1027
|
+
let formatted = llmContent;
|
|
1028
|
+
if (!formatted.includes(`## ${pkg.name}`)) {
|
|
1029
|
+
const header = `## ${pkg.name} ${pkg.next}
|
|
1030
|
+
|
|
1031
|
+
`;
|
|
1032
|
+
formatted = header + formatted;
|
|
1033
|
+
}
|
|
1034
|
+
const footer = buildChangelogFooter(locale);
|
|
1035
|
+
formatted = formatted.trimEnd() + "\n\n" + footer;
|
|
1036
|
+
return formatted;
|
|
1037
|
+
}
|
|
1038
|
+
function buildChangelogFooter(locale) {
|
|
1039
|
+
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
1040
|
+
if (locale === "ru") {
|
|
1041
|
+
return `---
|
|
1042
|
+
|
|
1043
|
+
*\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E [**@kb-labs/release-manager**](https://github.com/kb-labs/kb-labs)*
|
|
1044
|
+
*\u0427\u0430\u0441\u0442\u044C \u044D\u043A\u043E\u0441\u0438\u0441\u0442\u0435\u043C\u044B **KB Labs Platform** \u2014 \u043F\u0440\u043E\u0444\u0435\u0441\u0441\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u044B\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0434\u043B\u044F \u0440\u0430\u0437\u0440\u0430\u0431\u043E\u0442\u043A\u0438*
|
|
1045
|
+
|
|
1046
|
+
<sub>\xA9 ${year} KB Labs. Released under KB Public License v1.1</sub>`;
|
|
1047
|
+
}
|
|
1048
|
+
return `---
|
|
1049
|
+
|
|
1050
|
+
*Generated automatically by [**@kb-labs/release-manager**](https://github.com/kb-labs/kb-labs)*
|
|
1051
|
+
*Part of the **KB Labs Platform** \u2014 Professional developer tools ecosystem*
|
|
1052
|
+
|
|
1053
|
+
<sub>\xA9 ${year} KB Labs. Released under KB Public License v1.1</sub>`;
|
|
1054
|
+
}
|
|
1055
|
+
function groupByType(changes) {
|
|
1056
|
+
const grouped = {};
|
|
1057
|
+
for (const change of changes) {
|
|
1058
|
+
if (!grouped[change.type]) {
|
|
1059
|
+
grouped[change.type] = [];
|
|
1060
|
+
}
|
|
1061
|
+
grouped[change.type].push(change);
|
|
1062
|
+
}
|
|
1063
|
+
return grouped;
|
|
1064
|
+
}
|
|
1065
|
+
function validateLLMChangelog(llmOutput, sourceChanges) {
|
|
1066
|
+
const errors = [];
|
|
1067
|
+
if (!llmOutput || llmOutput.trim().length === 0) {
|
|
1068
|
+
errors.push("Empty output");
|
|
1069
|
+
return { valid: false, errors };
|
|
1070
|
+
}
|
|
1071
|
+
if (!llmOutput.includes("#")) {
|
|
1072
|
+
errors.push("No markdown headers found");
|
|
1073
|
+
}
|
|
1074
|
+
new Set(sourceChanges.map((c) => c.subject.toLowerCase().trim()));
|
|
1075
|
+
new Set(sourceChanges.map((c) => c.scope?.toLowerCase().trim()).filter(Boolean));
|
|
1076
|
+
const shaPattern = /\b[0-9a-f]{7,40}\b/gi;
|
|
1077
|
+
const mentionedShas = llmOutput.match(shaPattern) || [];
|
|
1078
|
+
const sourceShas = new Set(sourceChanges.map((c) => c.sha.toLowerCase()));
|
|
1079
|
+
for (const sha of mentionedShas) {
|
|
1080
|
+
const lowerSha = sha.toLowerCase();
|
|
1081
|
+
const isValid = Array.from(sourceShas).some((sourceSha) => sourceSha.startsWith(lowerSha));
|
|
1082
|
+
if (!isValid) {
|
|
1083
|
+
errors.push(`Hallucinated SHA: ${sha}`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const prPattern = /#(\d+)/g;
|
|
1087
|
+
const mentionedPRs = /* @__PURE__ */ new Set();
|
|
1088
|
+
let match;
|
|
1089
|
+
while ((match = prPattern.exec(llmOutput)) !== null) {
|
|
1090
|
+
mentionedPRs.add(match[1]);
|
|
1091
|
+
}
|
|
1092
|
+
const sourceRefs = new Set(sourceChanges.flatMap((c) => c.refs.map((r) => r.id)));
|
|
1093
|
+
for (const pr of mentionedPRs) {
|
|
1094
|
+
if (!sourceRefs.has(pr)) {
|
|
1095
|
+
errors.push(`Hallucinated PR/issue: #${pr}`);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
const lineCount = llmOutput.split("\n").length;
|
|
1099
|
+
if (lineCount < 3) {
|
|
1100
|
+
errors.push("Output too short (< 3 lines)");
|
|
1101
|
+
}
|
|
1102
|
+
if (lineCount > 200) {
|
|
1103
|
+
errors.push("Output too verbose (> 200 lines)");
|
|
1104
|
+
}
|
|
1105
|
+
const forbiddenPatterns = [
|
|
1106
|
+
/\[REDACTED\]/i,
|
|
1107
|
+
/\[PLACEHOLDER\]/i,
|
|
1108
|
+
/\[TODO\]/i,
|
|
1109
|
+
/\[EXAMPLE\]/i
|
|
1110
|
+
];
|
|
1111
|
+
for (const pattern of forbiddenPatterns) {
|
|
1112
|
+
if (pattern.test(llmOutput)) {
|
|
1113
|
+
errors.push(`Forbidden pattern found: ${pattern.source}`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return {
|
|
1117
|
+
valid: errors.length === 0,
|
|
1118
|
+
errors
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// src/templates/types.ts
|
|
1123
|
+
function groupChangesByType2(changes) {
|
|
1124
|
+
const grouped = {};
|
|
1125
|
+
for (const change of changes) {
|
|
1126
|
+
const type = change.type;
|
|
1127
|
+
if (!grouped[type]) {
|
|
1128
|
+
grouped[type] = [];
|
|
1129
|
+
}
|
|
1130
|
+
grouped[type].push(change);
|
|
1131
|
+
}
|
|
1132
|
+
return grouped;
|
|
1133
|
+
}
|
|
1134
|
+
function packageToTemplateData(pkg, locale = "en", metadata) {
|
|
1135
|
+
return {
|
|
1136
|
+
package: {
|
|
1137
|
+
name: pkg.name,
|
|
1138
|
+
prev: pkg.prev,
|
|
1139
|
+
next: pkg.next,
|
|
1140
|
+
bump: pkg.bump,
|
|
1141
|
+
reason: pkg.reason,
|
|
1142
|
+
rippleFrom: pkg.rippleFrom
|
|
1143
|
+
},
|
|
1144
|
+
breaking: pkg.breaking,
|
|
1145
|
+
changes: groupChangesByType2(pkg.changes),
|
|
1146
|
+
locale,
|
|
1147
|
+
metadata
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
var __filename$1 = fileURLToPath(import.meta.url);
|
|
1151
|
+
var __dirname$1 = dirname(__filename$1);
|
|
1152
|
+
var BUILTIN_TEMPLATES = ["corporate", "corporate-ai", "technical", "compact"];
|
|
1153
|
+
async function loadTemplate(templateName, cwd) {
|
|
1154
|
+
if (isBuiltinTemplate(templateName)) {
|
|
1155
|
+
return loadBuiltinTemplate(templateName);
|
|
1156
|
+
}
|
|
1157
|
+
return loadCustomTemplate(templateName, cwd);
|
|
1158
|
+
}
|
|
1159
|
+
function isBuiltinTemplate(name) {
|
|
1160
|
+
return BUILTIN_TEMPLATES.includes(name);
|
|
1161
|
+
}
|
|
1162
|
+
async function loadBuiltinTemplate(name) {
|
|
1163
|
+
try {
|
|
1164
|
+
const templatePath = join(__dirname$1, "templates", "builtin", `${name}.js`);
|
|
1165
|
+
const templateUrl = pathToFileURL(templatePath).href;
|
|
1166
|
+
const module = await import(templateUrl);
|
|
1167
|
+
return validateTemplate(module, name);
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
throw new Error(
|
|
1170
|
+
`Failed to load built-in template "${name}": ${error instanceof Error ? error.message : String(error)}`
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async function loadCustomTemplate(templatePath, cwd) {
|
|
1175
|
+
const fullPath = isAbsolute(templatePath) ? templatePath : join(cwd, templatePath);
|
|
1176
|
+
try {
|
|
1177
|
+
await access(fullPath);
|
|
1178
|
+
} catch {
|
|
1179
|
+
throw new Error(`Template file not found: ${fullPath}`);
|
|
1180
|
+
}
|
|
1181
|
+
try {
|
|
1182
|
+
const fileUrl = pathToFileURL(fullPath).href;
|
|
1183
|
+
const module = await import(fileUrl);
|
|
1184
|
+
return validateTemplate(module, templatePath);
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
throw new Error(
|
|
1187
|
+
`Failed to load custom template "${templatePath}": ${error instanceof Error ? error.message : String(error)}`
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function validateTemplate(module, templateName) {
|
|
1192
|
+
if (!module.version || module.version !== "1.0") {
|
|
1193
|
+
throw new Error(
|
|
1194
|
+
`Template "${templateName}" has invalid version (expected "1.0", got "${module.version || "undefined"}")`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
if (typeof module.render !== "function") {
|
|
1198
|
+
throw new Error(`Template "${templateName}" must export a render() function`);
|
|
1199
|
+
}
|
|
1200
|
+
return module;
|
|
1201
|
+
}
|
|
1202
|
+
function listBuiltinTemplates() {
|
|
1203
|
+
return [
|
|
1204
|
+
{
|
|
1205
|
+
name: "corporate",
|
|
1206
|
+
description: "Professional changelog with emoji and grouped sections (sync, fast)"
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
name: "corporate-ai",
|
|
1210
|
+
description: "Corporate format with AI-enhanced descriptions (async, smart)"
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
name: "technical",
|
|
1214
|
+
description: "Developer-focused with commit SHAs, authors, and all commit types"
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
name: "compact",
|
|
1218
|
+
description: "Minimal one-line format for quick release notes"
|
|
1219
|
+
}
|
|
1220
|
+
];
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// src/changelog-generator.ts
|
|
1224
|
+
async function generateChangelog(options) {
|
|
1225
|
+
const {
|
|
1226
|
+
repoRoot,
|
|
1227
|
+
gitCwd = repoRoot,
|
|
1228
|
+
packages,
|
|
1229
|
+
range: rangeOptions,
|
|
1230
|
+
changelog: changelogConfig,
|
|
1231
|
+
git: gitConfig,
|
|
1232
|
+
platform,
|
|
1233
|
+
onProgress
|
|
1234
|
+
} = options;
|
|
1235
|
+
const locale = changelogConfig?.locale || "en";
|
|
1236
|
+
onProgress?.("Resolving git range...");
|
|
1237
|
+
const range = await resolveGitRange({
|
|
1238
|
+
cwd: gitCwd,
|
|
1239
|
+
from: rangeOptions?.from,
|
|
1240
|
+
to: rangeOptions?.to || "HEAD",
|
|
1241
|
+
sinceTag: rangeOptions?.sinceTag,
|
|
1242
|
+
autoUnshallow: gitConfig?.autoUnshallow,
|
|
1243
|
+
requireSignedTags: gitConfig?.requireSignedTags
|
|
1244
|
+
});
|
|
1245
|
+
onProgress?.("Detecting git provider...");
|
|
1246
|
+
const provider = await detectProvider(gitCwd, gitConfig?.baseUrl);
|
|
1247
|
+
onProgress?.("Parsing commits...");
|
|
1248
|
+
const changes = await parseCommits({
|
|
1249
|
+
cwd: gitCwd,
|
|
1250
|
+
from: range.from,
|
|
1251
|
+
to: range.to,
|
|
1252
|
+
ignoreAuthors: changelogConfig?.ignoreAuthors || [],
|
|
1253
|
+
includeTypes: changelogConfig?.includeTypes,
|
|
1254
|
+
excludeTypes: changelogConfig?.excludeTypes,
|
|
1255
|
+
collapseMerges: changelogConfig?.collapseMerges,
|
|
1256
|
+
collapseReverts: changelogConfig?.collapseReverts,
|
|
1257
|
+
preferMergeSummary: changelogConfig?.preferMergeSummary
|
|
1258
|
+
});
|
|
1259
|
+
onProgress?.("Enhancing changes with links...");
|
|
1260
|
+
const enhancedChanges = changes.map((change) => enhanceChangeWithLinks(change, provider));
|
|
1261
|
+
onProgress?.("Building package releases...");
|
|
1262
|
+
function normalizePkgPath(pkgPath) {
|
|
1263
|
+
let rel;
|
|
1264
|
+
if (pkgPath.startsWith(gitCwd)) {
|
|
1265
|
+
rel = pkgPath.slice(gitCwd.length).replace(/^\/+/, "");
|
|
1266
|
+
} else if (pkgPath.startsWith(repoRoot)) {
|
|
1267
|
+
rel = pkgPath.slice(repoRoot.length).replace(/^\/+/, "");
|
|
1268
|
+
} else {
|
|
1269
|
+
rel = pkgPath.replace(/^\/+/, "");
|
|
1270
|
+
}
|
|
1271
|
+
return rel.endsWith("/") ? rel : rel + "/";
|
|
1272
|
+
}
|
|
1273
|
+
const packageReleases = packages.map((pkg) => {
|
|
1274
|
+
const pkgPrefix = normalizePkgPath(pkg.path);
|
|
1275
|
+
const isRoot = pkgPrefix === "/" || pkgPrefix === "" || pkgPrefix === "./";
|
|
1276
|
+
const pkgChanges = isRoot ? enhancedChanges : enhancedChanges.filter(
|
|
1277
|
+
(c) => !c.filesChanged || c.filesChanged.length === 0 ? true : c.filesChanged.some((f) => f.startsWith(pkgPrefix))
|
|
1278
|
+
);
|
|
1279
|
+
const hasBreaking = pkgChanges.some((c) => c.breaking && c.breaking.length > 0);
|
|
1280
|
+
const hasFeat = pkgChanges.some((c) => c.type === "feat");
|
|
1281
|
+
const hasFix = pkgChanges.some((c) => c.type === "fix");
|
|
1282
|
+
const hasPerf = pkgChanges.some((c) => c.type === "perf");
|
|
1283
|
+
let reason = "manual";
|
|
1284
|
+
if (hasBreaking) {
|
|
1285
|
+
reason = "breaking";
|
|
1286
|
+
} else if (hasFeat) {
|
|
1287
|
+
reason = "feat";
|
|
1288
|
+
} else if (hasFix) {
|
|
1289
|
+
reason = "fix";
|
|
1290
|
+
} else if (hasPerf) {
|
|
1291
|
+
reason = "perf";
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
name: pkg.name,
|
|
1295
|
+
prev: pkg.currentVersion,
|
|
1296
|
+
next: pkg.nextVersion,
|
|
1297
|
+
bump: pkg.bump,
|
|
1298
|
+
reason,
|
|
1299
|
+
breaking: pkgChanges.filter((c) => c.breaking && c.breaking.length > 0).flatMap((c) => c.breaking),
|
|
1300
|
+
changes: pkgChanges
|
|
1301
|
+
};
|
|
1302
|
+
});
|
|
1303
|
+
const manifest = createReleaseManifest(range, packageReleases);
|
|
1304
|
+
const uniqueNextVersions = new Set(packageReleases.map((p) => p.next));
|
|
1305
|
+
const isLockstep = packageReleases.length > 1 && uniqueNextVersions.size === 1;
|
|
1306
|
+
let markdown;
|
|
1307
|
+
const templateName = changelogConfig?.template || "corporate-ai";
|
|
1308
|
+
onProgress?.(`Loading template "${templateName}"...`);
|
|
1309
|
+
const template = await loadTemplate(templateName, repoRoot);
|
|
1310
|
+
if (isLockstep) {
|
|
1311
|
+
onProgress?.("Formatting consolidated lockstep changelog...");
|
|
1312
|
+
const sharedVersion = packageReleases[0].next;
|
|
1313
|
+
const sharedPrev = packageReleases[0].prev;
|
|
1314
|
+
const mergedChanges = enhancedChanges;
|
|
1315
|
+
const seenBreaking = /* @__PURE__ */ new Set();
|
|
1316
|
+
const mergedBreaking = enhancedChanges.filter((c) => c.breaking && c.breaking.length > 0).flatMap((c) => c.breaking).filter((b) => {
|
|
1317
|
+
if (seenBreaking.has(b.summary)) {
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1320
|
+
seenBreaking.add(b.summary);
|
|
1321
|
+
return true;
|
|
1322
|
+
});
|
|
1323
|
+
const hasBreaking = mergedBreaking.length > 0;
|
|
1324
|
+
const hasFeat = mergedChanges.some((c) => c.type === "feat");
|
|
1325
|
+
const hasFix = mergedChanges.some((c) => c.type === "fix");
|
|
1326
|
+
const hasPerf = mergedChanges.some((c) => c.type === "perf");
|
|
1327
|
+
let reason = "manual";
|
|
1328
|
+
if (hasBreaking) {
|
|
1329
|
+
reason = "breaking";
|
|
1330
|
+
} else if (hasFeat) {
|
|
1331
|
+
reason = "feat";
|
|
1332
|
+
} else if (hasFix) {
|
|
1333
|
+
reason = "fix";
|
|
1334
|
+
} else if (hasPerf) {
|
|
1335
|
+
reason = "perf";
|
|
1336
|
+
}
|
|
1337
|
+
const scopeName = packages.length > 0 ? packages[0].name.replace(/\/[^/]+$/, "") : "monorepo";
|
|
1338
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1339
|
+
const pkgWord = locale === "ru" ? "\u043F\u0430\u043A\u0435\u0442\u043E\u0432" : packages.length === 1 ? "package" : "packages";
|
|
1340
|
+
const headerLines = [
|
|
1341
|
+
`## [${sharedVersion}] - ${date}`,
|
|
1342
|
+
"",
|
|
1343
|
+
`**${packageReleases.length} ${pkgWord}** bumped to v${sharedVersion}`,
|
|
1344
|
+
"",
|
|
1345
|
+
`| ${locale === "ru" ? "\u041F\u0430\u043A\u0435\u0442" : "Package"} | ${locale === "ru" ? "\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0430\u044F" : "Previous"} | ${locale === "ru" ? "\u0422\u0438\u043F" : "Bump"} |`,
|
|
1346
|
+
`|---------|----------|------|`,
|
|
1347
|
+
...packageReleases.filter((p) => p.bump !== "none").map((p) => `| \`${p.name}\` | ${p.prev} | ${p.bump} |`),
|
|
1348
|
+
""
|
|
1349
|
+
];
|
|
1350
|
+
const mergedRelease = {
|
|
1351
|
+
name: scopeName,
|
|
1352
|
+
prev: sharedPrev,
|
|
1353
|
+
next: sharedVersion,
|
|
1354
|
+
bump: packageReleases[0].bump,
|
|
1355
|
+
reason,
|
|
1356
|
+
breaking: mergedBreaking,
|
|
1357
|
+
changes: mergedChanges
|
|
1358
|
+
};
|
|
1359
|
+
onProgress?.("Enhancing lockstep changelog with template...");
|
|
1360
|
+
const templateData = packageToTemplateData(mergedRelease, locale, changelogConfig?.metadata);
|
|
1361
|
+
const result = template.render(templateData, platform);
|
|
1362
|
+
const rendered = typeof result === "string" ? result : await result;
|
|
1363
|
+
const renderedLines = rendered.split("\n");
|
|
1364
|
+
let contentStartIdx = 0;
|
|
1365
|
+
for (let i = 0; i < renderedLines.length; i++) {
|
|
1366
|
+
const line = renderedLines[i];
|
|
1367
|
+
if (line.startsWith("## [") || line.startsWith("> **") || line.trim() === "") {
|
|
1368
|
+
contentStartIdx = i + 1;
|
|
1369
|
+
} else {
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const contentBody = renderedLines.slice(contentStartIdx).join("\n").trim();
|
|
1374
|
+
markdown = headerLines.join("\n") + (contentBody ? "\n" + contentBody : `
|
|
1375
|
+
*${locale === "ru" ? "\u0411\u0435\u0437 \u0444\u0443\u043D\u043A\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u044B\u0445 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439." : "No functional changes."}*`);
|
|
1376
|
+
} else {
|
|
1377
|
+
const formattedPackages = [];
|
|
1378
|
+
for (let i = 0; i < packageReleases.length; i++) {
|
|
1379
|
+
const pkg = packageReleases[i];
|
|
1380
|
+
if (!pkg) {
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
onProgress?.(`Formatting changelog for ${pkg.name} (${i + 1}/${packageReleases.length})...`);
|
|
1384
|
+
const templateData = packageToTemplateData(pkg, locale, changelogConfig?.metadata);
|
|
1385
|
+
const result = template.render(templateData, platform);
|
|
1386
|
+
const formatted = typeof result === "string" ? result : await result;
|
|
1387
|
+
formattedPackages.push(formatted);
|
|
1388
|
+
}
|
|
1389
|
+
markdown = formattedPackages.join("\n\n");
|
|
1390
|
+
}
|
|
1391
|
+
return {
|
|
1392
|
+
markdown,
|
|
1393
|
+
manifest: JSON.parse(formatAsJson(manifest)),
|
|
1394
|
+
changes: enhancedChanges,
|
|
1395
|
+
range,
|
|
1396
|
+
packages: packageReleases
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
function generateSimpleChangelog(packages, locale = "en") {
|
|
1400
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1401
|
+
const uniqueVersions = new Set(packages.map((p) => p.nextVersion));
|
|
1402
|
+
const isLockstep = packages.length > 1 && uniqueVersions.size === 1;
|
|
1403
|
+
if (isLockstep) {
|
|
1404
|
+
const version = packages[0].nextVersion;
|
|
1405
|
+
const pkgWord = locale === "ru" ? "\u043F\u0430\u043A\u0435\u0442\u043E\u0432" : packages.length === 1 ? "package" : "packages";
|
|
1406
|
+
const lines2 = [
|
|
1407
|
+
`## [${version}] - ${date}`,
|
|
1408
|
+
"",
|
|
1409
|
+
`**${packages.length} ${pkgWord}** bumped to v${version}`,
|
|
1410
|
+
"",
|
|
1411
|
+
`| Package | Previous | Bump |`,
|
|
1412
|
+
`|---------|----------|------|`,
|
|
1413
|
+
...packages.map((p) => `| \`${p.name}\` | ${p.currentVersion} | ${p.bump} |`)
|
|
1414
|
+
];
|
|
1415
|
+
return lines2.join("\n");
|
|
1416
|
+
}
|
|
1417
|
+
const title = locale === "ru" ? "\u0420\u0435\u043B\u0438\u0437" : "Release";
|
|
1418
|
+
const lines = [`## [${date}] ${title}
|
|
1419
|
+
|
|
1420
|
+
`];
|
|
1421
|
+
for (const pkg of packages) {
|
|
1422
|
+
lines.push(`- **${pkg.name}**: ${pkg.currentVersion} \u2192 ${pkg.nextVersion}`);
|
|
1423
|
+
}
|
|
1424
|
+
return lines.join("\n");
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
export { BUILTIN_TEMPLATES, acquireLock, applyVersionPolicy, computeBump, computeNextVersion, createReleaseManifest, detectProvider, enhanceChangeWithLinks, findLastTag, findPackageTag, formatAsJson, formatCommitLink, formatIssueLink, formatLockstepChangelog, formatPackageAsMarkdown, formatPackageWithLLM, formatPrLink, generateChangelog, generateSimpleChangelog, getAffectedPackages, getCachedChange, getImpactReason, getLastTag, getRipplePackages, groupChangesByType2 as groupChangesByType, isCacheValid, listBuiltinTemplates, loadCache, loadTemplate, packageToTemplateData, parseCommits, parseGitUrl, resolveGitRange, saveCache, saveGraphSnapshot, updateCache, updateHead, updateLastTag };
|
|
1428
|
+
//# sourceMappingURL=index.js.map
|
|
1429
|
+
//# sourceMappingURL=index.js.map
|