@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.
- package/LICENSE +21 -0
- package/README.md +3 -1
- package/dist/aggregator-YA7LC3KJ.js +13 -0
- package/dist/chunk-ATNNRK62.js +165 -0
- package/dist/{chunk-BLWJTLRD.js → chunk-IKER5VME.js} +687 -429
- package/dist/chunk-SAUES3BF.js +231 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +13 -29
- package/dist/index.d.ts +8 -182
- package/dist/index.js +13 -7
- package/package.json +43 -36
- package/dist/cli.cjs +0 -1750
- package/dist/cli.d.cts +0 -1
- package/dist/index.cjs +0 -1656
- package/dist/index.d.cts +0 -182
|
@@ -1,12 +1,376 @@
|
|
|
1
|
-
// src/core/config.ts
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
277
|
-
import * as
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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 =
|
|
304
|
-
if (!
|
|
305
|
-
|
|
538
|
+
const dir = path4.dirname(outputPath);
|
|
539
|
+
if (!fs5.existsSync(dir)) {
|
|
540
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
306
541
|
}
|
|
307
|
-
|
|
308
|
-
|
|
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
|
|
313
|
-
import * as
|
|
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:
|
|
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
|
|
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) =>
|
|
523
|
-
|
|
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 (
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (scopes.length > 0) {
|
|
530
|
-
scopeInstructions = `
|
|
531
|
-
|
|
532
|
-
For the "Developer" category, you MUST assign a scope from this exact list: ${scopes.join(", ")}.
|
|
533
|
-
`;
|
|
534
|
-
}
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
571
|
-
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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,
|
|
618
|
-
const styleText =
|
|
619
|
-
const
|
|
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) =>
|
|
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
|
|
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
|
|
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 =
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
1308
|
+
if (!fs6.existsSync(filePath)) {
|
|
974
1309
|
throw new TemplateError(`Template file not found: ${filePath}`);
|
|
975
1310
|
}
|
|
976
|
-
const template =
|
|
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
|
|
982
|
-
import * as
|
|
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 (!
|
|
1343
|
+
if (!fs7.existsSync(filePath)) {
|
|
1009
1344
|
throw new TemplateError(`Template file not found: ${filePath}`);
|
|
1010
1345
|
}
|
|
1011
|
-
const template =
|
|
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 =
|
|
1017
|
-
const entryPath =
|
|
1018
|
-
const documentPath =
|
|
1019
|
-
if (!
|
|
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 (
|
|
1023
|
-
Handlebars.registerPartial("version",
|
|
1357
|
+
if (fs7.existsSync(versionPath)) {
|
|
1358
|
+
Handlebars.registerPartial("version", fs7.readFileSync(versionPath, "utf-8"));
|
|
1024
1359
|
}
|
|
1025
|
-
if (
|
|
1026
|
-
Handlebars.registerPartial("entry",
|
|
1360
|
+
if (fs7.existsSync(entryPath)) {
|
|
1361
|
+
Handlebars.registerPartial("entry", fs7.readFileSync(entryPath, "utf-8"));
|
|
1027
1362
|
}
|
|
1028
1363
|
try {
|
|
1029
|
-
const compiled = Handlebars.compile(
|
|
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
|
|
1038
|
-
import * as
|
|
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 (!
|
|
1391
|
+
if (!fs8.existsSync(filePath)) {
|
|
1057
1392
|
throw new TemplateError(`Template file not found: ${filePath}`);
|
|
1058
1393
|
}
|
|
1059
|
-
const template =
|
|
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 =
|
|
1064
|
-
if (!
|
|
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
|
|
1077
|
-
import * as
|
|
1411
|
+
import * as fs9 from "fs";
|
|
1412
|
+
import * as path7 from "path";
|
|
1078
1413
|
function getEngineFromFile(filePath) {
|
|
1079
|
-
const ext =
|
|
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 (!
|
|
1448
|
+
if (!fs9.existsSync(templatePath)) {
|
|
1114
1449
|
throw new TemplateError(`Template path not found: ${templatePath}`);
|
|
1115
1450
|
}
|
|
1116
|
-
const stat =
|
|
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 =
|
|
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 =
|
|
1158
|
-
if (!
|
|
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 =
|
|
1162
|
-
const entryPath =
|
|
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 =
|
|
1165
|
-
const versionTemplate =
|
|
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(
|
|
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/${
|
|
1564
|
+
return `${repoUrl}/-/compare/${fromVersion}...${toVersion}`;
|
|
1220
1565
|
}
|
|
1221
1566
|
if (/bitbucket\.org/i.test(repoUrl)) {
|
|
1222
|
-
return `${repoUrl}/branches/compare/${
|
|
1567
|
+
return `${repoUrl}/branches/compare/${fromVersion}..${toVersion}`;
|
|
1223
1568
|
}
|
|
1224
|
-
return `${repoUrl}/compare/${
|
|
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
|
-
|
|
1618
|
+
info(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
|
|
1272
1619
|
if (config.llm.baseURL) {
|
|
1273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1632
|
+
info(`Running LLM tasks: ${activeTasks.join(", ")}`);
|
|
1283
1633
|
if (tasks.enhance && tasks.categorize) {
|
|
1284
|
-
|
|
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
|
-
|
|
1641
|
+
info(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
|
|
1292
1642
|
} else {
|
|
1293
1643
|
if (tasks.enhance) {
|
|
1294
|
-
|
|
1644
|
+
info("Enhancing entries with LLM...");
|
|
1295
1645
|
enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
|
|
1296
|
-
|
|
1646
|
+
info(`Enhanced ${enhanced.entries.length} entries`);
|
|
1297
1647
|
}
|
|
1298
1648
|
if (tasks.categorize) {
|
|
1299
|
-
|
|
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
|
-
|
|
1655
|
+
info(`Created ${categorized.length} categories`);
|
|
1306
1656
|
}
|
|
1307
1657
|
}
|
|
1308
1658
|
if (tasks.summarize) {
|
|
1309
|
-
|
|
1659
|
+
info("Summarizing entries with LLM...");
|
|
1310
1660
|
enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
|
|
1311
1661
|
if (enhanced.summary) {
|
|
1312
|
-
|
|
1662
|
+
info("Summary generated successfully");
|
|
1313
1663
|
debug(`Summary: ${enhanced.summary.substring(0, 100)}...`);
|
|
1314
1664
|
} else {
|
|
1315
|
-
|
|
1665
|
+
warn("Summary generation returned empty result");
|
|
1316
1666
|
}
|
|
1317
1667
|
}
|
|
1318
1668
|
if (tasks.releaseNotes) {
|
|
1319
|
-
|
|
1669
|
+
info("Generating release notes with LLM...");
|
|
1320
1670
|
enhanced.releaseNotes = await generateReleaseNotes(provider, enhanced.entries, llmContext);
|
|
1321
1671
|
if (enhanced.releaseNotes) {
|
|
1322
|
-
|
|
1672
|
+
info("Release notes generated successfully");
|
|
1323
1673
|
} else {
|
|
1324
|
-
|
|
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
|
-
|
|
1334
|
-
|
|
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 =
|
|
1343
|
-
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
|
|
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 =
|
|
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
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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 =
|
|
1372
|
-
if (!
|
|
1373
|
-
|
|
1721
|
+
const dir = path8.dirname(outputPath);
|
|
1722
|
+
if (!fs10.existsSync(dir)) {
|
|
1723
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
1374
1724
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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
|
|
1399
|
-
|
|
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
|
-
|
|
1774
|
+
info("[DRY RUN] Would create GitHub release");
|
|
1405
1775
|
break;
|
|
1406
1776
|
}
|
|
1407
1777
|
const firstContext = contexts[0];
|
|
1408
1778
|
if (!firstContext) {
|
|
1409
|
-
|
|
1779
|
+
warn("No context available for GitHub release");
|
|
1410
1780
|
break;
|
|
1411
1781
|
}
|
|
1412
1782
|
const repoUrl = firstContext.repoUrl;
|
|
1413
1783
|
if (!repoUrl) {
|
|
1414
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1449
|
-
return byPackage;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// src/monorepo/aggregator.ts
|
|
1453
|
-
function writeFile(outputPath, content, dryRun) {
|
|
1454
|
-
if (dryRun) {
|
|
1455
|
-
info5(`[DRY RUN] Would write to ${outputPath}`);
|
|
1456
|
-
console.log(content);
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
const dir = path7.dirname(outputPath);
|
|
1460
|
-
if (!fs9.existsSync(dir)) {
|
|
1461
|
-
fs9.mkdirSync(dir, { recursive: true });
|
|
1462
|
-
}
|
|
1463
|
-
fs9.writeFileSync(outputPath, content, "utf-8");
|
|
1464
|
-
success5(`Changelog written to ${outputPath}`);
|
|
1465
|
-
}
|
|
1466
|
-
function aggregateToRoot(contexts) {
|
|
1467
|
-
const aggregated = {
|
|
1468
|
-
packageName: "monorepo",
|
|
1469
|
-
version: contexts[0]?.version ?? "0.0.0",
|
|
1470
|
-
previousVersion: contexts[0]?.previousVersion ?? null,
|
|
1471
|
-
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "",
|
|
1472
|
-
repoUrl: contexts[0]?.repoUrl ?? null,
|
|
1473
|
-
entries: []
|
|
1474
|
-
};
|
|
1475
|
-
for (const ctx of contexts) {
|
|
1476
|
-
for (const entry of ctx.entries) {
|
|
1477
|
-
aggregated.entries.push({
|
|
1478
|
-
...entry,
|
|
1479
|
-
scope: entry.scope ? `${ctx.packageName}/${entry.scope}` : ctx.packageName
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
return aggregated;
|
|
1484
|
-
}
|
|
1485
|
-
function writeMonorepoChangelogs(contexts, options, config, dryRun) {
|
|
1486
|
-
if (options.mode === "root" || options.mode === "both") {
|
|
1487
|
-
const aggregated = aggregateToRoot(contexts);
|
|
1488
|
-
const rootPath = path7.join(options.rootPath, "CHANGELOG.md");
|
|
1489
|
-
info5(`Writing root changelog to ${rootPath}`);
|
|
1490
|
-
const rootContent = config.updateStrategy === "prepend" && fs9.existsSync(rootPath) ? prependVersion(rootPath, aggregated) : renderMarkdown([aggregated]);
|
|
1491
|
-
writeFile(rootPath, rootContent, dryRun);
|
|
1492
|
-
}
|
|
1493
|
-
if (options.mode === "packages" || options.mode === "both") {
|
|
1494
|
-
const byPackage = splitByPackage(contexts);
|
|
1495
|
-
const packageDirMap = buildPackageDirMap(options.rootPath, options.packagesPath);
|
|
1496
|
-
for (const [packageName, ctx] of byPackage) {
|
|
1497
|
-
const simpleName = packageName.split("/").pop();
|
|
1498
|
-
const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
|
|
1499
|
-
if (packageDir) {
|
|
1500
|
-
const changelogPath = path7.join(packageDir, "CHANGELOG.md");
|
|
1501
|
-
info5(`Writing changelog for ${packageName} to ${changelogPath}`);
|
|
1502
|
-
const pkgContent = config.updateStrategy === "prepend" && fs9.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
|
|
1503
|
-
writeFile(changelogPath, pkgContent, dryRun);
|
|
1504
|
-
} else {
|
|
1505
|
-
info5(`Could not find directory for package ${packageName}, skipping`);
|
|
1506
|
-
}
|
|
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
|
-
|
|
1511
|
-
|
|
1512
|
-
const packagesDir = path7.join(rootPath, packagesPath);
|
|
1513
|
-
if (!fs9.existsSync(packagesDir)) {
|
|
1514
|
-
return map;
|
|
1515
|
-
}
|
|
1516
|
-
for (const entry of fs9.readdirSync(packagesDir, { withFileTypes: true })) {
|
|
1517
|
-
if (!entry.isDirectory()) continue;
|
|
1518
|
-
const dirPath = path7.join(packagesDir, entry.name);
|
|
1519
|
-
map.set(entry.name, dirPath);
|
|
1520
|
-
const packageJsonPath = path7.join(dirPath, "package.json");
|
|
1521
|
-
if (fs9.existsSync(packageJsonPath)) {
|
|
1522
|
-
try {
|
|
1523
|
-
const pkg = JSON.parse(fs9.readFileSync(packageJsonPath, "utf-8"));
|
|
1524
|
-
if (pkg.name) {
|
|
1525
|
-
map.set(pkg.name, dirPath);
|
|
1526
|
-
}
|
|
1527
|
-
} catch {
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1820
|
+
const packageNotes = {};
|
|
1821
|
+
for (const ctx of contexts) {
|
|
1822
|
+
packageNotes[ctx.packageName] = formatVersion(ctx);
|
|
1530
1823
|
}
|
|
1531
|
-
return
|
|
1824
|
+
return { packageNotes, files };
|
|
1532
1825
|
}
|
|
1533
|
-
function
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1536
|
-
if (fs9.existsSync(pnpmWorkspacesPath)) {
|
|
1537
|
-
const content = fs9.readFileSync(pnpmWorkspacesPath, "utf-8");
|
|
1538
|
-
const packagesMatch = content.match(/packages:\s*\n\s*-\s*['"]([^'"]+)['"]/);
|
|
1539
|
-
if (packagesMatch?.[1]) {
|
|
1540
|
-
const packagesGlob = packagesMatch[1];
|
|
1541
|
-
const packagesPath = packagesGlob.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
|
|
1542
|
-
return { isMonorepo: true, packagesPath: packagesPath || "packages" };
|
|
1543
|
-
}
|
|
1544
|
-
return { isMonorepo: true, packagesPath: "packages" };
|
|
1545
|
-
}
|
|
1546
|
-
if (fs9.existsSync(packageJsonPath)) {
|
|
1547
|
-
try {
|
|
1548
|
-
const content = fs9.readFileSync(packageJsonPath, "utf-8");
|
|
1549
|
-
const pkg = JSON.parse(content);
|
|
1550
|
-
if (pkg.workspaces) {
|
|
1551
|
-
const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
|
|
1552
|
-
if (workspaces?.length) {
|
|
1553
|
-
const firstWorkspace = workspaces[0];
|
|
1554
|
-
if (firstWorkspace) {
|
|
1555
|
-
const packagesPath = firstWorkspace.replace(/\/?\*$/, "").replace(/\/\*\*$/, "");
|
|
1556
|
-
return { isMonorepo: true, packagesPath: packagesPath || "packages" };
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
} catch {
|
|
1561
|
-
return { isMonorepo: false, packagesPath: "" };
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
return { isMonorepo: false, packagesPath: "" };
|
|
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
|
};
|