@releasekit/notes 0.2.0-next.9 → 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,137 +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 { info, success } from "@releasekit/core";
152
- var TYPE_ORDER = ["added", "changed", "deprecated", "removed", "fixed", "security"];
153
- var TYPE_LABELS = {
154
- added: "Added",
155
- changed: "Changed",
156
- deprecated: "Deprecated",
157
- removed: "Removed",
158
- fixed: "Fixed",
159
- security: "Security"
160
- };
161
- function groupEntriesByType(entries) {
162
- const grouped = /* @__PURE__ */ new Map();
163
- for (const type of TYPE_ORDER) {
164
- grouped.set(type, []);
165
- }
166
- for (const entry of entries) {
167
- const existing = grouped.get(entry.type) ?? [];
168
- existing.push(entry);
169
- grouped.set(entry.type, existing);
170
- }
171
- return grouped;
172
- }
173
- function formatEntry(entry) {
174
- let line;
175
- if (entry.breaking && entry.scope) {
176
- line = `- **BREAKING** **${entry.scope}**: ${entry.description}`;
177
- } else if (entry.breaking) {
178
- line = `- **BREAKING** ${entry.description}`;
179
- } else if (entry.scope) {
180
- line = `- **${entry.scope}**: ${entry.description}`;
181
- } else {
182
- line = `- ${entry.description}`;
183
- }
184
- if (entry.issueIds && entry.issueIds.length > 0) {
185
- line += ` (${entry.issueIds.join(", ")})`;
186
- }
187
- return line;
188
- }
189
- function formatVersion(context) {
190
- const lines = [];
191
- const versionHeader = context.previousVersion ? `## [${context.version}]` : `## ${context.version}`;
192
- lines.push(`${versionHeader} - ${context.date}`);
193
- lines.push("");
194
- if (context.compareUrl) {
195
- lines.push(`[Full Changelog](${context.compareUrl})`);
196
- lines.push("");
197
- }
198
- if (context.enhanced?.summary) {
199
- lines.push(context.enhanced.summary);
200
- lines.push("");
201
- }
202
- const grouped = groupEntriesByType(context.entries);
203
- for (const [type, entries] of grouped) {
204
- if (entries.length === 0) continue;
205
- lines.push(`### ${TYPE_LABELS[type]}`);
206
- for (const entry of entries) {
207
- lines.push(formatEntry(entry));
208
- }
209
- lines.push("");
210
- }
211
- return lines.join("\n");
212
- }
213
- function formatHeader() {
214
- return `# Changelog
215
-
216
- All notable changes to this project will be documented in this file.
217
-
218
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
219
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
220
-
221
- `;
222
- }
223
- function renderMarkdown(contexts) {
224
- const sections = [formatHeader()];
225
- for (const context of contexts) {
226
- sections.push(formatVersion(context));
227
- }
228
- return sections.join("\n");
229
- }
230
- function prependVersion(existingPath, context) {
231
- let existing = "";
232
- if (fs2.existsSync(existingPath)) {
233
- existing = fs2.readFileSync(existingPath, "utf-8");
234
- const headerEnd = existing.indexOf("\n## ");
235
- if (headerEnd >= 0) {
236
- const header = existing.slice(0, headerEnd);
237
- const body = existing.slice(headerEnd + 1);
238
- const newVersion = formatVersion(context);
239
- return `${header}
240
-
241
- ${newVersion}
242
- ${body}`;
243
- }
244
- }
245
- return renderMarkdown([context]);
246
- }
247
- function writeMarkdown(outputPath, contexts, config, dryRun) {
248
- const content = renderMarkdown(contexts);
249
- if (dryRun) {
250
- info("--- Changelog Preview ---");
251
- console.log(content);
252
- info("--- End Preview ---");
253
- return;
254
- }
255
- const dir = path.dirname(outputPath);
256
- if (!fs2.existsSync(dir)) {
257
- fs2.mkdirSync(dir, { recursive: true });
258
- }
259
- if (outputPath === "-") {
260
- process.stdout.write(content);
261
- return;
262
- }
263
- if (config.updateStrategy === "prepend" && fs2.existsSync(outputPath) && contexts.length === 1) {
264
- const firstContext = contexts[0];
265
- if (firstContext) {
266
- const updated = prependVersion(outputPath, firstContext);
267
- fs2.writeFileSync(outputPath, updated, "utf-8");
268
- }
269
- } else {
270
- fs2.writeFileSync(outputPath, content, "utf-8");
271
- }
272
- success(`Changelog written to ${outputPath}`);
273
- }
274
-
275
510
  // src/output/json.ts
276
- import * as fs3 from "fs";
277
- import * as path2 from "path";
278
- import { info as info2, success as success2 } from "@releasekit/core";
511
+ import * as fs5 from "fs";
512
+ import * as path4 from "path";
279
513
  function renderJson(contexts) {
280
514
  return JSON.stringify(
281
515
  {
@@ -295,28 +529,28 @@ function renderJson(contexts) {
295
529
  function writeJson(outputPath, contexts, dryRun) {
296
530
  const content = renderJson(contexts);
297
531
  if (dryRun) {
298
- info2("--- JSON Output Preview ---");
299
- console.log(content);
300
- info2("--- End Preview ---");
532
+ info(`Would write JSON output to ${outputPath}`);
533
+ debug("--- JSON Output Preview ---");
534
+ debug(content);
535
+ debug("--- End Preview ---");
301
536
  return;
302
537
  }
303
- const dir = path2.dirname(outputPath);
304
- if (!fs3.existsSync(dir)) {
305
- fs3.mkdirSync(dir, { recursive: true });
538
+ const dir = path4.dirname(outputPath);
539
+ if (!fs5.existsSync(dir)) {
540
+ fs5.mkdirSync(dir, { recursive: true });
306
541
  }
307
- fs3.writeFileSync(outputPath, content, "utf-8");
308
- success2(`JSON output written to ${outputPath}`);
542
+ fs5.writeFileSync(outputPath, content, "utf-8");
543
+ success(`JSON output written to ${outputPath}`);
309
544
  }
310
545
 
311
546
  // src/core/pipeline.ts
312
- import * as fs8 from "fs";
313
- import * as path6 from "path";
314
- import { debug, info as info4, success as success4, warn as warn3 } from "@releasekit/core";
547
+ import * as fs10 from "fs";
548
+ import * as path8 from "path";
315
549
 
316
550
  // src/llm/defaults.ts
317
551
  var LLM_DEFAULTS = {
318
552
  timeout: 6e4,
319
- maxTokens: 2e3,
553
+ maxTokens: 4e3,
320
554
  temperature: 0.7,
321
555
  concurrency: 5,
322
556
  retry: {
@@ -409,7 +643,7 @@ var OllamaProvider = class extends BaseLLMProvider {
409
643
  "Content-Type": "application/json"
410
644
  };
411
645
  if (this.apiKey) {
412
- headers["Authorization"] = `Bearer ${this.apiKey}`;
646
+ headers.Authorization = `Bearer ${this.apiKey}`;
413
647
  }
414
648
  const baseUrl = this.baseURL.endsWith("/api") ? this.baseURL.slice(0, -4) : this.baseURL;
415
649
  const response = await fetch(`${baseUrl}/api/chat`, {
@@ -506,8 +740,91 @@ var OpenAICompatibleProvider = class extends BaseLLMProvider {
506
740
  }
507
741
  };
508
742
 
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
+ }
752
+
753
+ // src/llm/prompts.ts
754
+ function resolvePrompt(taskName, defaultPrompt, promptsConfig) {
755
+ if (!promptsConfig) return defaultPrompt;
756
+ const fullTemplate = promptsConfig.templates?.[taskName];
757
+ if (fullTemplate) return fullTemplate;
758
+ const additionalInstructions = promptsConfig.instructions?.[taskName];
759
+ if (additionalInstructions) {
760
+ const insertionPoint = defaultPrompt.lastIndexOf("Output only valid JSON");
761
+ if (insertionPoint !== -1) {
762
+ return `${defaultPrompt.slice(0, insertionPoint)}Additional instructions:
763
+ ${additionalInstructions}
764
+
765
+ ${defaultPrompt.slice(insertionPoint)}`;
766
+ }
767
+ return `${defaultPrompt}
768
+
769
+ Additional instructions:
770
+ ${additionalInstructions}`;
771
+ }
772
+ return defaultPrompt;
773
+ }
774
+
775
+ // src/llm/scopes.ts
776
+ function getAllowedScopesFromCategories(categories) {
777
+ const scopeMap = /* @__PURE__ */ new Map();
778
+ for (const cat of categories) {
779
+ if (cat.scopes && cat.scopes.length > 0) {
780
+ scopeMap.set(cat.name, cat.scopes);
781
+ }
782
+ }
783
+ return scopeMap;
784
+ }
785
+ function resolveAllowedScopes(scopeConfig, categories, packageNames) {
786
+ if (!scopeConfig || scopeConfig.mode === "unrestricted") return null;
787
+ if (scopeConfig.mode === "none") return [];
788
+ if (scopeConfig.mode === "packages") return packageNames ?? [];
789
+ if (scopeConfig.mode === "restricted") {
790
+ const explicit = scopeConfig.rules?.allowed ?? [];
791
+ const all = new Set(explicit);
792
+ if (categories) {
793
+ const fromCategories = getAllowedScopesFromCategories(categories);
794
+ for (const scopes of fromCategories.values()) {
795
+ for (const s of scopes) all.add(s);
796
+ }
797
+ }
798
+ return [...all];
799
+ }
800
+ return null;
801
+ }
802
+ function validateScope(scope, allowedScopes, rules) {
803
+ if (!scope || allowedScopes === null) return scope;
804
+ if (allowedScopes.length === 0) return void 0;
805
+ const caseSensitive = rules?.caseSensitive ?? false;
806
+ const normalise = (s) => caseSensitive ? s : s.toLowerCase();
807
+ const isAllowed = allowedScopes.some((a) => normalise(a) === normalise(scope));
808
+ if (isAllowed) return scope;
809
+ switch (rules?.invalidScopeAction ?? "remove") {
810
+ case "keep":
811
+ return scope;
812
+ case "fallback":
813
+ return rules?.fallbackScope;
814
+ default:
815
+ return void 0;
816
+ }
817
+ }
818
+ function validateEntryScopes(entries, scopeConfig, categories) {
819
+ const allowedScopes = resolveAllowedScopes(scopeConfig, categories);
820
+ if (allowedScopes === null) return entries;
821
+ return entries.map((entry) => ({
822
+ ...entry,
823
+ scope: validateScope(entry.scope, allowedScopes, scopeConfig?.rules)
824
+ }));
825
+ }
826
+
509
827
  // src/llm/tasks/categorize.ts
510
- import { warn } from "@releasekit/core";
511
828
  var DEFAULT_CATEGORIZE_PROMPT = `You are categorizing changelog entries for a software release.
512
829
 
513
830
  Given the following entries, group them into meaningful categories (e.g., "Core", "UI", "API", "Performance", "Bug Fixes", "Documentation").
@@ -519,20 +836,21 @@ Entries:
519
836
 
520
837
  Output only valid JSON, nothing else:`;
521
838
  function buildCustomCategorizePrompt(categories) {
522
- const categoryList = categories.map((c) => `- "${c.name}": ${c.description}`).join("\n");
523
- const developerCategory = categories.find((c) => c.name === "Developer");
839
+ const categoryList = categories.map((c) => {
840
+ const scopeInfo = c.scopes?.length ? ` Allowed scopes: ${c.scopes.join(", ")}.` : "";
841
+ return `- "${c.name}": ${c.description}${scopeInfo}`;
842
+ }).join("\n");
843
+ const scopeMap = getAllowedScopesFromCategories(categories);
524
844
  let scopeInstructions = "";
525
- if (developerCategory) {
526
- const scopeMatch = developerCategory.description.match(/from:\s*([^.]+)/);
527
- if (scopeMatch?.[1]) {
528
- const scopes = scopeMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
529
- if (scopes.length > 0) {
530
- scopeInstructions = `
531
-
532
- For the "Developer" category, you MUST assign a scope from this exact list: ${scopes.join(", ")}.
533
- `;
534
- }
845
+ if (scopeMap.size > 0) {
846
+ const entries = [];
847
+ for (const [catName, scopes] of scopeMap) {
848
+ entries.push(`For "${catName}", assign a scope from: ${scopes.join(", ")}.`);
535
849
  }
850
+ scopeInstructions = `
851
+
852
+ ${entries.join("\n")}
853
+ Only use scopes from these predefined lists. If an entry does not fit any scope, set scope to null.`;
536
854
  }
537
855
  return `You are categorizing changelog entries for a software release.
538
856
 
@@ -540,9 +858,10 @@ Given the following entries, group them into the specified categories. Only use
540
858
 
541
859
  Categories:
542
860
  ${categoryList}${scopeInstructions}
861
+
543
862
  Output a JSON object with two fields:
544
863
  - "categories": an object where keys are category names and values are arrays of entry indices (0-based)
545
- - "scopes": an object where keys are entry indices (as strings) and values are scope labels
864
+ - "scopes": an object where keys are entry indices (as strings) and values are scope labels. Only include entries that have a valid scope from the predefined list.
546
865
 
547
866
  Entries:
548
867
  {{entries}}
@@ -553,27 +872,29 @@ async function categorizeEntries(provider, entries, context) {
553
872
  if (entries.length === 0) {
554
873
  return [];
555
874
  }
556
- const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
875
+ const entriesCopy = entries.map((e) => ({ ...e, scope: void 0 }));
876
+ const entriesText = entriesCopy.map((e, i) => `${i}. [${e.type}]: ${e.description}`).join("\n");
557
877
  const hasCustomCategories = context.categories && context.categories.length > 0;
558
- const promptTemplate = hasCustomCategories ? buildCustomCategorizePrompt(context.categories) : DEFAULT_CATEGORIZE_PROMPT;
878
+ const defaultPrompt = hasCustomCategories ? buildCustomCategorizePrompt(context.categories) : DEFAULT_CATEGORIZE_PROMPT;
879
+ const promptTemplate = resolvePrompt("categorize", defaultPrompt, context.prompts);
559
880
  const prompt = promptTemplate.replace("{{entries}}", entriesText);
560
881
  try {
561
882
  const response = await provider.complete(prompt);
562
- const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
563
- const parsed = JSON.parse(cleaned);
883
+ const parsed = JSON.parse(extractJsonFromResponse(response));
564
884
  const result = [];
565
885
  if (hasCustomCategories && parsed.categories) {
566
886
  const categoryMap = parsed.categories;
567
887
  const scopeMap = parsed.scopes || {};
568
888
  for (const [indexStr, scope] of Object.entries(scopeMap)) {
569
889
  const idx = Number.parseInt(indexStr, 10);
570
- if (entries[idx] && scope) {
571
- entries[idx] = { ...entries[idx], scope };
890
+ if (entriesCopy[idx] && scope && scope.trim()) {
891
+ entriesCopy[idx] = { ...entriesCopy[idx], scope: scope.trim() };
572
892
  }
573
893
  }
894
+ const validatedEntries = validateEntryScopes(entriesCopy, context.scopes, context.categories);
574
895
  for (const [category, rawIndices] of Object.entries(categoryMap)) {
575
896
  const indices = Array.isArray(rawIndices) ? rawIndices : [];
576
- const categoryEntries = indices.map((i) => entries[i]).filter((e) => e !== void 0);
897
+ const categoryEntries = indices.map((i) => validatedEntries[i]).filter((e) => e !== void 0);
577
898
  if (categoryEntries.length > 0) {
578
899
  result.push({ category, entries: categoryEntries });
579
900
  }
@@ -582,7 +903,7 @@ async function categorizeEntries(provider, entries, context) {
582
903
  const categoryMap = parsed;
583
904
  for (const [category, rawIndices] of Object.entries(categoryMap)) {
584
905
  const indices = Array.isArray(rawIndices) ? rawIndices : [];
585
- const categoryEntries = indices.map((i) => entries[i]).filter((e) => e !== void 0);
906
+ const categoryEntries = indices.map((i) => entriesCopy[i]).filter((e) => e !== void 0);
586
907
  if (categoryEntries.length > 0) {
587
908
  result.push({ category, entries: categoryEntries });
588
909
  }
@@ -593,12 +914,12 @@ async function categorizeEntries(provider, entries, context) {
593
914
  warn(
594
915
  `LLM categorization failed, falling back to General: ${error instanceof Error ? error.message : String(error)}`
595
916
  );
596
- return [{ category: "General", entries }];
917
+ return [{ category: "General", entries: entriesCopy }];
597
918
  }
598
919
  }
599
920
 
600
921
  // src/llm/tasks/enhance.ts
601
- var ENHANCE_PROMPT = `You are improving changelog entries for a software project.
922
+ var DEFAULT_ENHANCE_PROMPT = `You are improving changelog entries for a software project.
602
923
  Given a technical commit message, rewrite it as a clear, user-friendly changelog entry.
603
924
 
604
925
  Rules:
@@ -614,9 +935,10 @@ Type: {{type}}
614
935
  Description: {{description}}
615
936
 
616
937
  Rewritten description (only output the new description, nothing else):`;
617
- async function enhanceEntry(provider, entry, _context) {
618
- const styleText = _context.style ? `- ${_context.style}` : '- Use present tense ("Add feature" not "Added feature")';
619
- const prompt = ENHANCE_PROMPT.replace("{{style}}", styleText).replace("{{type}}", entry.type).replace("{{#if scope}}Scope: {{scope}}{{/if}}", entry.scope ? `Scope: ${entry.scope}` : "").replace("{{description}}", entry.description);
938
+ async function enhanceEntry(provider, entry, context) {
939
+ const styleText = context.style ? `- ${context.style}` : '- Use present tense ("Add feature" not "Added feature")';
940
+ const defaultPrompt = DEFAULT_ENHANCE_PROMPT.replace("{{style}}", styleText).replace("{{type}}", entry.type).replace("{{#if scope}}Scope: {{scope}}{{/if}}", entry.scope ? `Scope: ${entry.scope}` : "").replace("{{description}}", entry.description);
941
+ const prompt = resolvePrompt("enhance", defaultPrompt, context.prompts);
620
942
  const response = await provider.complete(prompt);
621
943
  return response.trim();
622
944
  }
@@ -639,9 +961,6 @@ async function enhanceEntries(provider, entries, context, concurrency = LLM_DEFA
639
961
  return results;
640
962
  }
641
963
 
642
- // src/llm/tasks/enhance-and-categorize.ts
643
- import { warn as warn2 } from "@releasekit/core";
644
-
645
964
  // src/utils/retry.ts
646
965
  function sleep(ms) {
647
966
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -672,7 +991,23 @@ function buildPrompt(entries, categories, style) {
672
991
  const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
673
992
  const styleText = style || 'Use present tense ("Add feature" not "Added feature"). Be concise.';
674
993
  const categorySection = categories ? `Categories (use ONLY these):
675
- ${categories.map((c) => `- "${c.name}": ${c.description}`).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
994
+ ${categories.map((c) => {
995
+ const scopeInfo = c.scopes?.length ? ` Allowed scopes: ${c.scopes.join(", ")}.` : "";
996
+ return `- "${c.name}": ${c.description}${scopeInfo}`;
997
+ }).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
998
+ let scopeInstruction = "";
999
+ if (categories) {
1000
+ const scopeMap = getAllowedScopesFromCategories(categories);
1001
+ if (scopeMap.size > 0) {
1002
+ const parts = [];
1003
+ for (const [catName, scopes] of scopeMap) {
1004
+ parts.push(`For "${catName}" entries, assign a scope from: ${scopes.join(", ")}.`);
1005
+ }
1006
+ scopeInstruction = `
1007
+ ${parts.join("\n")}
1008
+ Only use scopes from these predefined lists. Set scope to null if no scope applies.`;
1009
+ }
1010
+ }
676
1011
  return `You are generating release notes for a software project. Given the following changelog entries, do two things:
677
1012
 
678
1013
  1. **Rewrite** each entry as a clear, user-friendly description
@@ -683,9 +1018,7 @@ Style guidelines:
683
1018
  - Be concise (1 short sentence per entry)
684
1019
  - Focus on what changed, not implementation details
685
1020
 
686
- ${categorySection}
687
-
688
- ${categories ? 'For entries in categories involving internal/developer changes, set a "scope" field with a short subcategory label (e.g., "CI", "Dependencies", "Testing").' : ""}
1021
+ ${categorySection}${scopeInstruction}
689
1022
 
690
1023
  Entries:
691
1024
  ${entriesText}
@@ -702,10 +1035,10 @@ async function enhanceAndCategorize(provider, entries, context) {
702
1035
  const retryOpts = LLM_DEFAULTS.retry;
703
1036
  try {
704
1037
  return await withRetry(async () => {
705
- const prompt = buildPrompt(entries, context.categories, context.style);
1038
+ const defaultPrompt = buildPrompt(entries, context.categories, context.style);
1039
+ const prompt = resolvePrompt("enhanceAndCategorize", defaultPrompt, context.prompts);
706
1040
  const response = await provider.complete(prompt);
707
- const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
708
- const parsed = JSON.parse(cleaned);
1041
+ const parsed = JSON.parse(extractJsonFromResponse(response));
709
1042
  if (!Array.isArray(parsed.entries)) {
710
1043
  throw new Error('Response missing "entries" array');
711
1044
  }
@@ -718,25 +1051,26 @@ async function enhanceAndCategorize(provider, entries, context) {
718
1051
  scope: result.scope || original.scope
719
1052
  };
720
1053
  });
1054
+ const validatedEntries = validateEntryScopes(enhancedEntries, context.scopes, context.categories);
721
1055
  const categoryMap = /* @__PURE__ */ new Map();
722
1056
  for (let i = 0; i < parsed.entries.length; i++) {
723
1057
  const result = parsed.entries[i];
724
1058
  const category = result?.category || "General";
725
- const entry = enhancedEntries[i];
1059
+ const entry = validatedEntries[i];
726
1060
  if (!entry) continue;
727
1061
  if (!categoryMap.has(category)) {
728
1062
  categoryMap.set(category, []);
729
1063
  }
730
- categoryMap.get(category).push(entry);
1064
+ categoryMap.get(category)?.push(entry);
731
1065
  }
732
1066
  const categories = [];
733
1067
  for (const [category, catEntries] of categoryMap) {
734
1068
  categories.push({ category, entries: catEntries });
735
1069
  }
736
- return { enhancedEntries, categories };
1070
+ return { enhancedEntries: validatedEntries, categories };
737
1071
  }, retryOpts);
738
1072
  } catch (error) {
739
- warn2(
1073
+ warn(
740
1074
  `Combined enhance+categorize failed after ${retryOpts.maxAttempts} attempts: ${error instanceof Error ? error.message : String(error)}`
741
1075
  );
742
1076
  return {
@@ -747,7 +1081,7 @@ async function enhanceAndCategorize(provider, entries, context) {
747
1081
  }
748
1082
 
749
1083
  // src/llm/tasks/release-notes.ts
750
- var RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
1084
+ var DEFAULT_RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
751
1085
 
752
1086
  Create engaging, user-friendly release notes for the following changes.
753
1087
 
@@ -780,16 +1114,17 @@ No notable changes in this release.`;
780
1114
  if (e.breaking) line += " **BREAKING**";
781
1115
  return line;
782
1116
  }).join("\n");
783
- const prompt = RELEASE_NOTES_PROMPT.replace("{{version}}", context.version ?? "v1.0.0").replace(
1117
+ const defaultPrompt = DEFAULT_RELEASE_NOTES_PROMPT.replace("{{version}}", context.version ?? "v1.0.0").replace(
784
1118
  "{{#if previousVersion}}Previous version: {{previousVersion}}{{/if}}",
785
1119
  context.previousVersion ? `Previous version: ${context.previousVersion}` : ""
786
1120
  ).replace("{{date}}", context.date ?? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "").replace("{{entries}}", entriesText);
1121
+ const prompt = resolvePrompt("releaseNotes", defaultPrompt, context.prompts);
787
1122
  const response = await provider.complete(prompt);
788
1123
  return response.trim();
789
1124
  }
790
1125
 
791
1126
  // src/llm/tasks/summarize.ts
792
- var SUMMARIZE_PROMPT = `You are creating a summary of changes for a software release.
1127
+ var DEFAULT_SUMMARIZE_PROMPT = `You are creating a summary of changes for a software release.
793
1128
 
794
1129
  Given the following changelog entries, create a brief summary (2-3 sentences) that captures the main themes of this release.
795
1130
 
@@ -797,12 +1132,13 @@ Entries:
797
1132
  {{entries}}
798
1133
 
799
1134
  Summary (only output the summary, nothing else):`;
800
- async function summarizeEntries(provider, entries, _context) {
1135
+ async function summarizeEntries(provider, entries, context) {
801
1136
  if (entries.length === 0) {
802
1137
  return "";
803
1138
  }
804
1139
  const entriesText = entries.map((e) => `- [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
805
- const prompt = SUMMARIZE_PROMPT.replace("{{entries}}", entriesText);
1140
+ const defaultPrompt = DEFAULT_SUMMARIZE_PROMPT.replace("{{entries}}", entriesText);
1141
+ const prompt = resolvePrompt("summarize", defaultPrompt, context.prompts);
806
1142
  const response = await provider.complete(prompt);
807
1143
  return response.trim();
808
1144
  }
@@ -846,7 +1182,6 @@ function createProvider(config) {
846
1182
 
847
1183
  // src/output/github-release.ts
848
1184
  import { Octokit } from "@octokit/rest";
849
- import { info as info3, success as success3 } from "@releasekit/core";
850
1185
  var GitHubClient = class {
851
1186
  octokit;
852
1187
  owner;
@@ -868,7 +1203,7 @@ var GitHubClient = class {
868
1203
  } else {
869
1204
  body = renderMarkdown([context]);
870
1205
  }
871
- info3(`Creating GitHub release for ${tagName}`);
1206
+ info(`Creating GitHub release for ${tagName}`);
872
1207
  try {
873
1208
  const response = await this.octokit.repos.createRelease({
874
1209
  owner: this.owner,
@@ -880,7 +1215,7 @@ var GitHubClient = class {
880
1215
  prerelease: options.prerelease ?? false,
881
1216
  generate_release_notes: options.generateNotes ?? false
882
1217
  });
883
- success3(`Release created: ${response.data.html_url}`);
1218
+ success(`Release created: ${response.data.html_url}`);
884
1219
  return {
885
1220
  id: response.data.id,
886
1221
  htmlUrl: response.data.html_url,
@@ -898,7 +1233,7 @@ var GitHubClient = class {
898
1233
  } else {
899
1234
  body = renderMarkdown([context]);
900
1235
  }
901
- info3(`Updating GitHub release ${releaseId}`);
1236
+ info(`Updating GitHub release ${releaseId}`);
902
1237
  try {
903
1238
  const response = await this.octokit.repos.updateRelease({
904
1239
  owner: this.owner,
@@ -910,7 +1245,7 @@ var GitHubClient = class {
910
1245
  draft: options.draft ?? false,
911
1246
  prerelease: options.prerelease ?? false
912
1247
  });
913
- success3(`Release updated: ${response.data.html_url}`);
1248
+ success(`Release updated: ${response.data.html_url}`);
914
1249
  return {
915
1250
  id: response.data.id,
916
1251
  htmlUrl: response.data.html_url,
@@ -960,7 +1295,7 @@ async function createGitHubRelease(context, options) {
960
1295
  }
961
1296
 
962
1297
  // src/templates/ejs.ts
963
- import * as fs4 from "fs";
1298
+ import * as fs6 from "fs";
964
1299
  import ejs from "ejs";
965
1300
  function renderEjs(template, context) {
966
1301
  try {
@@ -970,16 +1305,16 @@ function renderEjs(template, context) {
970
1305
  }
971
1306
  }
972
1307
  function renderEjsFile(filePath, context) {
973
- if (!fs4.existsSync(filePath)) {
1308
+ if (!fs6.existsSync(filePath)) {
974
1309
  throw new TemplateError(`Template file not found: ${filePath}`);
975
1310
  }
976
- const template = fs4.readFileSync(filePath, "utf-8");
1311
+ const template = fs6.readFileSync(filePath, "utf-8");
977
1312
  return renderEjs(template, context);
978
1313
  }
979
1314
 
980
1315
  // src/templates/handlebars.ts
981
- import * as fs5 from "fs";
982
- import * as path3 from "path";
1316
+ import * as fs7 from "fs";
1317
+ import * as path5 from "path";
983
1318
  import Handlebars from "handlebars";
984
1319
  function registerHandlebarsHelpers() {
985
1320
  Handlebars.registerHelper("capitalize", (str) => {
@@ -1005,28 +1340,28 @@ function renderHandlebars(template, context) {
1005
1340
  }
1006
1341
  }
1007
1342
  function renderHandlebarsFile(filePath, context) {
1008
- if (!fs5.existsSync(filePath)) {
1343
+ if (!fs7.existsSync(filePath)) {
1009
1344
  throw new TemplateError(`Template file not found: ${filePath}`);
1010
1345
  }
1011
- const template = fs5.readFileSync(filePath, "utf-8");
1346
+ const template = fs7.readFileSync(filePath, "utf-8");
1012
1347
  return renderHandlebars(template, context);
1013
1348
  }
1014
1349
  function renderHandlebarsComposable(templateDir, context) {
1015
1350
  registerHandlebarsHelpers();
1016
- const versionPath = path3.join(templateDir, "version.hbs");
1017
- const entryPath = path3.join(templateDir, "entry.hbs");
1018
- const documentPath = path3.join(templateDir, "document.hbs");
1019
- 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)) {
1020
1355
  throw new TemplateError(`Document template not found: ${documentPath}`);
1021
1356
  }
1022
- if (fs5.existsSync(versionPath)) {
1023
- Handlebars.registerPartial("version", fs5.readFileSync(versionPath, "utf-8"));
1357
+ if (fs7.existsSync(versionPath)) {
1358
+ Handlebars.registerPartial("version", fs7.readFileSync(versionPath, "utf-8"));
1024
1359
  }
1025
- if (fs5.existsSync(entryPath)) {
1026
- Handlebars.registerPartial("entry", fs5.readFileSync(entryPath, "utf-8"));
1360
+ if (fs7.existsSync(entryPath)) {
1361
+ Handlebars.registerPartial("entry", fs7.readFileSync(entryPath, "utf-8"));
1027
1362
  }
1028
1363
  try {
1029
- const compiled = Handlebars.compile(fs5.readFileSync(documentPath, "utf-8"));
1364
+ const compiled = Handlebars.compile(fs7.readFileSync(documentPath, "utf-8"));
1030
1365
  return compiled(context);
1031
1366
  } catch (error) {
1032
1367
  throw new TemplateError(`Handlebars render error: ${error instanceof Error ? error.message : String(error)}`);
@@ -1034,8 +1369,8 @@ function renderHandlebarsComposable(templateDir, context) {
1034
1369
  }
1035
1370
 
1036
1371
  // src/templates/liquid.ts
1037
- import * as fs6 from "fs";
1038
- import * as path4 from "path";
1372
+ import * as fs8 from "fs";
1373
+ import * as path6 from "path";
1039
1374
  import { Liquid } from "liquidjs";
1040
1375
  function createLiquidEngine(root) {
1041
1376
  return new Liquid({
@@ -1053,15 +1388,15 @@ function renderLiquid(template, context) {
1053
1388
  }
1054
1389
  }
1055
1390
  function renderLiquidFile(filePath, context) {
1056
- if (!fs6.existsSync(filePath)) {
1391
+ if (!fs8.existsSync(filePath)) {
1057
1392
  throw new TemplateError(`Template file not found: ${filePath}`);
1058
1393
  }
1059
- const template = fs6.readFileSync(filePath, "utf-8");
1394
+ const template = fs8.readFileSync(filePath, "utf-8");
1060
1395
  return renderLiquid(template, context);
1061
1396
  }
1062
1397
  function renderLiquidComposable(templateDir, context) {
1063
- const documentPath = path4.join(templateDir, "document.liquid");
1064
- if (!fs6.existsSync(documentPath)) {
1398
+ const documentPath = path6.join(templateDir, "document.liquid");
1399
+ if (!fs8.existsSync(documentPath)) {
1065
1400
  throw new TemplateError(`Document template not found: ${documentPath}`);
1066
1401
  }
1067
1402
  const engine = createLiquidEngine(templateDir);
@@ -1073,10 +1408,10 @@ function renderLiquidComposable(templateDir, context) {
1073
1408
  }
1074
1409
 
1075
1410
  // src/templates/loader.ts
1076
- import * as fs7 from "fs";
1077
- import * as path5 from "path";
1411
+ import * as fs9 from "fs";
1412
+ import * as path7 from "path";
1078
1413
  function getEngineFromFile(filePath) {
1079
- const ext = path5.extname(filePath).toLowerCase();
1414
+ const ext = path7.extname(filePath).toLowerCase();
1080
1415
  switch (ext) {
1081
1416
  case ".liquid":
1082
1417
  return "liquid";
@@ -1110,10 +1445,10 @@ function getRenderFileFn(engine) {
1110
1445
  }
1111
1446
  }
1112
1447
  function detectTemplateMode(templatePath) {
1113
- if (!fs7.existsSync(templatePath)) {
1448
+ if (!fs9.existsSync(templatePath)) {
1114
1449
  throw new TemplateError(`Template path not found: ${templatePath}`);
1115
1450
  }
1116
- const stat = fs7.statSync(templatePath);
1451
+ const stat = fs9.statSync(templatePath);
1117
1452
  if (stat.isFile()) {
1118
1453
  return "single";
1119
1454
  }
@@ -1131,7 +1466,7 @@ function renderSingleFile(templatePath, context, engine) {
1131
1466
  };
1132
1467
  }
1133
1468
  function renderComposable(templateDir, context, engine) {
1134
- const files = fs7.readdirSync(templateDir);
1469
+ const files = fs9.readdirSync(templateDir);
1135
1470
  const engineMap = {
1136
1471
  liquid: { document: "document.liquid", version: "version.liquid", entry: "entry.liquid" },
1137
1472
  handlebars: { document: "document.hbs", version: "version.hbs", entry: "entry.hbs" },
@@ -1154,15 +1489,15 @@ function renderComposable(templateDir, context, engine) {
1154
1489
  return { content: renderHandlebarsComposable(templateDir, context), engine: resolvedEngine };
1155
1490
  }
1156
1491
  const expectedFiles = engineMap[resolvedEngine];
1157
- const documentPath = path5.join(templateDir, expectedFiles.document);
1158
- if (!fs7.existsSync(documentPath)) {
1492
+ const documentPath = path7.join(templateDir, expectedFiles.document);
1493
+ if (!fs9.existsSync(documentPath)) {
1159
1494
  throw new TemplateError(`Document template not found: ${expectedFiles.document}`);
1160
1495
  }
1161
- const versionPath = path5.join(templateDir, expectedFiles.version);
1162
- const entryPath = path5.join(templateDir, expectedFiles.entry);
1496
+ const versionPath = path7.join(templateDir, expectedFiles.version);
1497
+ const entryPath = path7.join(templateDir, expectedFiles.entry);
1163
1498
  const render = getRenderFn(resolvedEngine);
1164
- const entryTemplate = fs7.existsSync(entryPath) ? fs7.readFileSync(entryPath, "utf-8") : null;
1165
- 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;
1166
1501
  if (entryTemplate && versionTemplate) {
1167
1502
  const versionsWithEntries = context.versions.map((versionCtx) => {
1168
1503
  const entries = versionCtx.entries.map((entry) => {
@@ -1173,7 +1508,7 @@ function renderComposable(templateDir, context, engine) {
1173
1508
  });
1174
1509
  const docContext = { ...context, renderedVersions: versionsWithEntries };
1175
1510
  return {
1176
- content: render(fs7.readFileSync(documentPath, "utf-8"), docContext),
1511
+ content: render(fs9.readFileSync(documentPath, "utf-8"), docContext),
1177
1512
  engine: resolvedEngine
1178
1513
  };
1179
1514
  }
@@ -1214,17 +1549,27 @@ function renderTemplate(templatePath, context, engine) {
1214
1549
  }
1215
1550
 
1216
1551
  // src/core/pipeline.ts
1217
- function generateCompareUrl(repoUrl, from, to) {
1552
+ function generateCompareUrl(repoUrl, from, to, packageName) {
1553
+ const isPackageSpecific = from.includes("@") && packageName && from.includes(packageName);
1554
+ let fromVersion;
1555
+ let toVersion;
1556
+ if (isPackageSpecific) {
1557
+ fromVersion = from;
1558
+ toVersion = `${packageName}@${to.startsWith("v") ? "" : "v"}${to}`;
1559
+ } else {
1560
+ fromVersion = from.replace(/^v/, "");
1561
+ toVersion = to.replace(/^v/, "");
1562
+ }
1218
1563
  if (/gitlab\.com/i.test(repoUrl)) {
1219
- return `${repoUrl}/-/compare/${from}...${to}`;
1564
+ return `${repoUrl}/-/compare/${fromVersion}...${toVersion}`;
1220
1565
  }
1221
1566
  if (/bitbucket\.org/i.test(repoUrl)) {
1222
- return `${repoUrl}/branches/compare/${from}..${to}`;
1567
+ return `${repoUrl}/branches/compare/${fromVersion}..${toVersion}`;
1223
1568
  }
1224
- return `${repoUrl}/compare/${from}...${to}`;
1569
+ return `${repoUrl}/compare/${fromVersion}...${toVersion}`;
1225
1570
  }
1226
1571
  function createTemplateContext(pkg) {
1227
- const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version) : void 0;
1572
+ const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version, pkg.packageName) : void 0;
1228
1573
  return {
1229
1574
  packageName: pkg.packageName,
1230
1575
  version: pkg.version,
@@ -1262,76 +1607,80 @@ async function processWithLLM(context, config) {
1262
1607
  previousVersion: context.previousVersion ?? void 0,
1263
1608
  date: context.date,
1264
1609
  categories: config.llm.categories,
1265
- style: config.llm.style
1610
+ style: config.llm.style,
1611
+ scopes: config.llm.scopes,
1612
+ prompts: config.llm.prompts
1266
1613
  };
1267
1614
  const enhanced = {
1268
1615
  entries: context.entries
1269
1616
  };
1270
1617
  try {
1271
- 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})` : ""}`);
1272
1619
  if (config.llm.baseURL) {
1273
- info4(`LLM base URL: ${config.llm.baseURL}`);
1620
+ info(`LLM base URL: ${config.llm.baseURL}`);
1274
1621
  }
1275
1622
  const rawProvider = createProvider(config.llm);
1276
1623
  const retryOpts = config.llm.retry ?? LLM_DEFAULTS.retry;
1624
+ const configOptions = config.llm.options;
1277
1625
  const provider = {
1278
1626
  name: rawProvider.name,
1279
- 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)
1280
1630
  };
1281
1631
  const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
1282
- info4(`Running LLM tasks: ${activeTasks.join(", ")}`);
1632
+ info(`Running LLM tasks: ${activeTasks.join(", ")}`);
1283
1633
  if (tasks.enhance && tasks.categorize) {
1284
- info4("Enhancing and categorizing entries with LLM...");
1634
+ info("Enhancing and categorizing entries with LLM...");
1285
1635
  const result = await enhanceAndCategorize(provider, context.entries, llmContext);
1286
1636
  enhanced.entries = result.enhancedEntries;
1287
1637
  enhanced.categories = {};
1288
1638
  for (const cat of result.categories) {
1289
1639
  enhanced.categories[cat.category] = cat.entries;
1290
1640
  }
1291
- info4(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
1641
+ info(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
1292
1642
  } else {
1293
1643
  if (tasks.enhance) {
1294
- info4("Enhancing entries with LLM...");
1644
+ info("Enhancing entries with LLM...");
1295
1645
  enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1296
- info4(`Enhanced ${enhanced.entries.length} entries`);
1646
+ info(`Enhanced ${enhanced.entries.length} entries`);
1297
1647
  }
1298
1648
  if (tasks.categorize) {
1299
- info4("Categorizing entries with LLM...");
1649
+ info("Categorizing entries with LLM...");
1300
1650
  const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1301
1651
  enhanced.categories = {};
1302
1652
  for (const cat of categorized) {
1303
1653
  enhanced.categories[cat.category] = cat.entries;
1304
1654
  }
1305
- info4(`Created ${categorized.length} categories`);
1655
+ info(`Created ${categorized.length} categories`);
1306
1656
  }
1307
1657
  }
1308
1658
  if (tasks.summarize) {
1309
- info4("Summarizing entries with LLM...");
1659
+ info("Summarizing entries with LLM...");
1310
1660
  enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
1311
1661
  if (enhanced.summary) {
1312
- info4("Summary generated successfully");
1662
+ info("Summary generated successfully");
1313
1663
  debug(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1314
1664
  } else {
1315
- warn3("Summary generation returned empty result");
1665
+ warn("Summary generation returned empty result");
1316
1666
  }
1317
1667
  }
1318
1668
  if (tasks.releaseNotes) {
1319
- info4("Generating release notes with LLM...");
1669
+ info("Generating release notes with LLM...");
1320
1670
  enhanced.releaseNotes = await generateReleaseNotes(provider, enhanced.entries, llmContext);
1321
1671
  if (enhanced.releaseNotes) {
1322
- info4("Release notes generated successfully");
1672
+ info("Release notes generated successfully");
1323
1673
  } else {
1324
- warn3("Release notes generation returned empty result");
1674
+ warn("Release notes generation returned empty result");
1325
1675
  }
1326
1676
  }
1327
1677
  return {
1328
1678
  ...context,
1329
- entries: enhanced.entries,
1330
1679
  enhanced
1331
1680
  };
1332
1681
  } catch (error) {
1333
- warn3(`LLM processing failed: ${error instanceof Error ? error.message : String(error)}`);
1334
- 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");
1335
1684
  return context;
1336
1685
  }
1337
1686
  }
@@ -1339,17 +1688,17 @@ function getBuiltinTemplatePath(style) {
1339
1688
  let packageRoot;
1340
1689
  try {
1341
1690
  const currentUrl = import.meta.url;
1342
- packageRoot = path6.dirname(new URL(currentUrl).pathname);
1343
- packageRoot = path6.join(packageRoot, "..", "..");
1691
+ packageRoot = path8.dirname(new URL(currentUrl).pathname);
1692
+ packageRoot = path8.join(packageRoot, "..", "..");
1344
1693
  } catch {
1345
1694
  packageRoot = __dirname;
1346
1695
  }
1347
- return path6.join(packageRoot, "templates", style);
1696
+ return path8.join(packageRoot, "templates", style);
1348
1697
  }
1349
1698
  async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1350
1699
  let templatePath;
1351
1700
  if (config.templates?.path) {
1352
- templatePath = path6.resolve(config.templates.path);
1701
+ templatePath = path8.resolve(config.templates.path);
1353
1702
  } else {
1354
1703
  templatePath = getBuiltinTemplatePath("keep-a-changelog");
1355
1704
  }
@@ -1359,64 +1708,85 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1359
1708
  );
1360
1709
  const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
1361
1710
  if (dryRun) {
1362
- info4("--- Changelog Preview ---");
1363
- console.log(result.content);
1364
- info4("--- End Preview ---");
1711
+ info(`Would write templated output to ${outputPath}`);
1712
+ debug("--- Changelog Preview ---");
1713
+ debug(result.content);
1714
+ debug("--- End Preview ---");
1365
1715
  return;
1366
1716
  }
1367
1717
  if (outputPath === "-") {
1368
1718
  process.stdout.write(result.content);
1369
1719
  return;
1370
1720
  }
1371
- const dir = path6.dirname(outputPath);
1372
- if (!fs8.existsSync(dir)) {
1373
- fs8.mkdirSync(dir, { recursive: true });
1721
+ const dir = path8.dirname(outputPath);
1722
+ if (!fs10.existsSync(dir)) {
1723
+ fs10.mkdirSync(dir, { recursive: true });
1374
1724
  }
1375
- fs8.writeFileSync(outputPath, result.content, "utf-8");
1376
- 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)`);
1377
1728
  }
1378
1729
  async function runPipeline(input, config, dryRun) {
1379
1730
  debug(`Processing ${input.packages.length} package(s)`);
1380
1731
  let contexts = input.packages.map(createTemplateContext);
1381
1732
  if (config.llm && !process.env.CHANGELOG_NO_LLM) {
1382
- info4("Processing with LLM enhancement");
1733
+ info("Processing with LLM enhancement");
1383
1734
  contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, config)));
1384
1735
  }
1736
+ const files = [];
1737
+ const fmtOpts = {
1738
+ includePackageName: contexts.length > 1 || contexts.some((c) => c.packageName.includes("/"))
1739
+ };
1385
1740
  for (const output of config.output) {
1386
- 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}`);
1387
1745
  switch (output.format) {
1388
1746
  case "markdown": {
1389
- const file = output.file ?? "CHANGELOG.md";
1390
- if (config.templates?.path || output.options?.template) {
1391
- await generateWithTemplate(contexts, config, file, dryRun);
1392
- } else {
1393
- 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)}`);
1394
1759
  }
1395
1760
  break;
1396
1761
  }
1397
1762
  case "json": {
1398
- const file = output.file ?? "changelog.json";
1399
- 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
+ }
1400
1770
  break;
1401
1771
  }
1402
1772
  case "github-release": {
1403
1773
  if (dryRun) {
1404
- info4("[DRY RUN] Would create GitHub release");
1774
+ info("[DRY RUN] Would create GitHub release");
1405
1775
  break;
1406
1776
  }
1407
1777
  const firstContext = contexts[0];
1408
1778
  if (!firstContext) {
1409
- warn3("No context available for GitHub release");
1779
+ warn("No context available for GitHub release");
1410
1780
  break;
1411
1781
  }
1412
1782
  const repoUrl = firstContext.repoUrl;
1413
1783
  if (!repoUrl) {
1414
- warn3("No repo URL available, cannot create GitHub release");
1784
+ warn("No repo URL available, cannot create GitHub release");
1415
1785
  break;
1416
1786
  }
1417
1787
  const parsed = parseRepoUrl(repoUrl);
1418
1788
  if (!parsed) {
1419
- warn3(`Could not parse repo URL: ${repoUrl}`);
1789
+ warn(`Could not parse repo URL: ${repoUrl}`);
1420
1790
  break;
1421
1791
  }
1422
1792
  await createGitHubRelease(firstContext, {
@@ -1429,165 +1799,53 @@ async function runPipeline(input, config, dryRun) {
1429
1799
  }
1430
1800
  }
1431
1801
  }
1432
- }
1433
- async function processInput(inputJson, config, dryRun) {
1434
- const input = parsePackageVersioner(inputJson);
1435
- await runPipeline(input, config, dryRun);
1436
- }
1437
-
1438
- // src/monorepo/aggregator.ts
1439
- import * as fs9 from "fs";
1440
- import * as path7 from "path";
1441
- import { info as info5, success as success5 } from "@releasekit/core";
1442
-
1443
- // src/monorepo/splitter.ts
1444
- function splitByPackage(contexts) {
1445
- const byPackage = /* @__PURE__ */ new Map();
1446
- for (const ctx of contexts) {
1447
- byPackage.set(ctx.packageName, ctx);
1448
- }
1449
- return byPackage;
1450
- }
1451
-
1452
- // src/monorepo/aggregator.ts
1453
- function writeFile(outputPath, content, dryRun) {
1454
- if (dryRun) {
1455
- info5(`[DRY RUN] Would write to ${outputPath}`);
1456
- console.log(content);
1457
- return;
1458
- }
1459
- const dir = path7.dirname(outputPath);
1460
- if (!fs9.existsSync(dir)) {
1461
- fs9.mkdirSync(dir, { recursive: true });
1462
- }
1463
- fs9.writeFileSync(outputPath, content, "utf-8");
1464
- success5(`Changelog written to ${outputPath}`);
1465
- }
1466
- function aggregateToRoot(contexts) {
1467
- const aggregated = {
1468
- packageName: "monorepo",
1469
- version: contexts[0]?.version ?? "0.0.0",
1470
- previousVersion: contexts[0]?.previousVersion ?? null,
1471
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "",
1472
- repoUrl: contexts[0]?.repoUrl ?? null,
1473
- entries: []
1474
- };
1475
- for (const ctx of contexts) {
1476
- for (const entry of ctx.entries) {
1477
- aggregated.entries.push({
1478
- ...entry,
1479
- scope: entry.scope ? `${ctx.packageName}/${entry.scope}` : ctx.packageName
1480
- });
1481
- }
1482
- }
1483
- return aggregated;
1484
- }
1485
- function writeMonorepoChangelogs(contexts, options, config, dryRun) {
1486
- if (options.mode === "root" || options.mode === "both") {
1487
- const aggregated = aggregateToRoot(contexts);
1488
- const rootPath = path7.join(options.rootPath, "CHANGELOG.md");
1489
- info5(`Writing root changelog to ${rootPath}`);
1490
- const rootContent = config.updateStrategy === "prepend" && fs9.existsSync(rootPath) ? prependVersion(rootPath, aggregated) : renderMarkdown([aggregated]);
1491
- writeFile(rootPath, rootContent, dryRun);
1492
- }
1493
- if (options.mode === "packages" || options.mode === "both") {
1494
- const byPackage = splitByPackage(contexts);
1495
- const packageDirMap = buildPackageDirMap(options.rootPath, options.packagesPath);
1496
- for (const [packageName, ctx] of byPackage) {
1497
- const simpleName = packageName.split("/").pop();
1498
- const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
1499
- if (packageDir) {
1500
- const changelogPath = path7.join(packageDir, "CHANGELOG.md");
1501
- info5(`Writing changelog for ${packageName} to ${changelogPath}`);
1502
- const pkgContent = config.updateStrategy === "prepend" && fs9.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
1503
- writeFile(changelogPath, pkgContent, dryRun);
1504
- } else {
1505
- info5(`Could not find directory for package ${packageName}, skipping`);
1506
- }
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);
1507
1818
  }
1508
1819
  }
1509
- }
1510
- function buildPackageDirMap(rootPath, packagesPath) {
1511
- const map = /* @__PURE__ */ new Map();
1512
- const packagesDir = path7.join(rootPath, packagesPath);
1513
- if (!fs9.existsSync(packagesDir)) {
1514
- return map;
1515
- }
1516
- for (const entry of fs9.readdirSync(packagesDir, { withFileTypes: true })) {
1517
- if (!entry.isDirectory()) continue;
1518
- const dirPath = path7.join(packagesDir, entry.name);
1519
- map.set(entry.name, dirPath);
1520
- const packageJsonPath = path7.join(dirPath, "package.json");
1521
- if (fs9.existsSync(packageJsonPath)) {
1522
- try {
1523
- const pkg = JSON.parse(fs9.readFileSync(packageJsonPath, "utf-8"));
1524
- if (pkg.name) {
1525
- map.set(pkg.name, dirPath);
1526
- }
1527
- } catch {
1528
- }
1529
- }
1820
+ const packageNotes = {};
1821
+ for (const ctx of contexts) {
1822
+ packageNotes[ctx.packageName] = formatVersion(ctx);
1530
1823
  }
1531
- return map;
1824
+ return { packageNotes, files };
1532
1825
  }
1533
- function detectMonorepo(cwd) {
1534
- const pnpmWorkspacesPath = path7.join(cwd, "pnpm-workspace.yaml");
1535
- const packageJsonPath = path7.join(cwd, "package.json");
1536
- if (fs9.existsSync(pnpmWorkspacesPath)) {
1537
- const content = fs9.readFileSync(pnpmWorkspacesPath, "utf-8");
1538
- const packagesMatch = content.match(/packages:\s*\n\s*-\s*['"]([^'"]+)['"]/);
1539
- if (packagesMatch?.[1]) {
1540
- const packagesGlob = packagesMatch[1];
1541
- const packagesPath = packagesGlob.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
1542
- return { isMonorepo: true, packagesPath: packagesPath || "packages" };
1543
- }
1544
- return { isMonorepo: true, packagesPath: "packages" };
1545
- }
1546
- if (fs9.existsSync(packageJsonPath)) {
1547
- try {
1548
- const content = fs9.readFileSync(packageJsonPath, "utf-8");
1549
- const pkg = JSON.parse(content);
1550
- if (pkg.workspaces) {
1551
- const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
1552
- if (workspaces?.length) {
1553
- const firstWorkspace = workspaces[0];
1554
- if (firstWorkspace) {
1555
- const packagesPath = firstWorkspace.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
1556
- return { isMonorepo: true, packagesPath: packagesPath || "packages" };
1557
- }
1558
- }
1559
- }
1560
- } catch {
1561
- return { isMonorepo: false, packagesPath: "" };
1562
- }
1563
- }
1564
- return { isMonorepo: false, packagesPath: "" };
1826
+ async function processInput(inputJson, config, dryRun) {
1827
+ const input = parsePackageVersioner(inputJson);
1828
+ return runPipeline(input, config, dryRun);
1565
1829
  }
1566
1830
 
1567
1831
  export {
1568
1832
  loadAuth,
1569
1833
  saveAuth,
1570
- loadConfig,
1834
+ loadConfig2 as loadConfig,
1571
1835
  getDefaultConfig,
1572
1836
  NotesError,
1573
1837
  InputParseError,
1574
1838
  TemplateError,
1575
1839
  LLMError,
1576
1840
  GitHubError,
1577
- ConfigError,
1841
+ ConfigError2 as ConfigError,
1578
1842
  getExitCode,
1579
- EXIT_CODES2 as EXIT_CODES,
1580
1843
  parsePackageVersioner,
1581
1844
  parsePackageVersionerFile,
1582
1845
  parsePackageVersionerStdin,
1583
- renderMarkdown,
1584
- writeMarkdown,
1585
1846
  renderJson,
1586
1847
  writeJson,
1587
1848
  createTemplateContext,
1588
1849
  runPipeline,
1589
- processInput,
1590
- aggregateToRoot,
1591
- writeMonorepoChangelogs,
1592
- detectMonorepo
1850
+ processInput
1593
1851
  };