@releasekit/notes 0.2.0-next.1 → 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/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 import_core9 = require("@releasekit/core");
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 import_core7 = require("@releasekit/core");
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
@@ -474,6 +485,113 @@ async function enhanceEntries(provider, entries, context, concurrency = LLM_DEFA
474
485
  return results;
475
486
  }
476
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
+
477
595
  // src/llm/tasks/release-notes.ts
478
596
  var RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
479
597
 
@@ -574,12 +692,12 @@ function createProvider(config) {
574
692
 
575
693
  // src/output/github-release.ts
576
694
  var import_rest = require("@octokit/rest");
577
- var import_core5 = require("@releasekit/core");
695
+ var import_core6 = require("@releasekit/core");
578
696
 
579
697
  // src/output/markdown.ts
580
698
  var fs2 = __toESM(require("fs"), 1);
581
699
  var path = __toESM(require("path"), 1);
582
- var import_core4 = require("@releasekit/core");
700
+ var import_core5 = require("@releasekit/core");
583
701
  var TYPE_ORDER = ["added", "changed", "deprecated", "removed", "fixed", "security"];
584
702
  var TYPE_LABELS = {
585
703
  added: "Added",
@@ -678,15 +796,19 @@ ${body}`;
678
796
  function writeMarkdown(outputPath, contexts, config, dryRun) {
679
797
  const content = renderMarkdown(contexts);
680
798
  if (dryRun) {
681
- (0, import_core4.info)("--- Changelog Preview ---");
799
+ (0, import_core5.info)("--- Changelog Preview ---");
682
800
  console.log(content);
683
- (0, import_core4.info)("--- End Preview ---");
801
+ (0, import_core5.info)("--- End Preview ---");
684
802
  return;
685
803
  }
686
804
  const dir = path.dirname(outputPath);
687
805
  if (!fs2.existsSync(dir)) {
688
806
  fs2.mkdirSync(dir, { recursive: true });
689
807
  }
808
+ if (outputPath === "-") {
809
+ process.stdout.write(content);
810
+ return;
811
+ }
690
812
  if (config.updateStrategy === "prepend" && fs2.existsSync(outputPath) && contexts.length === 1) {
691
813
  const firstContext = contexts[0];
692
814
  if (firstContext) {
@@ -696,7 +818,7 @@ function writeMarkdown(outputPath, contexts, config, dryRun) {
696
818
  } else {
697
819
  fs2.writeFileSync(outputPath, content, "utf-8");
698
820
  }
699
- (0, import_core4.success)(`Changelog written to ${outputPath}`);
821
+ (0, import_core5.success)(`Changelog written to ${outputPath}`);
700
822
  }
701
823
 
702
824
  // src/output/github-release.ts
@@ -721,7 +843,7 @@ var GitHubClient = class {
721
843
  } else {
722
844
  body = renderMarkdown([context]);
723
845
  }
724
- (0, import_core5.info)(`Creating GitHub release for ${tagName}`);
846
+ (0, import_core6.info)(`Creating GitHub release for ${tagName}`);
725
847
  try {
726
848
  const response = await this.octokit.repos.createRelease({
727
849
  owner: this.owner,
@@ -733,7 +855,7 @@ var GitHubClient = class {
733
855
  prerelease: options.prerelease ?? false,
734
856
  generate_release_notes: options.generateNotes ?? false
735
857
  });
736
- (0, import_core5.success)(`Release created: ${response.data.html_url}`);
858
+ (0, import_core6.success)(`Release created: ${response.data.html_url}`);
737
859
  return {
738
860
  id: response.data.id,
739
861
  htmlUrl: response.data.html_url,
@@ -751,7 +873,7 @@ var GitHubClient = class {
751
873
  } else {
752
874
  body = renderMarkdown([context]);
753
875
  }
754
- (0, import_core5.info)(`Updating GitHub release ${releaseId}`);
876
+ (0, import_core6.info)(`Updating GitHub release ${releaseId}`);
755
877
  try {
756
878
  const response = await this.octokit.repos.updateRelease({
757
879
  owner: this.owner,
@@ -763,7 +885,7 @@ var GitHubClient = class {
763
885
  draft: options.draft ?? false,
764
886
  prerelease: options.prerelease ?? false
765
887
  });
766
- (0, import_core5.success)(`Release updated: ${response.data.html_url}`);
888
+ (0, import_core6.success)(`Release updated: ${response.data.html_url}`);
767
889
  return {
768
890
  id: response.data.id,
769
891
  htmlUrl: response.data.html_url,
@@ -815,7 +937,7 @@ async function createGitHubRelease(context, options) {
815
937
  // src/output/json.ts
816
938
  var fs3 = __toESM(require("fs"), 1);
817
939
  var path2 = __toESM(require("path"), 1);
818
- var import_core6 = require("@releasekit/core");
940
+ var import_core7 = require("@releasekit/core");
819
941
  function renderJson(contexts) {
820
942
  return JSON.stringify(
821
943
  {
@@ -835,9 +957,9 @@ function renderJson(contexts) {
835
957
  function writeJson(outputPath, contexts, dryRun) {
836
958
  const content = renderJson(contexts);
837
959
  if (dryRun) {
838
- (0, import_core6.info)("--- JSON Output Preview ---");
960
+ (0, import_core7.info)("--- JSON Output Preview ---");
839
961
  console.log(content);
840
- (0, import_core6.info)("--- End Preview ---");
962
+ (0, import_core7.info)("--- End Preview ---");
841
963
  return;
842
964
  }
843
965
  const dir = path2.dirname(outputPath);
@@ -845,7 +967,7 @@ function writeJson(outputPath, contexts, dryRun) {
845
967
  fs3.mkdirSync(dir, { recursive: true });
846
968
  }
847
969
  fs3.writeFileSync(outputPath, content, "utf-8");
848
- (0, import_core6.success)(`JSON output written to ${outputPath}`);
970
+ (0, import_core7.success)(`JSON output written to ${outputPath}`);
849
971
  }
850
972
 
851
973
  // src/templates/ejs.ts
@@ -1102,41 +1224,24 @@ function renderTemplate(templatePath, context, engine) {
1102
1224
  return renderComposable(templatePath, context, engine);
1103
1225
  }
1104
1226
 
1105
- // src/utils/retry.ts
1106
- function sleep(ms) {
1107
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1108
- }
1109
- async function withRetry(fn, options = {}) {
1110
- const maxAttempts = options.maxAttempts ?? 3;
1111
- const initialDelay = options.initialDelay ?? 1e3;
1112
- const maxDelay = options.maxDelay ?? 3e4;
1113
- const backoffFactor = options.backoffFactor ?? 2;
1114
- let lastError;
1115
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1116
- try {
1117
- return await fn();
1118
- } catch (error2) {
1119
- lastError = error2;
1120
- if (attempt < maxAttempts - 1) {
1121
- const base = Math.min(initialDelay * backoffFactor ** attempt, maxDelay);
1122
- const jitter = base * 0.2 * (Math.random() * 2 - 1);
1123
- await sleep(Math.max(0, base + jitter));
1124
- }
1125
- }
1126
- }
1127
- throw lastError;
1128
- }
1129
-
1130
1227
  // src/core/pipeline.ts
1131
1228
  var import_meta = {};
1229
+ function extractVersionFromTag(tag) {
1230
+ if (tag.includes("@") && !tag.startsWith("@")) {
1231
+ return tag;
1232
+ }
1233
+ return tag.replace(/^v/, "");
1234
+ }
1132
1235
  function generateCompareUrl(repoUrl, from, to) {
1236
+ const fromVersion = extractVersionFromTag(from);
1237
+ const toVersion = extractVersionFromTag(to);
1133
1238
  if (/gitlab\.com/i.test(repoUrl)) {
1134
- return `${repoUrl}/-/compare/${from}...${to}`;
1239
+ return `${repoUrl}/-/compare/${fromVersion}...${toVersion}`;
1135
1240
  }
1136
1241
  if (/bitbucket\.org/i.test(repoUrl)) {
1137
- return `${repoUrl}/branches/compare/${from}..${to}`;
1242
+ return `${repoUrl}/branches/compare/${fromVersion}..${toVersion}`;
1138
1243
  }
1139
- return `${repoUrl}/compare/${from}...${to}`;
1244
+ return `${repoUrl}/compare/${fromVersion}...${toVersion}`;
1140
1245
  }
1141
1246
  function createTemplateContext(pkg) {
1142
1247
  const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version) : void 0;
@@ -1183,9 +1288,9 @@ async function processWithLLM(context, config) {
1183
1288
  entries: context.entries
1184
1289
  };
1185
1290
  try {
1186
- (0, import_core7.info)(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1291
+ (0, import_core8.info)(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1187
1292
  if (config.llm.baseURL) {
1188
- (0, import_core7.info)(`LLM base URL: ${config.llm.baseURL}`);
1293
+ (0, import_core8.info)(`LLM base URL: ${config.llm.baseURL}`);
1189
1294
  }
1190
1295
  const rawProvider = createProvider(config.llm);
1191
1296
  const retryOpts = config.llm.retry ?? LLM_DEFAULTS.retry;
@@ -1194,38 +1299,49 @@ async function processWithLLM(context, config) {
1194
1299
  complete: (prompt, opts) => withRetry(() => rawProvider.complete(prompt, opts), retryOpts)
1195
1300
  };
1196
1301
  const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
1197
- (0, import_core7.info)(`Running LLM tasks: ${activeTasks.join(", ")}`);
1198
- if (tasks.enhance) {
1199
- (0, import_core7.info)("Enhancing entries with LLM...");
1200
- enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1201
- (0, import_core7.info)(`Enhanced ${enhanced.entries.length} entries`);
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
+ }
1202
1327
  }
1203
1328
  if (tasks.summarize) {
1204
- (0, import_core7.info)("Summarizing entries with LLM...");
1329
+ (0, import_core8.info)("Summarizing entries with LLM...");
1205
1330
  enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
1206
1331
  if (enhanced.summary) {
1207
- (0, import_core7.info)("Summary generated successfully");
1208
- (0, import_core7.debug)(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1332
+ (0, import_core8.info)("Summary generated successfully");
1333
+ (0, import_core8.debug)(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1209
1334
  } else {
1210
- (0, import_core7.warn)("Summary generation returned empty result");
1211
- }
1212
- }
1213
- if (tasks.categorize) {
1214
- (0, import_core7.info)("Categorizing entries with LLM...");
1215
- const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1216
- enhanced.categories = {};
1217
- for (const cat of categorized) {
1218
- enhanced.categories[cat.category] = cat.entries;
1335
+ (0, import_core8.warn)("Summary generation returned empty result");
1219
1336
  }
1220
- (0, import_core7.info)(`Created ${categorized.length} categories`);
1221
1337
  }
1222
1338
  if (tasks.releaseNotes) {
1223
- (0, import_core7.info)("Generating release notes with LLM...");
1339
+ (0, import_core8.info)("Generating release notes with LLM...");
1224
1340
  enhanced.releaseNotes = await generateReleaseNotes(provider, enhanced.entries, llmContext);
1225
1341
  if (enhanced.releaseNotes) {
1226
- (0, import_core7.info)("Release notes generated successfully");
1342
+ (0, import_core8.info)("Release notes generated successfully");
1227
1343
  } else {
1228
- (0, import_core7.warn)("Release notes generation returned empty result");
1344
+ (0, import_core8.warn)("Release notes generation returned empty result");
1229
1345
  }
1230
1346
  }
1231
1347
  return {
@@ -1234,8 +1350,8 @@ async function processWithLLM(context, config) {
1234
1350
  enhanced
1235
1351
  };
1236
1352
  } catch (error2) {
1237
- (0, import_core7.warn)(`LLM processing failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
1238
- (0, import_core7.warn)("Falling back to raw entries");
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");
1239
1355
  return context;
1240
1356
  }
1241
1357
  }
@@ -1263,9 +1379,13 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1263
1379
  );
1264
1380
  const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
1265
1381
  if (dryRun) {
1266
- (0, import_core7.info)("--- Changelog Preview ---");
1382
+ (0, import_core8.info)("--- Changelog Preview ---");
1267
1383
  console.log(result.content);
1268
- (0, import_core7.info)("--- End Preview ---");
1384
+ (0, import_core8.info)("--- End Preview ---");
1385
+ return;
1386
+ }
1387
+ if (outputPath === "-") {
1388
+ process.stdout.write(result.content);
1269
1389
  return;
1270
1390
  }
1271
1391
  const dir = path6.dirname(outputPath);
@@ -1273,17 +1393,17 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1273
1393
  fs8.mkdirSync(dir, { recursive: true });
1274
1394
  }
1275
1395
  fs8.writeFileSync(outputPath, result.content, "utf-8");
1276
- (0, import_core7.success)(`Changelog written to ${outputPath} (using ${result.engine} template)`);
1396
+ (0, import_core8.success)(`Changelog written to ${outputPath} (using ${result.engine} template)`);
1277
1397
  }
1278
1398
  async function runPipeline(input, config, dryRun) {
1279
- (0, import_core7.debug)(`Processing ${input.packages.length} package(s)`);
1399
+ (0, import_core8.debug)(`Processing ${input.packages.length} package(s)`);
1280
1400
  let contexts = input.packages.map(createTemplateContext);
1281
1401
  if (config.llm && !process.env.CHANGELOG_NO_LLM) {
1282
- (0, import_core7.info)("Processing with LLM enhancement");
1402
+ (0, import_core8.info)("Processing with LLM enhancement");
1283
1403
  contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, config)));
1284
1404
  }
1285
1405
  for (const output of config.output) {
1286
- (0, import_core7.info)(`Generating ${output.format} output`);
1406
+ (0, import_core8.info)(`Generating ${output.format} output`);
1287
1407
  switch (output.format) {
1288
1408
  case "markdown": {
1289
1409
  const file = output.file ?? "CHANGELOG.md";
@@ -1301,22 +1421,22 @@ async function runPipeline(input, config, dryRun) {
1301
1421
  }
1302
1422
  case "github-release": {
1303
1423
  if (dryRun) {
1304
- (0, import_core7.info)("[DRY RUN] Would create GitHub release");
1424
+ (0, import_core8.info)("[DRY RUN] Would create GitHub release");
1305
1425
  break;
1306
1426
  }
1307
1427
  const firstContext = contexts[0];
1308
1428
  if (!firstContext) {
1309
- (0, import_core7.warn)("No context available for GitHub release");
1429
+ (0, import_core8.warn)("No context available for GitHub release");
1310
1430
  break;
1311
1431
  }
1312
1432
  const repoUrl = firstContext.repoUrl;
1313
1433
  if (!repoUrl) {
1314
- (0, import_core7.warn)("No repo URL available, cannot create GitHub release");
1434
+ (0, import_core8.warn)("No repo URL available, cannot create GitHub release");
1315
1435
  break;
1316
1436
  }
1317
1437
  const parsed = parseRepoUrl(repoUrl);
1318
1438
  if (!parsed) {
1319
- (0, import_core7.warn)(`Could not parse repo URL: ${repoUrl}`);
1439
+ (0, import_core8.warn)(`Could not parse repo URL: ${repoUrl}`);
1320
1440
  break;
1321
1441
  }
1322
1442
  await createGitHubRelease(firstContext, {
@@ -1334,7 +1454,7 @@ async function runPipeline(input, config, dryRun) {
1334
1454
  // src/monorepo/aggregator.ts
1335
1455
  var fs9 = __toESM(require("fs"), 1);
1336
1456
  var path7 = __toESM(require("path"), 1);
1337
- var import_core8 = require("@releasekit/core");
1457
+ var import_core9 = require("@releasekit/core");
1338
1458
 
1339
1459
  // src/monorepo/splitter.ts
1340
1460
  function splitByPackage(contexts) {
@@ -1348,7 +1468,7 @@ function splitByPackage(contexts) {
1348
1468
  // src/monorepo/aggregator.ts
1349
1469
  function writeFile(outputPath, content, dryRun) {
1350
1470
  if (dryRun) {
1351
- (0, import_core8.info)(`[DRY RUN] Would write to ${outputPath}`);
1471
+ (0, import_core9.info)(`[DRY RUN] Would write to ${outputPath}`);
1352
1472
  console.log(content);
1353
1473
  return;
1354
1474
  }
@@ -1357,7 +1477,7 @@ function writeFile(outputPath, content, dryRun) {
1357
1477
  fs9.mkdirSync(dir, { recursive: true });
1358
1478
  }
1359
1479
  fs9.writeFileSync(outputPath, content, "utf-8");
1360
- (0, import_core8.success)(`Changelog written to ${outputPath}`);
1480
+ (0, import_core9.success)(`Changelog written to ${outputPath}`);
1361
1481
  }
1362
1482
  function aggregateToRoot(contexts) {
1363
1483
  const aggregated = {
@@ -1382,7 +1502,7 @@ function writeMonorepoChangelogs(contexts, options, config, dryRun) {
1382
1502
  if (options.mode === "root" || options.mode === "both") {
1383
1503
  const aggregated = aggregateToRoot(contexts);
1384
1504
  const rootPath = path7.join(options.rootPath, "CHANGELOG.md");
1385
- (0, import_core8.info)(`Writing root changelog to ${rootPath}`);
1505
+ (0, import_core9.info)(`Writing root changelog to ${rootPath}`);
1386
1506
  const rootContent = config.updateStrategy === "prepend" && fs9.existsSync(rootPath) ? prependVersion(rootPath, aggregated) : renderMarkdown([aggregated]);
1387
1507
  writeFile(rootPath, rootContent, dryRun);
1388
1508
  }
@@ -1394,11 +1514,11 @@ function writeMonorepoChangelogs(contexts, options, config, dryRun) {
1394
1514
  const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
1395
1515
  if (packageDir) {
1396
1516
  const changelogPath = path7.join(packageDir, "CHANGELOG.md");
1397
- (0, import_core8.info)(`Writing changelog for ${packageName} to ${changelogPath}`);
1517
+ (0, import_core9.info)(`Writing changelog for ${packageName} to ${changelogPath}`);
1398
1518
  const pkgContent = config.updateStrategy === "prepend" && fs9.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
1399
1519
  writeFile(changelogPath, pkgContent, dryRun);
1400
1520
  } else {
1401
- (0, import_core8.info)(`Could not find directory for package ${packageName}, skipping`);
1521
+ (0, import_core9.info)(`Could not find directory for package ${packageName}, skipping`);
1402
1522
  }
1403
1523
  }
1404
1524
  }
@@ -1463,9 +1583,9 @@ function detectMonorepo(cwd) {
1463
1583
  // src/cli.ts
1464
1584
  var program = new import_commander.Command();
1465
1585
  program.name("releasekit-notes").description("Generate changelogs with LLM-powered enhancement and flexible templating").version("0.1.0");
1466
- 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) => {
1467
1587
  setVerbosity(options.verbose);
1468
- if (options.quiet) (0, import_core9.setQuietMode)(true);
1588
+ if (options.quiet) (0, import_core10.setQuietMode)(true);
1469
1589
  try {
1470
1590
  const config = loadConfig(process.cwd(), options.config);
1471
1591
  if (options.output.length > 0) {
@@ -1484,7 +1604,7 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1484
1604
  config.templates = { ...config.templates, engine: options.engine };
1485
1605
  }
1486
1606
  if (options.llm === false) {
1487
- (0, import_core9.info)("LLM processing disabled via --no-llm flag");
1607
+ (0, import_core10.info)("LLM processing disabled via --no-llm flag");
1488
1608
  delete config.llm;
1489
1609
  } else if (options.llmProvider || options.llmModel || options.llmBaseUrl || options.llmTasks) {
1490
1610
  config.llm = config.llm ?? { provider: "openai-compatible", model: "" };
@@ -1500,13 +1620,13 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1500
1620
  releaseNotes: taskNames.includes("release-notes") || taskNames.includes("releaseNotes")
1501
1621
  };
1502
1622
  }
1503
- (0, import_core9.info)(`LLM configured: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1623
+ (0, import_core10.info)(`LLM configured: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1504
1624
  if (config.llm.baseURL) {
1505
- (0, import_core9.info)(`LLM base URL: ${config.llm.baseURL}`);
1625
+ (0, import_core10.info)(`LLM base URL: ${config.llm.baseURL}`);
1506
1626
  }
1507
1627
  const taskList = Object.entries(config.llm.tasks || {}).filter(([, enabled]) => enabled).map(([name]) => name).join(", ");
1508
1628
  if (taskList) {
1509
- (0, import_core9.info)(`LLM tasks: ${taskList}`);
1629
+ (0, import_core10.info)(`LLM tasks: ${taskList}`);
1510
1630
  }
1511
1631
  }
1512
1632
  let inputJson;
@@ -1516,14 +1636,23 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1516
1636
  inputJson = await readStdin();
1517
1637
  }
1518
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
+ }
1519
1648
  if (options.monorepo || config.monorepo) {
1520
1649
  const monorepoMode = options.monorepo ?? config.monorepo?.mode ?? "both";
1521
1650
  const detected = detectMonorepo(process.cwd());
1522
1651
  if (!detected.isMonorepo) {
1523
- (0, import_core9.info)("No monorepo detected, using single package mode");
1652
+ (0, import_core10.info)("No monorepo detected, using single package mode");
1524
1653
  await runPipeline(input, config, options.dryRun ?? false);
1525
1654
  } else {
1526
- (0, import_core9.info)(`Monorepo detected with packages at ${detected.packagesPath}`);
1655
+ (0, import_core10.info)(`Monorepo detected with packages at ${detected.packagesPath}`);
1527
1656
  const contexts = input.packages.map(createTemplateContext);
1528
1657
  writeMonorepoChangelogs(
1529
1658
  contexts,
@@ -1540,9 +1669,9 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1540
1669
  await runPipeline(input, config, options.dryRun ?? false);
1541
1670
  }
1542
1671
  if (options.dryRun) {
1543
- (0, import_core9.info)("Dry run complete - no files were written");
1672
+ (0, import_core10.info)("Dry run complete - no files were written");
1544
1673
  } else {
1545
- (0, import_core9.success)("Changelog generation complete");
1674
+ (0, import_core10.success)("Changelog generation complete");
1546
1675
  }
1547
1676
  } catch (err) {
1548
1677
  handleError(err);
@@ -1551,7 +1680,7 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1551
1680
  program.command("init").description("Create default configuration file").option("-f, --force", "Overwrite existing config").action((options) => {
1552
1681
  const configPath = "releasekit.config.json";
1553
1682
  if (fs10.existsSync(configPath) && !options.force) {
1554
- (0, import_core9.error)(`Config file already exists at ${configPath}. Use --force to overwrite.`);
1683
+ (0, import_core10.error)(`Config file already exists at ${configPath}. Use --force to overwrite.`);
1555
1684
  process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
1556
1685
  }
1557
1686
  const defaultConfig = {
@@ -1562,7 +1691,7 @@ program.command("init").description("Create default configuration file").option(
1562
1691
  }
1563
1692
  };
1564
1693
  fs10.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), "utf-8");
1565
- (0, import_core9.success)(`Created config file at ${configPath}`);
1694
+ (0, import_core10.success)(`Created config file at ${configPath}`);
1566
1695
  });
1567
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) => {
1568
1697
  let apiKey;
@@ -1572,14 +1701,14 @@ program.command("auth <provider>").description("Configure API key for an LLM pro
1572
1701
  apiKey = await promptSecret(`Enter API key for ${provider}: `);
1573
1702
  }
1574
1703
  if (!apiKey.trim()) {
1575
- (0, import_core9.error)("API key cannot be empty");
1704
+ (0, import_core10.error)("API key cannot be empty");
1576
1705
  process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
1577
1706
  }
1578
1707
  (0, import_config.saveAuth)(provider, apiKey.trim());
1579
- (0, import_core9.success)(`API key saved for ${provider}`);
1708
+ (0, import_core10.success)(`API key saved for ${provider}`);
1580
1709
  });
1581
1710
  program.command("providers").description("List available LLM providers").action(() => {
1582
- (0, import_core9.info)("Available LLM providers:");
1711
+ (0, import_core10.info)("Available LLM providers:");
1583
1712
  console.log(" openai - OpenAI (GPT models)");
1584
1713
  console.log(" anthropic - Anthropic (Claude models)");
1585
1714
  console.log(" ollama - Ollama (local models)");
@@ -1600,7 +1729,7 @@ function increaseVerbosity(_, previous) {
1600
1729
  }
1601
1730
  function setVerbosity(level) {
1602
1731
  const levels = ["info", "debug", "trace"];
1603
- (0, import_core9.setLogLevel)(levels[Math.min(level, levels.length - 1)] ?? "info");
1732
+ (0, import_core10.setLogLevel)(levels[Math.min(level, levels.length - 1)] ?? "info");
1604
1733
  }
1605
1734
  async function readStdin() {
1606
1735
  const chunks = [];
@@ -1623,7 +1752,7 @@ function handleError(err) {
1623
1752
  err.logError();
1624
1753
  process.exit(getExitCode(err));
1625
1754
  }
1626
- (0, import_core9.error)(err instanceof Error ? err.message : String(err));
1755
+ (0, import_core10.error)(err instanceof Error ? err.message : String(err));
1627
1756
  process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
1628
1757
  }
1629
1758
  program.parse();