@releasekit/notes 0.2.0-next.9 → 0.3.0-next.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 +21 -0
- package/README.md +1 -1
- package/dist/aggregator-BDTUZWOA.js +13 -0
- package/dist/chunk-H7G2HRHI.js +134 -0
- package/dist/chunk-O4VCGEZT.js +147 -0
- package/dist/{chunk-BLWJTLRD.js → chunk-X4LY5WGG.js} +289 -394
- package/dist/cli.cjs +542 -369
- package/dist/cli.js +6 -27
- package/dist/index.cjs +544 -350
- package/dist/index.d.cts +41 -8
- package/dist/index.d.ts +41 -8
- package/dist/index.js +12 -6
- package/package.json +42 -31
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatVersion,
|
|
3
|
+
renderMarkdown,
|
|
4
|
+
writeMarkdown
|
|
5
|
+
} from "./chunk-H7G2HRHI.js";
|
|
6
|
+
|
|
1
7
|
// src/core/config.ts
|
|
2
8
|
import {
|
|
3
9
|
loadAuth,
|
|
@@ -145,137 +151,10 @@ async function parsePackageVersionerStdin() {
|
|
|
145
151
|
return parsePackageVersioner(content);
|
|
146
152
|
}
|
|
147
153
|
|
|
148
|
-
// src/output/
|
|
154
|
+
// src/output/json.ts
|
|
149
155
|
import * as fs2 from "fs";
|
|
150
156
|
import * as path from "path";
|
|
151
|
-
import { info, success } from "@releasekit/core";
|
|
152
|
-
var TYPE_ORDER = ["added", "changed", "deprecated", "removed", "fixed", "security"];
|
|
153
|
-
var TYPE_LABELS = {
|
|
154
|
-
added: "Added",
|
|
155
|
-
changed: "Changed",
|
|
156
|
-
deprecated: "Deprecated",
|
|
157
|
-
removed: "Removed",
|
|
158
|
-
fixed: "Fixed",
|
|
159
|
-
security: "Security"
|
|
160
|
-
};
|
|
161
|
-
function groupEntriesByType(entries) {
|
|
162
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
163
|
-
for (const type of TYPE_ORDER) {
|
|
164
|
-
grouped.set(type, []);
|
|
165
|
-
}
|
|
166
|
-
for (const entry of entries) {
|
|
167
|
-
const existing = grouped.get(entry.type) ?? [];
|
|
168
|
-
existing.push(entry);
|
|
169
|
-
grouped.set(entry.type, existing);
|
|
170
|
-
}
|
|
171
|
-
return grouped;
|
|
172
|
-
}
|
|
173
|
-
function formatEntry(entry) {
|
|
174
|
-
let line;
|
|
175
|
-
if (entry.breaking && entry.scope) {
|
|
176
|
-
line = `- **BREAKING** **${entry.scope}**: ${entry.description}`;
|
|
177
|
-
} else if (entry.breaking) {
|
|
178
|
-
line = `- **BREAKING** ${entry.description}`;
|
|
179
|
-
} else if (entry.scope) {
|
|
180
|
-
line = `- **${entry.scope}**: ${entry.description}`;
|
|
181
|
-
} else {
|
|
182
|
-
line = `- ${entry.description}`;
|
|
183
|
-
}
|
|
184
|
-
if (entry.issueIds && entry.issueIds.length > 0) {
|
|
185
|
-
line += ` (${entry.issueIds.join(", ")})`;
|
|
186
|
-
}
|
|
187
|
-
return line;
|
|
188
|
-
}
|
|
189
|
-
function formatVersion(context) {
|
|
190
|
-
const lines = [];
|
|
191
|
-
const versionHeader = context.previousVersion ? `## [${context.version}]` : `## ${context.version}`;
|
|
192
|
-
lines.push(`${versionHeader} - ${context.date}`);
|
|
193
|
-
lines.push("");
|
|
194
|
-
if (context.compareUrl) {
|
|
195
|
-
lines.push(`[Full Changelog](${context.compareUrl})`);
|
|
196
|
-
lines.push("");
|
|
197
|
-
}
|
|
198
|
-
if (context.enhanced?.summary) {
|
|
199
|
-
lines.push(context.enhanced.summary);
|
|
200
|
-
lines.push("");
|
|
201
|
-
}
|
|
202
|
-
const grouped = groupEntriesByType(context.entries);
|
|
203
|
-
for (const [type, entries] of grouped) {
|
|
204
|
-
if (entries.length === 0) continue;
|
|
205
|
-
lines.push(`### ${TYPE_LABELS[type]}`);
|
|
206
|
-
for (const entry of entries) {
|
|
207
|
-
lines.push(formatEntry(entry));
|
|
208
|
-
}
|
|
209
|
-
lines.push("");
|
|
210
|
-
}
|
|
211
|
-
return lines.join("\n");
|
|
212
|
-
}
|
|
213
|
-
function formatHeader() {
|
|
214
|
-
return `# Changelog
|
|
215
|
-
|
|
216
|
-
All notable changes to this project will be documented in this file.
|
|
217
|
-
|
|
218
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
219
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
220
|
-
|
|
221
|
-
`;
|
|
222
|
-
}
|
|
223
|
-
function renderMarkdown(contexts) {
|
|
224
|
-
const sections = [formatHeader()];
|
|
225
|
-
for (const context of contexts) {
|
|
226
|
-
sections.push(formatVersion(context));
|
|
227
|
-
}
|
|
228
|
-
return sections.join("\n");
|
|
229
|
-
}
|
|
230
|
-
function prependVersion(existingPath, context) {
|
|
231
|
-
let existing = "";
|
|
232
|
-
if (fs2.existsSync(existingPath)) {
|
|
233
|
-
existing = fs2.readFileSync(existingPath, "utf-8");
|
|
234
|
-
const headerEnd = existing.indexOf("\n## ");
|
|
235
|
-
if (headerEnd >= 0) {
|
|
236
|
-
const header = existing.slice(0, headerEnd);
|
|
237
|
-
const body = existing.slice(headerEnd + 1);
|
|
238
|
-
const newVersion = formatVersion(context);
|
|
239
|
-
return `${header}
|
|
240
|
-
|
|
241
|
-
${newVersion}
|
|
242
|
-
${body}`;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return renderMarkdown([context]);
|
|
246
|
-
}
|
|
247
|
-
function writeMarkdown(outputPath, contexts, config, dryRun) {
|
|
248
|
-
const content = renderMarkdown(contexts);
|
|
249
|
-
if (dryRun) {
|
|
250
|
-
info("--- Changelog Preview ---");
|
|
251
|
-
console.log(content);
|
|
252
|
-
info("--- End Preview ---");
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const dir = path.dirname(outputPath);
|
|
256
|
-
if (!fs2.existsSync(dir)) {
|
|
257
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
258
|
-
}
|
|
259
|
-
if (outputPath === "-") {
|
|
260
|
-
process.stdout.write(content);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (config.updateStrategy === "prepend" && fs2.existsSync(outputPath) && contexts.length === 1) {
|
|
264
|
-
const firstContext = contexts[0];
|
|
265
|
-
if (firstContext) {
|
|
266
|
-
const updated = prependVersion(outputPath, firstContext);
|
|
267
|
-
fs2.writeFileSync(outputPath, updated, "utf-8");
|
|
268
|
-
}
|
|
269
|
-
} else {
|
|
270
|
-
fs2.writeFileSync(outputPath, content, "utf-8");
|
|
271
|
-
}
|
|
272
|
-
success(`Changelog written to ${outputPath}`);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// src/output/json.ts
|
|
276
|
-
import * as fs3 from "fs";
|
|
277
|
-
import * as path2 from "path";
|
|
278
|
-
import { info as info2, success as success2 } from "@releasekit/core";
|
|
157
|
+
import { debug, info, success } from "@releasekit/core";
|
|
279
158
|
function renderJson(contexts) {
|
|
280
159
|
return JSON.stringify(
|
|
281
160
|
{
|
|
@@ -295,23 +174,24 @@ function renderJson(contexts) {
|
|
|
295
174
|
function writeJson(outputPath, contexts, dryRun) {
|
|
296
175
|
const content = renderJson(contexts);
|
|
297
176
|
if (dryRun) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
177
|
+
info(`Would write JSON output to ${outputPath}`);
|
|
178
|
+
debug("--- JSON Output Preview ---");
|
|
179
|
+
debug(content);
|
|
180
|
+
debug("--- End Preview ---");
|
|
301
181
|
return;
|
|
302
182
|
}
|
|
303
|
-
const dir =
|
|
304
|
-
if (!
|
|
305
|
-
|
|
183
|
+
const dir = path.dirname(outputPath);
|
|
184
|
+
if (!fs2.existsSync(dir)) {
|
|
185
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
306
186
|
}
|
|
307
|
-
|
|
308
|
-
|
|
187
|
+
fs2.writeFileSync(outputPath, content, "utf-8");
|
|
188
|
+
success(`JSON output written to ${outputPath}`);
|
|
309
189
|
}
|
|
310
190
|
|
|
311
191
|
// src/core/pipeline.ts
|
|
312
|
-
import * as
|
|
313
|
-
import * as
|
|
314
|
-
import { debug, info as
|
|
192
|
+
import * as fs7 from "fs";
|
|
193
|
+
import * as path5 from "path";
|
|
194
|
+
import { debug as debug2, info as info3, success as success3, warn as warn3 } from "@releasekit/core";
|
|
315
195
|
|
|
316
196
|
// src/llm/defaults.ts
|
|
317
197
|
var LLM_DEFAULTS = {
|
|
@@ -508,6 +388,83 @@ var OpenAICompatibleProvider = class extends BaseLLMProvider {
|
|
|
508
388
|
|
|
509
389
|
// src/llm/tasks/categorize.ts
|
|
510
390
|
import { warn } from "@releasekit/core";
|
|
391
|
+
|
|
392
|
+
// src/llm/prompts.ts
|
|
393
|
+
function resolvePrompt(taskName, defaultPrompt, promptsConfig) {
|
|
394
|
+
if (!promptsConfig) return defaultPrompt;
|
|
395
|
+
const fullTemplate = promptsConfig.templates?.[taskName];
|
|
396
|
+
if (fullTemplate) return fullTemplate;
|
|
397
|
+
const additionalInstructions = promptsConfig.instructions?.[taskName];
|
|
398
|
+
if (additionalInstructions) {
|
|
399
|
+
const insertionPoint = defaultPrompt.lastIndexOf("Output only valid JSON");
|
|
400
|
+
if (insertionPoint !== -1) {
|
|
401
|
+
return `${defaultPrompt.slice(0, insertionPoint)}Additional instructions:
|
|
402
|
+
${additionalInstructions}
|
|
403
|
+
|
|
404
|
+
${defaultPrompt.slice(insertionPoint)}`;
|
|
405
|
+
}
|
|
406
|
+
return `${defaultPrompt}
|
|
407
|
+
|
|
408
|
+
Additional instructions:
|
|
409
|
+
${additionalInstructions}`;
|
|
410
|
+
}
|
|
411
|
+
return defaultPrompt;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/llm/scopes.ts
|
|
415
|
+
function getAllowedScopesFromCategories(categories) {
|
|
416
|
+
const scopeMap = /* @__PURE__ */ new Map();
|
|
417
|
+
for (const cat of categories) {
|
|
418
|
+
if (cat.scopes && cat.scopes.length > 0) {
|
|
419
|
+
scopeMap.set(cat.name, cat.scopes);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return scopeMap;
|
|
423
|
+
}
|
|
424
|
+
function resolveAllowedScopes(scopeConfig, categories, packageNames) {
|
|
425
|
+
if (!scopeConfig || scopeConfig.mode === "unrestricted") return null;
|
|
426
|
+
if (scopeConfig.mode === "none") return [];
|
|
427
|
+
if (scopeConfig.mode === "packages") return packageNames ?? [];
|
|
428
|
+
if (scopeConfig.mode === "restricted") {
|
|
429
|
+
const explicit = scopeConfig.rules?.allowed ?? [];
|
|
430
|
+
const all = new Set(explicit);
|
|
431
|
+
if (categories) {
|
|
432
|
+
const fromCategories = getAllowedScopesFromCategories(categories);
|
|
433
|
+
for (const scopes of fromCategories.values()) {
|
|
434
|
+
for (const s of scopes) all.add(s);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return [...all];
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
function validateScope(scope, allowedScopes, rules) {
|
|
442
|
+
if (!scope || allowedScopes === null) return scope;
|
|
443
|
+
if (allowedScopes.length === 0) return void 0;
|
|
444
|
+
const caseSensitive = rules?.caseSensitive ?? false;
|
|
445
|
+
const normalise = (s) => caseSensitive ? s : s.toLowerCase();
|
|
446
|
+
const isAllowed = allowedScopes.some((a) => normalise(a) === normalise(scope));
|
|
447
|
+
if (isAllowed) return scope;
|
|
448
|
+
switch (rules?.invalidScopeAction ?? "remove") {
|
|
449
|
+
case "keep":
|
|
450
|
+
return scope;
|
|
451
|
+
case "fallback":
|
|
452
|
+
return rules?.fallbackScope;
|
|
453
|
+
case "remove":
|
|
454
|
+
default:
|
|
455
|
+
return void 0;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function validateEntryScopes(entries, scopeConfig, categories) {
|
|
459
|
+
const allowedScopes = resolveAllowedScopes(scopeConfig, categories);
|
|
460
|
+
if (allowedScopes === null) return entries;
|
|
461
|
+
return entries.map((entry) => ({
|
|
462
|
+
...entry,
|
|
463
|
+
scope: validateScope(entry.scope, allowedScopes, scopeConfig?.rules)
|
|
464
|
+
}));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/llm/tasks/categorize.ts
|
|
511
468
|
var DEFAULT_CATEGORIZE_PROMPT = `You are categorizing changelog entries for a software release.
|
|
512
469
|
|
|
513
470
|
Given the following entries, group them into meaningful categories (e.g., "Core", "UI", "API", "Performance", "Bug Fixes", "Documentation").
|
|
@@ -519,20 +476,21 @@ Entries:
|
|
|
519
476
|
|
|
520
477
|
Output only valid JSON, nothing else:`;
|
|
521
478
|
function buildCustomCategorizePrompt(categories) {
|
|
522
|
-
const categoryList = categories.map((c) =>
|
|
523
|
-
|
|
479
|
+
const categoryList = categories.map((c) => {
|
|
480
|
+
const scopeInfo = c.scopes?.length ? ` Allowed scopes: ${c.scopes.join(", ")}.` : "";
|
|
481
|
+
return `- "${c.name}": ${c.description}${scopeInfo}`;
|
|
482
|
+
}).join("\n");
|
|
483
|
+
const scopeMap = getAllowedScopesFromCategories(categories);
|
|
524
484
|
let scopeInstructions = "";
|
|
525
|
-
if (
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (scopes.length > 0) {
|
|
530
|
-
scopeInstructions = `
|
|
531
|
-
|
|
532
|
-
For the "Developer" category, you MUST assign a scope from this exact list: ${scopes.join(", ")}.
|
|
533
|
-
`;
|
|
534
|
-
}
|
|
485
|
+
if (scopeMap.size > 0) {
|
|
486
|
+
const entries = [];
|
|
487
|
+
for (const [catName, scopes] of scopeMap) {
|
|
488
|
+
entries.push(`For "${catName}", assign a scope from: ${scopes.join(", ")}.`);
|
|
535
489
|
}
|
|
490
|
+
scopeInstructions = `
|
|
491
|
+
|
|
492
|
+
${entries.join("\n")}
|
|
493
|
+
Only use scopes from these predefined lists. If an entry does not fit any scope, set scope to null.`;
|
|
536
494
|
}
|
|
537
495
|
return `You are categorizing changelog entries for a software release.
|
|
538
496
|
|
|
@@ -540,9 +498,10 @@ Given the following entries, group them into the specified categories. Only use
|
|
|
540
498
|
|
|
541
499
|
Categories:
|
|
542
500
|
${categoryList}${scopeInstructions}
|
|
501
|
+
|
|
543
502
|
Output a JSON object with two fields:
|
|
544
503
|
- "categories": an object where keys are category names and values are arrays of entry indices (0-based)
|
|
545
|
-
- "scopes": an object where keys are entry indices (as strings) and values are scope labels
|
|
504
|
+
- "scopes": an object where keys are entry indices (as strings) and values are scope labels. Only include entries that have a valid scope from the predefined list.
|
|
546
505
|
|
|
547
506
|
Entries:
|
|
548
507
|
{{entries}}
|
|
@@ -553,9 +512,11 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
553
512
|
if (entries.length === 0) {
|
|
554
513
|
return [];
|
|
555
514
|
}
|
|
556
|
-
const
|
|
515
|
+
const entriesCopy = entries.map((e) => ({ ...e, scope: void 0 }));
|
|
516
|
+
const entriesText = entriesCopy.map((e, i) => `${i}. [${e.type}]: ${e.description}`).join("\n");
|
|
557
517
|
const hasCustomCategories = context.categories && context.categories.length > 0;
|
|
558
|
-
const
|
|
518
|
+
const defaultPrompt = hasCustomCategories ? buildCustomCategorizePrompt(context.categories) : DEFAULT_CATEGORIZE_PROMPT;
|
|
519
|
+
const promptTemplate = resolvePrompt("categorize", defaultPrompt, context.prompts);
|
|
559
520
|
const prompt = promptTemplate.replace("{{entries}}", entriesText);
|
|
560
521
|
try {
|
|
561
522
|
const response = await provider.complete(prompt);
|
|
@@ -567,13 +528,14 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
567
528
|
const scopeMap = parsed.scopes || {};
|
|
568
529
|
for (const [indexStr, scope] of Object.entries(scopeMap)) {
|
|
569
530
|
const idx = Number.parseInt(indexStr, 10);
|
|
570
|
-
if (
|
|
571
|
-
|
|
531
|
+
if (entriesCopy[idx] && scope && scope.trim()) {
|
|
532
|
+
entriesCopy[idx] = { ...entriesCopy[idx], scope: scope.trim() };
|
|
572
533
|
}
|
|
573
534
|
}
|
|
535
|
+
const validatedEntries = validateEntryScopes(entriesCopy, context.scopes, context.categories);
|
|
574
536
|
for (const [category, rawIndices] of Object.entries(categoryMap)) {
|
|
575
537
|
const indices = Array.isArray(rawIndices) ? rawIndices : [];
|
|
576
|
-
const categoryEntries = indices.map((i) =>
|
|
538
|
+
const categoryEntries = indices.map((i) => validatedEntries[i]).filter((e) => e !== void 0);
|
|
577
539
|
if (categoryEntries.length > 0) {
|
|
578
540
|
result.push({ category, entries: categoryEntries });
|
|
579
541
|
}
|
|
@@ -582,7 +544,7 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
582
544
|
const categoryMap = parsed;
|
|
583
545
|
for (const [category, rawIndices] of Object.entries(categoryMap)) {
|
|
584
546
|
const indices = Array.isArray(rawIndices) ? rawIndices : [];
|
|
585
|
-
const categoryEntries = indices.map((i) =>
|
|
547
|
+
const categoryEntries = indices.map((i) => entriesCopy[i]).filter((e) => e !== void 0);
|
|
586
548
|
if (categoryEntries.length > 0) {
|
|
587
549
|
result.push({ category, entries: categoryEntries });
|
|
588
550
|
}
|
|
@@ -593,12 +555,12 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
593
555
|
warn(
|
|
594
556
|
`LLM categorization failed, falling back to General: ${error instanceof Error ? error.message : String(error)}`
|
|
595
557
|
);
|
|
596
|
-
return [{ category: "General", entries }];
|
|
558
|
+
return [{ category: "General", entries: entriesCopy }];
|
|
597
559
|
}
|
|
598
560
|
}
|
|
599
561
|
|
|
600
562
|
// src/llm/tasks/enhance.ts
|
|
601
|
-
var
|
|
563
|
+
var DEFAULT_ENHANCE_PROMPT = `You are improving changelog entries for a software project.
|
|
602
564
|
Given a technical commit message, rewrite it as a clear, user-friendly changelog entry.
|
|
603
565
|
|
|
604
566
|
Rules:
|
|
@@ -614,9 +576,10 @@ Type: {{type}}
|
|
|
614
576
|
Description: {{description}}
|
|
615
577
|
|
|
616
578
|
Rewritten description (only output the new description, nothing else):`;
|
|
617
|
-
async function enhanceEntry(provider, entry,
|
|
618
|
-
const styleText =
|
|
619
|
-
const
|
|
579
|
+
async function enhanceEntry(provider, entry, context) {
|
|
580
|
+
const styleText = context.style ? `- ${context.style}` : '- Use present tense ("Add feature" not "Added feature")';
|
|
581
|
+
const defaultPrompt = DEFAULT_ENHANCE_PROMPT.replace("{{style}}", styleText).replace("{{type}}", entry.type).replace("{{#if scope}}Scope: {{scope}}{{/if}}", entry.scope ? `Scope: ${entry.scope}` : "").replace("{{description}}", entry.description);
|
|
582
|
+
const prompt = resolvePrompt("enhance", defaultPrompt, context.prompts);
|
|
620
583
|
const response = await provider.complete(prompt);
|
|
621
584
|
return response.trim();
|
|
622
585
|
}
|
|
@@ -672,7 +635,23 @@ function buildPrompt(entries, categories, style) {
|
|
|
672
635
|
const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
|
|
673
636
|
const styleText = style || 'Use present tense ("Add feature" not "Added feature"). Be concise.';
|
|
674
637
|
const categorySection = categories ? `Categories (use ONLY these):
|
|
675
|
-
${categories.map((c) =>
|
|
638
|
+
${categories.map((c) => {
|
|
639
|
+
const scopeInfo = c.scopes?.length ? ` Allowed scopes: ${c.scopes.join(", ")}.` : "";
|
|
640
|
+
return `- "${c.name}": ${c.description}${scopeInfo}`;
|
|
641
|
+
}).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
|
|
642
|
+
let scopeInstruction = "";
|
|
643
|
+
if (categories) {
|
|
644
|
+
const scopeMap = getAllowedScopesFromCategories(categories);
|
|
645
|
+
if (scopeMap.size > 0) {
|
|
646
|
+
const parts = [];
|
|
647
|
+
for (const [catName, scopes] of scopeMap) {
|
|
648
|
+
parts.push(`For "${catName}" entries, assign a scope from: ${scopes.join(", ")}.`);
|
|
649
|
+
}
|
|
650
|
+
scopeInstruction = `
|
|
651
|
+
${parts.join("\n")}
|
|
652
|
+
Only use scopes from these predefined lists. Set scope to null if no scope applies.`;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
676
655
|
return `You are generating release notes for a software project. Given the following changelog entries, do two things:
|
|
677
656
|
|
|
678
657
|
1. **Rewrite** each entry as a clear, user-friendly description
|
|
@@ -683,9 +662,7 @@ Style guidelines:
|
|
|
683
662
|
- Be concise (1 short sentence per entry)
|
|
684
663
|
- Focus on what changed, not implementation details
|
|
685
664
|
|
|
686
|
-
${categorySection}
|
|
687
|
-
|
|
688
|
-
${categories ? 'For entries in categories involving internal/developer changes, set a "scope" field with a short subcategory label (e.g., "CI", "Dependencies", "Testing").' : ""}
|
|
665
|
+
${categorySection}${scopeInstruction}
|
|
689
666
|
|
|
690
667
|
Entries:
|
|
691
668
|
${entriesText}
|
|
@@ -702,7 +679,8 @@ async function enhanceAndCategorize(provider, entries, context) {
|
|
|
702
679
|
const retryOpts = LLM_DEFAULTS.retry;
|
|
703
680
|
try {
|
|
704
681
|
return await withRetry(async () => {
|
|
705
|
-
const
|
|
682
|
+
const defaultPrompt = buildPrompt(entries, context.categories, context.style);
|
|
683
|
+
const prompt = resolvePrompt("enhanceAndCategorize", defaultPrompt, context.prompts);
|
|
706
684
|
const response = await provider.complete(prompt);
|
|
707
685
|
const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
708
686
|
const parsed = JSON.parse(cleaned);
|
|
@@ -718,22 +696,23 @@ async function enhanceAndCategorize(provider, entries, context) {
|
|
|
718
696
|
scope: result.scope || original.scope
|
|
719
697
|
};
|
|
720
698
|
});
|
|
699
|
+
const validatedEntries = validateEntryScopes(enhancedEntries, context.scopes, context.categories);
|
|
721
700
|
const categoryMap = /* @__PURE__ */ new Map();
|
|
722
701
|
for (let i = 0; i < parsed.entries.length; i++) {
|
|
723
702
|
const result = parsed.entries[i];
|
|
724
703
|
const category = result?.category || "General";
|
|
725
|
-
const entry =
|
|
704
|
+
const entry = validatedEntries[i];
|
|
726
705
|
if (!entry) continue;
|
|
727
706
|
if (!categoryMap.has(category)) {
|
|
728
707
|
categoryMap.set(category, []);
|
|
729
708
|
}
|
|
730
|
-
categoryMap.get(category)
|
|
709
|
+
categoryMap.get(category)?.push(entry);
|
|
731
710
|
}
|
|
732
711
|
const categories = [];
|
|
733
712
|
for (const [category, catEntries] of categoryMap) {
|
|
734
713
|
categories.push({ category, entries: catEntries });
|
|
735
714
|
}
|
|
736
|
-
return { enhancedEntries, categories };
|
|
715
|
+
return { enhancedEntries: validatedEntries, categories };
|
|
737
716
|
}, retryOpts);
|
|
738
717
|
} catch (error) {
|
|
739
718
|
warn2(
|
|
@@ -747,7 +726,7 @@ async function enhanceAndCategorize(provider, entries, context) {
|
|
|
747
726
|
}
|
|
748
727
|
|
|
749
728
|
// src/llm/tasks/release-notes.ts
|
|
750
|
-
var
|
|
729
|
+
var DEFAULT_RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
|
|
751
730
|
|
|
752
731
|
Create engaging, user-friendly release notes for the following changes.
|
|
753
732
|
|
|
@@ -780,16 +759,17 @@ No notable changes in this release.`;
|
|
|
780
759
|
if (e.breaking) line += " **BREAKING**";
|
|
781
760
|
return line;
|
|
782
761
|
}).join("\n");
|
|
783
|
-
const
|
|
762
|
+
const defaultPrompt = DEFAULT_RELEASE_NOTES_PROMPT.replace("{{version}}", context.version ?? "v1.0.0").replace(
|
|
784
763
|
"{{#if previousVersion}}Previous version: {{previousVersion}}{{/if}}",
|
|
785
764
|
context.previousVersion ? `Previous version: ${context.previousVersion}` : ""
|
|
786
765
|
).replace("{{date}}", context.date ?? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "").replace("{{entries}}", entriesText);
|
|
766
|
+
const prompt = resolvePrompt("releaseNotes", defaultPrompt, context.prompts);
|
|
787
767
|
const response = await provider.complete(prompt);
|
|
788
768
|
return response.trim();
|
|
789
769
|
}
|
|
790
770
|
|
|
791
771
|
// src/llm/tasks/summarize.ts
|
|
792
|
-
var
|
|
772
|
+
var DEFAULT_SUMMARIZE_PROMPT = `You are creating a summary of changes for a software release.
|
|
793
773
|
|
|
794
774
|
Given the following changelog entries, create a brief summary (2-3 sentences) that captures the main themes of this release.
|
|
795
775
|
|
|
@@ -797,12 +777,13 @@ Entries:
|
|
|
797
777
|
{{entries}}
|
|
798
778
|
|
|
799
779
|
Summary (only output the summary, nothing else):`;
|
|
800
|
-
async function summarizeEntries(provider, entries,
|
|
780
|
+
async function summarizeEntries(provider, entries, context) {
|
|
801
781
|
if (entries.length === 0) {
|
|
802
782
|
return "";
|
|
803
783
|
}
|
|
804
784
|
const entriesText = entries.map((e) => `- [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
|
|
805
|
-
const
|
|
785
|
+
const defaultPrompt = DEFAULT_SUMMARIZE_PROMPT.replace("{{entries}}", entriesText);
|
|
786
|
+
const prompt = resolvePrompt("summarize", defaultPrompt, context.prompts);
|
|
806
787
|
const response = await provider.complete(prompt);
|
|
807
788
|
return response.trim();
|
|
808
789
|
}
|
|
@@ -846,7 +827,7 @@ function createProvider(config) {
|
|
|
846
827
|
|
|
847
828
|
// src/output/github-release.ts
|
|
848
829
|
import { Octokit } from "@octokit/rest";
|
|
849
|
-
import { info as
|
|
830
|
+
import { info as info2, success as success2 } from "@releasekit/core";
|
|
850
831
|
var GitHubClient = class {
|
|
851
832
|
octokit;
|
|
852
833
|
owner;
|
|
@@ -868,7 +849,7 @@ var GitHubClient = class {
|
|
|
868
849
|
} else {
|
|
869
850
|
body = renderMarkdown([context]);
|
|
870
851
|
}
|
|
871
|
-
|
|
852
|
+
info2(`Creating GitHub release for ${tagName}`);
|
|
872
853
|
try {
|
|
873
854
|
const response = await this.octokit.repos.createRelease({
|
|
874
855
|
owner: this.owner,
|
|
@@ -880,7 +861,7 @@ var GitHubClient = class {
|
|
|
880
861
|
prerelease: options.prerelease ?? false,
|
|
881
862
|
generate_release_notes: options.generateNotes ?? false
|
|
882
863
|
});
|
|
883
|
-
|
|
864
|
+
success2(`Release created: ${response.data.html_url}`);
|
|
884
865
|
return {
|
|
885
866
|
id: response.data.id,
|
|
886
867
|
htmlUrl: response.data.html_url,
|
|
@@ -898,7 +879,7 @@ var GitHubClient = class {
|
|
|
898
879
|
} else {
|
|
899
880
|
body = renderMarkdown([context]);
|
|
900
881
|
}
|
|
901
|
-
|
|
882
|
+
info2(`Updating GitHub release ${releaseId}`);
|
|
902
883
|
try {
|
|
903
884
|
const response = await this.octokit.repos.updateRelease({
|
|
904
885
|
owner: this.owner,
|
|
@@ -910,7 +891,7 @@ var GitHubClient = class {
|
|
|
910
891
|
draft: options.draft ?? false,
|
|
911
892
|
prerelease: options.prerelease ?? false
|
|
912
893
|
});
|
|
913
|
-
|
|
894
|
+
success2(`Release updated: ${response.data.html_url}`);
|
|
914
895
|
return {
|
|
915
896
|
id: response.data.id,
|
|
916
897
|
htmlUrl: response.data.html_url,
|
|
@@ -960,7 +941,7 @@ async function createGitHubRelease(context, options) {
|
|
|
960
941
|
}
|
|
961
942
|
|
|
962
943
|
// src/templates/ejs.ts
|
|
963
|
-
import * as
|
|
944
|
+
import * as fs3 from "fs";
|
|
964
945
|
import ejs from "ejs";
|
|
965
946
|
function renderEjs(template, context) {
|
|
966
947
|
try {
|
|
@@ -970,16 +951,16 @@ function renderEjs(template, context) {
|
|
|
970
951
|
}
|
|
971
952
|
}
|
|
972
953
|
function renderEjsFile(filePath, context) {
|
|
973
|
-
if (!
|
|
954
|
+
if (!fs3.existsSync(filePath)) {
|
|
974
955
|
throw new TemplateError(`Template file not found: ${filePath}`);
|
|
975
956
|
}
|
|
976
|
-
const template =
|
|
957
|
+
const template = fs3.readFileSync(filePath, "utf-8");
|
|
977
958
|
return renderEjs(template, context);
|
|
978
959
|
}
|
|
979
960
|
|
|
980
961
|
// src/templates/handlebars.ts
|
|
981
|
-
import * as
|
|
982
|
-
import * as
|
|
962
|
+
import * as fs4 from "fs";
|
|
963
|
+
import * as path2 from "path";
|
|
983
964
|
import Handlebars from "handlebars";
|
|
984
965
|
function registerHandlebarsHelpers() {
|
|
985
966
|
Handlebars.registerHelper("capitalize", (str) => {
|
|
@@ -1005,28 +986,28 @@ function renderHandlebars(template, context) {
|
|
|
1005
986
|
}
|
|
1006
987
|
}
|
|
1007
988
|
function renderHandlebarsFile(filePath, context) {
|
|
1008
|
-
if (!
|
|
989
|
+
if (!fs4.existsSync(filePath)) {
|
|
1009
990
|
throw new TemplateError(`Template file not found: ${filePath}`);
|
|
1010
991
|
}
|
|
1011
|
-
const template =
|
|
992
|
+
const template = fs4.readFileSync(filePath, "utf-8");
|
|
1012
993
|
return renderHandlebars(template, context);
|
|
1013
994
|
}
|
|
1014
995
|
function renderHandlebarsComposable(templateDir, context) {
|
|
1015
996
|
registerHandlebarsHelpers();
|
|
1016
|
-
const versionPath =
|
|
1017
|
-
const entryPath =
|
|
1018
|
-
const documentPath =
|
|
1019
|
-
if (!
|
|
997
|
+
const versionPath = path2.join(templateDir, "version.hbs");
|
|
998
|
+
const entryPath = path2.join(templateDir, "entry.hbs");
|
|
999
|
+
const documentPath = path2.join(templateDir, "document.hbs");
|
|
1000
|
+
if (!fs4.existsSync(documentPath)) {
|
|
1020
1001
|
throw new TemplateError(`Document template not found: ${documentPath}`);
|
|
1021
1002
|
}
|
|
1022
|
-
if (
|
|
1023
|
-
Handlebars.registerPartial("version",
|
|
1003
|
+
if (fs4.existsSync(versionPath)) {
|
|
1004
|
+
Handlebars.registerPartial("version", fs4.readFileSync(versionPath, "utf-8"));
|
|
1024
1005
|
}
|
|
1025
|
-
if (
|
|
1026
|
-
Handlebars.registerPartial("entry",
|
|
1006
|
+
if (fs4.existsSync(entryPath)) {
|
|
1007
|
+
Handlebars.registerPartial("entry", fs4.readFileSync(entryPath, "utf-8"));
|
|
1027
1008
|
}
|
|
1028
1009
|
try {
|
|
1029
|
-
const compiled = Handlebars.compile(
|
|
1010
|
+
const compiled = Handlebars.compile(fs4.readFileSync(documentPath, "utf-8"));
|
|
1030
1011
|
return compiled(context);
|
|
1031
1012
|
} catch (error) {
|
|
1032
1013
|
throw new TemplateError(`Handlebars render error: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1034,8 +1015,8 @@ function renderHandlebarsComposable(templateDir, context) {
|
|
|
1034
1015
|
}
|
|
1035
1016
|
|
|
1036
1017
|
// src/templates/liquid.ts
|
|
1037
|
-
import * as
|
|
1038
|
-
import * as
|
|
1018
|
+
import * as fs5 from "fs";
|
|
1019
|
+
import * as path3 from "path";
|
|
1039
1020
|
import { Liquid } from "liquidjs";
|
|
1040
1021
|
function createLiquidEngine(root) {
|
|
1041
1022
|
return new Liquid({
|
|
@@ -1053,15 +1034,15 @@ function renderLiquid(template, context) {
|
|
|
1053
1034
|
}
|
|
1054
1035
|
}
|
|
1055
1036
|
function renderLiquidFile(filePath, context) {
|
|
1056
|
-
if (!
|
|
1037
|
+
if (!fs5.existsSync(filePath)) {
|
|
1057
1038
|
throw new TemplateError(`Template file not found: ${filePath}`);
|
|
1058
1039
|
}
|
|
1059
|
-
const template =
|
|
1040
|
+
const template = fs5.readFileSync(filePath, "utf-8");
|
|
1060
1041
|
return renderLiquid(template, context);
|
|
1061
1042
|
}
|
|
1062
1043
|
function renderLiquidComposable(templateDir, context) {
|
|
1063
|
-
const documentPath =
|
|
1064
|
-
if (!
|
|
1044
|
+
const documentPath = path3.join(templateDir, "document.liquid");
|
|
1045
|
+
if (!fs5.existsSync(documentPath)) {
|
|
1065
1046
|
throw new TemplateError(`Document template not found: ${documentPath}`);
|
|
1066
1047
|
}
|
|
1067
1048
|
const engine = createLiquidEngine(templateDir);
|
|
@@ -1073,10 +1054,10 @@ function renderLiquidComposable(templateDir, context) {
|
|
|
1073
1054
|
}
|
|
1074
1055
|
|
|
1075
1056
|
// src/templates/loader.ts
|
|
1076
|
-
import * as
|
|
1077
|
-
import * as
|
|
1057
|
+
import * as fs6 from "fs";
|
|
1058
|
+
import * as path4 from "path";
|
|
1078
1059
|
function getEngineFromFile(filePath) {
|
|
1079
|
-
const ext =
|
|
1060
|
+
const ext = path4.extname(filePath).toLowerCase();
|
|
1080
1061
|
switch (ext) {
|
|
1081
1062
|
case ".liquid":
|
|
1082
1063
|
return "liquid";
|
|
@@ -1110,10 +1091,10 @@ function getRenderFileFn(engine) {
|
|
|
1110
1091
|
}
|
|
1111
1092
|
}
|
|
1112
1093
|
function detectTemplateMode(templatePath) {
|
|
1113
|
-
if (!
|
|
1094
|
+
if (!fs6.existsSync(templatePath)) {
|
|
1114
1095
|
throw new TemplateError(`Template path not found: ${templatePath}`);
|
|
1115
1096
|
}
|
|
1116
|
-
const stat =
|
|
1097
|
+
const stat = fs6.statSync(templatePath);
|
|
1117
1098
|
if (stat.isFile()) {
|
|
1118
1099
|
return "single";
|
|
1119
1100
|
}
|
|
@@ -1131,7 +1112,7 @@ function renderSingleFile(templatePath, context, engine) {
|
|
|
1131
1112
|
};
|
|
1132
1113
|
}
|
|
1133
1114
|
function renderComposable(templateDir, context, engine) {
|
|
1134
|
-
const files =
|
|
1115
|
+
const files = fs6.readdirSync(templateDir);
|
|
1135
1116
|
const engineMap = {
|
|
1136
1117
|
liquid: { document: "document.liquid", version: "version.liquid", entry: "entry.liquid" },
|
|
1137
1118
|
handlebars: { document: "document.hbs", version: "version.hbs", entry: "entry.hbs" },
|
|
@@ -1154,15 +1135,15 @@ function renderComposable(templateDir, context, engine) {
|
|
|
1154
1135
|
return { content: renderHandlebarsComposable(templateDir, context), engine: resolvedEngine };
|
|
1155
1136
|
}
|
|
1156
1137
|
const expectedFiles = engineMap[resolvedEngine];
|
|
1157
|
-
const documentPath =
|
|
1158
|
-
if (!
|
|
1138
|
+
const documentPath = path4.join(templateDir, expectedFiles.document);
|
|
1139
|
+
if (!fs6.existsSync(documentPath)) {
|
|
1159
1140
|
throw new TemplateError(`Document template not found: ${expectedFiles.document}`);
|
|
1160
1141
|
}
|
|
1161
|
-
const versionPath =
|
|
1162
|
-
const entryPath =
|
|
1142
|
+
const versionPath = path4.join(templateDir, expectedFiles.version);
|
|
1143
|
+
const entryPath = path4.join(templateDir, expectedFiles.entry);
|
|
1163
1144
|
const render = getRenderFn(resolvedEngine);
|
|
1164
|
-
const entryTemplate =
|
|
1165
|
-
const versionTemplate =
|
|
1145
|
+
const entryTemplate = fs6.existsSync(entryPath) ? fs6.readFileSync(entryPath, "utf-8") : null;
|
|
1146
|
+
const versionTemplate = fs6.existsSync(versionPath) ? fs6.readFileSync(versionPath, "utf-8") : null;
|
|
1166
1147
|
if (entryTemplate && versionTemplate) {
|
|
1167
1148
|
const versionsWithEntries = context.versions.map((versionCtx) => {
|
|
1168
1149
|
const entries = versionCtx.entries.map((entry) => {
|
|
@@ -1173,7 +1154,7 @@ function renderComposable(templateDir, context, engine) {
|
|
|
1173
1154
|
});
|
|
1174
1155
|
const docContext = { ...context, renderedVersions: versionsWithEntries };
|
|
1175
1156
|
return {
|
|
1176
|
-
content: render(
|
|
1157
|
+
content: render(fs6.readFileSync(documentPath, "utf-8"), docContext),
|
|
1177
1158
|
engine: resolvedEngine
|
|
1178
1159
|
};
|
|
1179
1160
|
}
|
|
@@ -1214,17 +1195,27 @@ function renderTemplate(templatePath, context, engine) {
|
|
|
1214
1195
|
}
|
|
1215
1196
|
|
|
1216
1197
|
// src/core/pipeline.ts
|
|
1217
|
-
function generateCompareUrl(repoUrl, from, to) {
|
|
1198
|
+
function generateCompareUrl(repoUrl, from, to, packageName) {
|
|
1199
|
+
const isPackageSpecific = from.includes("@") && packageName && from.includes(packageName);
|
|
1200
|
+
let fromVersion;
|
|
1201
|
+
let toVersion;
|
|
1202
|
+
if (isPackageSpecific) {
|
|
1203
|
+
fromVersion = from;
|
|
1204
|
+
toVersion = `${packageName}@${to.startsWith("v") ? "" : "v"}${to}`;
|
|
1205
|
+
} else {
|
|
1206
|
+
fromVersion = from.replace(/^v/, "");
|
|
1207
|
+
toVersion = to.replace(/^v/, "");
|
|
1208
|
+
}
|
|
1218
1209
|
if (/gitlab\.com/i.test(repoUrl)) {
|
|
1219
|
-
return `${repoUrl}/-/compare/${
|
|
1210
|
+
return `${repoUrl}/-/compare/${fromVersion}...${toVersion}`;
|
|
1220
1211
|
}
|
|
1221
1212
|
if (/bitbucket\.org/i.test(repoUrl)) {
|
|
1222
|
-
return `${repoUrl}/branches/compare/${
|
|
1213
|
+
return `${repoUrl}/branches/compare/${fromVersion}..${toVersion}`;
|
|
1223
1214
|
}
|
|
1224
|
-
return `${repoUrl}/compare/${
|
|
1215
|
+
return `${repoUrl}/compare/${fromVersion}...${toVersion}`;
|
|
1225
1216
|
}
|
|
1226
1217
|
function createTemplateContext(pkg) {
|
|
1227
|
-
const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version) : void 0;
|
|
1218
|
+
const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version, pkg.packageName) : void 0;
|
|
1228
1219
|
return {
|
|
1229
1220
|
packageName: pkg.packageName,
|
|
1230
1221
|
version: pkg.version,
|
|
@@ -1262,15 +1253,17 @@ async function processWithLLM(context, config) {
|
|
|
1262
1253
|
previousVersion: context.previousVersion ?? void 0,
|
|
1263
1254
|
date: context.date,
|
|
1264
1255
|
categories: config.llm.categories,
|
|
1265
|
-
style: config.llm.style
|
|
1256
|
+
style: config.llm.style,
|
|
1257
|
+
scopes: config.llm.scopes,
|
|
1258
|
+
prompts: config.llm.prompts
|
|
1266
1259
|
};
|
|
1267
1260
|
const enhanced = {
|
|
1268
1261
|
entries: context.entries
|
|
1269
1262
|
};
|
|
1270
1263
|
try {
|
|
1271
|
-
|
|
1264
|
+
info3(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
|
|
1272
1265
|
if (config.llm.baseURL) {
|
|
1273
|
-
|
|
1266
|
+
info3(`LLM base URL: ${config.llm.baseURL}`);
|
|
1274
1267
|
}
|
|
1275
1268
|
const rawProvider = createProvider(config.llm);
|
|
1276
1269
|
const retryOpts = config.llm.retry ?? LLM_DEFAULTS.retry;
|
|
@@ -1279,54 +1272,53 @@ async function processWithLLM(context, config) {
|
|
|
1279
1272
|
complete: (prompt, opts) => withRetry(() => rawProvider.complete(prompt, opts), retryOpts)
|
|
1280
1273
|
};
|
|
1281
1274
|
const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
|
|
1282
|
-
|
|
1275
|
+
info3(`Running LLM tasks: ${activeTasks.join(", ")}`);
|
|
1283
1276
|
if (tasks.enhance && tasks.categorize) {
|
|
1284
|
-
|
|
1277
|
+
info3("Enhancing and categorizing entries with LLM...");
|
|
1285
1278
|
const result = await enhanceAndCategorize(provider, context.entries, llmContext);
|
|
1286
1279
|
enhanced.entries = result.enhancedEntries;
|
|
1287
1280
|
enhanced.categories = {};
|
|
1288
1281
|
for (const cat of result.categories) {
|
|
1289
1282
|
enhanced.categories[cat.category] = cat.entries;
|
|
1290
1283
|
}
|
|
1291
|
-
|
|
1284
|
+
info3(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
|
|
1292
1285
|
} else {
|
|
1293
1286
|
if (tasks.enhance) {
|
|
1294
|
-
|
|
1287
|
+
info3("Enhancing entries with LLM...");
|
|
1295
1288
|
enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
|
|
1296
|
-
|
|
1289
|
+
info3(`Enhanced ${enhanced.entries.length} entries`);
|
|
1297
1290
|
}
|
|
1298
1291
|
if (tasks.categorize) {
|
|
1299
|
-
|
|
1292
|
+
info3("Categorizing entries with LLM...");
|
|
1300
1293
|
const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
|
|
1301
1294
|
enhanced.categories = {};
|
|
1302
1295
|
for (const cat of categorized) {
|
|
1303
1296
|
enhanced.categories[cat.category] = cat.entries;
|
|
1304
1297
|
}
|
|
1305
|
-
|
|
1298
|
+
info3(`Created ${categorized.length} categories`);
|
|
1306
1299
|
}
|
|
1307
1300
|
}
|
|
1308
1301
|
if (tasks.summarize) {
|
|
1309
|
-
|
|
1302
|
+
info3("Summarizing entries with LLM...");
|
|
1310
1303
|
enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
|
|
1311
1304
|
if (enhanced.summary) {
|
|
1312
|
-
|
|
1313
|
-
|
|
1305
|
+
info3("Summary generated successfully");
|
|
1306
|
+
debug2(`Summary: ${enhanced.summary.substring(0, 100)}...`);
|
|
1314
1307
|
} else {
|
|
1315
1308
|
warn3("Summary generation returned empty result");
|
|
1316
1309
|
}
|
|
1317
1310
|
}
|
|
1318
1311
|
if (tasks.releaseNotes) {
|
|
1319
|
-
|
|
1312
|
+
info3("Generating release notes with LLM...");
|
|
1320
1313
|
enhanced.releaseNotes = await generateReleaseNotes(provider, enhanced.entries, llmContext);
|
|
1321
1314
|
if (enhanced.releaseNotes) {
|
|
1322
|
-
|
|
1315
|
+
info3("Release notes generated successfully");
|
|
1323
1316
|
} else {
|
|
1324
1317
|
warn3("Release notes generation returned empty result");
|
|
1325
1318
|
}
|
|
1326
1319
|
}
|
|
1327
1320
|
return {
|
|
1328
1321
|
...context,
|
|
1329
|
-
entries: enhanced.entries,
|
|
1330
1322
|
enhanced
|
|
1331
1323
|
};
|
|
1332
1324
|
} catch (error) {
|
|
@@ -1339,17 +1331,17 @@ function getBuiltinTemplatePath(style) {
|
|
|
1339
1331
|
let packageRoot;
|
|
1340
1332
|
try {
|
|
1341
1333
|
const currentUrl = import.meta.url;
|
|
1342
|
-
packageRoot =
|
|
1343
|
-
packageRoot =
|
|
1334
|
+
packageRoot = path5.dirname(new URL(currentUrl).pathname);
|
|
1335
|
+
packageRoot = path5.join(packageRoot, "..", "..");
|
|
1344
1336
|
} catch {
|
|
1345
1337
|
packageRoot = __dirname;
|
|
1346
1338
|
}
|
|
1347
|
-
return
|
|
1339
|
+
return path5.join(packageRoot, "templates", style);
|
|
1348
1340
|
}
|
|
1349
1341
|
async function generateWithTemplate(contexts, config, outputPath, dryRun) {
|
|
1350
1342
|
let templatePath;
|
|
1351
1343
|
if (config.templates?.path) {
|
|
1352
|
-
templatePath =
|
|
1344
|
+
templatePath = path5.resolve(config.templates.path);
|
|
1353
1345
|
} else {
|
|
1354
1346
|
templatePath = getBuiltinTemplatePath("keep-a-changelog");
|
|
1355
1347
|
}
|
|
@@ -1359,49 +1351,63 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
|
|
|
1359
1351
|
);
|
|
1360
1352
|
const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
|
|
1361
1353
|
if (dryRun) {
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1354
|
+
info3(`Would write templated output to ${outputPath}`);
|
|
1355
|
+
debug2("--- Changelog Preview ---");
|
|
1356
|
+
debug2(result.content);
|
|
1357
|
+
debug2("--- End Preview ---");
|
|
1365
1358
|
return;
|
|
1366
1359
|
}
|
|
1367
1360
|
if (outputPath === "-") {
|
|
1368
1361
|
process.stdout.write(result.content);
|
|
1369
1362
|
return;
|
|
1370
1363
|
}
|
|
1371
|
-
const dir =
|
|
1372
|
-
if (!
|
|
1373
|
-
|
|
1364
|
+
const dir = path5.dirname(outputPath);
|
|
1365
|
+
if (!fs7.existsSync(dir)) {
|
|
1366
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
1374
1367
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1368
|
+
fs7.writeFileSync(outputPath, result.content, "utf-8");
|
|
1369
|
+
success3(`Changelog written to ${outputPath} (using ${result.engine} template)`);
|
|
1377
1370
|
}
|
|
1378
1371
|
async function runPipeline(input, config, dryRun) {
|
|
1379
|
-
|
|
1372
|
+
debug2(`Processing ${input.packages.length} package(s)`);
|
|
1380
1373
|
let contexts = input.packages.map(createTemplateContext);
|
|
1381
1374
|
if (config.llm && !process.env.CHANGELOG_NO_LLM) {
|
|
1382
|
-
|
|
1375
|
+
info3("Processing with LLM enhancement");
|
|
1383
1376
|
contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, config)));
|
|
1384
1377
|
}
|
|
1378
|
+
const files = [];
|
|
1385
1379
|
for (const output of config.output) {
|
|
1386
|
-
|
|
1380
|
+
info3(`Generating ${output.format} output`);
|
|
1387
1381
|
switch (output.format) {
|
|
1388
1382
|
case "markdown": {
|
|
1389
1383
|
const file = output.file ?? "CHANGELOG.md";
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1384
|
+
try {
|
|
1385
|
+
const effectiveTemplateConfig = output.templates ?? config.templates;
|
|
1386
|
+
if (effectiveTemplateConfig?.path || output.options?.template) {
|
|
1387
|
+
const configWithTemplate = { ...config, templates: effectiveTemplateConfig };
|
|
1388
|
+
await generateWithTemplate(contexts, configWithTemplate, file, dryRun);
|
|
1389
|
+
} else {
|
|
1390
|
+
writeMarkdown(file, contexts, config, dryRun);
|
|
1391
|
+
}
|
|
1392
|
+
if (!dryRun) files.push(file);
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
warn3(`Failed to write ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1394
1395
|
}
|
|
1395
1396
|
break;
|
|
1396
1397
|
}
|
|
1397
1398
|
case "json": {
|
|
1398
1399
|
const file = output.file ?? "changelog.json";
|
|
1399
|
-
|
|
1400
|
+
try {
|
|
1401
|
+
writeJson(file, contexts, dryRun);
|
|
1402
|
+
if (!dryRun) files.push(file);
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
warn3(`Failed to write ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1405
|
+
}
|
|
1400
1406
|
break;
|
|
1401
1407
|
}
|
|
1402
1408
|
case "github-release": {
|
|
1403
1409
|
if (dryRun) {
|
|
1404
|
-
|
|
1410
|
+
info3("[DRY RUN] Would create GitHub release");
|
|
1405
1411
|
break;
|
|
1406
1412
|
}
|
|
1407
1413
|
const firstContext = contexts[0];
|
|
@@ -1429,139 +1435,33 @@ async function runPipeline(input, config, dryRun) {
|
|
|
1429
1435
|
}
|
|
1430
1436
|
}
|
|
1431
1437
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1449
|
-
return byPackage;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// src/monorepo/aggregator.ts
|
|
1453
|
-
function writeFile(outputPath, content, dryRun) {
|
|
1454
|
-
if (dryRun) {
|
|
1455
|
-
info5(`[DRY RUN] Would write to ${outputPath}`);
|
|
1456
|
-
console.log(content);
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
const dir = path7.dirname(outputPath);
|
|
1460
|
-
if (!fs9.existsSync(dir)) {
|
|
1461
|
-
fs9.mkdirSync(dir, { recursive: true });
|
|
1462
|
-
}
|
|
1463
|
-
fs9.writeFileSync(outputPath, content, "utf-8");
|
|
1464
|
-
success5(`Changelog written to ${outputPath}`);
|
|
1465
|
-
}
|
|
1466
|
-
function aggregateToRoot(contexts) {
|
|
1467
|
-
const aggregated = {
|
|
1468
|
-
packageName: "monorepo",
|
|
1469
|
-
version: contexts[0]?.version ?? "0.0.0",
|
|
1470
|
-
previousVersion: contexts[0]?.previousVersion ?? null,
|
|
1471
|
-
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "",
|
|
1472
|
-
repoUrl: contexts[0]?.repoUrl ?? null,
|
|
1473
|
-
entries: []
|
|
1474
|
-
};
|
|
1475
|
-
for (const ctx of contexts) {
|
|
1476
|
-
for (const entry of ctx.entries) {
|
|
1477
|
-
aggregated.entries.push({
|
|
1478
|
-
...entry,
|
|
1479
|
-
scope: entry.scope ? `${ctx.packageName}/${entry.scope}` : ctx.packageName
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
return aggregated;
|
|
1484
|
-
}
|
|
1485
|
-
function writeMonorepoChangelogs(contexts, options, config, dryRun) {
|
|
1486
|
-
if (options.mode === "root" || options.mode === "both") {
|
|
1487
|
-
const aggregated = aggregateToRoot(contexts);
|
|
1488
|
-
const rootPath = path7.join(options.rootPath, "CHANGELOG.md");
|
|
1489
|
-
info5(`Writing root changelog to ${rootPath}`);
|
|
1490
|
-
const rootContent = config.updateStrategy === "prepend" && fs9.existsSync(rootPath) ? prependVersion(rootPath, aggregated) : renderMarkdown([aggregated]);
|
|
1491
|
-
writeFile(rootPath, rootContent, dryRun);
|
|
1492
|
-
}
|
|
1493
|
-
if (options.mode === "packages" || options.mode === "both") {
|
|
1494
|
-
const byPackage = splitByPackage(contexts);
|
|
1495
|
-
const packageDirMap = buildPackageDirMap(options.rootPath, options.packagesPath);
|
|
1496
|
-
for (const [packageName, ctx] of byPackage) {
|
|
1497
|
-
const simpleName = packageName.split("/").pop();
|
|
1498
|
-
const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
|
|
1499
|
-
if (packageDir) {
|
|
1500
|
-
const changelogPath = path7.join(packageDir, "CHANGELOG.md");
|
|
1501
|
-
info5(`Writing changelog for ${packageName} to ${changelogPath}`);
|
|
1502
|
-
const pkgContent = config.updateStrategy === "prepend" && fs9.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
|
|
1503
|
-
writeFile(changelogPath, pkgContent, dryRun);
|
|
1504
|
-
} else {
|
|
1505
|
-
info5(`Could not find directory for package ${packageName}, skipping`);
|
|
1506
|
-
}
|
|
1438
|
+
if (config.monorepo?.mode) {
|
|
1439
|
+
const { detectMonorepo, writeMonorepoChangelogs } = await import("./aggregator-BDTUZWOA.js");
|
|
1440
|
+
const cwd = process.cwd();
|
|
1441
|
+
const detected = detectMonorepo(cwd);
|
|
1442
|
+
if (detected.isMonorepo) {
|
|
1443
|
+
const monoFiles = writeMonorepoChangelogs(
|
|
1444
|
+
contexts,
|
|
1445
|
+
{
|
|
1446
|
+
rootPath: config.monorepo.rootPath ?? cwd,
|
|
1447
|
+
packagesPath: config.monorepo.packagesPath ?? detected.packagesPath,
|
|
1448
|
+
mode: config.monorepo.mode
|
|
1449
|
+
},
|
|
1450
|
+
config,
|
|
1451
|
+
dryRun
|
|
1452
|
+
);
|
|
1453
|
+
files.push(...monoFiles);
|
|
1507
1454
|
}
|
|
1508
1455
|
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
const packagesDir = path7.join(rootPath, packagesPath);
|
|
1513
|
-
if (!fs9.existsSync(packagesDir)) {
|
|
1514
|
-
return map;
|
|
1515
|
-
}
|
|
1516
|
-
for (const entry of fs9.readdirSync(packagesDir, { withFileTypes: true })) {
|
|
1517
|
-
if (!entry.isDirectory()) continue;
|
|
1518
|
-
const dirPath = path7.join(packagesDir, entry.name);
|
|
1519
|
-
map.set(entry.name, dirPath);
|
|
1520
|
-
const packageJsonPath = path7.join(dirPath, "package.json");
|
|
1521
|
-
if (fs9.existsSync(packageJsonPath)) {
|
|
1522
|
-
try {
|
|
1523
|
-
const pkg = JSON.parse(fs9.readFileSync(packageJsonPath, "utf-8"));
|
|
1524
|
-
if (pkg.name) {
|
|
1525
|
-
map.set(pkg.name, dirPath);
|
|
1526
|
-
}
|
|
1527
|
-
} catch {
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1456
|
+
const packageNotes = {};
|
|
1457
|
+
for (const ctx of contexts) {
|
|
1458
|
+
packageNotes[ctx.packageName] = formatVersion(ctx);
|
|
1530
1459
|
}
|
|
1531
|
-
return
|
|
1460
|
+
return { packageNotes, files };
|
|
1532
1461
|
}
|
|
1533
|
-
function
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1536
|
-
if (fs9.existsSync(pnpmWorkspacesPath)) {
|
|
1537
|
-
const content = fs9.readFileSync(pnpmWorkspacesPath, "utf-8");
|
|
1538
|
-
const packagesMatch = content.match(/packages:\s*\n\s*-\s*['"]([^'"]+)['"]/);
|
|
1539
|
-
if (packagesMatch?.[1]) {
|
|
1540
|
-
const packagesGlob = packagesMatch[1];
|
|
1541
|
-
const packagesPath = packagesGlob.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
|
|
1542
|
-
return { isMonorepo: true, packagesPath: packagesPath || "packages" };
|
|
1543
|
-
}
|
|
1544
|
-
return { isMonorepo: true, packagesPath: "packages" };
|
|
1545
|
-
}
|
|
1546
|
-
if (fs9.existsSync(packageJsonPath)) {
|
|
1547
|
-
try {
|
|
1548
|
-
const content = fs9.readFileSync(packageJsonPath, "utf-8");
|
|
1549
|
-
const pkg = JSON.parse(content);
|
|
1550
|
-
if (pkg.workspaces) {
|
|
1551
|
-
const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
|
|
1552
|
-
if (workspaces?.length) {
|
|
1553
|
-
const firstWorkspace = workspaces[0];
|
|
1554
|
-
if (firstWorkspace) {
|
|
1555
|
-
const packagesPath = firstWorkspace.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
|
|
1556
|
-
return { isMonorepo: true, packagesPath: packagesPath || "packages" };
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
} catch {
|
|
1561
|
-
return { isMonorepo: false, packagesPath: "" };
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
return { isMonorepo: false, packagesPath: "" };
|
|
1462
|
+
async function processInput(inputJson, config, dryRun) {
|
|
1463
|
+
const input = parsePackageVersioner(inputJson);
|
|
1464
|
+
return runPipeline(input, config, dryRun);
|
|
1565
1465
|
}
|
|
1566
1466
|
|
|
1567
1467
|
export {
|
|
@@ -1580,14 +1480,9 @@ export {
|
|
|
1580
1480
|
parsePackageVersioner,
|
|
1581
1481
|
parsePackageVersionerFile,
|
|
1582
1482
|
parsePackageVersionerStdin,
|
|
1583
|
-
renderMarkdown,
|
|
1584
|
-
writeMarkdown,
|
|
1585
1483
|
renderJson,
|
|
1586
1484
|
writeJson,
|
|
1587
1485
|
createTemplateContext,
|
|
1588
1486
|
runPipeline,
|
|
1589
|
-
processInput
|
|
1590
|
-
aggregateToRoot,
|
|
1591
|
-
writeMonorepoChangelogs,
|
|
1592
|
-
detectMonorepo
|
|
1487
|
+
processInput
|
|
1593
1488
|
};
|