tst-changelog 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +32 -4
- package/dist/index.js +245 -62
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.d.ts
CHANGED
|
@@ -2,13 +2,23 @@ interface AIConfig {
|
|
|
2
2
|
provider: 'openrouter';
|
|
3
3
|
model: string;
|
|
4
4
|
token: string;
|
|
5
|
+
prompt?: string;
|
|
5
6
|
}
|
|
6
7
|
interface ChangelogConfig {
|
|
7
8
|
file: string;
|
|
8
9
|
format: 'keepachangelog' | 'simple';
|
|
9
10
|
includeDate: boolean;
|
|
11
|
+
includeTime?: boolean;
|
|
12
|
+
includeAuthor?: boolean;
|
|
13
|
+
includeCommitLink?: boolean;
|
|
14
|
+
repoUrl?: string;
|
|
10
15
|
groupBy: 'type' | 'scope' | 'none';
|
|
11
16
|
}
|
|
17
|
+
interface ChangelogMetadata {
|
|
18
|
+
author?: string;
|
|
19
|
+
commitHash?: string;
|
|
20
|
+
timestamp?: Date;
|
|
21
|
+
}
|
|
12
22
|
interface GitConfig {
|
|
13
23
|
baseBranch: string;
|
|
14
24
|
analyzeDepth: number;
|
|
@@ -47,16 +57,22 @@ interface CLIOptions {
|
|
|
47
57
|
config?: string;
|
|
48
58
|
dryRun?: boolean;
|
|
49
59
|
verbose?: boolean;
|
|
60
|
+
fromCommits?: number;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
/**
|
|
53
64
|
* Load configuration from yaml file
|
|
54
65
|
*/
|
|
55
66
|
declare function loadConfig(configPath?: string): Config;
|
|
67
|
+
interface InitResult {
|
|
68
|
+
configPath: string;
|
|
69
|
+
huskyConfigured: boolean;
|
|
70
|
+
huskyHook?: string;
|
|
71
|
+
}
|
|
56
72
|
/**
|
|
57
73
|
* Initialize a new config file with defaults
|
|
58
74
|
*/
|
|
59
|
-
declare function initConfig(fileName?: string):
|
|
75
|
+
declare function initConfig(fileName?: string): InitResult;
|
|
60
76
|
|
|
61
77
|
declare class GitService {
|
|
62
78
|
private git;
|
|
@@ -94,6 +110,14 @@ declare class GitService {
|
|
|
94
110
|
* Check if we're in a git repository
|
|
95
111
|
*/
|
|
96
112
|
isGitRepo(): Promise<boolean>;
|
|
113
|
+
/**
|
|
114
|
+
* Get current git user name
|
|
115
|
+
*/
|
|
116
|
+
getCurrentUser(): Promise<string>;
|
|
117
|
+
/**
|
|
118
|
+
* Get HEAD commit hash
|
|
119
|
+
*/
|
|
120
|
+
getHeadCommit(): Promise<string>;
|
|
97
121
|
}
|
|
98
122
|
|
|
99
123
|
declare class AIService {
|
|
@@ -119,13 +143,17 @@ declare class ChangelogService {
|
|
|
119
143
|
/**
|
|
120
144
|
* Generate new changelog entry
|
|
121
145
|
*/
|
|
122
|
-
formatEntry(generated: GeneratedChangelog, version?: string): string;
|
|
146
|
+
formatEntry(generated: GeneratedChangelog, version?: string, metadata?: ChangelogMetadata): string;
|
|
123
147
|
private sortSections;
|
|
124
148
|
private formatSection;
|
|
125
149
|
/**
|
|
126
150
|
* Update or create changelog with new entry
|
|
127
151
|
*/
|
|
128
|
-
update(generated: GeneratedChangelog, version?: string): string;
|
|
152
|
+
update(generated: GeneratedChangelog, version?: string, metadata?: ChangelogMetadata): string;
|
|
153
|
+
/**
|
|
154
|
+
* Merge new entries into existing Unreleased section
|
|
155
|
+
*/
|
|
156
|
+
private mergeUnreleasedSection;
|
|
129
157
|
private findSectionEnd;
|
|
130
158
|
/**
|
|
131
159
|
* Write changelog to file
|
|
@@ -137,4 +165,4 @@ declare class ChangelogService {
|
|
|
137
165
|
getFilePath(): string;
|
|
138
166
|
}
|
|
139
167
|
|
|
140
|
-
export { type AIConfig, AIService, type CLIOptions, type ChangeType, type ChangelogConfig, type ChangelogEntry, type ChangelogSection, ChangelogService, type CommitInfo, type Config, type GeneratedChangelog, type GitConfig, GitService, type StagedChanges, initConfig, loadConfig };
|
|
168
|
+
export { type AIConfig, AIService, type CLIOptions, type ChangeType, type ChangelogConfig, type ChangelogEntry, type ChangelogMetadata, type ChangelogSection, ChangelogService, type CommitInfo, type Config, type GeneratedChangelog, type GitConfig, GitService, type InitResult, type StagedChanges, initConfig, loadConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import "dotenv/config";
|
|
4
5
|
import { Command } from "commander";
|
|
6
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
5
9
|
|
|
6
10
|
// src/config.ts
|
|
7
|
-
import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
11
|
+
import { readFileSync, existsSync, writeFileSync, appendFileSync } from "fs";
|
|
8
12
|
import { resolve } from "path";
|
|
9
13
|
import { parse, stringify } from "yaml";
|
|
14
|
+
var DEFAULT_PROMPT = `You are a changelog generator. Your task is to analyze git commits and staged changes to generate changelog entries following the Keep a Changelog format.
|
|
15
|
+
|
|
16
|
+
Output Format:
|
|
17
|
+
Return ONLY a JSON object with the following structure:
|
|
18
|
+
{
|
|
19
|
+
"sections": [
|
|
20
|
+
{
|
|
21
|
+
"type": "Added" | "Changed" | "Deprecated" | "Removed" | "Fixed" | "Security",
|
|
22
|
+
"items": ["description 1", "description 2"]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Guidelines:
|
|
28
|
+
- Use present tense (e.g., "Add feature" not "Added feature")
|
|
29
|
+
- Be concise but descriptive
|
|
30
|
+
- Group related changes together
|
|
31
|
+
- Focus on user-facing changes
|
|
32
|
+
- Ignore merge commits and trivial changes
|
|
33
|
+
- Parse conventional commits (feat:, fix:, etc.) into appropriate sections:
|
|
34
|
+
- feat: -> Added
|
|
35
|
+
- fix: -> Fixed
|
|
36
|
+
- docs:, style:, refactor:, perf:, test:, chore: -> Changed
|
|
37
|
+
- BREAKING CHANGE: -> Changed (mention breaking)
|
|
38
|
+
- deprecate: -> Deprecated
|
|
39
|
+
- remove: -> Removed
|
|
40
|
+
- security: -> Security`;
|
|
10
41
|
var DEFAULT_CONFIG = {
|
|
11
42
|
ai: {
|
|
12
43
|
provider: "openrouter",
|
|
13
|
-
model: "anthropic/claude-
|
|
14
|
-
token: ""
|
|
44
|
+
model: "anthropic/claude-sonnet-4.5",
|
|
45
|
+
token: "",
|
|
46
|
+
prompt: DEFAULT_PROMPT
|
|
15
47
|
},
|
|
16
48
|
changelog: {
|
|
17
49
|
file: "CHANGELOG.md",
|
|
18
50
|
format: "keepachangelog",
|
|
19
51
|
includeDate: true,
|
|
52
|
+
includeTime: true,
|
|
53
|
+
includeAuthor: true,
|
|
54
|
+
includeCommitLink: true,
|
|
55
|
+
repoUrl: "",
|
|
20
56
|
groupBy: "type"
|
|
21
57
|
},
|
|
22
58
|
git: {
|
|
@@ -90,13 +126,18 @@ function initConfig(fileName = "tst-changelog.yaml") {
|
|
|
90
126
|
const configTemplate = {
|
|
91
127
|
ai: {
|
|
92
128
|
provider: "openrouter",
|
|
93
|
-
model: "anthropic/claude-
|
|
94
|
-
token: "${OPENROUTER_API_KEY}"
|
|
129
|
+
model: "anthropic/claude-sonnet-4.5",
|
|
130
|
+
token: "${OPENROUTER_API_KEY}",
|
|
131
|
+
prompt: DEFAULT_PROMPT
|
|
95
132
|
},
|
|
96
133
|
changelog: {
|
|
97
134
|
file: "CHANGELOG.md",
|
|
98
135
|
format: "keepachangelog",
|
|
99
136
|
includeDate: true,
|
|
137
|
+
includeTime: true,
|
|
138
|
+
includeAuthor: true,
|
|
139
|
+
includeCommitLink: true,
|
|
140
|
+
repoUrl: "",
|
|
100
141
|
groupBy: "type"
|
|
101
142
|
},
|
|
102
143
|
git: {
|
|
@@ -106,7 +147,35 @@ function initConfig(fileName = "tst-changelog.yaml") {
|
|
|
106
147
|
};
|
|
107
148
|
const content = stringify(configTemplate, { lineWidth: 0 });
|
|
108
149
|
writeFileSync(filePath, content, "utf-8");
|
|
109
|
-
|
|
150
|
+
const huskyResult = configureHusky();
|
|
151
|
+
return {
|
|
152
|
+
configPath: filePath,
|
|
153
|
+
...huskyResult
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function configureHusky() {
|
|
157
|
+
const huskyDir = resolve(process.cwd(), ".husky");
|
|
158
|
+
if (!existsSync(huskyDir)) {
|
|
159
|
+
return { huskyConfigured: false };
|
|
160
|
+
}
|
|
161
|
+
const preCommitPath = resolve(huskyDir, "pre-commit");
|
|
162
|
+
const command = "npx tst-changelog";
|
|
163
|
+
if (existsSync(preCommitPath)) {
|
|
164
|
+
const content = readFileSync(preCommitPath, "utf-8");
|
|
165
|
+
if (content.includes("tst-changelog")) {
|
|
166
|
+
return { huskyConfigured: false };
|
|
167
|
+
}
|
|
168
|
+
appendFileSync(preCommitPath, `
|
|
169
|
+
${command}
|
|
170
|
+
`);
|
|
171
|
+
} else {
|
|
172
|
+
writeFileSync(preCommitPath, `#!/usr/bin/env sh
|
|
173
|
+
. "$(dirname -- "$0")/_/husky.sh"
|
|
174
|
+
|
|
175
|
+
${command}
|
|
176
|
+
`, { mode: 493 });
|
|
177
|
+
}
|
|
178
|
+
return { huskyConfigured: true, huskyHook: preCommitPath };
|
|
110
179
|
}
|
|
111
180
|
|
|
112
181
|
// src/git.ts
|
|
@@ -216,37 +285,32 @@ var GitService = class {
|
|
|
216
285
|
return false;
|
|
217
286
|
}
|
|
218
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Get current git user name
|
|
290
|
+
*/
|
|
291
|
+
async getCurrentUser() {
|
|
292
|
+
try {
|
|
293
|
+
const name = await this.git.getConfig("user.name");
|
|
294
|
+
return name.value ?? "";
|
|
295
|
+
} catch {
|
|
296
|
+
return "";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get HEAD commit hash
|
|
301
|
+
*/
|
|
302
|
+
async getHeadCommit() {
|
|
303
|
+
try {
|
|
304
|
+
const hash = await this.git.revparse(["HEAD"]);
|
|
305
|
+
return hash.trim();
|
|
306
|
+
} catch {
|
|
307
|
+
return "";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
219
310
|
};
|
|
220
311
|
|
|
221
312
|
// src/ai.ts
|
|
222
313
|
var OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
|
|
223
|
-
var SYSTEM_PROMPT = `You are a changelog generator. Your task is to analyze git commits and staged changes to generate changelog entries following the Keep a Changelog format.
|
|
224
|
-
|
|
225
|
-
Output Format:
|
|
226
|
-
Return ONLY a JSON object with the following structure:
|
|
227
|
-
{
|
|
228
|
-
"sections": [
|
|
229
|
-
{
|
|
230
|
-
"type": "Added" | "Changed" | "Deprecated" | "Removed" | "Fixed" | "Security",
|
|
231
|
-
"items": ["description 1", "description 2"]
|
|
232
|
-
}
|
|
233
|
-
]
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
Guidelines:
|
|
237
|
-
- Use present tense (e.g., "Add feature" not "Added feature")
|
|
238
|
-
- Be concise but descriptive
|
|
239
|
-
- Group related changes together
|
|
240
|
-
- Focus on user-facing changes
|
|
241
|
-
- Ignore merge commits and trivial changes
|
|
242
|
-
- Parse conventional commits (feat:, fix:, etc.) into appropriate sections:
|
|
243
|
-
- feat: -> Added
|
|
244
|
-
- fix: -> Fixed
|
|
245
|
-
- docs:, style:, refactor:, perf:, test:, chore: -> Changed
|
|
246
|
-
- BREAKING CHANGE: -> Changed (mention breaking)
|
|
247
|
-
- deprecate: -> Deprecated
|
|
248
|
-
- remove: -> Removed
|
|
249
|
-
- security: -> Security`;
|
|
250
314
|
var AIService = class {
|
|
251
315
|
config;
|
|
252
316
|
constructor(config) {
|
|
@@ -298,7 +362,7 @@ var AIService = class {
|
|
|
298
362
|
body: JSON.stringify({
|
|
299
363
|
model: this.config.model,
|
|
300
364
|
messages: [
|
|
301
|
-
{ role: "system", content:
|
|
365
|
+
{ role: "system", content: this.config.prompt ?? "" },
|
|
302
366
|
{ role: "user", content: prompt }
|
|
303
367
|
],
|
|
304
368
|
temperature: 0.3,
|
|
@@ -376,10 +440,31 @@ var ChangelogService = class {
|
|
|
376
440
|
/**
|
|
377
441
|
* Generate new changelog entry
|
|
378
442
|
*/
|
|
379
|
-
formatEntry(generated, version) {
|
|
380
|
-
const date = this.config.includeDate ? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] : "";
|
|
443
|
+
formatEntry(generated, version, metadata) {
|
|
381
444
|
const versionStr = version ?? "Unreleased";
|
|
382
|
-
const
|
|
445
|
+
const headerParts = [`## [${versionStr}]`];
|
|
446
|
+
if (this.config.includeDate) {
|
|
447
|
+
const now = metadata?.timestamp ?? /* @__PURE__ */ new Date();
|
|
448
|
+
let dateStr = now.toISOString().split("T")[0];
|
|
449
|
+
if (this.config.includeTime) {
|
|
450
|
+
const timeStr = now.toTimeString().split(" ")[0].slice(0, 5);
|
|
451
|
+
dateStr += ` ${timeStr}`;
|
|
452
|
+
}
|
|
453
|
+
headerParts.push(`- ${dateStr}`);
|
|
454
|
+
}
|
|
455
|
+
if (this.config.includeAuthor && metadata?.author) {
|
|
456
|
+
headerParts.push(`by ${metadata.author}`);
|
|
457
|
+
}
|
|
458
|
+
if (this.config.includeCommitLink && metadata?.commitHash) {
|
|
459
|
+
const shortHash = metadata.commitHash.slice(0, 7);
|
|
460
|
+
if (this.config.repoUrl) {
|
|
461
|
+
const commitUrl = `${this.config.repoUrl.replace(/\/$/, "")}/commit/${metadata.commitHash}`;
|
|
462
|
+
headerParts.push(`([${shortHash}](${commitUrl}))`);
|
|
463
|
+
} else {
|
|
464
|
+
headerParts.push(`(${shortHash})`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const headerLine = headerParts.join(" ");
|
|
383
468
|
const sections = this.sortSections(generated.sections);
|
|
384
469
|
const sectionLines = sections.map((section) => this.formatSection(section)).join("\n\n");
|
|
385
470
|
return `${headerLine}
|
|
@@ -402,11 +487,11 @@ ${items}`;
|
|
|
402
487
|
/**
|
|
403
488
|
* Update or create changelog with new entry
|
|
404
489
|
*/
|
|
405
|
-
update(generated, version) {
|
|
406
|
-
const newEntry = this.formatEntry(generated, version);
|
|
490
|
+
update(generated, version, metadata) {
|
|
407
491
|
const existing = this.read();
|
|
408
492
|
if (!existing) {
|
|
409
|
-
|
|
493
|
+
const newEntry2 = this.formatEntry(generated, version, metadata);
|
|
494
|
+
return KEEPACHANGELOG_HEADER + newEntry2 + "\n";
|
|
410
495
|
}
|
|
411
496
|
const lines = existing.split("\n");
|
|
412
497
|
let insertIndex = -1;
|
|
@@ -414,15 +499,18 @@ ${items}`;
|
|
|
414
499
|
const line = lines[i];
|
|
415
500
|
if (line.startsWith("## [Unreleased]")) {
|
|
416
501
|
const endIndex = this.findSectionEnd(lines, i + 1);
|
|
502
|
+
const existingSectionLines = lines.slice(i, endIndex);
|
|
503
|
+
const mergedSection = this.mergeUnreleasedSection(existingSectionLines, generated, metadata);
|
|
417
504
|
const before2 = lines.slice(0, i).join("\n");
|
|
418
505
|
const after2 = lines.slice(endIndex).join("\n");
|
|
419
|
-
return before2 + (before2.endsWith("\n") ? "" : "\n") +
|
|
506
|
+
return before2 + (before2.endsWith("\n") ? "" : "\n") + mergedSection + "\n" + after2;
|
|
420
507
|
}
|
|
421
508
|
if (line.startsWith("## [") && insertIndex === -1) {
|
|
422
509
|
insertIndex = i;
|
|
423
510
|
break;
|
|
424
511
|
}
|
|
425
512
|
}
|
|
513
|
+
const newEntry = this.formatEntry(generated, version, metadata);
|
|
426
514
|
if (insertIndex === -1) {
|
|
427
515
|
return existing.trimEnd() + "\n\n" + newEntry + "\n";
|
|
428
516
|
}
|
|
@@ -430,6 +518,67 @@ ${items}`;
|
|
|
430
518
|
const after = lines.slice(insertIndex).join("\n");
|
|
431
519
|
return before + (before.endsWith("\n") ? "" : "\n") + newEntry + "\n\n" + after;
|
|
432
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Merge new entries into existing Unreleased section
|
|
523
|
+
*/
|
|
524
|
+
mergeUnreleasedSection(existingLines, generated, metadata) {
|
|
525
|
+
const existingSections = /* @__PURE__ */ new Map();
|
|
526
|
+
let currentSection = null;
|
|
527
|
+
for (const line of existingLines) {
|
|
528
|
+
if (line.startsWith("### ")) {
|
|
529
|
+
currentSection = line.slice(4).trim();
|
|
530
|
+
if (!existingSections.has(currentSection)) {
|
|
531
|
+
existingSections.set(currentSection, []);
|
|
532
|
+
}
|
|
533
|
+
} else if (currentSection && line.startsWith("- ")) {
|
|
534
|
+
existingSections.get(currentSection).push(line.slice(2));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
for (const section of generated.sections) {
|
|
538
|
+
const existing = existingSections.get(section.type) ?? [];
|
|
539
|
+
for (const item of section.items) {
|
|
540
|
+
if (!existing.includes(item)) {
|
|
541
|
+
existing.push(item);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
existingSections.set(section.type, existing);
|
|
545
|
+
}
|
|
546
|
+
const versionStr = "Unreleased";
|
|
547
|
+
const headerParts = [`## [${versionStr}]`];
|
|
548
|
+
if (this.config.includeDate) {
|
|
549
|
+
const now = metadata?.timestamp ?? /* @__PURE__ */ new Date();
|
|
550
|
+
let dateStr = now.toISOString().split("T")[0];
|
|
551
|
+
if (this.config.includeTime) {
|
|
552
|
+
const timeStr = now.toTimeString().split(" ")[0].slice(0, 5);
|
|
553
|
+
dateStr += ` ${timeStr}`;
|
|
554
|
+
}
|
|
555
|
+
headerParts.push(`- ${dateStr}`);
|
|
556
|
+
}
|
|
557
|
+
if (this.config.includeAuthor && metadata?.author) {
|
|
558
|
+
headerParts.push(`by ${metadata.author}`);
|
|
559
|
+
}
|
|
560
|
+
if (this.config.includeCommitLink && metadata?.commitHash) {
|
|
561
|
+
const shortHash = metadata.commitHash.slice(0, 7);
|
|
562
|
+
if (this.config.repoUrl) {
|
|
563
|
+
const commitUrl = `${this.config.repoUrl.replace(/\/$/, "")}/commit/${metadata.commitHash}`;
|
|
564
|
+
headerParts.push(`([${shortHash}](${commitUrl}))`);
|
|
565
|
+
} else {
|
|
566
|
+
headerParts.push(`(${shortHash})`);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const headerLine = headerParts.join(" ");
|
|
570
|
+
const sortedSections = [];
|
|
571
|
+
for (const type of SECTION_ORDER) {
|
|
572
|
+
const items = existingSections.get(type);
|
|
573
|
+
if (items && items.length > 0) {
|
|
574
|
+
sortedSections.push({ type, items });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const sectionLines = sortedSections.map((section) => this.formatSection(section)).join("\n\n");
|
|
578
|
+
return `${headerLine}
|
|
579
|
+
|
|
580
|
+
${sectionLines}`;
|
|
581
|
+
}
|
|
433
582
|
findSectionEnd(lines, startIndex) {
|
|
434
583
|
for (let i = startIndex; i < lines.length; i++) {
|
|
435
584
|
if (lines[i].startsWith("## [")) {
|
|
@@ -453,22 +602,29 @@ ${items}`;
|
|
|
453
602
|
};
|
|
454
603
|
|
|
455
604
|
// src/index.ts
|
|
605
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
606
|
+
var pkg = JSON.parse(readFileSync3(resolve3(__dirname, "../package.json"), "utf-8"));
|
|
456
607
|
var program = new Command();
|
|
457
|
-
program.name("tst-changelog").description("AI-powered changelog generator using OpenRouter").version(
|
|
608
|
+
program.name("tst-changelog").description("AI-powered changelog generator using OpenRouter").version(pkg.version);
|
|
458
609
|
program.command("init").description("Create a default configuration file").option("-f, --file <name>", "Config file name", "tst-changelog.yaml").action((options) => {
|
|
459
610
|
try {
|
|
460
|
-
const
|
|
461
|
-
console.log(`Created config file: ${
|
|
611
|
+
const result = initConfig(options.file);
|
|
612
|
+
console.log(`Created config file: ${result.configPath}`);
|
|
613
|
+
if (result.huskyConfigured) {
|
|
614
|
+
console.log(`Configured husky hook: ${result.huskyHook}`);
|
|
615
|
+
}
|
|
462
616
|
console.log("\nNext steps:");
|
|
463
617
|
console.log("1. Set the OPENROUTER_API_KEY environment variable");
|
|
464
618
|
console.log("2. Adjust settings in the config file as needed");
|
|
465
|
-
|
|
619
|
+
if (!result.huskyConfigured) {
|
|
620
|
+
console.log("3. Run: npx tst-changelog generate");
|
|
621
|
+
}
|
|
466
622
|
} catch (error) {
|
|
467
623
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
468
624
|
process.exit(1);
|
|
469
625
|
}
|
|
470
626
|
});
|
|
471
|
-
program.command("generate", { isDefault: true }).description("Generate changelog from staged changes").option("-c, --config <path>", "Path to config file").option("-d, --dry-run", "Preview without modifying files").option("-v, --verbose", "Enable verbose output").action(run);
|
|
627
|
+
program.command("generate", { isDefault: true }).description("Generate changelog from staged changes or commit history").option("-c, --config <path>", "Path to config file").option("-d, --dry-run", "Preview without modifying files").option("-v, --verbose", "Enable verbose output").option("--from-commits <count>", "Generate from last N commits instead of staged changes", parseInt).action(run);
|
|
472
628
|
async function run(options) {
|
|
473
629
|
const verbose = options.verbose ?? false;
|
|
474
630
|
try {
|
|
@@ -482,19 +638,32 @@ async function run(options) {
|
|
|
482
638
|
console.error("Error: Not a git repository");
|
|
483
639
|
process.exit(1);
|
|
484
640
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (
|
|
488
|
-
console.log(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
641
|
+
let commits;
|
|
642
|
+
let staged;
|
|
643
|
+
if (options.fromCommits) {
|
|
644
|
+
if (verbose) console.log(`Getting last ${options.fromCommits} commits...`);
|
|
645
|
+
commits = await git.getRecentCommits(options.fromCommits);
|
|
646
|
+
if (commits.length === 0) {
|
|
647
|
+
console.log("No commits found.");
|
|
648
|
+
process.exit(0);
|
|
649
|
+
}
|
|
650
|
+
if (verbose) console.log(`Found ${commits.length} commits`);
|
|
651
|
+
staged = { files: [], diff: "" };
|
|
652
|
+
} else {
|
|
653
|
+
if (verbose) console.log("Getting staged changes...");
|
|
654
|
+
staged = await git.getStagedChanges();
|
|
655
|
+
if (staged.files.length === 0) {
|
|
656
|
+
console.log("No staged changes found. Stage some changes first.");
|
|
657
|
+
process.exit(0);
|
|
658
|
+
}
|
|
659
|
+
if (verbose) {
|
|
660
|
+
console.log(`Staged files: ${staged.files.join(", ")}`);
|
|
661
|
+
console.log(`Diff length: ${staged.diff.length} chars`);
|
|
662
|
+
}
|
|
663
|
+
if (verbose) console.log("Getting recent commits...");
|
|
664
|
+
commits = await git.getUnmergedCommits();
|
|
665
|
+
if (verbose) console.log(`Found ${commits.length} unmerged commits`);
|
|
494
666
|
}
|
|
495
|
-
if (verbose) console.log("Getting recent commits...");
|
|
496
|
-
const commits = await git.getUnmergedCommits();
|
|
497
|
-
if (verbose) console.log(`Found ${commits.length} unmerged commits`);
|
|
498
667
|
console.log("Generating changelog with AI...");
|
|
499
668
|
const generated = await ai.generateChangelog(commits, staged);
|
|
500
669
|
if (generated.sections.length === 0) {
|
|
@@ -511,19 +680,33 @@ async function run(options) {
|
|
|
511
680
|
}
|
|
512
681
|
}
|
|
513
682
|
}
|
|
514
|
-
const
|
|
683
|
+
const [author, commitHash] = await Promise.all([
|
|
684
|
+
git.getCurrentUser(),
|
|
685
|
+
git.getHeadCommit()
|
|
686
|
+
]);
|
|
687
|
+
const metadata = {
|
|
688
|
+
author: author || void 0,
|
|
689
|
+
commitHash: commitHash || void 0,
|
|
690
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
691
|
+
};
|
|
692
|
+
if (verbose) {
|
|
693
|
+
console.log("Metadata:", metadata);
|
|
694
|
+
}
|
|
695
|
+
const newContent = changelog.update(generated, void 0, metadata);
|
|
515
696
|
if (options.dryRun) {
|
|
516
697
|
console.log("\n--- DRY RUN ---");
|
|
517
698
|
console.log("Would update changelog at:", changelog.getFilePath());
|
|
518
699
|
console.log("\n--- New entry ---");
|
|
519
|
-
console.log(changelog.formatEntry(generated));
|
|
700
|
+
console.log(changelog.formatEntry(generated, void 0, metadata));
|
|
520
701
|
console.log("--- End ---\n");
|
|
521
702
|
process.exit(0);
|
|
522
703
|
}
|
|
523
704
|
changelog.write(newContent);
|
|
524
705
|
console.log(`Updated: ${changelog.getFilePath()}`);
|
|
525
|
-
|
|
526
|
-
|
|
706
|
+
if (!options.fromCommits) {
|
|
707
|
+
await git.stageFile(config.changelog.file);
|
|
708
|
+
console.log("Staged changelog file");
|
|
709
|
+
}
|
|
527
710
|
console.log("Done!");
|
|
528
711
|
} catch (error) {
|
|
529
712
|
console.error("Error:", error instanceof Error ? error.message : error);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/git.ts","../src/ai.ts","../src/changelog.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { loadConfig, initConfig } from './config.js';\nimport { GitService } from './git.js';\nimport { AIService } from './ai.js';\nimport { ChangelogService } from './changelog.js';\nimport type { CLIOptions } from './types.js';\n\nconst program = new Command();\n\nprogram\n .name('tst-changelog')\n .description('AI-powered changelog generator using OpenRouter')\n .version('0.1.0');\n\nprogram\n .command('init')\n .description('Create a default configuration file')\n .option('-f, --file <name>', 'Config file name', 'tst-changelog.yaml')\n .action((options: { file: string }) => {\n try {\n const filePath = initConfig(options.file);\n console.log(`Created config file: ${filePath}`);\n console.log('\\nNext steps:');\n console.log('1. Set the OPENROUTER_API_KEY environment variable');\n console.log('2. Adjust settings in the config file as needed');\n console.log('3. Run: npx tst-changelog generate');\n } catch (error) {\n console.error('Error:', error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('generate', { isDefault: true })\n .description('Generate changelog from staged changes')\n .option('-c, --config <path>', 'Path to config file')\n .option('-d, --dry-run', 'Preview without modifying files')\n .option('-v, --verbose', 'Enable verbose output')\n .action(run);\n\nasync function run(options: CLIOptions): Promise<void> {\n const verbose = options.verbose ?? false;\n\n try {\n // Load configuration\n if (verbose) console.log('Loading configuration...');\n const config = loadConfig(options.config);\n if (verbose) console.log('Config loaded:', JSON.stringify(config, null, 2));\n\n // Initialize services\n const git = new GitService(config.git);\n const ai = new AIService(config.ai);\n const changelog = new ChangelogService(config.changelog);\n\n // Check if we're in a git repo\n if (!(await git.isGitRepo())) {\n console.error('Error: Not a git repository');\n process.exit(1);\n }\n\n // Get staged changes\n if (verbose) console.log('Getting staged changes...');\n const staged = await git.getStagedChanges();\n\n if (staged.files.length === 0) {\n console.log('No staged changes found. Stage some changes first.');\n process.exit(0);\n }\n\n if (verbose) {\n console.log(`Staged files: ${staged.files.join(', ')}`);\n console.log(`Diff length: ${staged.diff.length} chars`);\n }\n\n // Get recent commits for context\n if (verbose) console.log('Getting recent commits...');\n const commits = await git.getUnmergedCommits();\n if (verbose) console.log(`Found ${commits.length} unmerged commits`);\n\n // Generate changelog using AI\n console.log('Generating changelog with AI...');\n const generated = await ai.generateChangelog(commits, staged);\n\n if (generated.sections.length === 0) {\n console.log('No changelog entries generated.');\n if (verbose) console.log('Raw AI response:', generated.raw);\n process.exit(0);\n }\n\n if (verbose) {\n console.log('Generated sections:');\n for (const section of generated.sections) {\n console.log(` ${section.type}:`);\n for (const item of section.items) {\n console.log(` - ${item}`);\n }\n }\n }\n\n // Update changelog\n const newContent = changelog.update(generated);\n\n if (options.dryRun) {\n console.log('\\n--- DRY RUN ---');\n console.log('Would update changelog at:', changelog.getFilePath());\n console.log('\\n--- New entry ---');\n console.log(changelog.formatEntry(generated));\n console.log('--- End ---\\n');\n process.exit(0);\n }\n\n // Write and stage\n changelog.write(newContent);\n console.log(`Updated: ${changelog.getFilePath()}`);\n\n await git.stageFile(config.changelog.file);\n console.log('Staged changelog file');\n\n console.log('Done!');\n } catch (error) {\n console.error('Error:', error instanceof Error ? error.message : error);\n if (verbose && error instanceof Error) {\n console.error(error.stack);\n }\n process.exit(1);\n }\n}\n\nprogram.parse();\n\n// Export for programmatic usage\nexport { loadConfig, initConfig } from './config.js';\nexport { GitService } from './git.js';\nexport { AIService } from './ai.js';\nexport { ChangelogService } from './changelog.js';\nexport * from './types.js';\n","import { readFileSync, existsSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse, stringify } from 'yaml';\nimport type { Config } from './types.js';\n\nconst DEFAULT_CONFIG: Config = {\n ai: {\n provider: 'openrouter',\n model: 'anthropic/claude-3-haiku',\n token: '',\n },\n changelog: {\n file: 'CHANGELOG.md',\n format: 'keepachangelog',\n includeDate: true,\n groupBy: 'type',\n },\n git: {\n baseBranch: 'main',\n analyzeDepth: 50,\n },\n};\n\n/**\n * Resolve environment variables in config values\n * Supports ${ENV_VAR} syntax\n */\nfunction resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\n/**\n * Recursively resolve env vars in object\n */\nfunction resolveConfigEnvVars<T>(obj: T): T {\n if (typeof obj === 'string') {\n return resolveEnvVars(obj) as T;\n }\n if (Array.isArray(obj)) {\n return obj.map(resolveConfigEnvVars) as T;\n }\n if (obj && typeof obj === 'object') {\n const resolved: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n resolved[key] = resolveConfigEnvVars(value);\n }\n return resolved as T;\n }\n return obj;\n}\n\n/**\n * Deep merge two objects\n */\nfunction deepMerge(target: Config, source: Partial<Config>): Config {\n return {\n ai: { ...target.ai, ...source.ai },\n changelog: { ...target.changelog, ...source.changelog },\n git: { ...target.git, ...source.git },\n };\n}\n\n/**\n * Load configuration from yaml file\n */\nexport function loadConfig(configPath?: string): Config {\n const defaultPaths = ['tst-changelog.yaml', 'tst-changelog.yml', '.tst-changelog.yaml'];\n\n let filePath: string | undefined;\n\n if (configPath) {\n filePath = resolve(process.cwd(), configPath);\n if (!existsSync(filePath)) {\n throw new Error(`Config file not found: ${filePath}`);\n }\n } else {\n for (const p of defaultPaths) {\n const fullPath = resolve(process.cwd(), p);\n if (existsSync(fullPath)) {\n filePath = fullPath;\n break;\n }\n }\n }\n\n if (!filePath) {\n console.warn('No config file found, using defaults');\n return resolveConfigEnvVars(DEFAULT_CONFIG);\n }\n\n const content = readFileSync(filePath, 'utf-8');\n const parsed = parse(content) as Partial<Config>;\n\n const merged = deepMerge(DEFAULT_CONFIG, parsed);\n return resolveConfigEnvVars(merged);\n}\n\n/**\n * Initialize a new config file with defaults\n */\nexport function initConfig(fileName = 'tst-changelog.yaml'): string {\n const filePath = resolve(process.cwd(), fileName);\n\n if (existsSync(filePath)) {\n throw new Error(`Config file already exists: ${filePath}`);\n }\n\n const configTemplate: Config = {\n ai: {\n provider: 'openrouter',\n model: 'anthropic/claude-3-haiku',\n token: '${OPENROUTER_API_KEY}',\n },\n changelog: {\n file: 'CHANGELOG.md',\n format: 'keepachangelog',\n includeDate: true,\n groupBy: 'type',\n },\n git: {\n baseBranch: 'main',\n analyzeDepth: 50,\n },\n };\n\n const content = stringify(configTemplate, { lineWidth: 0 });\n writeFileSync(filePath, content, 'utf-8');\n\n return filePath;\n}\n","import simpleGit, { SimpleGit } from 'simple-git';\nimport type { CommitInfo, StagedChanges, GitConfig } from './types.js';\n\nexport class GitService {\n private git: SimpleGit;\n private config: GitConfig;\n\n constructor(config: GitConfig, cwd?: string) {\n this.git = simpleGit(cwd);\n this.config = config;\n }\n\n /**\n * Get list of staged files\n */\n async getStagedFiles(): Promise<string[]> {\n const status = await this.git.status();\n return status.staged;\n }\n\n /**\n * Get diff of staged changes\n */\n async getStagedDiff(): Promise<string> {\n const diff = await this.git.diff(['--cached']);\n return diff;\n }\n\n /**\n * Get staged changes (files + diff)\n */\n async getStagedChanges(): Promise<StagedChanges> {\n const [files, diff] = await Promise.all([\n this.getStagedFiles(),\n this.getStagedDiff(),\n ]);\n return { files, diff };\n }\n\n /**\n * Get recent commits from the base branch\n */\n async getRecentCommits(count?: number): Promise<CommitInfo[]> {\n const limit = count ?? this.config.analyzeDepth;\n const log = await this.git.log({\n maxCount: limit,\n format: {\n hash: '%H',\n message: '%s',\n author: '%an',\n date: '%aI',\n body: '%b',\n },\n });\n\n return log.all.map((commit) => ({\n hash: commit.hash,\n message: commit.message,\n author: commit.author,\n date: commit.date,\n body: commit.body?.trim() || undefined,\n }));\n }\n\n /**\n * Get commits not yet in base branch (for feature branches)\n */\n async getUnmergedCommits(): Promise<CommitInfo[]> {\n try {\n const log = await this.git.log({\n from: this.config.baseBranch,\n to: 'HEAD',\n format: {\n hash: '%H',\n message: '%s',\n author: '%an',\n date: '%aI',\n body: '%b',\n },\n });\n\n return log.all.map((commit) => ({\n hash: commit.hash,\n message: commit.message,\n author: commit.author,\n date: commit.date,\n body: commit.body?.trim() || undefined,\n }));\n } catch {\n // If base branch doesn't exist, return empty\n return [];\n }\n }\n\n /**\n * Get current branch name\n */\n async getCurrentBranch(): Promise<string> {\n const branch = await this.git.revparse(['--abbrev-ref', 'HEAD']);\n return branch.trim();\n }\n\n /**\n * Stage a file\n */\n async stageFile(filePath: string): Promise<void> {\n await this.git.add(filePath);\n }\n\n /**\n * Check if we're in a git repository\n */\n async isGitRepo(): Promise<boolean> {\n try {\n await this.git.revparse(['--git-dir']);\n return true;\n } catch {\n return false;\n }\n }\n}\n","import type { AIConfig, CommitInfo, StagedChanges, GeneratedChangelog, ChangelogSection, ChangeType } from './types.js';\n\nconst OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';\n\nconst SYSTEM_PROMPT = `You are a changelog generator. Your task is to analyze git commits and staged changes to generate changelog entries following the Keep a Changelog format.\n\nOutput Format:\nReturn ONLY a JSON object with the following structure:\n{\n \"sections\": [\n {\n \"type\": \"Added\" | \"Changed\" | \"Deprecated\" | \"Removed\" | \"Fixed\" | \"Security\",\n \"items\": [\"description 1\", \"description 2\"]\n }\n ]\n}\n\nGuidelines:\n- Use present tense (e.g., \"Add feature\" not \"Added feature\")\n- Be concise but descriptive\n- Group related changes together\n- Focus on user-facing changes\n- Ignore merge commits and trivial changes\n- Parse conventional commits (feat:, fix:, etc.) into appropriate sections:\n - feat: -> Added\n - fix: -> Fixed\n - docs:, style:, refactor:, perf:, test:, chore: -> Changed\n - BREAKING CHANGE: -> Changed (mention breaking)\n - deprecate: -> Deprecated\n - remove: -> Removed\n - security: -> Security`;\n\ninterface OpenRouterResponse {\n choices: Array<{\n message: {\n content: string;\n };\n }>;\n}\n\nexport class AIService {\n private config: AIConfig;\n\n constructor(config: AIConfig) {\n this.config = config;\n }\n\n /**\n * Generate changelog entries from commits and staged changes\n */\n async generateChangelog(\n commits: CommitInfo[],\n staged: StagedChanges\n ): Promise<GeneratedChangelog> {\n const userPrompt = this.buildPrompt(commits, staged);\n const response = await this.callOpenRouter(userPrompt);\n return this.parseResponse(response);\n }\n\n private buildPrompt(commits: CommitInfo[], staged: StagedChanges): string {\n const parts: string[] = [];\n\n if (commits.length > 0) {\n parts.push('## Recent Commits:');\n for (const commit of commits.slice(0, 20)) {\n parts.push(`- ${commit.message}`);\n if (commit.body) {\n parts.push(` ${commit.body}`);\n }\n }\n }\n\n if (staged.files.length > 0) {\n parts.push('\\n## Staged Files:');\n parts.push(staged.files.join('\\n'));\n }\n\n if (staged.diff) {\n // Limit diff size to avoid token limits\n const maxDiffLength = 4000;\n const truncatedDiff = staged.diff.length > maxDiffLength\n ? staged.diff.slice(0, maxDiffLength) + '\\n... (truncated)'\n : staged.diff;\n parts.push('\\n## Staged Diff:');\n parts.push('```diff');\n parts.push(truncatedDiff);\n parts.push('```');\n }\n\n parts.push('\\nGenerate changelog entries for these changes.');\n\n return parts.join('\\n');\n }\n\n private async callOpenRouter(prompt: string): Promise<string> {\n const response = await fetch(OPENROUTER_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.config.token}`,\n 'HTTP-Referer': 'https://github.com/testudosrl/tst-libs',\n 'X-Title': 'tst-changelog',\n },\n body: JSON.stringify({\n model: this.config.model,\n messages: [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: prompt },\n ],\n temperature: 0.3,\n max_tokens: 1000,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`OpenRouter API error: ${response.status} - ${error}`);\n }\n\n const data = (await response.json()) as OpenRouterResponse;\n return data.choices[0]?.message?.content ?? '';\n }\n\n private parseResponse(response: string): GeneratedChangelog {\n try {\n // Extract JSON from response (handle markdown code blocks or raw JSON)\n let jsonStr = response;\n\n // Try markdown code block first\n const codeBlockMatch = response.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n if (codeBlockMatch) {\n jsonStr = codeBlockMatch[1];\n } else {\n // Try to find JSON object in the response\n const jsonObjectMatch = response.match(/\\{[\\s\\S]*\"sections\"[\\s\\S]*\\}/);\n if (jsonObjectMatch) {\n jsonStr = jsonObjectMatch[0];\n }\n }\n\n const parsed = JSON.parse(jsonStr.trim()) as { sections: ChangelogSection[] };\n\n // Validate and normalize sections\n const validTypes: ChangeType[] = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'];\n const sections = parsed.sections\n .filter((s) => validTypes.includes(s.type))\n .map((s) => ({\n type: s.type,\n items: Array.isArray(s.items) ? s.items.filter((i) => typeof i === 'string') : [],\n }))\n .filter((s) => s.items.length > 0);\n\n return {\n raw: response,\n sections,\n };\n } catch (error) {\n console.error('Failed to parse AI response:', error);\n console.error('Raw response:', response);\n return {\n raw: response,\n sections: [],\n };\n }\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { ChangelogConfig, ChangelogSection, GeneratedChangelog } from './types.js';\n\nconst KEEPACHANGELOG_HEADER = `# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n`;\n\nconst SECTION_ORDER = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'] as const;\n\nexport class ChangelogService {\n private config: ChangelogConfig;\n private filePath: string;\n\n constructor(config: ChangelogConfig, cwd?: string) {\n this.config = config;\n this.filePath = resolve(cwd ?? process.cwd(), config.file);\n }\n\n /**\n * Read existing changelog content\n */\n read(): string | null {\n if (!existsSync(this.filePath)) {\n return null;\n }\n return readFileSync(this.filePath, 'utf-8');\n }\n\n /**\n * Generate new changelog entry\n */\n formatEntry(generated: GeneratedChangelog, version?: string): string {\n const date = this.config.includeDate\n ? new Date().toISOString().split('T')[0]\n : '';\n\n const versionStr = version ?? 'Unreleased';\n const headerLine = date ? `## [${versionStr}] - ${date}` : `## [${versionStr}]`;\n\n const sections = this.sortSections(generated.sections);\n const sectionLines = sections\n .map((section) => this.formatSection(section))\n .join('\\n\\n');\n\n return `${headerLine}\\n\\n${sectionLines}`;\n }\n\n private sortSections(sections: ChangelogSection[]): ChangelogSection[] {\n return [...sections].sort((a, b) => {\n const indexA = SECTION_ORDER.indexOf(a.type as typeof SECTION_ORDER[number]);\n const indexB = SECTION_ORDER.indexOf(b.type as typeof SECTION_ORDER[number]);\n return indexA - indexB;\n });\n }\n\n private formatSection(section: ChangelogSection): string {\n const items = section.items.map((item) => `- ${item}`).join('\\n');\n return `### ${section.type}\\n\\n${items}`;\n }\n\n /**\n * Update or create changelog with new entry\n */\n update(generated: GeneratedChangelog, version?: string): string {\n const newEntry = this.formatEntry(generated, version);\n const existing = this.read();\n\n if (!existing) {\n // Create new changelog\n return KEEPACHANGELOG_HEADER + newEntry + '\\n';\n }\n\n // Find where to insert new entry (after header, before first version)\n const lines = existing.split('\\n');\n let insertIndex = -1;\n\n // Look for ## [Unreleased] or first ## [ section\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.startsWith('## [Unreleased]')) {\n // Replace unreleased section\n const endIndex = this.findSectionEnd(lines, i + 1);\n const before = lines.slice(0, i).join('\\n');\n const after = lines.slice(endIndex).join('\\n');\n return before + (before.endsWith('\\n') ? '' : '\\n') + newEntry + '\\n\\n' + after;\n }\n if (line.startsWith('## [') && insertIndex === -1) {\n insertIndex = i;\n break;\n }\n }\n\n if (insertIndex === -1) {\n // No version sections found, append after header\n return existing.trimEnd() + '\\n\\n' + newEntry + '\\n';\n }\n\n // Insert before first version\n const before = lines.slice(0, insertIndex).join('\\n');\n const after = lines.slice(insertIndex).join('\\n');\n return before + (before.endsWith('\\n') ? '' : '\\n') + newEntry + '\\n\\n' + after;\n }\n\n private findSectionEnd(lines: string[], startIndex: number): number {\n for (let i = startIndex; i < lines.length; i++) {\n if (lines[i].startsWith('## [')) {\n return i;\n }\n }\n return lines.length;\n }\n\n /**\n * Write changelog to file\n */\n write(content: string): void {\n writeFileSync(this.filePath, content, 'utf-8');\n }\n\n /**\n * Get the file path\n */\n getFilePath(): string {\n return this.filePath;\n }\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;;;ACAxB,SAAS,cAAc,YAAY,qBAAqB;AACxD,SAAS,eAAe;AACxB,SAAS,OAAO,iBAAiB;AAGjC,IAAM,iBAAyB;AAAA,EAC7B,IAAI;AAAA,IACF,UAAU;AAAA,IACV,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AAAA,EACA,KAAK;AAAA,IACH,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AACF;AAMA,SAAS,eAAe,OAAuB;AAC7C,SAAO,MAAM,QAAQ,kBAAkB,CAAC,GAAG,WAAW;AACpD,UAAM,WAAW,QAAQ,IAAI,MAAM;AACnC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,wBAAwB,MAAM,aAAa;AAAA,IAC7D;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAKA,SAAS,qBAAwB,KAAW;AAC1C,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO,eAAe,GAAG;AAAA,EAC3B;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,oBAAoB;AAAA,EACrC;AACA,MAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,UAAM,WAAoC,CAAC;AAC3C,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,eAAS,GAAG,IAAI,qBAAqB,KAAK;AAAA,IAC5C;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAKA,SAAS,UAAU,QAAgB,QAAiC;AAClE,SAAO;AAAA,IACL,IAAI,EAAE,GAAG,OAAO,IAAI,GAAG,OAAO,GAAG;AAAA,IACjC,WAAW,EAAE,GAAG,OAAO,WAAW,GAAG,OAAO,UAAU;AAAA,IACtD,KAAK,EAAE,GAAG,OAAO,KAAK,GAAG,OAAO,IAAI;AAAA,EACtC;AACF;AAKO,SAAS,WAAW,YAA6B;AACtD,QAAM,eAAe,CAAC,sBAAsB,qBAAqB,qBAAqB;AAEtF,MAAI;AAEJ,MAAI,YAAY;AACd,eAAW,QAAQ,QAAQ,IAAI,GAAG,UAAU;AAC5C,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,YAAM,IAAI,MAAM,0BAA0B,QAAQ,EAAE;AAAA,IACtD;AAAA,EACF,OAAO;AACL,eAAW,KAAK,cAAc;AAC5B,YAAM,WAAW,QAAQ,QAAQ,IAAI,GAAG,CAAC;AACzC,UAAI,WAAW,QAAQ,GAAG;AACxB,mBAAW;AACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,UAAU;AACb,YAAQ,KAAK,sCAAsC;AACnD,WAAO,qBAAqB,cAAc;AAAA,EAC5C;AAEA,QAAM,UAAU,aAAa,UAAU,OAAO;AAC9C,QAAM,SAAS,MAAM,OAAO;AAE5B,QAAM,SAAS,UAAU,gBAAgB,MAAM;AAC/C,SAAO,qBAAqB,MAAM;AACpC;AAKO,SAAS,WAAW,WAAW,sBAA8B;AAClE,QAAM,WAAW,QAAQ,QAAQ,IAAI,GAAG,QAAQ;AAEhD,MAAI,WAAW,QAAQ,GAAG;AACxB,UAAM,IAAI,MAAM,+BAA+B,QAAQ,EAAE;AAAA,EAC3D;AAEA,QAAM,iBAAyB;AAAA,IAC7B,IAAI;AAAA,MACF,UAAU;AAAA,MACV,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAAA,IACA,WAAW;AAAA,MACT,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,KAAK;AAAA,MACH,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,UAAU,gBAAgB,EAAE,WAAW,EAAE,CAAC;AAC1D,gBAAc,UAAU,SAAS,OAAO;AAExC,SAAO;AACT;;;ACvIA,OAAO,eAA8B;AAG9B,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EAER,YAAY,QAAmB,KAAc;AAC3C,SAAK,MAAM,UAAU,GAAG;AACxB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAoC;AACxC,UAAM,SAAS,MAAM,KAAK,IAAI,OAAO;AACrC,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAiC;AACrC,UAAM,OAAO,MAAM,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC;AAC7C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAA2C;AAC/C,UAAM,CAAC,OAAO,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,MACtC,KAAK,eAAe;AAAA,MACpB,KAAK,cAAc;AAAA,IACrB,CAAC;AACD,WAAO,EAAE,OAAO,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAAuC;AAC5D,UAAM,QAAQ,SAAS,KAAK,OAAO;AACnC,UAAM,MAAM,MAAM,KAAK,IAAI,IAAI;AAAA,MAC7B,UAAU;AAAA,MACV,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF,CAAC;AAED,WAAO,IAAI,IAAI,IAAI,CAAC,YAAY;AAAA,MAC9B,MAAM,OAAO;AAAA,MACb,SAAS,OAAO;AAAA,MAChB,QAAQ,OAAO;AAAA,MACf,MAAM,OAAO;AAAA,MACb,MAAM,OAAO,MAAM,KAAK,KAAK;AAAA,IAC/B,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAA4C;AAChD,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,IAAI,IAAI;AAAA,QAC7B,MAAM,KAAK,OAAO;AAAA,QAClB,IAAI;AAAA,QACJ,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF,CAAC;AAED,aAAO,IAAI,IAAI,IAAI,CAAC,YAAY;AAAA,QAC9B,MAAM,OAAO;AAAA,QACb,SAAS,OAAO;AAAA,QAChB,QAAQ,OAAO;AAAA,QACf,MAAM,OAAO;AAAA,QACb,MAAM,OAAO,MAAM,KAAK,KAAK;AAAA,MAC/B,EAAE;AAAA,IACJ,QAAQ;AAEN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAoC;AACxC,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,CAAC,gBAAgB,MAAM,CAAC;AAC/D,WAAO,OAAO,KAAK;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,UAAiC;AAC/C,UAAM,KAAK,IAAI,IAAI,QAAQ;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA8B;AAClC,QAAI;AACF,YAAM,KAAK,IAAI,SAAS,CAAC,WAAW,CAAC;AACrC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACtHA,IAAM,qBAAqB;AAE3B,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoCf,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EAER,YAAY,QAAkB;AAC5B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBACJ,SACA,QAC6B;AAC7B,UAAM,aAAa,KAAK,YAAY,SAAS,MAAM;AACnD,UAAM,WAAW,MAAM,KAAK,eAAe,UAAU;AACrD,WAAO,KAAK,cAAc,QAAQ;AAAA,EACpC;AAAA,EAEQ,YAAY,SAAuB,QAA+B;AACxE,UAAM,QAAkB,CAAC;AAEzB,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,KAAK,oBAAoB;AAC/B,iBAAW,UAAU,QAAQ,MAAM,GAAG,EAAE,GAAG;AACzC,cAAM,KAAK,KAAK,OAAO,OAAO,EAAE;AAChC,YAAI,OAAO,MAAM;AACf,gBAAM,KAAK,KAAK,OAAO,IAAI,EAAE;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,MAAM,SAAS,GAAG;AAC3B,YAAM,KAAK,oBAAoB;AAC/B,YAAM,KAAK,OAAO,MAAM,KAAK,IAAI,CAAC;AAAA,IACpC;AAEA,QAAI,OAAO,MAAM;AAEf,YAAM,gBAAgB;AACtB,YAAM,gBAAgB,OAAO,KAAK,SAAS,gBACvC,OAAO,KAAK,MAAM,GAAG,aAAa,IAAI,sBACtC,OAAO;AACX,YAAM,KAAK,mBAAmB;AAC9B,YAAM,KAAK,SAAS;AACpB,YAAM,KAAK,aAAa;AACxB,YAAM,KAAK,KAAK;AAAA,IAClB;AAEA,UAAM,KAAK,iDAAiD;AAE5D,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAAA,EAEA,MAAc,eAAe,QAAiC;AAC5D,UAAM,WAAW,MAAM,MAAM,oBAAoB;AAAA,MAC/C,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB,UAAU,KAAK,OAAO,KAAK;AAAA,QAC5C,gBAAgB;AAAA,QAChB,WAAW;AAAA,MACb;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,OAAO,KAAK,OAAO;AAAA,QACnB,UAAU;AAAA,UACR,EAAE,MAAM,UAAU,SAAS,cAAc;AAAA,UACzC,EAAE,MAAM,QAAQ,SAAS,OAAO;AAAA,QAClC;AAAA,QACA,aAAa;AAAA,QACb,YAAY;AAAA,MACd,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM,SAAS,KAAK;AAClC,YAAM,IAAI,MAAM,yBAAyB,SAAS,MAAM,MAAM,KAAK,EAAE;AAAA,IACvE;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,KAAK,QAAQ,CAAC,GAAG,SAAS,WAAW;AAAA,EAC9C;AAAA,EAEQ,cAAc,UAAsC;AAC1D,QAAI;AAEF,UAAI,UAAU;AAGd,YAAM,iBAAiB,SAAS,MAAM,8BAA8B;AACpE,UAAI,gBAAgB;AAClB,kBAAU,eAAe,CAAC;AAAA,MAC5B,OAAO;AAEL,cAAM,kBAAkB,SAAS,MAAM,8BAA8B;AACrE,YAAI,iBAAiB;AACnB,oBAAU,gBAAgB,CAAC;AAAA,QAC7B;AAAA,MACF;AAEA,YAAM,SAAS,KAAK,MAAM,QAAQ,KAAK,CAAC;AAGxC,YAAM,aAA2B,CAAC,SAAS,WAAW,cAAc,WAAW,SAAS,UAAU;AAClG,YAAM,WAAW,OAAO,SACrB,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,IAAI,CAAC,EACzC,IAAI,CAAC,OAAO;AAAA,QACX,MAAM,EAAE;AAAA,QACR,OAAO,MAAM,QAAQ,EAAE,KAAK,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,OAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,MAClF,EAAE,EACD,OAAO,CAAC,MAAM,EAAE,MAAM,SAAS,CAAC;AAEnC,aAAO;AAAA,QACL,KAAK;AAAA,QACL;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,gCAAgC,KAAK;AACnD,cAAQ,MAAM,iBAAiB,QAAQ;AACvC,aAAO;AAAA,QACL,KAAK;AAAA,QACL,UAAU,CAAC;AAAA,MACb;AAAA,IACF;AAAA,EACF;AACF;;;ACrKA,SAAS,gBAAAA,eAAc,iBAAAC,gBAAe,cAAAC,mBAAkB;AACxD,SAAS,WAAAC,gBAAe;AAGxB,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS9B,IAAM,gBAAgB,CAAC,SAAS,WAAW,cAAc,WAAW,SAAS,UAAU;AAEhF,IAAM,mBAAN,MAAuB;AAAA,EACpB;AAAA,EACA;AAAA,EAER,YAAY,QAAyB,KAAc;AACjD,SAAK,SAAS;AACd,SAAK,WAAWA,SAAQ,OAAO,QAAQ,IAAI,GAAG,OAAO,IAAI;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,OAAsB;AACpB,QAAI,CAACD,YAAW,KAAK,QAAQ,GAAG;AAC9B,aAAO;AAAA,IACT;AACA,WAAOF,cAAa,KAAK,UAAU,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,WAA+B,SAA0B;AACnE,UAAM,OAAO,KAAK,OAAO,eACrB,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,IACrC;AAEJ,UAAM,aAAa,WAAW;AAC9B,UAAM,aAAa,OAAO,OAAO,UAAU,OAAO,IAAI,KAAK,OAAO,UAAU;AAE5E,UAAM,WAAW,KAAK,aAAa,UAAU,QAAQ;AACrD,UAAM,eAAe,SAClB,IAAI,CAAC,YAAY,KAAK,cAAc,OAAO,CAAC,EAC5C,KAAK,MAAM;AAEd,WAAO,GAAG,UAAU;AAAA;AAAA,EAAO,YAAY;AAAA,EACzC;AAAA,EAEQ,aAAa,UAAkD;AACrE,WAAO,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM;AAClC,YAAM,SAAS,cAAc,QAAQ,EAAE,IAAoC;AAC3E,YAAM,SAAS,cAAc,QAAQ,EAAE,IAAoC;AAC3E,aAAO,SAAS;AAAA,IAClB,CAAC;AAAA,EACH;AAAA,EAEQ,cAAc,SAAmC;AACvD,UAAM,QAAQ,QAAQ,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,EAAE,KAAK,IAAI;AAChE,WAAO,OAAO,QAAQ,IAAI;AAAA;AAAA,EAAO,KAAK;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,WAA+B,SAA0B;AAC9D,UAAM,WAAW,KAAK,YAAY,WAAW,OAAO;AACpD,UAAM,WAAW,KAAK,KAAK;AAE3B,QAAI,CAAC,UAAU;AAEb,aAAO,wBAAwB,WAAW;AAAA,IAC5C;AAGA,UAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,QAAI,cAAc;AAGlB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,OAAO,MAAM,CAAC;AACpB,UAAI,KAAK,WAAW,iBAAiB,GAAG;AAEtC,cAAM,WAAW,KAAK,eAAe,OAAO,IAAI,CAAC;AACjD,cAAMI,UAAS,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAC1C,cAAMC,SAAQ,MAAM,MAAM,QAAQ,EAAE,KAAK,IAAI;AAC7C,eAAOD,WAAUA,QAAO,SAAS,IAAI,IAAI,KAAK,QAAQ,WAAW,SAASC;AAAA,MAC5E;AACA,UAAI,KAAK,WAAW,MAAM,KAAK,gBAAgB,IAAI;AACjD,sBAAc;AACd;AAAA,MACF;AAAA,IACF;AAEA,QAAI,gBAAgB,IAAI;AAEtB,aAAO,SAAS,QAAQ,IAAI,SAAS,WAAW;AAAA,IAClD;AAGA,UAAM,SAAS,MAAM,MAAM,GAAG,WAAW,EAAE,KAAK,IAAI;AACpD,UAAM,QAAQ,MAAM,MAAM,WAAW,EAAE,KAAK,IAAI;AAChD,WAAO,UAAU,OAAO,SAAS,IAAI,IAAI,KAAK,QAAQ,WAAW,SAAS;AAAA,EAC5E;AAAA,EAEQ,eAAe,OAAiB,YAA4B;AAClE,aAAS,IAAI,YAAY,IAAI,MAAM,QAAQ,KAAK;AAC9C,UAAI,MAAM,CAAC,EAAE,WAAW,MAAM,GAAG;AAC/B,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO,MAAM;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAuB;AAC3B,IAAAJ,eAAc,KAAK,UAAU,SAAS,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AACF;;;AJ5HA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,eAAe,EACpB,YAAY,iDAAiD,EAC7D,QAAQ,OAAO;AAElB,QACG,QAAQ,MAAM,EACd,YAAY,qCAAqC,EACjD,OAAO,qBAAqB,oBAAoB,oBAAoB,EACpE,OAAO,CAAC,YAA8B;AACrC,MAAI;AACF,UAAM,WAAW,WAAW,QAAQ,IAAI;AACxC,YAAQ,IAAI,wBAAwB,QAAQ,EAAE;AAC9C,YAAQ,IAAI,eAAe;AAC3B,YAAQ,IAAI,oDAAoD;AAChE,YAAQ,IAAI,iDAAiD;AAC7D,YAAQ,IAAI,oCAAoC;AAAA,EAClD,SAAS,OAAO;AACd,YAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACtE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,YAAY,EAAE,WAAW,KAAK,CAAC,EACvC,YAAY,wCAAwC,EACpD,OAAO,uBAAuB,qBAAqB,EACnD,OAAO,iBAAiB,iCAAiC,EACzD,OAAO,iBAAiB,uBAAuB,EAC/C,OAAO,GAAG;AAEb,eAAe,IAAI,SAAoC;AACrD,QAAM,UAAU,QAAQ,WAAW;AAEnC,MAAI;AAEF,QAAI,QAAS,SAAQ,IAAI,0BAA0B;AACnD,UAAM,SAAS,WAAW,QAAQ,MAAM;AACxC,QAAI,QAAS,SAAQ,IAAI,kBAAkB,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAG1E,UAAM,MAAM,IAAI,WAAW,OAAO,GAAG;AACrC,UAAM,KAAK,IAAI,UAAU,OAAO,EAAE;AAClC,UAAM,YAAY,IAAI,iBAAiB,OAAO,SAAS;AAGvD,QAAI,CAAE,MAAM,IAAI,UAAU,GAAI;AAC5B,cAAQ,MAAM,6BAA6B;AAC3C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI,QAAS,SAAQ,IAAI,2BAA2B;AACpD,UAAM,SAAS,MAAM,IAAI,iBAAiB;AAE1C,QAAI,OAAO,MAAM,WAAW,GAAG;AAC7B,cAAQ,IAAI,oDAAoD;AAChE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,SAAS;AACX,cAAQ,IAAI,iBAAiB,OAAO,MAAM,KAAK,IAAI,CAAC,EAAE;AACtD,cAAQ,IAAI,gBAAgB,OAAO,KAAK,MAAM,QAAQ;AAAA,IACxD;AAGA,QAAI,QAAS,SAAQ,IAAI,2BAA2B;AACpD,UAAM,UAAU,MAAM,IAAI,mBAAmB;AAC7C,QAAI,QAAS,SAAQ,IAAI,SAAS,QAAQ,MAAM,mBAAmB;AAGnE,YAAQ,IAAI,iCAAiC;AAC7C,UAAM,YAAY,MAAM,GAAG,kBAAkB,SAAS,MAAM;AAE5D,QAAI,UAAU,SAAS,WAAW,GAAG;AACnC,cAAQ,IAAI,iCAAiC;AAC7C,UAAI,QAAS,SAAQ,IAAI,oBAAoB,UAAU,GAAG;AAC1D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,SAAS;AACX,cAAQ,IAAI,qBAAqB;AACjC,iBAAW,WAAW,UAAU,UAAU;AACxC,gBAAQ,IAAI,KAAK,QAAQ,IAAI,GAAG;AAChC,mBAAW,QAAQ,QAAQ,OAAO;AAChC,kBAAQ,IAAI,SAAS,IAAI,EAAE;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAGA,UAAM,aAAa,UAAU,OAAO,SAAS;AAE7C,QAAI,QAAQ,QAAQ;AAClB,cAAQ,IAAI,mBAAmB;AAC/B,cAAQ,IAAI,8BAA8B,UAAU,YAAY,CAAC;AACjE,cAAQ,IAAI,qBAAqB;AACjC,cAAQ,IAAI,UAAU,YAAY,SAAS,CAAC;AAC5C,cAAQ,IAAI,eAAe;AAC3B,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,cAAU,MAAM,UAAU;AAC1B,YAAQ,IAAI,YAAY,UAAU,YAAY,CAAC,EAAE;AAEjD,UAAM,IAAI,UAAU,OAAO,UAAU,IAAI;AACzC,YAAQ,IAAI,uBAAuB;AAEnC,YAAQ,IAAI,OAAO;AAAA,EACrB,SAAS,OAAO;AACd,YAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACtE,QAAI,WAAW,iBAAiB,OAAO;AACrC,cAAQ,MAAM,MAAM,KAAK;AAAA,IAC3B;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,QAAQ,MAAM;","names":["readFileSync","writeFileSync","existsSync","resolve","before","after"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/git.ts","../src/ai.ts","../src/changelog.ts"],"sourcesContent":["import 'dotenv/config';\nimport { Command } from 'commander';\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve } from 'node:path';\nimport { loadConfig, initConfig } from './config.js';\nimport { GitService } from './git.js';\nimport { AIService } from './ai.js';\nimport { ChangelogService } from './changelog.js';\nimport type { CLIOptions, ChangelogMetadata } from './types.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));\n\nconst program = new Command();\n\nprogram\n .name('tst-changelog')\n .description('AI-powered changelog generator using OpenRouter')\n .version(pkg.version);\n\nprogram\n .command('init')\n .description('Create a default configuration file')\n .option('-f, --file <name>', 'Config file name', 'tst-changelog.yaml')\n .action((options: { file: string }) => {\n try {\n const result = initConfig(options.file);\n console.log(`Created config file: ${result.configPath}`);\n\n if (result.huskyConfigured) {\n console.log(`Configured husky hook: ${result.huskyHook}`);\n }\n\n console.log('\\nNext steps:');\n console.log('1. Set the OPENROUTER_API_KEY environment variable');\n console.log('2. Adjust settings in the config file as needed');\n if (!result.huskyConfigured) {\n console.log('3. Run: npx tst-changelog generate');\n }\n } catch (error) {\n console.error('Error:', error instanceof Error ? error.message : error);\n process.exit(1);\n }\n });\n\nprogram\n .command('generate', { isDefault: true })\n .description('Generate changelog from staged changes or commit history')\n .option('-c, --config <path>', 'Path to config file')\n .option('-d, --dry-run', 'Preview without modifying files')\n .option('-v, --verbose', 'Enable verbose output')\n .option('--from-commits <count>', 'Generate from last N commits instead of staged changes', parseInt)\n .action(run);\n\nasync function run(options: CLIOptions): Promise<void> {\n const verbose = options.verbose ?? false;\n\n try {\n // Load configuration\n if (verbose) console.log('Loading configuration...');\n const config = loadConfig(options.config);\n if (verbose) console.log('Config loaded:', JSON.stringify(config, null, 2));\n\n // Initialize services\n const git = new GitService(config.git);\n const ai = new AIService(config.ai);\n const changelog = new ChangelogService(config.changelog);\n\n // Check if we're in a git repo\n if (!(await git.isGitRepo())) {\n console.error('Error: Not a git repository');\n process.exit(1);\n }\n\n let commits;\n let staged;\n\n if (options.fromCommits) {\n // Generate from commit history\n if (verbose) console.log(`Getting last ${options.fromCommits} commits...`);\n commits = await git.getRecentCommits(options.fromCommits);\n\n if (commits.length === 0) {\n console.log('No commits found.');\n process.exit(0);\n }\n\n if (verbose) console.log(`Found ${commits.length} commits`);\n\n // Empty staged for history mode\n staged = { files: [], diff: '' };\n } else {\n // Get staged changes (default behavior)\n if (verbose) console.log('Getting staged changes...');\n staged = await git.getStagedChanges();\n\n if (staged.files.length === 0) {\n console.log('No staged changes found. Stage some changes first.');\n process.exit(0);\n }\n\n if (verbose) {\n console.log(`Staged files: ${staged.files.join(', ')}`);\n console.log(`Diff length: ${staged.diff.length} chars`);\n }\n\n // Get recent commits for context\n if (verbose) console.log('Getting recent commits...');\n commits = await git.getUnmergedCommits();\n if (verbose) console.log(`Found ${commits.length} unmerged commits`);\n }\n\n // Generate changelog using AI\n console.log('Generating changelog with AI...');\n const generated = await ai.generateChangelog(commits, staged);\n\n if (generated.sections.length === 0) {\n console.log('No changelog entries generated.');\n if (verbose) console.log('Raw AI response:', generated.raw);\n process.exit(0);\n }\n\n if (verbose) {\n console.log('Generated sections:');\n for (const section of generated.sections) {\n console.log(` ${section.type}:`);\n for (const item of section.items) {\n console.log(` - ${item}`);\n }\n }\n }\n\n // Collect metadata for changelog entry\n const [author, commitHash] = await Promise.all([\n git.getCurrentUser(),\n git.getHeadCommit(),\n ]);\n\n const metadata: ChangelogMetadata = {\n author: author || undefined,\n commitHash: commitHash || undefined,\n timestamp: new Date(),\n };\n\n if (verbose) {\n console.log('Metadata:', metadata);\n }\n\n // Update changelog\n const newContent = changelog.update(generated, undefined, metadata);\n\n if (options.dryRun) {\n console.log('\\n--- DRY RUN ---');\n console.log('Would update changelog at:', changelog.getFilePath());\n console.log('\\n--- New entry ---');\n console.log(changelog.formatEntry(generated, undefined, metadata));\n console.log('--- End ---\\n');\n process.exit(0);\n }\n\n // Write changelog\n changelog.write(newContent);\n console.log(`Updated: ${changelog.getFilePath()}`);\n\n // Only auto-stage when using staged changes mode (not --from-commits)\n if (!options.fromCommits) {\n await git.stageFile(config.changelog.file);\n console.log('Staged changelog file');\n }\n\n console.log('Done!');\n } catch (error) {\n console.error('Error:', error instanceof Error ? error.message : error);\n if (verbose && error instanceof Error) {\n console.error(error.stack);\n }\n process.exit(1);\n }\n}\n\nprogram.parse();\n\n// Export for programmatic usage\nexport { loadConfig, initConfig, type InitResult } from './config.js';\nexport { GitService } from './git.js';\nexport { AIService } from './ai.js';\nexport { ChangelogService } from './changelog.js';\nexport * from './types.js';\n","import { readFileSync, existsSync, writeFileSync, appendFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse, stringify } from 'yaml';\nimport type { Config } from './types.js';\n\nconst DEFAULT_PROMPT = `You are a changelog generator. Your task is to analyze git commits and staged changes to generate changelog entries following the Keep a Changelog format.\n\nOutput Format:\nReturn ONLY a JSON object with the following structure:\n{\n \"sections\": [\n {\n \"type\": \"Added\" | \"Changed\" | \"Deprecated\" | \"Removed\" | \"Fixed\" | \"Security\",\n \"items\": [\"description 1\", \"description 2\"]\n }\n ]\n}\n\nGuidelines:\n- Use present tense (e.g., \"Add feature\" not \"Added feature\")\n- Be concise but descriptive\n- Group related changes together\n- Focus on user-facing changes\n- Ignore merge commits and trivial changes\n- Parse conventional commits (feat:, fix:, etc.) into appropriate sections:\n - feat: -> Added\n - fix: -> Fixed\n - docs:, style:, refactor:, perf:, test:, chore: -> Changed\n - BREAKING CHANGE: -> Changed (mention breaking)\n - deprecate: -> Deprecated\n - remove: -> Removed\n - security: -> Security`;\n\nconst DEFAULT_CONFIG: Config = {\n ai: {\n provider: 'openrouter',\n model: 'anthropic/claude-sonnet-4.5',\n token: '',\n prompt: DEFAULT_PROMPT,\n },\n changelog: {\n file: 'CHANGELOG.md',\n format: 'keepachangelog',\n includeDate: true,\n includeTime: true,\n includeAuthor: true,\n includeCommitLink: true,\n repoUrl: '',\n groupBy: 'type',\n },\n git: {\n baseBranch: 'main',\n analyzeDepth: 50,\n },\n};\n\n/**\n * Resolve environment variables in config values\n * Supports ${ENV_VAR} syntax\n */\nfunction resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\n/**\n * Recursively resolve env vars in object\n */\nfunction resolveConfigEnvVars<T>(obj: T): T {\n if (typeof obj === 'string') {\n return resolveEnvVars(obj) as T;\n }\n if (Array.isArray(obj)) {\n return obj.map(resolveConfigEnvVars) as T;\n }\n if (obj && typeof obj === 'object') {\n const resolved: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n resolved[key] = resolveConfigEnvVars(value);\n }\n return resolved as T;\n }\n return obj;\n}\n\n/**\n * Deep merge two objects\n */\nfunction deepMerge(target: Config, source: Partial<Config>): Config {\n return {\n ai: { ...target.ai, ...source.ai },\n changelog: { ...target.changelog, ...source.changelog },\n git: { ...target.git, ...source.git },\n };\n}\n\n/**\n * Load configuration from yaml file\n */\nexport function loadConfig(configPath?: string): Config {\n const defaultPaths = ['tst-changelog.yaml', 'tst-changelog.yml', '.tst-changelog.yaml'];\n\n let filePath: string | undefined;\n\n if (configPath) {\n filePath = resolve(process.cwd(), configPath);\n if (!existsSync(filePath)) {\n throw new Error(`Config file not found: ${filePath}`);\n }\n } else {\n for (const p of defaultPaths) {\n const fullPath = resolve(process.cwd(), p);\n if (existsSync(fullPath)) {\n filePath = fullPath;\n break;\n }\n }\n }\n\n if (!filePath) {\n console.warn('No config file found, using defaults');\n return resolveConfigEnvVars(DEFAULT_CONFIG);\n }\n\n const content = readFileSync(filePath, 'utf-8');\n const parsed = parse(content) as Partial<Config>;\n\n const merged = deepMerge(DEFAULT_CONFIG, parsed);\n return resolveConfigEnvVars(merged);\n}\n\nexport interface InitResult {\n configPath: string;\n huskyConfigured: boolean;\n huskyHook?: string;\n}\n\n/**\n * Initialize a new config file with defaults\n */\nexport function initConfig(fileName = 'tst-changelog.yaml'): InitResult {\n const filePath = resolve(process.cwd(), fileName);\n\n if (existsSync(filePath)) {\n throw new Error(`Config file already exists: ${filePath}`);\n }\n\n const configTemplate: Config = {\n ai: {\n provider: 'openrouter',\n model: 'anthropic/claude-sonnet-4.5',\n token: '${OPENROUTER_API_KEY}',\n prompt: DEFAULT_PROMPT,\n },\n changelog: {\n file: 'CHANGELOG.md',\n format: 'keepachangelog',\n includeDate: true,\n includeTime: true,\n includeAuthor: true,\n includeCommitLink: true,\n repoUrl: '',\n groupBy: 'type',\n },\n git: {\n baseBranch: 'main',\n analyzeDepth: 50,\n },\n };\n\n const content = stringify(configTemplate, { lineWidth: 0 });\n writeFileSync(filePath, content, 'utf-8');\n\n // Check for husky and configure hook\n const huskyResult = configureHusky();\n\n return {\n configPath: filePath,\n ...huskyResult,\n };\n}\n\n/**\n * Configure husky pre-commit hook if husky is present\n */\nfunction configureHusky(): { huskyConfigured: boolean; huskyHook?: string } {\n const huskyDir = resolve(process.cwd(), '.husky');\n\n if (!existsSync(huskyDir)) {\n return { huskyConfigured: false };\n }\n\n const preCommitPath = resolve(huskyDir, 'pre-commit');\n const command = 'npx tst-changelog';\n\n // Check if hook already exists\n if (existsSync(preCommitPath)) {\n const content = readFileSync(preCommitPath, 'utf-8');\n if (content.includes('tst-changelog')) {\n return { huskyConfigured: false }; // Already configured\n }\n // Append to existing hook\n appendFileSync(preCommitPath, `\\n${command}\\n`);\n } else {\n // Create new hook\n writeFileSync(preCommitPath, `#!/usr/bin/env sh\\n. \"$(dirname -- \"$0\")/_/husky.sh\"\\n\\n${command}\\n`, { mode: 0o755 });\n }\n\n return { huskyConfigured: true, huskyHook: preCommitPath };\n}\n","import simpleGit, { SimpleGit } from 'simple-git';\nimport type { CommitInfo, StagedChanges, GitConfig } from './types.js';\n\nexport class GitService {\n private git: SimpleGit;\n private config: GitConfig;\n\n constructor(config: GitConfig, cwd?: string) {\n this.git = simpleGit(cwd);\n this.config = config;\n }\n\n /**\n * Get list of staged files\n */\n async getStagedFiles(): Promise<string[]> {\n const status = await this.git.status();\n return status.staged;\n }\n\n /**\n * Get diff of staged changes\n */\n async getStagedDiff(): Promise<string> {\n const diff = await this.git.diff(['--cached']);\n return diff;\n }\n\n /**\n * Get staged changes (files + diff)\n */\n async getStagedChanges(): Promise<StagedChanges> {\n const [files, diff] = await Promise.all([\n this.getStagedFiles(),\n this.getStagedDiff(),\n ]);\n return { files, diff };\n }\n\n /**\n * Get recent commits from the base branch\n */\n async getRecentCommits(count?: number): Promise<CommitInfo[]> {\n const limit = count ?? this.config.analyzeDepth;\n const log = await this.git.log({\n maxCount: limit,\n format: {\n hash: '%H',\n message: '%s',\n author: '%an',\n date: '%aI',\n body: '%b',\n },\n });\n\n return log.all.map((commit) => ({\n hash: commit.hash,\n message: commit.message,\n author: commit.author,\n date: commit.date,\n body: commit.body?.trim() || undefined,\n }));\n }\n\n /**\n * Get commits not yet in base branch (for feature branches)\n */\n async getUnmergedCommits(): Promise<CommitInfo[]> {\n try {\n const log = await this.git.log({\n from: this.config.baseBranch,\n to: 'HEAD',\n format: {\n hash: '%H',\n message: '%s',\n author: '%an',\n date: '%aI',\n body: '%b',\n },\n });\n\n return log.all.map((commit) => ({\n hash: commit.hash,\n message: commit.message,\n author: commit.author,\n date: commit.date,\n body: commit.body?.trim() || undefined,\n }));\n } catch {\n // If base branch doesn't exist, return empty\n return [];\n }\n }\n\n /**\n * Get current branch name\n */\n async getCurrentBranch(): Promise<string> {\n const branch = await this.git.revparse(['--abbrev-ref', 'HEAD']);\n return branch.trim();\n }\n\n /**\n * Stage a file\n */\n async stageFile(filePath: string): Promise<void> {\n await this.git.add(filePath);\n }\n\n /**\n * Check if we're in a git repository\n */\n async isGitRepo(): Promise<boolean> {\n try {\n await this.git.revparse(['--git-dir']);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get current git user name\n */\n async getCurrentUser(): Promise<string> {\n try {\n const name = await this.git.getConfig('user.name');\n return name.value ?? '';\n } catch {\n return '';\n }\n }\n\n /**\n * Get HEAD commit hash\n */\n async getHeadCommit(): Promise<string> {\n try {\n const hash = await this.git.revparse(['HEAD']);\n return hash.trim();\n } catch {\n return '';\n }\n }\n}\n","import type { AIConfig, CommitInfo, StagedChanges, GeneratedChangelog, ChangelogSection, ChangeType } from './types.js';\n\nconst OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';\n\ninterface OpenRouterResponse {\n choices: Array<{\n message: {\n content: string;\n };\n }>;\n}\n\nexport class AIService {\n private config: AIConfig;\n\n constructor(config: AIConfig) {\n this.config = config;\n }\n\n /**\n * Generate changelog entries from commits and staged changes\n */\n async generateChangelog(\n commits: CommitInfo[],\n staged: StagedChanges\n ): Promise<GeneratedChangelog> {\n const userPrompt = this.buildPrompt(commits, staged);\n const response = await this.callOpenRouter(userPrompt);\n return this.parseResponse(response);\n }\n\n private buildPrompt(commits: CommitInfo[], staged: StagedChanges): string {\n const parts: string[] = [];\n\n if (commits.length > 0) {\n parts.push('## Recent Commits:');\n for (const commit of commits.slice(0, 20)) {\n parts.push(`- ${commit.message}`);\n if (commit.body) {\n parts.push(` ${commit.body}`);\n }\n }\n }\n\n if (staged.files.length > 0) {\n parts.push('\\n## Staged Files:');\n parts.push(staged.files.join('\\n'));\n }\n\n if (staged.diff) {\n // Limit diff size to avoid token limits\n const maxDiffLength = 4000;\n const truncatedDiff = staged.diff.length > maxDiffLength\n ? staged.diff.slice(0, maxDiffLength) + '\\n... (truncated)'\n : staged.diff;\n parts.push('\\n## Staged Diff:');\n parts.push('```diff');\n parts.push(truncatedDiff);\n parts.push('```');\n }\n\n parts.push('\\nGenerate changelog entries for these changes.');\n\n return parts.join('\\n');\n }\n\n private async callOpenRouter(prompt: string): Promise<string> {\n const response = await fetch(OPENROUTER_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.config.token}`,\n 'HTTP-Referer': 'https://github.com/testudosrl/tst-libs',\n 'X-Title': 'tst-changelog',\n },\n body: JSON.stringify({\n model: this.config.model,\n messages: [\n { role: 'system', content: this.config.prompt ?? '' },\n { role: 'user', content: prompt },\n ],\n temperature: 0.3,\n max_tokens: 1000,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`OpenRouter API error: ${response.status} - ${error}`);\n }\n\n const data = (await response.json()) as OpenRouterResponse;\n return data.choices[0]?.message?.content ?? '';\n }\n\n private parseResponse(response: string): GeneratedChangelog {\n try {\n // Extract JSON from response (handle markdown code blocks or raw JSON)\n let jsonStr = response;\n\n // Try markdown code block first\n const codeBlockMatch = response.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n if (codeBlockMatch) {\n jsonStr = codeBlockMatch[1];\n } else {\n // Try to find JSON object in the response\n const jsonObjectMatch = response.match(/\\{[\\s\\S]*\"sections\"[\\s\\S]*\\}/);\n if (jsonObjectMatch) {\n jsonStr = jsonObjectMatch[0];\n }\n }\n\n const parsed = JSON.parse(jsonStr.trim()) as { sections: ChangelogSection[] };\n\n // Validate and normalize sections\n const validTypes: ChangeType[] = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'];\n const sections = parsed.sections\n .filter((s) => validTypes.includes(s.type))\n .map((s) => ({\n type: s.type,\n items: Array.isArray(s.items) ? s.items.filter((i) => typeof i === 'string') : [],\n }))\n .filter((s) => s.items.length > 0);\n\n return {\n raw: response,\n sections,\n };\n } catch (error) {\n console.error('Failed to parse AI response:', error);\n console.error('Raw response:', response);\n return {\n raw: response,\n sections: [],\n };\n }\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { ChangelogConfig, ChangelogSection, GeneratedChangelog, ChangelogMetadata } from './types.js';\n\nconst KEEPACHANGELOG_HEADER = `# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n`;\n\nconst SECTION_ORDER = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'] as const;\n\nexport class ChangelogService {\n private config: ChangelogConfig;\n private filePath: string;\n\n constructor(config: ChangelogConfig, cwd?: string) {\n this.config = config;\n this.filePath = resolve(cwd ?? process.cwd(), config.file);\n }\n\n /**\n * Read existing changelog content\n */\n read(): string | null {\n if (!existsSync(this.filePath)) {\n return null;\n }\n return readFileSync(this.filePath, 'utf-8');\n }\n\n /**\n * Generate new changelog entry\n */\n formatEntry(generated: GeneratedChangelog, version?: string, metadata?: ChangelogMetadata): string {\n const versionStr = version ?? 'Unreleased';\n const headerParts: string[] = [`## [${versionStr}]`];\n\n // Date and time\n if (this.config.includeDate) {\n const now = metadata?.timestamp ?? new Date();\n let dateStr = now.toISOString().split('T')[0];\n if (this.config.includeTime) {\n const timeStr = now.toTimeString().split(' ')[0].slice(0, 5); // HH:MM\n dateStr += ` ${timeStr}`;\n }\n headerParts.push(`- ${dateStr}`);\n }\n\n // Author\n if (this.config.includeAuthor && metadata?.author) {\n headerParts.push(`by ${metadata.author}`);\n }\n\n // Commit link\n if (this.config.includeCommitLink && metadata?.commitHash) {\n const shortHash = metadata.commitHash.slice(0, 7);\n if (this.config.repoUrl) {\n const commitUrl = `${this.config.repoUrl.replace(/\\/$/, '')}/commit/${metadata.commitHash}`;\n headerParts.push(`([${shortHash}](${commitUrl}))`);\n } else {\n headerParts.push(`(${shortHash})`);\n }\n }\n\n const headerLine = headerParts.join(' ');\n\n const sections = this.sortSections(generated.sections);\n const sectionLines = sections\n .map((section) => this.formatSection(section))\n .join('\\n\\n');\n\n return `${headerLine}\\n\\n${sectionLines}`;\n }\n\n private sortSections(sections: ChangelogSection[]): ChangelogSection[] {\n return [...sections].sort((a, b) => {\n const indexA = SECTION_ORDER.indexOf(a.type as typeof SECTION_ORDER[number]);\n const indexB = SECTION_ORDER.indexOf(b.type as typeof SECTION_ORDER[number]);\n return indexA - indexB;\n });\n }\n\n private formatSection(section: ChangelogSection): string {\n const items = section.items.map((item) => `- ${item}`).join('\\n');\n return `### ${section.type}\\n\\n${items}`;\n }\n\n /**\n * Update or create changelog with new entry\n */\n update(generated: GeneratedChangelog, version?: string, metadata?: ChangelogMetadata): string {\n const existing = this.read();\n\n if (!existing) {\n // Create new changelog\n const newEntry = this.formatEntry(generated, version, metadata);\n return KEEPACHANGELOG_HEADER + newEntry + '\\n';\n }\n\n // Find where to insert new entry (after header, before first version)\n const lines = existing.split('\\n');\n let insertIndex = -1;\n\n // Look for ## [Unreleased] or first ## [ section\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.startsWith('## [Unreleased]')) {\n // Merge with existing unreleased section\n const endIndex = this.findSectionEnd(lines, i + 1);\n const existingSectionLines = lines.slice(i, endIndex);\n const mergedSection = this.mergeUnreleasedSection(existingSectionLines, generated, metadata);\n const before = lines.slice(0, i).join('\\n');\n const after = lines.slice(endIndex).join('\\n');\n return before + (before.endsWith('\\n') ? '' : '\\n') + mergedSection + '\\n' + after;\n }\n if (line.startsWith('## [') && insertIndex === -1) {\n insertIndex = i;\n break;\n }\n }\n\n const newEntry = this.formatEntry(generated, version, metadata);\n\n if (insertIndex === -1) {\n // No version sections found, append after header\n return existing.trimEnd() + '\\n\\n' + newEntry + '\\n';\n }\n\n // Insert before first version\n const before = lines.slice(0, insertIndex).join('\\n');\n const after = lines.slice(insertIndex).join('\\n');\n return before + (before.endsWith('\\n') ? '' : '\\n') + newEntry + '\\n\\n' + after;\n }\n\n /**\n * Merge new entries into existing Unreleased section\n */\n private mergeUnreleasedSection(\n existingLines: string[],\n generated: GeneratedChangelog,\n metadata?: ChangelogMetadata\n ): string {\n // Parse existing sections\n const existingSections: Map<string, string[]> = new Map();\n let currentSection: string | null = null;\n\n for (const line of existingLines) {\n if (line.startsWith('### ')) {\n currentSection = line.slice(4).trim();\n if (!existingSections.has(currentSection)) {\n existingSections.set(currentSection, []);\n }\n } else if (currentSection && line.startsWith('- ')) {\n existingSections.get(currentSection)!.push(line.slice(2));\n }\n }\n\n // Merge new sections\n for (const section of generated.sections) {\n const existing = existingSections.get(section.type) ?? [];\n // Add new items that don't already exist\n for (const item of section.items) {\n if (!existing.includes(item)) {\n existing.push(item);\n }\n }\n existingSections.set(section.type, existing);\n }\n\n // Build merged entry\n const versionStr = 'Unreleased';\n const headerParts: string[] = [`## [${versionStr}]`];\n\n if (this.config.includeDate) {\n const now = metadata?.timestamp ?? new Date();\n let dateStr = now.toISOString().split('T')[0];\n if (this.config.includeTime) {\n const timeStr = now.toTimeString().split(' ')[0].slice(0, 5);\n dateStr += ` ${timeStr}`;\n }\n headerParts.push(`- ${dateStr}`);\n }\n\n if (this.config.includeAuthor && metadata?.author) {\n headerParts.push(`by ${metadata.author}`);\n }\n\n if (this.config.includeCommitLink && metadata?.commitHash) {\n const shortHash = metadata.commitHash.slice(0, 7);\n if (this.config.repoUrl) {\n const commitUrl = `${this.config.repoUrl.replace(/\\/$/, '')}/commit/${metadata.commitHash}`;\n headerParts.push(`([${shortHash}](${commitUrl}))`);\n } else {\n headerParts.push(`(${shortHash})`);\n }\n }\n\n const headerLine = headerParts.join(' ');\n\n // Sort and format sections\n const sortedSections: ChangelogSection[] = [];\n for (const type of SECTION_ORDER) {\n const items = existingSections.get(type);\n if (items && items.length > 0) {\n sortedSections.push({ type, items });\n }\n }\n\n const sectionLines = sortedSections\n .map((section) => this.formatSection(section))\n .join('\\n\\n');\n\n return `${headerLine}\\n\\n${sectionLines}`;\n }\n\n private findSectionEnd(lines: string[], startIndex: number): number {\n for (let i = startIndex; i < lines.length; i++) {\n if (lines[i].startsWith('## [')) {\n return i;\n }\n }\n return lines.length;\n }\n\n /**\n * Write changelog to file\n */\n write(content: string): void {\n writeFileSync(this.filePath, content, 'utf-8');\n }\n\n /**\n * Get the file path\n */\n getFilePath(): string {\n return this.filePath;\n }\n}\n"],"mappings":"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","names":["readFileSync","resolve","readFileSync","writeFileSync","existsSync","resolve","newEntry","before","after","readFileSync","resolve"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tst-changelog",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "AI-powered changelog generator using OpenRouter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,14 +18,15 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"
|
|
21
|
+
"commander": "^12.1.0",
|
|
22
|
+
"dotenv": "^17.2.3",
|
|
22
23
|
"simple-git": "^3.27.0",
|
|
23
|
-
"
|
|
24
|
+
"yaml": "^2.7.0"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"
|
|
27
|
+
"@types/node": "^20.17.14",
|
|
27
28
|
"tsup": "^8.3.5",
|
|
28
|
-
"
|
|
29
|
+
"typescript": "^5.7.3"
|
|
29
30
|
},
|
|
30
31
|
"keywords": [
|
|
31
32
|
"changelog",
|