@releasekit/notes 0.2.0-next.0 → 0.2.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-NHDLQLG2.js → chunk-ACXCEHQT.js} +177 -55
- package/dist/cli.cjs +239 -108
- package/dist/cli.js +11 -2
- package/dist/index.cjs +212 -90
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// src/cli.ts
|
|
27
27
|
var fs10 = __toESM(require("fs"), 1);
|
|
28
28
|
var readline = __toESM(require("readline"), 1);
|
|
29
|
-
var
|
|
29
|
+
var import_core10 = require("@releasekit/core");
|
|
30
30
|
var import_commander = require("commander");
|
|
31
31
|
|
|
32
32
|
// src/core/config.ts
|
|
@@ -45,7 +45,7 @@ function getDefaultConfig() {
|
|
|
45
45
|
// src/core/pipeline.ts
|
|
46
46
|
var fs8 = __toESM(require("fs"), 1);
|
|
47
47
|
var path6 = __toESM(require("path"), 1);
|
|
48
|
-
var
|
|
48
|
+
var import_core8 = require("@releasekit/core");
|
|
49
49
|
|
|
50
50
|
// src/input/package-versioner.ts
|
|
51
51
|
var fs = __toESM(require("fs"), 1);
|
|
@@ -366,15 +366,26 @@ Entries:
|
|
|
366
366
|
Output only valid JSON, nothing else:`;
|
|
367
367
|
function buildCustomCategorizePrompt(categories) {
|
|
368
368
|
const categoryList = categories.map((c) => `- "${c.name}": ${c.description}`).join("\n");
|
|
369
|
+
const developerCategory = categories.find((c) => c.name === "Developer");
|
|
370
|
+
let scopeInstructions = "";
|
|
371
|
+
if (developerCategory) {
|
|
372
|
+
const scopeMatch = developerCategory.description.match(/from:\s*([^.]+)/);
|
|
373
|
+
if (scopeMatch?.[1]) {
|
|
374
|
+
const scopes = scopeMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
375
|
+
if (scopes.length > 0) {
|
|
376
|
+
scopeInstructions = `
|
|
377
|
+
|
|
378
|
+
For the "Developer" category, you MUST assign a scope from this exact list: ${scopes.join(", ")}.
|
|
379
|
+
`;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
369
383
|
return `You are categorizing changelog entries for a software release.
|
|
370
384
|
|
|
371
|
-
Given the following entries, group them into the specified categories. Only use the categories listed below
|
|
385
|
+
Given the following entries, group them into the specified categories. Only use the categories listed below in this exact order:
|
|
372
386
|
|
|
373
387
|
Categories:
|
|
374
|
-
${categoryList}
|
|
375
|
-
|
|
376
|
-
For entries in categories that involve internal/developer changes, set a "scope" field on those entries with a short subcategory label (e.g., "CI", "Dependencies", "Testing", "Code Quality", "Build System").
|
|
377
|
-
|
|
388
|
+
${categoryList}${scopeInstructions}
|
|
378
389
|
Output a JSON object with two fields:
|
|
379
390
|
- "categories": an object where keys are category names and values are arrays of entry indices (0-based)
|
|
380
391
|
- "scopes": an object where keys are entry indices (as strings) and values are scope labels
|
|
@@ -406,7 +417,8 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
406
417
|
entries[idx] = { ...entries[idx], scope };
|
|
407
418
|
}
|
|
408
419
|
}
|
|
409
|
-
for (const [category,
|
|
420
|
+
for (const [category, rawIndices] of Object.entries(categoryMap)) {
|
|
421
|
+
const indices = Array.isArray(rawIndices) ? rawIndices : [];
|
|
410
422
|
const categoryEntries = indices.map((i) => entries[i]).filter((e) => e !== void 0);
|
|
411
423
|
if (categoryEntries.length > 0) {
|
|
412
424
|
result.push({ category, entries: categoryEntries });
|
|
@@ -414,7 +426,8 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
414
426
|
}
|
|
415
427
|
} else {
|
|
416
428
|
const categoryMap = parsed;
|
|
417
|
-
for (const [category,
|
|
429
|
+
for (const [category, rawIndices] of Object.entries(categoryMap)) {
|
|
430
|
+
const indices = Array.isArray(rawIndices) ? rawIndices : [];
|
|
418
431
|
const categoryEntries = indices.map((i) => entries[i]).filter((e) => e !== void 0);
|
|
419
432
|
if (categoryEntries.length > 0) {
|
|
420
433
|
result.push({ category, entries: categoryEntries });
|
|
@@ -472,6 +485,113 @@ async function enhanceEntries(provider, entries, context, concurrency = LLM_DEFA
|
|
|
472
485
|
return results;
|
|
473
486
|
}
|
|
474
487
|
|
|
488
|
+
// src/llm/tasks/enhance-and-categorize.ts
|
|
489
|
+
var import_core4 = require("@releasekit/core");
|
|
490
|
+
|
|
491
|
+
// src/utils/retry.ts
|
|
492
|
+
function sleep(ms) {
|
|
493
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
494
|
+
}
|
|
495
|
+
async function withRetry(fn, options = {}) {
|
|
496
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
497
|
+
const initialDelay = options.initialDelay ?? 1e3;
|
|
498
|
+
const maxDelay = options.maxDelay ?? 3e4;
|
|
499
|
+
const backoffFactor = options.backoffFactor ?? 2;
|
|
500
|
+
let lastError;
|
|
501
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
502
|
+
try {
|
|
503
|
+
return await fn();
|
|
504
|
+
} catch (error2) {
|
|
505
|
+
lastError = error2;
|
|
506
|
+
if (attempt < maxAttempts - 1) {
|
|
507
|
+
const base = Math.min(initialDelay * backoffFactor ** attempt, maxDelay);
|
|
508
|
+
const jitter = base * 0.2 * (Math.random() * 2 - 1);
|
|
509
|
+
await sleep(Math.max(0, base + jitter));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
throw lastError;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/llm/tasks/enhance-and-categorize.ts
|
|
517
|
+
function buildPrompt(entries, categories, style) {
|
|
518
|
+
const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
|
|
519
|
+
const styleText = style || 'Use present tense ("Add feature" not "Added feature"). Be concise.';
|
|
520
|
+
const categorySection = categories ? `Categories (use ONLY these):
|
|
521
|
+
${categories.map((c) => `- "${c.name}": ${c.description}`).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
|
|
522
|
+
return `You are generating release notes for a software project. Given the following changelog entries, do two things:
|
|
523
|
+
|
|
524
|
+
1. **Rewrite** each entry as a clear, user-friendly description
|
|
525
|
+
2. **Categorize** each entry into the appropriate category
|
|
526
|
+
|
|
527
|
+
Style guidelines:
|
|
528
|
+
- ${styleText}
|
|
529
|
+
- Be concise (1 short sentence per entry)
|
|
530
|
+
- Focus on what changed, not implementation details
|
|
531
|
+
|
|
532
|
+
${categorySection}
|
|
533
|
+
|
|
534
|
+
${categories ? 'For entries in categories involving internal/developer changes, set a "scope" field with a short subcategory label (e.g., "CI", "Dependencies", "Testing").' : ""}
|
|
535
|
+
|
|
536
|
+
Entries:
|
|
537
|
+
${entriesText}
|
|
538
|
+
|
|
539
|
+
Output a JSON object with:
|
|
540
|
+
- "entries": array of objects, one per input entry (same order), each with: { "description": "rewritten text", "category": "CategoryName", "scope": "optional subcategory label or null" }
|
|
541
|
+
|
|
542
|
+
Output only valid JSON, nothing else:`;
|
|
543
|
+
}
|
|
544
|
+
async function enhanceAndCategorize(provider, entries, context) {
|
|
545
|
+
if (entries.length === 0) {
|
|
546
|
+
return { enhancedEntries: [], categories: [] };
|
|
547
|
+
}
|
|
548
|
+
const retryOpts = LLM_DEFAULTS.retry;
|
|
549
|
+
try {
|
|
550
|
+
return await withRetry(async () => {
|
|
551
|
+
const prompt = buildPrompt(entries, context.categories, context.style);
|
|
552
|
+
const response = await provider.complete(prompt);
|
|
553
|
+
const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
554
|
+
const parsed = JSON.parse(cleaned);
|
|
555
|
+
if (!Array.isArray(parsed.entries)) {
|
|
556
|
+
throw new Error('Response missing "entries" array');
|
|
557
|
+
}
|
|
558
|
+
const enhancedEntries = entries.map((original, i) => {
|
|
559
|
+
const result = parsed.entries[i];
|
|
560
|
+
if (!result) return original;
|
|
561
|
+
return {
|
|
562
|
+
...original,
|
|
563
|
+
description: result.description || original.description,
|
|
564
|
+
scope: result.scope || original.scope
|
|
565
|
+
};
|
|
566
|
+
});
|
|
567
|
+
const categoryMap = /* @__PURE__ */ new Map();
|
|
568
|
+
for (let i = 0; i < parsed.entries.length; i++) {
|
|
569
|
+
const result = parsed.entries[i];
|
|
570
|
+
const category = result?.category || "General";
|
|
571
|
+
const entry = enhancedEntries[i];
|
|
572
|
+
if (!entry) continue;
|
|
573
|
+
if (!categoryMap.has(category)) {
|
|
574
|
+
categoryMap.set(category, []);
|
|
575
|
+
}
|
|
576
|
+
categoryMap.get(category).push(entry);
|
|
577
|
+
}
|
|
578
|
+
const categories = [];
|
|
579
|
+
for (const [category, catEntries] of categoryMap) {
|
|
580
|
+
categories.push({ category, entries: catEntries });
|
|
581
|
+
}
|
|
582
|
+
return { enhancedEntries, categories };
|
|
583
|
+
}, retryOpts);
|
|
584
|
+
} catch (error2) {
|
|
585
|
+
(0, import_core4.warn)(
|
|
586
|
+
`Combined enhance+categorize failed after ${retryOpts.maxAttempts} attempts: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
587
|
+
);
|
|
588
|
+
return {
|
|
589
|
+
enhancedEntries: entries,
|
|
590
|
+
categories: [{ category: "General", entries }]
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
475
595
|
// src/llm/tasks/release-notes.ts
|
|
476
596
|
var RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
|
|
477
597
|
|
|
@@ -572,12 +692,12 @@ function createProvider(config) {
|
|
|
572
692
|
|
|
573
693
|
// src/output/github-release.ts
|
|
574
694
|
var import_rest = require("@octokit/rest");
|
|
575
|
-
var
|
|
695
|
+
var import_core6 = require("@releasekit/core");
|
|
576
696
|
|
|
577
697
|
// src/output/markdown.ts
|
|
578
698
|
var fs2 = __toESM(require("fs"), 1);
|
|
579
699
|
var path = __toESM(require("path"), 1);
|
|
580
|
-
var
|
|
700
|
+
var import_core5 = require("@releasekit/core");
|
|
581
701
|
var TYPE_ORDER = ["added", "changed", "deprecated", "removed", "fixed", "security"];
|
|
582
702
|
var TYPE_LABELS = {
|
|
583
703
|
added: "Added",
|
|
@@ -676,15 +796,19 @@ ${body}`;
|
|
|
676
796
|
function writeMarkdown(outputPath, contexts, config, dryRun) {
|
|
677
797
|
const content = renderMarkdown(contexts);
|
|
678
798
|
if (dryRun) {
|
|
679
|
-
(0,
|
|
799
|
+
(0, import_core5.info)("--- Changelog Preview ---");
|
|
680
800
|
console.log(content);
|
|
681
|
-
(0,
|
|
801
|
+
(0, import_core5.info)("--- End Preview ---");
|
|
682
802
|
return;
|
|
683
803
|
}
|
|
684
804
|
const dir = path.dirname(outputPath);
|
|
685
805
|
if (!fs2.existsSync(dir)) {
|
|
686
806
|
fs2.mkdirSync(dir, { recursive: true });
|
|
687
807
|
}
|
|
808
|
+
if (outputPath === "-") {
|
|
809
|
+
process.stdout.write(content);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
688
812
|
if (config.updateStrategy === "prepend" && fs2.existsSync(outputPath) && contexts.length === 1) {
|
|
689
813
|
const firstContext = contexts[0];
|
|
690
814
|
if (firstContext) {
|
|
@@ -694,7 +818,7 @@ function writeMarkdown(outputPath, contexts, config, dryRun) {
|
|
|
694
818
|
} else {
|
|
695
819
|
fs2.writeFileSync(outputPath, content, "utf-8");
|
|
696
820
|
}
|
|
697
|
-
(0,
|
|
821
|
+
(0, import_core5.success)(`Changelog written to ${outputPath}`);
|
|
698
822
|
}
|
|
699
823
|
|
|
700
824
|
// src/output/github-release.ts
|
|
@@ -719,7 +843,7 @@ var GitHubClient = class {
|
|
|
719
843
|
} else {
|
|
720
844
|
body = renderMarkdown([context]);
|
|
721
845
|
}
|
|
722
|
-
(0,
|
|
846
|
+
(0, import_core6.info)(`Creating GitHub release for ${tagName}`);
|
|
723
847
|
try {
|
|
724
848
|
const response = await this.octokit.repos.createRelease({
|
|
725
849
|
owner: this.owner,
|
|
@@ -731,7 +855,7 @@ var GitHubClient = class {
|
|
|
731
855
|
prerelease: options.prerelease ?? false,
|
|
732
856
|
generate_release_notes: options.generateNotes ?? false
|
|
733
857
|
});
|
|
734
|
-
(0,
|
|
858
|
+
(0, import_core6.success)(`Release created: ${response.data.html_url}`);
|
|
735
859
|
return {
|
|
736
860
|
id: response.data.id,
|
|
737
861
|
htmlUrl: response.data.html_url,
|
|
@@ -749,7 +873,7 @@ var GitHubClient = class {
|
|
|
749
873
|
} else {
|
|
750
874
|
body = renderMarkdown([context]);
|
|
751
875
|
}
|
|
752
|
-
(0,
|
|
876
|
+
(0, import_core6.info)(`Updating GitHub release ${releaseId}`);
|
|
753
877
|
try {
|
|
754
878
|
const response = await this.octokit.repos.updateRelease({
|
|
755
879
|
owner: this.owner,
|
|
@@ -761,7 +885,7 @@ var GitHubClient = class {
|
|
|
761
885
|
draft: options.draft ?? false,
|
|
762
886
|
prerelease: options.prerelease ?? false
|
|
763
887
|
});
|
|
764
|
-
(0,
|
|
888
|
+
(0, import_core6.success)(`Release updated: ${response.data.html_url}`);
|
|
765
889
|
return {
|
|
766
890
|
id: response.data.id,
|
|
767
891
|
htmlUrl: response.data.html_url,
|
|
@@ -813,7 +937,7 @@ async function createGitHubRelease(context, options) {
|
|
|
813
937
|
// src/output/json.ts
|
|
814
938
|
var fs3 = __toESM(require("fs"), 1);
|
|
815
939
|
var path2 = __toESM(require("path"), 1);
|
|
816
|
-
var
|
|
940
|
+
var import_core7 = require("@releasekit/core");
|
|
817
941
|
function renderJson(contexts) {
|
|
818
942
|
return JSON.stringify(
|
|
819
943
|
{
|
|
@@ -833,9 +957,9 @@ function renderJson(contexts) {
|
|
|
833
957
|
function writeJson(outputPath, contexts, dryRun) {
|
|
834
958
|
const content = renderJson(contexts);
|
|
835
959
|
if (dryRun) {
|
|
836
|
-
(0,
|
|
960
|
+
(0, import_core7.info)("--- JSON Output Preview ---");
|
|
837
961
|
console.log(content);
|
|
838
|
-
(0,
|
|
962
|
+
(0, import_core7.info)("--- End Preview ---");
|
|
839
963
|
return;
|
|
840
964
|
}
|
|
841
965
|
const dir = path2.dirname(outputPath);
|
|
@@ -843,7 +967,7 @@ function writeJson(outputPath, contexts, dryRun) {
|
|
|
843
967
|
fs3.mkdirSync(dir, { recursive: true });
|
|
844
968
|
}
|
|
845
969
|
fs3.writeFileSync(outputPath, content, "utf-8");
|
|
846
|
-
(0,
|
|
970
|
+
(0, import_core7.success)(`JSON output written to ${outputPath}`);
|
|
847
971
|
}
|
|
848
972
|
|
|
849
973
|
// src/templates/ejs.ts
|
|
@@ -1100,41 +1224,24 @@ function renderTemplate(templatePath, context, engine) {
|
|
|
1100
1224
|
return renderComposable(templatePath, context, engine);
|
|
1101
1225
|
}
|
|
1102
1226
|
|
|
1103
|
-
// src/utils/retry.ts
|
|
1104
|
-
function sleep(ms) {
|
|
1105
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1106
|
-
}
|
|
1107
|
-
async function withRetry(fn, options = {}) {
|
|
1108
|
-
const maxAttempts = options.maxAttempts ?? 3;
|
|
1109
|
-
const initialDelay = options.initialDelay ?? 1e3;
|
|
1110
|
-
const maxDelay = options.maxDelay ?? 3e4;
|
|
1111
|
-
const backoffFactor = options.backoffFactor ?? 2;
|
|
1112
|
-
let lastError;
|
|
1113
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1114
|
-
try {
|
|
1115
|
-
return await fn();
|
|
1116
|
-
} catch (error2) {
|
|
1117
|
-
lastError = error2;
|
|
1118
|
-
if (attempt < maxAttempts - 1) {
|
|
1119
|
-
const base = Math.min(initialDelay * backoffFactor ** attempt, maxDelay);
|
|
1120
|
-
const jitter = base * 0.2 * (Math.random() * 2 - 1);
|
|
1121
|
-
await sleep(Math.max(0, base + jitter));
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
throw lastError;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
1227
|
// src/core/pipeline.ts
|
|
1129
1228
|
var import_meta = {};
|
|
1229
|
+
function extractVersionFromTag(tag) {
|
|
1230
|
+
if (tag.includes("@") && !tag.startsWith("@")) {
|
|
1231
|
+
return tag;
|
|
1232
|
+
}
|
|
1233
|
+
return tag.replace(/^v/, "");
|
|
1234
|
+
}
|
|
1130
1235
|
function generateCompareUrl(repoUrl, from, to) {
|
|
1236
|
+
const fromVersion = extractVersionFromTag(from);
|
|
1237
|
+
const toVersion = extractVersionFromTag(to);
|
|
1131
1238
|
if (/gitlab\.com/i.test(repoUrl)) {
|
|
1132
|
-
return `${repoUrl}/-/compare/${
|
|
1239
|
+
return `${repoUrl}/-/compare/${fromVersion}...${toVersion}`;
|
|
1133
1240
|
}
|
|
1134
1241
|
if (/bitbucket\.org/i.test(repoUrl)) {
|
|
1135
|
-
return `${repoUrl}/branches/compare/${
|
|
1242
|
+
return `${repoUrl}/branches/compare/${fromVersion}..${toVersion}`;
|
|
1136
1243
|
}
|
|
1137
|
-
return `${repoUrl}/compare/${
|
|
1244
|
+
return `${repoUrl}/compare/${fromVersion}...${toVersion}`;
|
|
1138
1245
|
}
|
|
1139
1246
|
function createTemplateContext(pkg) {
|
|
1140
1247
|
const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version) : void 0;
|
|
@@ -1181,9 +1288,9 @@ async function processWithLLM(context, config) {
|
|
|
1181
1288
|
entries: context.entries
|
|
1182
1289
|
};
|
|
1183
1290
|
try {
|
|
1184
|
-
(0,
|
|
1291
|
+
(0, import_core8.info)(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
|
|
1185
1292
|
if (config.llm.baseURL) {
|
|
1186
|
-
(0,
|
|
1293
|
+
(0, import_core8.info)(`LLM base URL: ${config.llm.baseURL}`);
|
|
1187
1294
|
}
|
|
1188
1295
|
const rawProvider = createProvider(config.llm);
|
|
1189
1296
|
const retryOpts = config.llm.retry ?? LLM_DEFAULTS.retry;
|
|
@@ -1192,38 +1299,49 @@ async function processWithLLM(context, config) {
|
|
|
1192
1299
|
complete: (prompt, opts) => withRetry(() => rawProvider.complete(prompt, opts), retryOpts)
|
|
1193
1300
|
};
|
|
1194
1301
|
const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
|
|
1195
|
-
(0,
|
|
1196
|
-
if (tasks.enhance) {
|
|
1197
|
-
(0,
|
|
1198
|
-
|
|
1199
|
-
|
|
1302
|
+
(0, import_core8.info)(`Running LLM tasks: ${activeTasks.join(", ")}`);
|
|
1303
|
+
if (tasks.enhance && tasks.categorize) {
|
|
1304
|
+
(0, import_core8.info)("Enhancing and categorizing entries with LLM...");
|
|
1305
|
+
const result = await enhanceAndCategorize(provider, context.entries, llmContext);
|
|
1306
|
+
enhanced.entries = result.enhancedEntries;
|
|
1307
|
+
enhanced.categories = {};
|
|
1308
|
+
for (const cat of result.categories) {
|
|
1309
|
+
enhanced.categories[cat.category] = cat.entries;
|
|
1310
|
+
}
|
|
1311
|
+
(0, import_core8.info)(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
|
|
1312
|
+
} else {
|
|
1313
|
+
if (tasks.enhance) {
|
|
1314
|
+
(0, import_core8.info)("Enhancing entries with LLM...");
|
|
1315
|
+
enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
|
|
1316
|
+
(0, import_core8.info)(`Enhanced ${enhanced.entries.length} entries`);
|
|
1317
|
+
}
|
|
1318
|
+
if (tasks.categorize) {
|
|
1319
|
+
(0, import_core8.info)("Categorizing entries with LLM...");
|
|
1320
|
+
const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
|
|
1321
|
+
enhanced.categories = {};
|
|
1322
|
+
for (const cat of categorized) {
|
|
1323
|
+
enhanced.categories[cat.category] = cat.entries;
|
|
1324
|
+
}
|
|
1325
|
+
(0, import_core8.info)(`Created ${categorized.length} categories`);
|
|
1326
|
+
}
|
|
1200
1327
|
}
|
|
1201
1328
|
if (tasks.summarize) {
|
|
1202
|
-
(0,
|
|
1329
|
+
(0, import_core8.info)("Summarizing entries with LLM...");
|
|
1203
1330
|
enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
|
|
1204
1331
|
if (enhanced.summary) {
|
|
1205
|
-
(0,
|
|
1206
|
-
(0,
|
|
1332
|
+
(0, import_core8.info)("Summary generated successfully");
|
|
1333
|
+
(0, import_core8.debug)(`Summary: ${enhanced.summary.substring(0, 100)}...`);
|
|
1207
1334
|
} else {
|
|
1208
|
-
(0,
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
if (tasks.categorize) {
|
|
1212
|
-
(0, import_core7.info)("Categorizing entries with LLM...");
|
|
1213
|
-
const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
|
|
1214
|
-
enhanced.categories = {};
|
|
1215
|
-
for (const cat of categorized) {
|
|
1216
|
-
enhanced.categories[cat.category] = cat.entries;
|
|
1335
|
+
(0, import_core8.warn)("Summary generation returned empty result");
|
|
1217
1336
|
}
|
|
1218
|
-
(0, import_core7.info)(`Created ${categorized.length} categories`);
|
|
1219
1337
|
}
|
|
1220
1338
|
if (tasks.releaseNotes) {
|
|
1221
|
-
(0,
|
|
1339
|
+
(0, import_core8.info)("Generating release notes with LLM...");
|
|
1222
1340
|
enhanced.releaseNotes = await generateReleaseNotes(provider, enhanced.entries, llmContext);
|
|
1223
1341
|
if (enhanced.releaseNotes) {
|
|
1224
|
-
(0,
|
|
1342
|
+
(0, import_core8.info)("Release notes generated successfully");
|
|
1225
1343
|
} else {
|
|
1226
|
-
(0,
|
|
1344
|
+
(0, import_core8.warn)("Release notes generation returned empty result");
|
|
1227
1345
|
}
|
|
1228
1346
|
}
|
|
1229
1347
|
return {
|
|
@@ -1232,8 +1350,8 @@ async function processWithLLM(context, config) {
|
|
|
1232
1350
|
enhanced
|
|
1233
1351
|
};
|
|
1234
1352
|
} catch (error2) {
|
|
1235
|
-
(0,
|
|
1236
|
-
(0,
|
|
1353
|
+
(0, import_core8.warn)(`LLM processing failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
1354
|
+
(0, import_core8.warn)("Falling back to raw entries");
|
|
1237
1355
|
return context;
|
|
1238
1356
|
}
|
|
1239
1357
|
}
|
|
@@ -1261,9 +1379,13 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
|
|
|
1261
1379
|
);
|
|
1262
1380
|
const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
|
|
1263
1381
|
if (dryRun) {
|
|
1264
|
-
(0,
|
|
1382
|
+
(0, import_core8.info)("--- Changelog Preview ---");
|
|
1265
1383
|
console.log(result.content);
|
|
1266
|
-
(0,
|
|
1384
|
+
(0, import_core8.info)("--- End Preview ---");
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (outputPath === "-") {
|
|
1388
|
+
process.stdout.write(result.content);
|
|
1267
1389
|
return;
|
|
1268
1390
|
}
|
|
1269
1391
|
const dir = path6.dirname(outputPath);
|
|
@@ -1271,17 +1393,17 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
|
|
|
1271
1393
|
fs8.mkdirSync(dir, { recursive: true });
|
|
1272
1394
|
}
|
|
1273
1395
|
fs8.writeFileSync(outputPath, result.content, "utf-8");
|
|
1274
|
-
(0,
|
|
1396
|
+
(0, import_core8.success)(`Changelog written to ${outputPath} (using ${result.engine} template)`);
|
|
1275
1397
|
}
|
|
1276
1398
|
async function runPipeline(input, config, dryRun) {
|
|
1277
|
-
(0,
|
|
1399
|
+
(0, import_core8.debug)(`Processing ${input.packages.length} package(s)`);
|
|
1278
1400
|
let contexts = input.packages.map(createTemplateContext);
|
|
1279
1401
|
if (config.llm && !process.env.CHANGELOG_NO_LLM) {
|
|
1280
|
-
(0,
|
|
1402
|
+
(0, import_core8.info)("Processing with LLM enhancement");
|
|
1281
1403
|
contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, config)));
|
|
1282
1404
|
}
|
|
1283
1405
|
for (const output of config.output) {
|
|
1284
|
-
(0,
|
|
1406
|
+
(0, import_core8.info)(`Generating ${output.format} output`);
|
|
1285
1407
|
switch (output.format) {
|
|
1286
1408
|
case "markdown": {
|
|
1287
1409
|
const file = output.file ?? "CHANGELOG.md";
|
|
@@ -1299,22 +1421,22 @@ async function runPipeline(input, config, dryRun) {
|
|
|
1299
1421
|
}
|
|
1300
1422
|
case "github-release": {
|
|
1301
1423
|
if (dryRun) {
|
|
1302
|
-
(0,
|
|
1424
|
+
(0, import_core8.info)("[DRY RUN] Would create GitHub release");
|
|
1303
1425
|
break;
|
|
1304
1426
|
}
|
|
1305
1427
|
const firstContext = contexts[0];
|
|
1306
1428
|
if (!firstContext) {
|
|
1307
|
-
(0,
|
|
1429
|
+
(0, import_core8.warn)("No context available for GitHub release");
|
|
1308
1430
|
break;
|
|
1309
1431
|
}
|
|
1310
1432
|
const repoUrl = firstContext.repoUrl;
|
|
1311
1433
|
if (!repoUrl) {
|
|
1312
|
-
(0,
|
|
1434
|
+
(0, import_core8.warn)("No repo URL available, cannot create GitHub release");
|
|
1313
1435
|
break;
|
|
1314
1436
|
}
|
|
1315
1437
|
const parsed = parseRepoUrl(repoUrl);
|
|
1316
1438
|
if (!parsed) {
|
|
1317
|
-
(0,
|
|
1439
|
+
(0, import_core8.warn)(`Could not parse repo URL: ${repoUrl}`);
|
|
1318
1440
|
break;
|
|
1319
1441
|
}
|
|
1320
1442
|
await createGitHubRelease(firstContext, {
|
|
@@ -1332,7 +1454,7 @@ async function runPipeline(input, config, dryRun) {
|
|
|
1332
1454
|
// src/monorepo/aggregator.ts
|
|
1333
1455
|
var fs9 = __toESM(require("fs"), 1);
|
|
1334
1456
|
var path7 = __toESM(require("path"), 1);
|
|
1335
|
-
var
|
|
1457
|
+
var import_core9 = require("@releasekit/core");
|
|
1336
1458
|
|
|
1337
1459
|
// src/monorepo/splitter.ts
|
|
1338
1460
|
function splitByPackage(contexts) {
|
|
@@ -1346,7 +1468,7 @@ function splitByPackage(contexts) {
|
|
|
1346
1468
|
// src/monorepo/aggregator.ts
|
|
1347
1469
|
function writeFile(outputPath, content, dryRun) {
|
|
1348
1470
|
if (dryRun) {
|
|
1349
|
-
(0,
|
|
1471
|
+
(0, import_core9.info)(`[DRY RUN] Would write to ${outputPath}`);
|
|
1350
1472
|
console.log(content);
|
|
1351
1473
|
return;
|
|
1352
1474
|
}
|
|
@@ -1355,7 +1477,7 @@ function writeFile(outputPath, content, dryRun) {
|
|
|
1355
1477
|
fs9.mkdirSync(dir, { recursive: true });
|
|
1356
1478
|
}
|
|
1357
1479
|
fs9.writeFileSync(outputPath, content, "utf-8");
|
|
1358
|
-
(0,
|
|
1480
|
+
(0, import_core9.success)(`Changelog written to ${outputPath}`);
|
|
1359
1481
|
}
|
|
1360
1482
|
function aggregateToRoot(contexts) {
|
|
1361
1483
|
const aggregated = {
|
|
@@ -1380,7 +1502,7 @@ function writeMonorepoChangelogs(contexts, options, config, dryRun) {
|
|
|
1380
1502
|
if (options.mode === "root" || options.mode === "both") {
|
|
1381
1503
|
const aggregated = aggregateToRoot(contexts);
|
|
1382
1504
|
const rootPath = path7.join(options.rootPath, "CHANGELOG.md");
|
|
1383
|
-
(0,
|
|
1505
|
+
(0, import_core9.info)(`Writing root changelog to ${rootPath}`);
|
|
1384
1506
|
const rootContent = config.updateStrategy === "prepend" && fs9.existsSync(rootPath) ? prependVersion(rootPath, aggregated) : renderMarkdown([aggregated]);
|
|
1385
1507
|
writeFile(rootPath, rootContent, dryRun);
|
|
1386
1508
|
}
|
|
@@ -1392,11 +1514,11 @@ function writeMonorepoChangelogs(contexts, options, config, dryRun) {
|
|
|
1392
1514
|
const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
|
|
1393
1515
|
if (packageDir) {
|
|
1394
1516
|
const changelogPath = path7.join(packageDir, "CHANGELOG.md");
|
|
1395
|
-
(0,
|
|
1517
|
+
(0, import_core9.info)(`Writing changelog for ${packageName} to ${changelogPath}`);
|
|
1396
1518
|
const pkgContent = config.updateStrategy === "prepend" && fs9.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
|
|
1397
1519
|
writeFile(changelogPath, pkgContent, dryRun);
|
|
1398
1520
|
} else {
|
|
1399
|
-
(0,
|
|
1521
|
+
(0, import_core9.info)(`Could not find directory for package ${packageName}, skipping`);
|
|
1400
1522
|
}
|
|
1401
1523
|
}
|
|
1402
1524
|
}
|
|
@@ -1461,9 +1583,9 @@ function detectMonorepo(cwd) {
|
|
|
1461
1583
|
// src/cli.ts
|
|
1462
1584
|
var program = new import_commander.Command();
|
|
1463
1585
|
program.name("releasekit-notes").description("Generate changelogs with LLM-powered enhancement and flexible templating").version("0.1.0");
|
|
1464
|
-
program.command("generate", { isDefault: true }).description("Generate changelog from input data").option("-i, --input <file>", "Input file (default: stdin)").option("-o, --output <spec>", "Output spec (format:file)", collectOutputs, []).option("-t, --template <path>", "Template file or directory").option("-e, --engine <engine>", "Template engine (handlebars|liquid|ejs)").option("--monorepo <mode>", "Monorepo mode (root|packages|both)").option("--llm-provider <provider>", "LLM provider").option("--llm-model <model>", "LLM model").option("--llm-base-url <url>", "LLM base URL (for openai-compatible provider)").option("--llm-tasks <tasks>", "Comma-separated LLM tasks").option("--no-llm", "Disable LLM processing").option("--config <path>", "Config file path").option("--dry-run", "Preview without writing").option("--regenerate", "Regenerate entire changelog").option("-v, --verbose", "Increase verbosity", increaseVerbosity, 0).option("-q, --quiet", "Suppress non-error output").action(async (options) => {
|
|
1586
|
+
program.command("generate", { isDefault: true }).description("Generate changelog from input data").option("-i, --input <file>", "Input file (default: stdin)").option("-o, --output <spec>", "Output spec (format:file)", collectOutputs, []).option("-t, --template <path>", "Template file or directory").option("-e, --engine <engine>", "Template engine (handlebars|liquid|ejs)").option("--monorepo <mode>", "Monorepo mode (root|packages|both)").option("--llm-provider <provider>", "LLM provider").option("--llm-model <model>", "LLM model").option("--llm-base-url <url>", "LLM base URL (for openai-compatible provider)").option("--llm-tasks <tasks>", "Comma-separated LLM tasks").option("--no-llm", "Disable LLM processing").option("--target <package>", "Filter to a specific package name").option("--config <path>", "Config file path").option("--dry-run", "Preview without writing").option("--regenerate", "Regenerate entire changelog").option("-v, --verbose", "Increase verbosity", increaseVerbosity, 0).option("-q, --quiet", "Suppress non-error output").action(async (options) => {
|
|
1465
1587
|
setVerbosity(options.verbose);
|
|
1466
|
-
if (options.quiet) (0,
|
|
1588
|
+
if (options.quiet) (0, import_core10.setQuietMode)(true);
|
|
1467
1589
|
try {
|
|
1468
1590
|
const config = loadConfig(process.cwd(), options.config);
|
|
1469
1591
|
if (options.output.length > 0) {
|
|
@@ -1482,7 +1604,7 @@ program.command("generate", { isDefault: true }).description("Generate changelog
|
|
|
1482
1604
|
config.templates = { ...config.templates, engine: options.engine };
|
|
1483
1605
|
}
|
|
1484
1606
|
if (options.llm === false) {
|
|
1485
|
-
(0,
|
|
1607
|
+
(0, import_core10.info)("LLM processing disabled via --no-llm flag");
|
|
1486
1608
|
delete config.llm;
|
|
1487
1609
|
} else if (options.llmProvider || options.llmModel || options.llmBaseUrl || options.llmTasks) {
|
|
1488
1610
|
config.llm = config.llm ?? { provider: "openai-compatible", model: "" };
|
|
@@ -1498,13 +1620,13 @@ program.command("generate", { isDefault: true }).description("Generate changelog
|
|
|
1498
1620
|
releaseNotes: taskNames.includes("release-notes") || taskNames.includes("releaseNotes")
|
|
1499
1621
|
};
|
|
1500
1622
|
}
|
|
1501
|
-
(0,
|
|
1623
|
+
(0, import_core10.info)(`LLM configured: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
|
|
1502
1624
|
if (config.llm.baseURL) {
|
|
1503
|
-
(0,
|
|
1625
|
+
(0, import_core10.info)(`LLM base URL: ${config.llm.baseURL}`);
|
|
1504
1626
|
}
|
|
1505
1627
|
const taskList = Object.entries(config.llm.tasks || {}).filter(([, enabled]) => enabled).map(([name]) => name).join(", ");
|
|
1506
1628
|
if (taskList) {
|
|
1507
|
-
(0,
|
|
1629
|
+
(0, import_core10.info)(`LLM tasks: ${taskList}`);
|
|
1508
1630
|
}
|
|
1509
1631
|
}
|
|
1510
1632
|
let inputJson;
|
|
@@ -1514,14 +1636,23 @@ program.command("generate", { isDefault: true }).description("Generate changelog
|
|
|
1514
1636
|
inputJson = await readStdin();
|
|
1515
1637
|
}
|
|
1516
1638
|
const input = parsePackageVersioner(inputJson);
|
|
1639
|
+
if (options.target) {
|
|
1640
|
+
const before = input.packages.length;
|
|
1641
|
+
input.packages = input.packages.filter((p) => p.packageName === options.target);
|
|
1642
|
+
if (input.packages.length === 0) {
|
|
1643
|
+
(0, import_core10.info)(`No changelog found for package "${options.target}" (had ${before} package(s))`);
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
(0, import_core10.info)(`Filtered to package: ${options.target}`);
|
|
1647
|
+
}
|
|
1517
1648
|
if (options.monorepo || config.monorepo) {
|
|
1518
1649
|
const monorepoMode = options.monorepo ?? config.monorepo?.mode ?? "both";
|
|
1519
1650
|
const detected = detectMonorepo(process.cwd());
|
|
1520
1651
|
if (!detected.isMonorepo) {
|
|
1521
|
-
(0,
|
|
1652
|
+
(0, import_core10.info)("No monorepo detected, using single package mode");
|
|
1522
1653
|
await runPipeline(input, config, options.dryRun ?? false);
|
|
1523
1654
|
} else {
|
|
1524
|
-
(0,
|
|
1655
|
+
(0, import_core10.info)(`Monorepo detected with packages at ${detected.packagesPath}`);
|
|
1525
1656
|
const contexts = input.packages.map(createTemplateContext);
|
|
1526
1657
|
writeMonorepoChangelogs(
|
|
1527
1658
|
contexts,
|
|
@@ -1538,9 +1669,9 @@ program.command("generate", { isDefault: true }).description("Generate changelog
|
|
|
1538
1669
|
await runPipeline(input, config, options.dryRun ?? false);
|
|
1539
1670
|
}
|
|
1540
1671
|
if (options.dryRun) {
|
|
1541
|
-
(0,
|
|
1672
|
+
(0, import_core10.info)("Dry run complete - no files were written");
|
|
1542
1673
|
} else {
|
|
1543
|
-
(0,
|
|
1674
|
+
(0, import_core10.success)("Changelog generation complete");
|
|
1544
1675
|
}
|
|
1545
1676
|
} catch (err) {
|
|
1546
1677
|
handleError(err);
|
|
@@ -1549,7 +1680,7 @@ program.command("generate", { isDefault: true }).description("Generate changelog
|
|
|
1549
1680
|
program.command("init").description("Create default configuration file").option("-f, --force", "Overwrite existing config").action((options) => {
|
|
1550
1681
|
const configPath = "releasekit.config.json";
|
|
1551
1682
|
if (fs10.existsSync(configPath) && !options.force) {
|
|
1552
|
-
(0,
|
|
1683
|
+
(0, import_core10.error)(`Config file already exists at ${configPath}. Use --force to overwrite.`);
|
|
1553
1684
|
process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
|
|
1554
1685
|
}
|
|
1555
1686
|
const defaultConfig = {
|
|
@@ -1560,7 +1691,7 @@ program.command("init").description("Create default configuration file").option(
|
|
|
1560
1691
|
}
|
|
1561
1692
|
};
|
|
1562
1693
|
fs10.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), "utf-8");
|
|
1563
|
-
(0,
|
|
1694
|
+
(0, import_core10.success)(`Created config file at ${configPath}`);
|
|
1564
1695
|
});
|
|
1565
1696
|
program.command("auth <provider>").description("Configure API key for an LLM provider").option("--key <key>", "API key (omit to be prompted)").action(async (provider, options) => {
|
|
1566
1697
|
let apiKey;
|
|
@@ -1570,14 +1701,14 @@ program.command("auth <provider>").description("Configure API key for an LLM pro
|
|
|
1570
1701
|
apiKey = await promptSecret(`Enter API key for ${provider}: `);
|
|
1571
1702
|
}
|
|
1572
1703
|
if (!apiKey.trim()) {
|
|
1573
|
-
(0,
|
|
1704
|
+
(0, import_core10.error)("API key cannot be empty");
|
|
1574
1705
|
process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
|
|
1575
1706
|
}
|
|
1576
1707
|
(0, import_config.saveAuth)(provider, apiKey.trim());
|
|
1577
|
-
(0,
|
|
1708
|
+
(0, import_core10.success)(`API key saved for ${provider}`);
|
|
1578
1709
|
});
|
|
1579
1710
|
program.command("providers").description("List available LLM providers").action(() => {
|
|
1580
|
-
(0,
|
|
1711
|
+
(0, import_core10.info)("Available LLM providers:");
|
|
1581
1712
|
console.log(" openai - OpenAI (GPT models)");
|
|
1582
1713
|
console.log(" anthropic - Anthropic (Claude models)");
|
|
1583
1714
|
console.log(" ollama - Ollama (local models)");
|
|
@@ -1598,7 +1729,7 @@ function increaseVerbosity(_, previous) {
|
|
|
1598
1729
|
}
|
|
1599
1730
|
function setVerbosity(level) {
|
|
1600
1731
|
const levels = ["info", "debug", "trace"];
|
|
1601
|
-
(0,
|
|
1732
|
+
(0, import_core10.setLogLevel)(levels[Math.min(level, levels.length - 1)] ?? "info");
|
|
1602
1733
|
}
|
|
1603
1734
|
async function readStdin() {
|
|
1604
1735
|
const chunks = [];
|
|
@@ -1621,7 +1752,7 @@ function handleError(err) {
|
|
|
1621
1752
|
err.logError();
|
|
1622
1753
|
process.exit(getExitCode(err));
|
|
1623
1754
|
}
|
|
1624
|
-
(0,
|
|
1755
|
+
(0, import_core10.error)(err instanceof Error ? err.message : String(err));
|
|
1625
1756
|
process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
|
|
1626
1757
|
}
|
|
1627
1758
|
program.parse();
|