@releasekit/notes 0.2.0 → 0.2.1

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.
@@ -1,12 +1,376 @@
1
- // src/core/config.ts
2
1
  import {
3
- loadAuth,
4
- loadNotesConfig as loadSharedNotesConfig,
5
- saveAuth
6
- } from "@releasekit/config";
7
- function loadConfig(projectDir = process.cwd(), configFile) {
2
+ EXIT_CODES,
3
+ ReleaseKitError,
4
+ debug,
5
+ formatVersion,
6
+ info,
7
+ renderMarkdown,
8
+ success,
9
+ warn,
10
+ writeMarkdown
11
+ } from "./chunk-SAUES3BF.js";
12
+
13
+ // ../config/dist/index.js
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import * as TOML from "smol-toml";
17
+ import * as fs3 from "fs";
18
+ import * as path3 from "path";
19
+ import { z as z2 } from "zod";
20
+ import { z } from "zod";
21
+ import * as fs2 from "fs";
22
+ import * as os from "os";
23
+ import * as path2 from "path";
24
+ var ConfigError = class extends ReleaseKitError {
25
+ code = "CONFIG_ERROR";
26
+ suggestions;
27
+ constructor(message, suggestions) {
28
+ super(message);
29
+ this.suggestions = suggestions ?? [
30
+ "Check that releasekit.config.json exists and is valid JSON",
31
+ "Run with --verbose for more details"
32
+ ];
33
+ }
34
+ };
35
+ var MAX_JSONC_LENGTH = 1e5;
36
+ function parseJsonc(content) {
37
+ if (content.length > MAX_JSONC_LENGTH) {
38
+ throw new Error(`JSONC content too long: ${content.length} characters (max ${MAX_JSONC_LENGTH})`);
39
+ }
40
+ try {
41
+ return JSON.parse(content);
42
+ } catch {
43
+ const cleaned = content.replace(/\/\/[^\r\n]{0,10000}$/gm, "").replace(/\/\*[\s\S]{0,50000}?\*\//g, "").trim();
44
+ return JSON.parse(cleaned);
45
+ }
46
+ }
47
+ var GitConfigSchema = z.object({
48
+ remote: z.string().default("origin"),
49
+ branch: z.string().default("main"),
50
+ pushMethod: z.enum(["auto", "ssh", "https"]).default("auto"),
51
+ /**
52
+ * Optional env var name containing a GitHub token for HTTPS pushes.
53
+ * When set, publish steps can use this token without mutating git remotes.
54
+ */
55
+ httpsTokenEnv: z.string().optional(),
56
+ push: z.boolean().optional(),
57
+ skipHooks: z.boolean().optional()
58
+ });
59
+ var MonorepoConfigSchema = z.object({
60
+ mode: z.enum(["root", "packages", "both"]).optional(),
61
+ rootPath: z.string().optional(),
62
+ packagesPath: z.string().optional(),
63
+ mainPackage: z.string().optional()
64
+ });
65
+ var BranchPatternSchema = z.object({
66
+ pattern: z.string(),
67
+ releaseType: z.enum(["major", "minor", "patch", "prerelease"])
68
+ });
69
+ var VersionCargoConfigSchema = z.object({
70
+ enabled: z.boolean().default(true),
71
+ paths: z.array(z.string()).optional()
72
+ });
73
+ var VersionConfigSchema = z.object({
74
+ tagTemplate: z.string().default("v{version}"),
75
+ packageSpecificTags: z.boolean().default(false),
76
+ preset: z.string().default("conventional"),
77
+ sync: z.boolean().default(true),
78
+ packages: z.array(z.string()).default([]),
79
+ mainPackage: z.string().optional(),
80
+ updateInternalDependencies: z.enum(["major", "minor", "patch", "no-internal-update"]).default("minor"),
81
+ skip: z.array(z.string()).optional(),
82
+ commitMessage: z.string().optional(),
83
+ versionStrategy: z.enum(["branchPattern", "commitMessage"]).default("commitMessage"),
84
+ branchPatterns: z.array(BranchPatternSchema).optional(),
85
+ defaultReleaseType: z.enum(["major", "minor", "patch", "prerelease"]).optional(),
86
+ mismatchStrategy: z.enum(["error", "warn", "ignore", "prefer-package", "prefer-git"]).default("warn"),
87
+ versionPrefix: z.string().default(""),
88
+ prereleaseIdentifier: z.string().optional(),
89
+ strictReachable: z.boolean().default(false),
90
+ cargo: VersionCargoConfigSchema.optional()
91
+ });
92
+ var NpmConfigSchema = z.object({
93
+ enabled: z.boolean().default(true),
94
+ auth: z.enum(["auto", "oidc", "token"]).default("auto"),
95
+ provenance: z.boolean().default(true),
96
+ access: z.enum(["public", "restricted"]).default("public"),
97
+ registry: z.string().default("https://registry.npmjs.org"),
98
+ copyFiles: z.array(z.string()).default(["LICENSE"]),
99
+ tag: z.string().default("latest")
100
+ });
101
+ var CargoPublishConfigSchema = z.object({
102
+ enabled: z.boolean().default(false),
103
+ noVerify: z.boolean().default(false),
104
+ publishOrder: z.array(z.string()).default([]),
105
+ clean: z.boolean().default(false)
106
+ });
107
+ var PublishGitConfigSchema = z.object({
108
+ push: z.boolean().default(true),
109
+ pushMethod: z.enum(["auto", "ssh", "https"]).optional(),
110
+ remote: z.string().optional(),
111
+ branch: z.string().optional(),
112
+ httpsTokenEnv: z.string().optional(),
113
+ skipHooks: z.boolean().optional()
114
+ });
115
+ var GitHubReleaseConfigSchema = z.object({
116
+ enabled: z.boolean().default(true),
117
+ draft: z.boolean().default(true),
118
+ perPackage: z.boolean().default(true),
119
+ prerelease: z.union([z.literal("auto"), z.boolean()]).default("auto"),
120
+ /**
121
+ * Controls how release notes are sourced for GitHub releases.
122
+ * - 'auto': Use RELEASE_NOTES.md if it exists, then per-package changelog
123
+ * data from the version output, then GitHub's auto-generated notes.
124
+ * - 'github': Always use GitHub's auto-generated notes.
125
+ * - 'none': No notes body.
126
+ * - Any other string: Treated as a file path to read notes from.
127
+ */
128
+ releaseNotes: z.union([z.literal("auto"), z.literal("github"), z.literal("none"), z.string()]).default("auto")
129
+ });
130
+ var VerifyRegistryConfigSchema = z.object({
131
+ enabled: z.boolean().default(true),
132
+ maxAttempts: z.number().int().positive().default(5),
133
+ initialDelay: z.number().int().positive().default(15e3),
134
+ backoffMultiplier: z.number().positive().default(2)
135
+ });
136
+ var VerifyConfigSchema = z.object({
137
+ npm: VerifyRegistryConfigSchema.default({
138
+ enabled: true,
139
+ maxAttempts: 5,
140
+ initialDelay: 15e3,
141
+ backoffMultiplier: 2
142
+ }),
143
+ cargo: VerifyRegistryConfigSchema.default({
144
+ enabled: true,
145
+ maxAttempts: 10,
146
+ initialDelay: 3e4,
147
+ backoffMultiplier: 2
148
+ })
149
+ });
150
+ var PublishConfigSchema = z.object({
151
+ git: PublishGitConfigSchema.optional(),
152
+ npm: NpmConfigSchema.default({
153
+ enabled: true,
154
+ auth: "auto",
155
+ provenance: true,
156
+ access: "public",
157
+ registry: "https://registry.npmjs.org",
158
+ copyFiles: ["LICENSE"],
159
+ tag: "latest"
160
+ }),
161
+ cargo: CargoPublishConfigSchema.default({
162
+ enabled: false,
163
+ noVerify: false,
164
+ publishOrder: [],
165
+ clean: false
166
+ }),
167
+ githubRelease: GitHubReleaseConfigSchema.default({
168
+ enabled: true,
169
+ draft: true,
170
+ perPackage: true,
171
+ prerelease: "auto",
172
+ releaseNotes: "auto"
173
+ }),
174
+ verify: VerifyConfigSchema.default({
175
+ npm: {
176
+ enabled: true,
177
+ maxAttempts: 5,
178
+ initialDelay: 15e3,
179
+ backoffMultiplier: 2
180
+ },
181
+ cargo: {
182
+ enabled: true,
183
+ maxAttempts: 10,
184
+ initialDelay: 3e4,
185
+ backoffMultiplier: 2
186
+ }
187
+ })
188
+ });
189
+ var TemplateConfigSchema = z.object({
190
+ path: z.string().optional(),
191
+ engine: z.enum(["handlebars", "liquid", "ejs"]).optional()
192
+ });
193
+ var OutputConfigSchema = z.object({
194
+ format: z.enum(["markdown", "github-release", "json"]),
195
+ file: z.string().optional(),
196
+ options: z.record(z.string(), z.unknown()).optional(),
197
+ templates: TemplateConfigSchema.optional()
198
+ });
199
+ var LLMOptionsSchema = z.object({
200
+ timeout: z.number().optional(),
201
+ maxTokens: z.number().optional(),
202
+ temperature: z.number().optional()
203
+ });
204
+ var LLMRetryConfigSchema = z.object({
205
+ maxAttempts: z.number().int().positive().optional(),
206
+ initialDelay: z.number().nonnegative().optional(),
207
+ maxDelay: z.number().positive().optional(),
208
+ backoffFactor: z.number().positive().optional()
209
+ });
210
+ var LLMTasksConfigSchema = z.object({
211
+ summarize: z.boolean().optional(),
212
+ enhance: z.boolean().optional(),
213
+ categorize: z.boolean().optional(),
214
+ releaseNotes: z.boolean().optional()
215
+ });
216
+ var LLMCategorySchema = z.object({
217
+ name: z.string(),
218
+ description: z.string(),
219
+ scopes: z.array(z.string()).optional()
220
+ });
221
+ var ScopeRulesSchema = z.object({
222
+ allowed: z.array(z.string()).optional(),
223
+ caseSensitive: z.boolean().default(false),
224
+ invalidScopeAction: z.enum(["remove", "keep", "fallback"]).default("remove"),
225
+ fallbackScope: z.string().optional()
226
+ });
227
+ var ScopeConfigSchema = z.object({
228
+ mode: z.enum(["restricted", "packages", "none", "unrestricted"]).default("unrestricted"),
229
+ rules: ScopeRulesSchema.optional()
230
+ });
231
+ var LLMPromptOverridesSchema = z.object({
232
+ enhance: z.string().optional(),
233
+ categorize: z.string().optional(),
234
+ enhanceAndCategorize: z.string().optional(),
235
+ summarize: z.string().optional(),
236
+ releaseNotes: z.string().optional()
237
+ });
238
+ var LLMPromptsConfigSchema = z.object({
239
+ instructions: LLMPromptOverridesSchema.optional(),
240
+ templates: LLMPromptOverridesSchema.optional()
241
+ });
242
+ var LLMConfigSchema = z.object({
243
+ provider: z.string(),
244
+ model: z.string(),
245
+ baseURL: z.string().optional(),
246
+ apiKey: z.string().optional(),
247
+ options: LLMOptionsSchema.optional(),
248
+ concurrency: z.number().int().positive().optional(),
249
+ retry: LLMRetryConfigSchema.optional(),
250
+ tasks: LLMTasksConfigSchema.optional(),
251
+ categories: z.array(LLMCategorySchema).optional(),
252
+ style: z.string().optional(),
253
+ scopes: ScopeConfigSchema.optional(),
254
+ prompts: LLMPromptsConfigSchema.optional()
255
+ });
256
+ var NotesInputConfigSchema = z.object({
257
+ source: z.string().optional(),
258
+ file: z.string().optional()
259
+ });
260
+ var NotesConfigSchema = z.object({
261
+ input: NotesInputConfigSchema.optional(),
262
+ output: z.array(OutputConfigSchema).default([{ format: "markdown", file: "CHANGELOG.md" }]),
263
+ monorepo: MonorepoConfigSchema.optional(),
264
+ templates: TemplateConfigSchema.optional(),
265
+ llm: LLMConfigSchema.optional(),
266
+ updateStrategy: z.enum(["prepend", "regenerate"]).default("prepend")
267
+ });
268
+ var ReleaseKitConfigSchema = z.object({
269
+ git: GitConfigSchema.optional(),
270
+ monorepo: MonorepoConfigSchema.optional(),
271
+ version: VersionConfigSchema.optional(),
272
+ publish: PublishConfigSchema.optional(),
273
+ notes: NotesConfigSchema.optional()
274
+ });
275
+ var MAX_INPUT_LENGTH = 1e4;
276
+ function substituteVariables(value) {
277
+ if (value.length > MAX_INPUT_LENGTH) {
278
+ throw new Error(`Input too long: ${value.length} characters (max ${MAX_INPUT_LENGTH})`);
279
+ }
280
+ const envPattern = /\{env:([^}]{1,1000})\}/g;
281
+ const filePattern = /\{file:([^}]{1,1000})\}/g;
282
+ let result = value;
283
+ result = result.replace(envPattern, (_, varName) => {
284
+ return process.env[varName] ?? "";
285
+ });
286
+ result = result.replace(filePattern, (_, filePath) => {
287
+ const expandedPath = filePath.startsWith("~") ? path2.join(os.homedir(), filePath.slice(1)) : filePath;
288
+ try {
289
+ return fs2.readFileSync(expandedPath, "utf-8").trim();
290
+ } catch {
291
+ return "";
292
+ }
293
+ });
294
+ return result;
295
+ }
296
+ var SOLE_REFERENCE_PATTERN = /^\{(?:env|file):[^}]+\}$/;
297
+ function substituteInObject(obj) {
298
+ if (typeof obj === "string") {
299
+ const result = substituteVariables(obj);
300
+ if (result === "" && SOLE_REFERENCE_PATTERN.test(obj)) {
301
+ return void 0;
302
+ }
303
+ return result;
304
+ }
305
+ if (Array.isArray(obj)) {
306
+ return obj.map((item) => substituteInObject(item));
307
+ }
308
+ if (obj && typeof obj === "object") {
309
+ const result = {};
310
+ for (const [key, value] of Object.entries(obj)) {
311
+ result[key] = substituteInObject(value);
312
+ }
313
+ return result;
314
+ }
315
+ return obj;
316
+ }
317
+ var AUTH_DIR = path2.join(os.homedir(), ".config", "releasekit");
318
+ var AUTH_FILE = path2.join(AUTH_DIR, "auth.json");
319
+ function loadAuth() {
320
+ if (fs2.existsSync(AUTH_FILE)) {
321
+ try {
322
+ const content = fs2.readFileSync(AUTH_FILE, "utf-8");
323
+ return JSON.parse(content);
324
+ } catch {
325
+ return {};
326
+ }
327
+ }
328
+ return {};
329
+ }
330
+ function saveAuth(provider, apiKey) {
331
+ if (!fs2.existsSync(AUTH_DIR)) {
332
+ fs2.mkdirSync(AUTH_DIR, { recursive: true });
333
+ }
334
+ const existing = loadAuth();
335
+ existing[provider] = apiKey;
336
+ fs2.writeFileSync(AUTH_FILE, JSON.stringify(existing, null, 2), { encoding: "utf-8", mode: 384 });
337
+ }
338
+ var CONFIG_FILE = "releasekit.config.json";
339
+ function loadConfigFile(configPath) {
340
+ if (!fs3.existsSync(configPath)) {
341
+ return {};
342
+ }
343
+ try {
344
+ const content = fs3.readFileSync(configPath, "utf-8");
345
+ const parsed = parseJsonc(content);
346
+ const substituted = substituteInObject(parsed);
347
+ return ReleaseKitConfigSchema.parse(substituted);
348
+ } catch (error) {
349
+ if (error instanceof z2.ZodError) {
350
+ const issues = error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
351
+ throw new ConfigError(`Config validation errors:
352
+ ${issues}`);
353
+ }
354
+ if (error instanceof SyntaxError) {
355
+ throw new ConfigError(`Invalid JSON in config file: ${error.message}`);
356
+ }
357
+ throw error;
358
+ }
359
+ }
360
+ function loadConfig(options) {
361
+ const cwd = options?.cwd ?? process.cwd();
362
+ const configPath = options?.configPath ?? path3.join(cwd, CONFIG_FILE);
363
+ return loadConfigFile(configPath);
364
+ }
365
+ function loadNotesConfig(options) {
366
+ const config = loadConfig(options);
367
+ return config.notes;
368
+ }
369
+
370
+ // src/core/config.ts
371
+ function loadConfig2(projectDir = process.cwd(), configFile) {
8
372
  const options = { cwd: projectDir, configPath: configFile };
9
- return loadSharedNotesConfig(options) ?? getDefaultConfig();
373
+ return loadNotesConfig(options) ?? getDefaultConfig();
10
374
  }
11
375
  function getDefaultConfig() {
12
376
  return {
@@ -16,8 +380,6 @@ function getDefaultConfig() {
16
380
  }
17
381
 
18
382
  // src/errors/index.ts
19
- import { EXIT_CODES, ReleaseKitError } from "@releasekit/core";
20
- import { EXIT_CODES as EXIT_CODES2 } from "@releasekit/core";
21
383
  var NotesError = class extends ReleaseKitError {
22
384
  };
23
385
  var InputParseError = class extends NotesError {
@@ -53,7 +415,7 @@ var GitHubError = class extends NotesError {
53
415
  "Verify repository exists and is accessible"
54
416
  ];
55
417
  };
56
- var ConfigError = class extends NotesError {
418
+ var ConfigError2 = class extends NotesError {
57
419
  code = "CONFIG_ERROR";
58
420
  suggestions = [
59
421
  "Check config file syntax",
@@ -79,7 +441,7 @@ function getExitCode(error) {
79
441
  }
80
442
 
81
443
  // src/input/package-versioner.ts
82
- import * as fs from "fs";
444
+ import * as fs4 from "fs";
83
445
  function normalizeEntryType(type) {
84
446
  const typeMap = {
85
447
  added: "added",
@@ -133,7 +495,7 @@ function parsePackageVersioner(json) {
133
495
  };
134
496
  }
135
497
  function parsePackageVersionerFile(filePath) {
136
- const content = fs.readFileSync(filePath, "utf-8");
498
+ const content = fs4.readFileSync(filePath, "utf-8");
137
499
  return parsePackageVersioner(content);
138
500
  }
139
501
  async function parsePackageVersionerStdin() {
@@ -145,138 +507,9 @@ async function parsePackageVersionerStdin() {
145
507
  return parsePackageVersioner(content);
146
508
  }
147
509
 
148
- // src/output/markdown.ts
149
- import * as fs2 from "fs";
150
- import * as path from "path";
151
- import { debug, 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(`Would write changelog to ${outputPath}`);
251
- debug("--- Changelog Preview ---");
252
- debug(content);
253
- debug("--- End Preview ---");
254
- return;
255
- }
256
- const dir = path.dirname(outputPath);
257
- if (!fs2.existsSync(dir)) {
258
- fs2.mkdirSync(dir, { recursive: true });
259
- }
260
- if (outputPath === "-") {
261
- process.stdout.write(content);
262
- return;
263
- }
264
- if (config.updateStrategy === "prepend" && fs2.existsSync(outputPath) && contexts.length === 1) {
265
- const firstContext = contexts[0];
266
- if (firstContext) {
267
- const updated = prependVersion(outputPath, firstContext);
268
- fs2.writeFileSync(outputPath, updated, "utf-8");
269
- }
270
- } else {
271
- fs2.writeFileSync(outputPath, content, "utf-8");
272
- }
273
- success(`Changelog written to ${outputPath}`);
274
- }
275
-
276
510
  // src/output/json.ts
277
- import * as fs3 from "fs";
278
- import * as path2 from "path";
279
- import { debug as debug2, info as info2, success as success2 } from "@releasekit/core";
511
+ import * as fs5 from "fs";
512
+ import * as path4 from "path";
280
513
  function renderJson(contexts) {
281
514
  return JSON.stringify(
282
515
  {
@@ -296,29 +529,28 @@ function renderJson(contexts) {
296
529
  function writeJson(outputPath, contexts, dryRun) {
297
530
  const content = renderJson(contexts);
298
531
  if (dryRun) {
299
- info2(`Would write JSON output to ${outputPath}`);
300
- debug2("--- JSON Output Preview ---");
301
- debug2(content);
302
- debug2("--- End Preview ---");
532
+ info(`Would write JSON output to ${outputPath}`);
533
+ debug("--- JSON Output Preview ---");
534
+ debug(content);
535
+ debug("--- End Preview ---");
303
536
  return;
304
537
  }
305
- const dir = path2.dirname(outputPath);
306
- if (!fs3.existsSync(dir)) {
307
- fs3.mkdirSync(dir, { recursive: true });
538
+ const dir = path4.dirname(outputPath);
539
+ if (!fs5.existsSync(dir)) {
540
+ fs5.mkdirSync(dir, { recursive: true });
308
541
  }
309
- fs3.writeFileSync(outputPath, content, "utf-8");
310
- success2(`JSON output written to ${outputPath}`);
542
+ fs5.writeFileSync(outputPath, content, "utf-8");
543
+ success(`JSON output written to ${outputPath}`);
311
544
  }
312
545
 
313
546
  // src/core/pipeline.ts
314
- import * as fs8 from "fs";
315
- import * as path6 from "path";
316
- import { debug as debug3, info as info4, success as success4, warn as warn3 } from "@releasekit/core";
547
+ import * as fs10 from "fs";
548
+ import * as path8 from "path";
317
549
 
318
550
  // src/llm/defaults.ts
319
551
  var LLM_DEFAULTS = {
320
552
  timeout: 6e4,
321
- maxTokens: 2e3,
553
+ maxTokens: 4e3,
322
554
  temperature: 0.7,
323
555
  concurrency: 5,
324
556
  retry: {
@@ -411,7 +643,7 @@ var OllamaProvider = class extends BaseLLMProvider {
411
643
  "Content-Type": "application/json"
412
644
  };
413
645
  if (this.apiKey) {
414
- headers["Authorization"] = `Bearer ${this.apiKey}`;
646
+ headers.Authorization = `Bearer ${this.apiKey}`;
415
647
  }
416
648
  const baseUrl = this.baseURL.endsWith("/api") ? this.baseURL.slice(0, -4) : this.baseURL;
417
649
  const response = await fetch(`${baseUrl}/api/chat`, {
@@ -508,8 +740,15 @@ var OpenAICompatibleProvider = class extends BaseLLMProvider {
508
740
  }
509
741
  };
510
742
 
511
- // src/llm/tasks/categorize.ts
512
- import { warn } from "@releasekit/core";
743
+ // src/utils/json.ts
744
+ function extractJsonFromResponse(response) {
745
+ const stripped = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
746
+ const objectMatch = stripped.match(/\{[\s\S]*\}/);
747
+ if (objectMatch) return objectMatch[0];
748
+ const arrayMatch = stripped.match(/\[[\s\S]*\]/);
749
+ if (arrayMatch) return arrayMatch[0];
750
+ return stripped;
751
+ }
513
752
 
514
753
  // src/llm/prompts.ts
515
754
  function resolvePrompt(taskName, defaultPrompt, promptsConfig) {
@@ -572,7 +811,6 @@ function validateScope(scope, allowedScopes, rules) {
572
811
  return scope;
573
812
  case "fallback":
574
813
  return rules?.fallbackScope;
575
- case "remove":
576
814
  default:
577
815
  return void 0;
578
816
  }
@@ -642,8 +880,7 @@ async function categorizeEntries(provider, entries, context) {
642
880
  const prompt = promptTemplate.replace("{{entries}}", entriesText);
643
881
  try {
644
882
  const response = await provider.complete(prompt);
645
- const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
646
- const parsed = JSON.parse(cleaned);
883
+ const parsed = JSON.parse(extractJsonFromResponse(response));
647
884
  const result = [];
648
885
  if (hasCustomCategories && parsed.categories) {
649
886
  const categoryMap = parsed.categories;
@@ -724,9 +961,6 @@ async function enhanceEntries(provider, entries, context, concurrency = LLM_DEFA
724
961
  return results;
725
962
  }
726
963
 
727
- // src/llm/tasks/enhance-and-categorize.ts
728
- import { warn as warn2 } from "@releasekit/core";
729
-
730
964
  // src/utils/retry.ts
731
965
  function sleep(ms) {
732
966
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -804,8 +1038,7 @@ async function enhanceAndCategorize(provider, entries, context) {
804
1038
  const defaultPrompt = buildPrompt(entries, context.categories, context.style);
805
1039
  const prompt = resolvePrompt("enhanceAndCategorize", defaultPrompt, context.prompts);
806
1040
  const response = await provider.complete(prompt);
807
- const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
808
- const parsed = JSON.parse(cleaned);
1041
+ const parsed = JSON.parse(extractJsonFromResponse(response));
809
1042
  if (!Array.isArray(parsed.entries)) {
810
1043
  throw new Error('Response missing "entries" array');
811
1044
  }
@@ -837,7 +1070,7 @@ async function enhanceAndCategorize(provider, entries, context) {
837
1070
  return { enhancedEntries: validatedEntries, categories };
838
1071
  }, retryOpts);
839
1072
  } catch (error) {
840
- warn2(
1073
+ warn(
841
1074
  `Combined enhance+categorize failed after ${retryOpts.maxAttempts} attempts: ${error instanceof Error ? error.message : String(error)}`
842
1075
  );
843
1076
  return {
@@ -949,7 +1182,6 @@ function createProvider(config) {
949
1182
 
950
1183
  // src/output/github-release.ts
951
1184
  import { Octokit } from "@octokit/rest";
952
- import { info as info3, success as success3 } from "@releasekit/core";
953
1185
  var GitHubClient = class {
954
1186
  octokit;
955
1187
  owner;
@@ -971,7 +1203,7 @@ var GitHubClient = class {
971
1203
  } else {
972
1204
  body = renderMarkdown([context]);
973
1205
  }
974
- info3(`Creating GitHub release for ${tagName}`);
1206
+ info(`Creating GitHub release for ${tagName}`);
975
1207
  try {
976
1208
  const response = await this.octokit.repos.createRelease({
977
1209
  owner: this.owner,
@@ -983,7 +1215,7 @@ var GitHubClient = class {
983
1215
  prerelease: options.prerelease ?? false,
984
1216
  generate_release_notes: options.generateNotes ?? false
985
1217
  });
986
- success3(`Release created: ${response.data.html_url}`);
1218
+ success(`Release created: ${response.data.html_url}`);
987
1219
  return {
988
1220
  id: response.data.id,
989
1221
  htmlUrl: response.data.html_url,
@@ -1001,7 +1233,7 @@ var GitHubClient = class {
1001
1233
  } else {
1002
1234
  body = renderMarkdown([context]);
1003
1235
  }
1004
- info3(`Updating GitHub release ${releaseId}`);
1236
+ info(`Updating GitHub release ${releaseId}`);
1005
1237
  try {
1006
1238
  const response = await this.octokit.repos.updateRelease({
1007
1239
  owner: this.owner,
@@ -1013,7 +1245,7 @@ var GitHubClient = class {
1013
1245
  draft: options.draft ?? false,
1014
1246
  prerelease: options.prerelease ?? false
1015
1247
  });
1016
- success3(`Release updated: ${response.data.html_url}`);
1248
+ success(`Release updated: ${response.data.html_url}`);
1017
1249
  return {
1018
1250
  id: response.data.id,
1019
1251
  htmlUrl: response.data.html_url,
@@ -1063,7 +1295,7 @@ async function createGitHubRelease(context, options) {
1063
1295
  }
1064
1296
 
1065
1297
  // src/templates/ejs.ts
1066
- import * as fs4 from "fs";
1298
+ import * as fs6 from "fs";
1067
1299
  import ejs from "ejs";
1068
1300
  function renderEjs(template, context) {
1069
1301
  try {
@@ -1073,16 +1305,16 @@ function renderEjs(template, context) {
1073
1305
  }
1074
1306
  }
1075
1307
  function renderEjsFile(filePath, context) {
1076
- if (!fs4.existsSync(filePath)) {
1308
+ if (!fs6.existsSync(filePath)) {
1077
1309
  throw new TemplateError(`Template file not found: ${filePath}`);
1078
1310
  }
1079
- const template = fs4.readFileSync(filePath, "utf-8");
1311
+ const template = fs6.readFileSync(filePath, "utf-8");
1080
1312
  return renderEjs(template, context);
1081
1313
  }
1082
1314
 
1083
1315
  // src/templates/handlebars.ts
1084
- import * as fs5 from "fs";
1085
- import * as path3 from "path";
1316
+ import * as fs7 from "fs";
1317
+ import * as path5 from "path";
1086
1318
  import Handlebars from "handlebars";
1087
1319
  function registerHandlebarsHelpers() {
1088
1320
  Handlebars.registerHelper("capitalize", (str) => {
@@ -1108,28 +1340,28 @@ function renderHandlebars(template, context) {
1108
1340
  }
1109
1341
  }
1110
1342
  function renderHandlebarsFile(filePath, context) {
1111
- if (!fs5.existsSync(filePath)) {
1343
+ if (!fs7.existsSync(filePath)) {
1112
1344
  throw new TemplateError(`Template file not found: ${filePath}`);
1113
1345
  }
1114
- const template = fs5.readFileSync(filePath, "utf-8");
1346
+ const template = fs7.readFileSync(filePath, "utf-8");
1115
1347
  return renderHandlebars(template, context);
1116
1348
  }
1117
1349
  function renderHandlebarsComposable(templateDir, context) {
1118
1350
  registerHandlebarsHelpers();
1119
- const versionPath = path3.join(templateDir, "version.hbs");
1120
- const entryPath = path3.join(templateDir, "entry.hbs");
1121
- const documentPath = path3.join(templateDir, "document.hbs");
1122
- if (!fs5.existsSync(documentPath)) {
1351
+ const versionPath = path5.join(templateDir, "version.hbs");
1352
+ const entryPath = path5.join(templateDir, "entry.hbs");
1353
+ const documentPath = path5.join(templateDir, "document.hbs");
1354
+ if (!fs7.existsSync(documentPath)) {
1123
1355
  throw new TemplateError(`Document template not found: ${documentPath}`);
1124
1356
  }
1125
- if (fs5.existsSync(versionPath)) {
1126
- Handlebars.registerPartial("version", fs5.readFileSync(versionPath, "utf-8"));
1357
+ if (fs7.existsSync(versionPath)) {
1358
+ Handlebars.registerPartial("version", fs7.readFileSync(versionPath, "utf-8"));
1127
1359
  }
1128
- if (fs5.existsSync(entryPath)) {
1129
- Handlebars.registerPartial("entry", fs5.readFileSync(entryPath, "utf-8"));
1360
+ if (fs7.existsSync(entryPath)) {
1361
+ Handlebars.registerPartial("entry", fs7.readFileSync(entryPath, "utf-8"));
1130
1362
  }
1131
1363
  try {
1132
- const compiled = Handlebars.compile(fs5.readFileSync(documentPath, "utf-8"));
1364
+ const compiled = Handlebars.compile(fs7.readFileSync(documentPath, "utf-8"));
1133
1365
  return compiled(context);
1134
1366
  } catch (error) {
1135
1367
  throw new TemplateError(`Handlebars render error: ${error instanceof Error ? error.message : String(error)}`);
@@ -1137,8 +1369,8 @@ function renderHandlebarsComposable(templateDir, context) {
1137
1369
  }
1138
1370
 
1139
1371
  // src/templates/liquid.ts
1140
- import * as fs6 from "fs";
1141
- import * as path4 from "path";
1372
+ import * as fs8 from "fs";
1373
+ import * as path6 from "path";
1142
1374
  import { Liquid } from "liquidjs";
1143
1375
  function createLiquidEngine(root) {
1144
1376
  return new Liquid({
@@ -1156,15 +1388,15 @@ function renderLiquid(template, context) {
1156
1388
  }
1157
1389
  }
1158
1390
  function renderLiquidFile(filePath, context) {
1159
- if (!fs6.existsSync(filePath)) {
1391
+ if (!fs8.existsSync(filePath)) {
1160
1392
  throw new TemplateError(`Template file not found: ${filePath}`);
1161
1393
  }
1162
- const template = fs6.readFileSync(filePath, "utf-8");
1394
+ const template = fs8.readFileSync(filePath, "utf-8");
1163
1395
  return renderLiquid(template, context);
1164
1396
  }
1165
1397
  function renderLiquidComposable(templateDir, context) {
1166
- const documentPath = path4.join(templateDir, "document.liquid");
1167
- if (!fs6.existsSync(documentPath)) {
1398
+ const documentPath = path6.join(templateDir, "document.liquid");
1399
+ if (!fs8.existsSync(documentPath)) {
1168
1400
  throw new TemplateError(`Document template not found: ${documentPath}`);
1169
1401
  }
1170
1402
  const engine = createLiquidEngine(templateDir);
@@ -1176,10 +1408,10 @@ function renderLiquidComposable(templateDir, context) {
1176
1408
  }
1177
1409
 
1178
1410
  // src/templates/loader.ts
1179
- import * as fs7 from "fs";
1180
- import * as path5 from "path";
1411
+ import * as fs9 from "fs";
1412
+ import * as path7 from "path";
1181
1413
  function getEngineFromFile(filePath) {
1182
- const ext = path5.extname(filePath).toLowerCase();
1414
+ const ext = path7.extname(filePath).toLowerCase();
1183
1415
  switch (ext) {
1184
1416
  case ".liquid":
1185
1417
  return "liquid";
@@ -1213,10 +1445,10 @@ function getRenderFileFn(engine) {
1213
1445
  }
1214
1446
  }
1215
1447
  function detectTemplateMode(templatePath) {
1216
- if (!fs7.existsSync(templatePath)) {
1448
+ if (!fs9.existsSync(templatePath)) {
1217
1449
  throw new TemplateError(`Template path not found: ${templatePath}`);
1218
1450
  }
1219
- const stat = fs7.statSync(templatePath);
1451
+ const stat = fs9.statSync(templatePath);
1220
1452
  if (stat.isFile()) {
1221
1453
  return "single";
1222
1454
  }
@@ -1234,7 +1466,7 @@ function renderSingleFile(templatePath, context, engine) {
1234
1466
  };
1235
1467
  }
1236
1468
  function renderComposable(templateDir, context, engine) {
1237
- const files = fs7.readdirSync(templateDir);
1469
+ const files = fs9.readdirSync(templateDir);
1238
1470
  const engineMap = {
1239
1471
  liquid: { document: "document.liquid", version: "version.liquid", entry: "entry.liquid" },
1240
1472
  handlebars: { document: "document.hbs", version: "version.hbs", entry: "entry.hbs" },
@@ -1257,15 +1489,15 @@ function renderComposable(templateDir, context, engine) {
1257
1489
  return { content: renderHandlebarsComposable(templateDir, context), engine: resolvedEngine };
1258
1490
  }
1259
1491
  const expectedFiles = engineMap[resolvedEngine];
1260
- const documentPath = path5.join(templateDir, expectedFiles.document);
1261
- if (!fs7.existsSync(documentPath)) {
1492
+ const documentPath = path7.join(templateDir, expectedFiles.document);
1493
+ if (!fs9.existsSync(documentPath)) {
1262
1494
  throw new TemplateError(`Document template not found: ${expectedFiles.document}`);
1263
1495
  }
1264
- const versionPath = path5.join(templateDir, expectedFiles.version);
1265
- const entryPath = path5.join(templateDir, expectedFiles.entry);
1496
+ const versionPath = path7.join(templateDir, expectedFiles.version);
1497
+ const entryPath = path7.join(templateDir, expectedFiles.entry);
1266
1498
  const render = getRenderFn(resolvedEngine);
1267
- const entryTemplate = fs7.existsSync(entryPath) ? fs7.readFileSync(entryPath, "utf-8") : null;
1268
- const versionTemplate = fs7.existsSync(versionPath) ? fs7.readFileSync(versionPath, "utf-8") : null;
1499
+ const entryTemplate = fs9.existsSync(entryPath) ? fs9.readFileSync(entryPath, "utf-8") : null;
1500
+ const versionTemplate = fs9.existsSync(versionPath) ? fs9.readFileSync(versionPath, "utf-8") : null;
1269
1501
  if (entryTemplate && versionTemplate) {
1270
1502
  const versionsWithEntries = context.versions.map((versionCtx) => {
1271
1503
  const entries = versionCtx.entries.map((entry) => {
@@ -1276,7 +1508,7 @@ function renderComposable(templateDir, context, engine) {
1276
1508
  });
1277
1509
  const docContext = { ...context, renderedVersions: versionsWithEntries };
1278
1510
  return {
1279
- content: render(fs7.readFileSync(documentPath, "utf-8"), docContext),
1511
+ content: render(fs9.readFileSync(documentPath, "utf-8"), docContext),
1280
1512
  engine: resolvedEngine
1281
1513
  };
1282
1514
  }
@@ -1383,60 +1615,63 @@ async function processWithLLM(context, config) {
1383
1615
  entries: context.entries
1384
1616
  };
1385
1617
  try {
1386
- info4(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1618
+ info(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1387
1619
  if (config.llm.baseURL) {
1388
- info4(`LLM base URL: ${config.llm.baseURL}`);
1620
+ info(`LLM base URL: ${config.llm.baseURL}`);
1389
1621
  }
1390
1622
  const rawProvider = createProvider(config.llm);
1391
1623
  const retryOpts = config.llm.retry ?? LLM_DEFAULTS.retry;
1624
+ const configOptions = config.llm.options;
1392
1625
  const provider = {
1393
1626
  name: rawProvider.name,
1394
- complete: (prompt, opts) => withRetry(() => rawProvider.complete(prompt, opts), retryOpts)
1627
+ // Merge user-configured options (timeout, maxTokens, temperature) as base defaults,
1628
+ // allowing any per-call overrides to take precedence.
1629
+ complete: (prompt, opts) => withRetry(() => rawProvider.complete(prompt, { ...configOptions, ...opts }), retryOpts)
1395
1630
  };
1396
1631
  const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
1397
- info4(`Running LLM tasks: ${activeTasks.join(", ")}`);
1632
+ info(`Running LLM tasks: ${activeTasks.join(", ")}`);
1398
1633
  if (tasks.enhance && tasks.categorize) {
1399
- info4("Enhancing and categorizing entries with LLM...");
1634
+ info("Enhancing and categorizing entries with LLM...");
1400
1635
  const result = await enhanceAndCategorize(provider, context.entries, llmContext);
1401
1636
  enhanced.entries = result.enhancedEntries;
1402
1637
  enhanced.categories = {};
1403
1638
  for (const cat of result.categories) {
1404
1639
  enhanced.categories[cat.category] = cat.entries;
1405
1640
  }
1406
- info4(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
1641
+ info(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
1407
1642
  } else {
1408
1643
  if (tasks.enhance) {
1409
- info4("Enhancing entries with LLM...");
1644
+ info("Enhancing entries with LLM...");
1410
1645
  enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1411
- info4(`Enhanced ${enhanced.entries.length} entries`);
1646
+ info(`Enhanced ${enhanced.entries.length} entries`);
1412
1647
  }
1413
1648
  if (tasks.categorize) {
1414
- info4("Categorizing entries with LLM...");
1649
+ info("Categorizing entries with LLM...");
1415
1650
  const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1416
1651
  enhanced.categories = {};
1417
1652
  for (const cat of categorized) {
1418
1653
  enhanced.categories[cat.category] = cat.entries;
1419
1654
  }
1420
- info4(`Created ${categorized.length} categories`);
1655
+ info(`Created ${categorized.length} categories`);
1421
1656
  }
1422
1657
  }
1423
1658
  if (tasks.summarize) {
1424
- info4("Summarizing entries with LLM...");
1659
+ info("Summarizing entries with LLM...");
1425
1660
  enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
1426
1661
  if (enhanced.summary) {
1427
- info4("Summary generated successfully");
1428
- debug3(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1662
+ info("Summary generated successfully");
1663
+ debug(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1429
1664
  } else {
1430
- warn3("Summary generation returned empty result");
1665
+ warn("Summary generation returned empty result");
1431
1666
  }
1432
1667
  }
1433
1668
  if (tasks.releaseNotes) {
1434
- info4("Generating release notes with LLM...");
1669
+ info("Generating release notes with LLM...");
1435
1670
  enhanced.releaseNotes = await generateReleaseNotes(provider, enhanced.entries, llmContext);
1436
1671
  if (enhanced.releaseNotes) {
1437
- info4("Release notes generated successfully");
1672
+ info("Release notes generated successfully");
1438
1673
  } else {
1439
- warn3("Release notes generation returned empty result");
1674
+ warn("Release notes generation returned empty result");
1440
1675
  }
1441
1676
  }
1442
1677
  return {
@@ -1444,8 +1679,8 @@ async function processWithLLM(context, config) {
1444
1679
  enhanced
1445
1680
  };
1446
1681
  } catch (error) {
1447
- warn3(`LLM processing failed: ${error instanceof Error ? error.message : String(error)}`);
1448
- warn3("Falling back to raw entries");
1682
+ warn(`LLM processing failed: ${error instanceof Error ? error.message : String(error)}`);
1683
+ warn("Falling back to raw entries");
1449
1684
  return context;
1450
1685
  }
1451
1686
  }
@@ -1453,17 +1688,17 @@ function getBuiltinTemplatePath(style) {
1453
1688
  let packageRoot;
1454
1689
  try {
1455
1690
  const currentUrl = import.meta.url;
1456
- packageRoot = path6.dirname(new URL(currentUrl).pathname);
1457
- packageRoot = path6.join(packageRoot, "..", "..");
1691
+ packageRoot = path8.dirname(new URL(currentUrl).pathname);
1692
+ packageRoot = path8.join(packageRoot, "..", "..");
1458
1693
  } catch {
1459
1694
  packageRoot = __dirname;
1460
1695
  }
1461
- return path6.join(packageRoot, "templates", style);
1696
+ return path8.join(packageRoot, "templates", style);
1462
1697
  }
1463
1698
  async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1464
1699
  let templatePath;
1465
1700
  if (config.templates?.path) {
1466
- templatePath = path6.resolve(config.templates.path);
1701
+ templatePath = path8.resolve(config.templates.path);
1467
1702
  } else {
1468
1703
  templatePath = getBuiltinTemplatePath("keep-a-changelog");
1469
1704
  }
@@ -1473,67 +1708,85 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1473
1708
  );
1474
1709
  const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
1475
1710
  if (dryRun) {
1476
- info4(`Would write templated output to ${outputPath}`);
1477
- debug3("--- Changelog Preview ---");
1478
- debug3(result.content);
1479
- debug3("--- End Preview ---");
1711
+ info(`Would write templated output to ${outputPath}`);
1712
+ debug("--- Changelog Preview ---");
1713
+ debug(result.content);
1714
+ debug("--- End Preview ---");
1480
1715
  return;
1481
1716
  }
1482
1717
  if (outputPath === "-") {
1483
1718
  process.stdout.write(result.content);
1484
1719
  return;
1485
1720
  }
1486
- const dir = path6.dirname(outputPath);
1487
- if (!fs8.existsSync(dir)) {
1488
- fs8.mkdirSync(dir, { recursive: true });
1721
+ const dir = path8.dirname(outputPath);
1722
+ if (!fs10.existsSync(dir)) {
1723
+ fs10.mkdirSync(dir, { recursive: true });
1489
1724
  }
1490
- fs8.writeFileSync(outputPath, result.content, "utf-8");
1491
- success4(`Changelog written to ${outputPath} (using ${result.engine} template)`);
1725
+ fs10.writeFileSync(outputPath, result.content, "utf-8");
1726
+ const label = /changelog/i.test(outputPath) ? "Changelog" : "Release notes";
1727
+ success(`${label} written to ${outputPath} (using ${result.engine} template)`);
1492
1728
  }
1493
1729
  async function runPipeline(input, config, dryRun) {
1494
- debug3(`Processing ${input.packages.length} package(s)`);
1730
+ debug(`Processing ${input.packages.length} package(s)`);
1495
1731
  let contexts = input.packages.map(createTemplateContext);
1496
1732
  if (config.llm && !process.env.CHANGELOG_NO_LLM) {
1497
- info4("Processing with LLM enhancement");
1733
+ info("Processing with LLM enhancement");
1498
1734
  contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, config)));
1499
1735
  }
1736
+ const files = [];
1737
+ const fmtOpts = {
1738
+ includePackageName: contexts.length > 1 || contexts.some((c) => c.packageName.includes("/"))
1739
+ };
1500
1740
  for (const output of config.output) {
1501
- info4(`Generating ${output.format} output`);
1741
+ const file = output.file ?? (output.format === "json" ? "changelog.json" : "CHANGELOG.md");
1742
+ const isChangelog = /changelog/i.test(file);
1743
+ const outputKind = isChangelog ? "changelog" : "release notes";
1744
+ info(`Generating ${outputKind} \u2192 ${file}`);
1502
1745
  switch (output.format) {
1503
1746
  case "markdown": {
1504
- const file = output.file ?? "CHANGELOG.md";
1505
- const effectiveTemplateConfig = output.templates ?? config.templates;
1506
- if (effectiveTemplateConfig?.path || output.options?.template) {
1507
- const configWithTemplate = { ...config, templates: effectiveTemplateConfig };
1508
- await generateWithTemplate(contexts, configWithTemplate, file, dryRun);
1509
- } else {
1510
- writeMarkdown(file, contexts, config, dryRun);
1747
+ const file2 = output.file ?? "CHANGELOG.md";
1748
+ try {
1749
+ const effectiveTemplateConfig = output.templates ?? config.templates;
1750
+ if (effectiveTemplateConfig?.path || output.options?.template) {
1751
+ const configWithTemplate = { ...config, templates: effectiveTemplateConfig };
1752
+ await generateWithTemplate(contexts, configWithTemplate, file2, dryRun);
1753
+ } else {
1754
+ writeMarkdown(file2, contexts, config, dryRun, fmtOpts);
1755
+ }
1756
+ if (!dryRun) files.push(file2);
1757
+ } catch (error) {
1758
+ warn(`Failed to write ${file2}: ${error instanceof Error ? error.message : String(error)}`);
1511
1759
  }
1512
1760
  break;
1513
1761
  }
1514
1762
  case "json": {
1515
- const file = output.file ?? "changelog.json";
1516
- writeJson(file, contexts, dryRun);
1763
+ const file2 = output.file ?? "changelog.json";
1764
+ try {
1765
+ writeJson(file2, contexts, dryRun);
1766
+ if (!dryRun) files.push(file2);
1767
+ } catch (error) {
1768
+ warn(`Failed to write ${file2}: ${error instanceof Error ? error.message : String(error)}`);
1769
+ }
1517
1770
  break;
1518
1771
  }
1519
1772
  case "github-release": {
1520
1773
  if (dryRun) {
1521
- info4("[DRY RUN] Would create GitHub release");
1774
+ info("[DRY RUN] Would create GitHub release");
1522
1775
  break;
1523
1776
  }
1524
1777
  const firstContext = contexts[0];
1525
1778
  if (!firstContext) {
1526
- warn3("No context available for GitHub release");
1779
+ warn("No context available for GitHub release");
1527
1780
  break;
1528
1781
  }
1529
1782
  const repoUrl = firstContext.repoUrl;
1530
1783
  if (!repoUrl) {
1531
- warn3("No repo URL available, cannot create GitHub release");
1784
+ warn("No repo URL available, cannot create GitHub release");
1532
1785
  break;
1533
1786
  }
1534
1787
  const parsed = parseRepoUrl(repoUrl);
1535
1788
  if (!parsed) {
1536
- warn3(`Could not parse repo URL: ${repoUrl}`);
1789
+ warn(`Could not parse repo URL: ${repoUrl}`);
1537
1790
  break;
1538
1791
  }
1539
1792
  await createGitHubRelease(firstContext, {
@@ -1546,165 +1799,53 @@ async function runPipeline(input, config, dryRun) {
1546
1799
  }
1547
1800
  }
1548
1801
  }
1549
- }
1550
- async function processInput(inputJson, config, dryRun) {
1551
- const input = parsePackageVersioner(inputJson);
1552
- await runPipeline(input, config, dryRun);
1553
- }
1554
-
1555
- // src/monorepo/aggregator.ts
1556
- import * as fs9 from "fs";
1557
- import * as path7 from "path";
1558
- import { debug as debug4, info as info5, success as success5 } from "@releasekit/core";
1559
-
1560
- // src/monorepo/splitter.ts
1561
- function splitByPackage(contexts) {
1562
- const byPackage = /* @__PURE__ */ new Map();
1563
- for (const ctx of contexts) {
1564
- byPackage.set(ctx.packageName, ctx);
1565
- }
1566
- return byPackage;
1567
- }
1568
-
1569
- // src/monorepo/aggregator.ts
1570
- function writeFile(outputPath, content, dryRun) {
1571
- if (dryRun) {
1572
- info5(`Would write to ${outputPath}`);
1573
- debug4(content);
1574
- return;
1575
- }
1576
- const dir = path7.dirname(outputPath);
1577
- if (!fs9.existsSync(dir)) {
1578
- fs9.mkdirSync(dir, { recursive: true });
1579
- }
1580
- fs9.writeFileSync(outputPath, content, "utf-8");
1581
- success5(`Changelog written to ${outputPath}`);
1582
- }
1583
- function aggregateToRoot(contexts) {
1584
- const aggregated = {
1585
- packageName: "monorepo",
1586
- version: contexts[0]?.version ?? "0.0.0",
1587
- previousVersion: contexts[0]?.previousVersion ?? null,
1588
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "",
1589
- repoUrl: contexts[0]?.repoUrl ?? null,
1590
- entries: []
1591
- };
1592
- for (const ctx of contexts) {
1593
- for (const entry of ctx.entries) {
1594
- aggregated.entries.push({
1595
- ...entry,
1596
- scope: entry.scope ? `${ctx.packageName}/${entry.scope}` : ctx.packageName
1597
- });
1802
+ if (config.monorepo?.mode) {
1803
+ const { detectMonorepo, writeMonorepoChangelogs } = await import("./aggregator-YA7LC3KJ.js");
1804
+ const cwd = process.cwd();
1805
+ const detected = detectMonorepo(cwd);
1806
+ if (detected.isMonorepo) {
1807
+ const monoFiles = writeMonorepoChangelogs(
1808
+ contexts,
1809
+ {
1810
+ rootPath: config.monorepo.rootPath ?? cwd,
1811
+ packagesPath: config.monorepo.packagesPath ?? detected.packagesPath,
1812
+ mode: config.monorepo.mode
1813
+ },
1814
+ config,
1815
+ dryRun
1816
+ );
1817
+ files.push(...monoFiles);
1598
1818
  }
1599
1819
  }
1600
- return aggregated;
1601
- }
1602
- function writeMonorepoChangelogs(contexts, options, config, dryRun) {
1603
- if (options.mode === "root" || options.mode === "both") {
1604
- const aggregated = aggregateToRoot(contexts);
1605
- const rootPath = path7.join(options.rootPath, "CHANGELOG.md");
1606
- info5(`Writing root changelog to ${rootPath}`);
1607
- const rootContent = config.updateStrategy === "prepend" && fs9.existsSync(rootPath) ? prependVersion(rootPath, aggregated) : renderMarkdown([aggregated]);
1608
- writeFile(rootPath, rootContent, dryRun);
1609
- }
1610
- if (options.mode === "packages" || options.mode === "both") {
1611
- const byPackage = splitByPackage(contexts);
1612
- const packageDirMap = buildPackageDirMap(options.rootPath, options.packagesPath);
1613
- for (const [packageName, ctx] of byPackage) {
1614
- const simpleName = packageName.split("/").pop();
1615
- const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
1616
- if (packageDir) {
1617
- const changelogPath = path7.join(packageDir, "CHANGELOG.md");
1618
- info5(`Writing changelog for ${packageName} to ${changelogPath}`);
1619
- const pkgContent = config.updateStrategy === "prepend" && fs9.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
1620
- writeFile(changelogPath, pkgContent, dryRun);
1621
- } else {
1622
- info5(`Could not find directory for package ${packageName}, skipping`);
1623
- }
1624
- }
1625
- }
1626
- }
1627
- function buildPackageDirMap(rootPath, packagesPath) {
1628
- const map = /* @__PURE__ */ new Map();
1629
- const packagesDir = path7.join(rootPath, packagesPath);
1630
- if (!fs9.existsSync(packagesDir)) {
1631
- return map;
1632
- }
1633
- for (const entry of fs9.readdirSync(packagesDir, { withFileTypes: true })) {
1634
- if (!entry.isDirectory()) continue;
1635
- const dirPath = path7.join(packagesDir, entry.name);
1636
- map.set(entry.name, dirPath);
1637
- const packageJsonPath = path7.join(dirPath, "package.json");
1638
- if (fs9.existsSync(packageJsonPath)) {
1639
- try {
1640
- const pkg = JSON.parse(fs9.readFileSync(packageJsonPath, "utf-8"));
1641
- if (pkg.name) {
1642
- map.set(pkg.name, dirPath);
1643
- }
1644
- } catch {
1645
- }
1646
- }
1820
+ const packageNotes = {};
1821
+ for (const ctx of contexts) {
1822
+ packageNotes[ctx.packageName] = formatVersion(ctx);
1647
1823
  }
1648
- return map;
1824
+ return { packageNotes, files };
1649
1825
  }
1650
- function detectMonorepo(cwd) {
1651
- const pnpmWorkspacesPath = path7.join(cwd, "pnpm-workspace.yaml");
1652
- const packageJsonPath = path7.join(cwd, "package.json");
1653
- if (fs9.existsSync(pnpmWorkspacesPath)) {
1654
- const content = fs9.readFileSync(pnpmWorkspacesPath, "utf-8");
1655
- const packagesMatch = content.match(/packages:\s*\n\s*-\s*['"]([^'"]+)['"]/);
1656
- if (packagesMatch?.[1]) {
1657
- const packagesGlob = packagesMatch[1];
1658
- const packagesPath = packagesGlob.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
1659
- return { isMonorepo: true, packagesPath: packagesPath || "packages" };
1660
- }
1661
- return { isMonorepo: true, packagesPath: "packages" };
1662
- }
1663
- if (fs9.existsSync(packageJsonPath)) {
1664
- try {
1665
- const content = fs9.readFileSync(packageJsonPath, "utf-8");
1666
- const pkg = JSON.parse(content);
1667
- if (pkg.workspaces) {
1668
- const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
1669
- if (workspaces?.length) {
1670
- const firstWorkspace = workspaces[0];
1671
- if (firstWorkspace) {
1672
- const packagesPath = firstWorkspace.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
1673
- return { isMonorepo: true, packagesPath: packagesPath || "packages" };
1674
- }
1675
- }
1676
- }
1677
- } catch {
1678
- return { isMonorepo: false, packagesPath: "" };
1679
- }
1680
- }
1681
- return { isMonorepo: false, packagesPath: "" };
1826
+ async function processInput(inputJson, config, dryRun) {
1827
+ const input = parsePackageVersioner(inputJson);
1828
+ return runPipeline(input, config, dryRun);
1682
1829
  }
1683
1830
 
1684
1831
  export {
1685
1832
  loadAuth,
1686
1833
  saveAuth,
1687
- loadConfig,
1834
+ loadConfig2 as loadConfig,
1688
1835
  getDefaultConfig,
1689
1836
  NotesError,
1690
1837
  InputParseError,
1691
1838
  TemplateError,
1692
1839
  LLMError,
1693
1840
  GitHubError,
1694
- ConfigError,
1841
+ ConfigError2 as ConfigError,
1695
1842
  getExitCode,
1696
- EXIT_CODES2 as EXIT_CODES,
1697
1843
  parsePackageVersioner,
1698
1844
  parsePackageVersionerFile,
1699
1845
  parsePackageVersionerStdin,
1700
- renderMarkdown,
1701
- writeMarkdown,
1702
1846
  renderJson,
1703
1847
  writeJson,
1704
1848
  createTemplateContext,
1705
1849
  runPipeline,
1706
- processInput,
1707
- aggregateToRoot,
1708
- writeMonorepoChangelogs,
1709
- detectMonorepo
1850
+ processInput
1710
1851
  };