Conversation
- Fix double blank lines between changelog version sections - Ensure generated changelogs end with single newline for markdownlint - Remove duplicate backup file creation (now creates only timestamped backup) - Improve overall changelog formatting consistency Fixes spacing issues that were causing markdownlint failures and eliminates redundant backup files that were being created during changelog generation.
There was a problem hiding this comment.
Pull Request Overview
This PR bumps the package version and introduces advanced changelog generation and parsing enhancements, including scenario-based merging, backup/rollback support, and optimized repository analysis.
- Added a
getCommitHashmethod in GitService. - Refactored changelog generation in KeepChangelogGenerator with scenario-based merging and newline trimming.
- Enhanced ChangelogParser and MagicRelease with magicr detection, dynamic imports, and safe write with backups.
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/core/git/GitService.ts | Added getCommitHash method |
| src/core/generator/KeepChangelogGenerator.ts | Replaced merge logic with processEntriesWithExistingChangelog and updated generate |
| src/core/generator/ChangelogParser.ts | Introduced methods for magicr detection, section extraction, and raw parsing |
| src/core/MagicRelease.ts | Added scenario-based processing, safeWriteChangelog, and backup/rollback logic |
| package.json | Bumped version from 0.1.0-beta to 0.1.1-beta |
| CHANGELOG.md | Inserted magicr HTML comment marker and new release entries |
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds two new chatmode configurations; implements scenario-aware, incremental and append-only changelog parsing/generation with caching, batch AI processing, deduplication, changelog integrity checks, and safe write with timestamped backups and rollback in MagicRelease; updates CHANGELOG header and package metadata; and adds a Git utility to resolve refs to commit hashes. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant MagicRelease
participant ChangelogParser
participant GitService
participant KeepChangelogGenerator
Note over MagicRelease,ChangelogParser: Start generation
User->>MagicRelease: generate(options)
MagicRelease->>ChangelogParser: parse existing changelog (if present)
ChangelogParser-->>MagicRelease: documented commits, unreleased raw/content, latest version
alt existing changelog present
MagicRelease->>MagicRelease: deduplicate commits vs documented set
MagicRelease->>GitService: getCommitHash(HEAD) / fetch commits
GitService-->>MagicRelease: head hash / commit list
MagicRelease->>MagicRelease: detectConversionScenario -> currentScenario
MagicRelease->>MagicRelease: optimizeAIProcessing (batch, cache)
MagicRelease->>KeepChangelogGenerator: processEntriesWithExistingChangelog(entries, existingContent, ...)
else no usable changelog
MagicRelease->>GitService: fetch repo commits
GitService-->>MagicRelease: commit list
MagicRelease->>MagicRelease: analyzeRepositoryFull -> categorize commits
MagicRelease->>KeepChangelogGenerator: generate(entries, ...)
end
KeepChangelogGenerator-->>MagicRelease: merged/updated changelog content
MagicRelease->>MagicRelease: create timestamped backup
MagicRelease->>MagicRelease: safeWriteChangelog (write, rollback on failure, cleanup old backups)
MagicRelease-->>User: updated changelog or error + restore
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/core/generator/KeepChangelogGenerator.ts (1)
149-151: Repeated dynamicimport()hurts performance
processEntriesWithExistingChangelogimportsChangelogParseron every invocation.
Instantiate the parser once at the top-level (there’s already a past comment about this).
🧹 Nitpick comments (4)
.github/chatmodes/WG Code Sentinel.chatmode.md (1)
68-68: Remove trailing blank line for consistency.-.github/chatmodes/WG Code Alchemist.chatmode.md (1)
74-74: Remove trailing blank line for consistency.-src/core/generator/ChangelogParser.ts (1)
199-206: Case-sensitive magicr indicator may yield false negatives
content.includes('Generated by Magic Release (magicr)')fails if the comment is lower-cased or reformatted.
Use a case-insensitive regex to be resilient to manual edits.-return content.includes(magicrIndicator); +return new RegExp(magicrIndicator, 'i').test(content);src/core/generator/KeepChangelogGenerator.ts (1)
606-615: Redundantcase 'no-conversion'
switchalready has adefault; keeping an explicitno-conversionbranch triggers Biome’s
noUselessSwitchCasewarning.- case 'no-conversion': - default: + default:
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
.github/chatmodes/WG Code Alchemist.chatmode.md(1 hunks).github/chatmodes/WG Code Sentinel.chatmode.md(1 hunks)CHANGELOG.md(3 hunks)package.json(1 hunks)src/core/MagicRelease.ts(11 hunks)src/core/generator/ChangelogParser.ts(2 hunks)src/core/generator/KeepChangelogGenerator.ts(5 hunks)src/core/git/GitService.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/core/generator/ChangelogParser.ts (2)
src/utils/logger.ts (1)
logger(319-319)src/types/interfaces.ts (1)
ChangelogEntry(140-151)
src/core/generator/KeepChangelogGenerator.ts (3)
src/types/interfaces.ts (1)
ChangelogEntry(140-151)src/utils/logger.ts (1)
logger(319-319)src/core/generator/ChangelogParser.ts (1)
isMagicrGenerated(203-206)
🪛 LanguageTool
.github/chatmodes/WG Code Alchemist.chatmode.md
[style] ~33-~33: The word ‘gonna’ is informal.
Context: ...n't Repeat Yourself), YAGNI (You Aren't Gonna Need It), and KISS (Keep It Simple, Stu...
(GONNA)
[typographical] ~34-~34: Except for inverted sentences, ‘Need It’ requires a question mark at the end of the sentence.
Context: ...toring patterns, and architectural best practices Code Transformation Approach: 1. ...
(MD_PRP_QUESTION_MARK)
[misspelling] ~43-~43: This word is normally spelled as one.
Context: ...eeply**: Identify specific code smells, anti-patterns, and improvement opportunities 3. **Exp...
(EN_COMPOUNDS_ANTI_PATTERNS)
🪛 Biome (1.9.4)
src/core/generator/KeepChangelogGenerator.ts
[error] 610-610: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
src/core/MagicRelease.ts
[error] 1085-1085: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 1085-1090: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 1118-1118: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
🔇 Additional comments (8)
.github/chatmodes/WG Code Sentinel.chatmode.md (1)
1-67: Excellent implementation of the security-focused chat mode.The WG Code Sentinel configuration is comprehensive and well-structured. The security domains, review approach, and JARVIS-inspired communication style are clearly defined and professionally presented. This will provide valuable security review capabilities.
.github/chatmodes/WG Code Alchemist.chatmode.md (1)
1-73: Excellent implementation of the Clean Code-focused chat mode.The WG Code Alchemist configuration perfectly complements the Code Sentinel mode. The coverage of Clean Code principles, SOLID design, and the transformation approach is comprehensive and well-articulated. The JARVIS-inspired communication style maintains consistency across both chat modes.
Note: The static analysis hints flagging "gonna" and "anti-patterns" are false positives - these are acceptable usage in the context of explaining YAGNI acronym and standard software terminology.
package.json (1)
3-3: Version bump looks correct for the v0.1.1-beta release.The version update from "0.1.0-beta" to "0.1.1-beta" properly follows semantic versioning conventions and aligns with the PR objectives.
src/core/git/GitService.ts (1)
300-315: Well-implemented method that follows established patterns.The
getCommitHashmethod is properly structured with:
- Consistent use of the existing
execGithelper- Appropriate error handling and logging
- Comprehensive JSDoc documentation
- Sensible default parameter (
'HEAD')This addition enhances the Git service capabilities for the changelog generation workflow.
CHANGELOG.md (3)
2-6: Good addition of the Magic Release header comment.The comment header properly identifies the file as managed by Magic Release and includes appropriate usage notes. This will help with automated changelog management.
16-23: Changelog entries are well-structured and properly categorized.The new Unreleased entries follow the Keep a Changelog format correctly, with appropriate categorization into "Added" and "Fixed" sections and proper commit hash references for traceability.
24-27: New version section follows consistent formatting.The 0.1.0-beta version section maintains consistency with the established changelog format and includes the proper date format.
src/core/MagicRelease.ts (1)
880-917: Deduplication – unnecessary O(N²) passYou already iterate
gitCommitsonce to builddocumentedHashLookup.
Filtering again over the full array doubles work. Build the lookup first, then produce
newCommitsin a single pass.[ suggest_optional_refactor ]
- Fix double blank lines between changelog version sections - Ensure generated changelogs end with single newline for markdownlint - Remove duplicate backup file creation (now creates only timestamped backup) - Improve overall changelog formatting consistency Fixes spacing issues that were causing markdownlint failures and eliminates redundant backup files that were being created during changelog generation.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (5)
src/core/generator/KeepChangelogGenerator.ts (5)
460-470: Direct file write without backup risks data lossThe
writeChangelogmethod writes directly to the file without creating a backup first. If the write operation fails partway through, the original changelog could be corrupted.Consider implementing atomic write operations or creating a backup before writing:
async writeChangelog(content: string, workingDir: string): Promise<void> { const filename = this.config.changelog?.filename ?? 'CHANGELOG.md'; const changelogPath = path.join(workingDir, filename); try { + // Write to temporary file first + const tempPath = `${changelogPath}.tmp`; + writeFileSync(tempPath, content, 'utf8'); + + // Atomically rename temp file to actual file + const fs = await import('fs/promises'); + await fs.rename(tempPath, changelogPath); + logger.info(`Changelog written to: ${changelogPath}`); } catch (error) { throw new ChangelogError(`Failed to write changelog: ${error}`); } }
27-28:appendOnlyflag is never used in the main generation flowThe
appendOnlyoption is declared but thegenerate()method doesn't check this flag to decide whether to usegenerateAppendOnly(). This renders the append-only feature inaccessible.Apply this fix to honor the
appendOnlyflag:async generate(entries: ChangelogEntry[], workingDir: string): Promise<string> { logger.debug('Generating Keep a Changelog format', { entriesCount: entries.length, includeLinks: this.options, }); const existingChangelog = await this.loadExistingChangelog(workingDir); + // Use append-only mode if flag is set and existing changelog exists + if (this.options.appendOnly && existingChangelog) { + return this.generateAppendOnly(entries, existingChangelog, workingDir); + } + // Handle the changelog generation with proper merging logic const finalEntries = await this.processEntriesWithExistingChangelog( entries, existingChangelog, workingDir );Also applies to: 65-105
84-96: Missing blank lines between changelog entriesThe
formatEntry()calls don't add blank line separators between entries, which could cause them to merge together and reduce readability.Add blank line separators after each entry:
// Add unreleased section if needed const unreleasedEntry = finalEntries.find(entry => entry.version === 'Unreleased'); if (unreleasedEntry && this.hasChanges(unreleasedEntry)) { - content += this.formatEntry(unreleasedEntry); + content += this.formatEntry(unreleasedEntry) + '\n'; } // Add released versions (sorted by version, newest first) const releasedEntries = finalEntries .filter(entry => entry.version !== 'Unreleased') .sort((a, b) => this.compareVersions(b.version, a.version)); for (const entry of releasedEntries) { - content += this.formatEntry(entry); + content += this.formatEntry(entry) + '\n'; }
149-149: Dynamic imports cause unnecessary runtime overheadUsing dynamic
import()inside frequently called methods introduces performance overhead. Each call re-imports the module.Import the ChangelogParser once at the top of the file:
+import ChangelogParser from './ChangelogParser.js'; import type { ChangelogEntry, Change, ChangeType, MagicReleaseConfig } from '../../types/index.js';Then replace all dynamic imports:
- const parser = new (await import('./ChangelogParser.js')).default(); + const parser = new ChangelogParser();Also applies to: 476-476, 500-500, 633-633, 741-741
137-238: Significant overlap between changelog processing methodsBoth
processEntriesWithExistingChangelogandprocessEntriesWithScenariohandle similar changelog merging logic with different approaches. This duplication increases maintenance complexity.Consider consolidating the merging logic into a single, flexible method that can handle all scenarios. This would reduce code duplication and simplify maintenance.
Also applies to: 480-611
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/core/generator/KeepChangelogGenerator.ts(5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/core/generator/KeepChangelogGenerator.ts (3)
src/types/interfaces.ts (1)
ChangelogEntry(140-151)src/utils/logger.ts (1)
logger(319-319)src/core/generator/ChangelogParser.ts (1)
isMagicrGenerated(203-206)
🪛 Biome (1.9.4)
src/core/generator/KeepChangelogGenerator.ts
[error] 606-606: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
🔇 Additional comments (1)
src/core/generator/KeepChangelogGenerator.ts (1)
265-269: Good addition of magicr identification headerThe new header comment effectively identifies magicr-generated changelogs, enabling intelligent updates in append-only mode.
Bumps the npm_and_yarn group with 1 update in the / directory: [form-data](https://github.com/form-data/form-data). Updates `form-data` from 4.0.3 to 4.0.4 - [Release notes](https://github.com/form-data/form-data/releases) - [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md) - [Commits](form-data/form-data@v4.0.3...v4.0.4) --- updated-dependencies: - dependency-name: form-data dependency-version: 4.0.4 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com>
Snyk has created this PR to upgrade zod from 3.25.67 to 3.25.76. See this package in yarn: zod See this project in Snyk: https://app.snyk.io/org/warengonzaga/project/7547e199-9ed8-4239-a65b-ff592ddd4c79?utm_source=github&utm_medium=referral&page=upgrade-pr
…nd_yarn-e04d5d616f Bump form-data from 4.0.3 to 4.0.4 in the npm_and_yarn group across 1 directory
…5444b0caba1833e2 [Snyk] Upgrade zod from 3.25.67 to 3.25.76
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/core/generator/KeepChangelogGenerator.ts (2)
329-353: PR/issue/commit “links” are emitted without reference definitionsCurrently you output [#123] and [
abc1234] but never define them; they render as plain text. Add reference link generation at the bottom.@@ if (this.options.includeCompareLinks && releasedEntries.length > 0) { content += this.generateCompareLinks(releasedEntries); } + + // Reference links for PRs, issues, and commits + if (this.options.includePRLinks || this.options.includeIssueLinks || this.options.includeCommitLinks) { + const allEntries = [ + ...(unreleasedEntry && this.hasChanges(unreleasedEntry) ? [unreleasedEntry] : []), + ...releasedEntries, + ]; + content += this.generateReferenceLinks(allEntries); + } @@ private generateCompareLinks(entries: ChangelogEntry[]): string { @@ return links; } + + /** Generate reference links for PRs, issues, and commits */ + private generateReferenceLinks(entries: ChangelogEntry[]): string { + const repoUrl = this.getRepositoryUrl(); + if (!repoUrl) return ''; + + const prs = new Set<number>(); + const issues = new Set<number>(); + const commits = new Set<string>(); + + for (const e of entries) { + for (const [, changes] of e.sections) { + for (const ch of changes) { + if (this.options.includePRLinks && typeof ch.pr === 'number') prs.add(ch.pr); + if (this.options.includeIssueLinks && Array.isArray(ch.issues)) { + for (const i of ch.issues) if (typeof i === 'number') issues.add(i); + } + if (this.options.includeCommitLinks && Array.isArray(ch.commits) && ch.commits.length > 0) { + const hash = ch.commits[0]?.hash?.slice(0, 7); + if (hash) commits.add(hash); + } + } + } + } + + if (prs.size === 0 && issues.size === 0 && commits.size === 0) return ''; + + let out = '\n<!-- Reference Links -->\n'; + for (const pr of prs) out += `[#${pr}]: ${repoUrl}/pull/${pr}\n`; + for (const is of issues) out += `[#${is}]: ${repoUrl}/issues/${is}\n`; + for (const c of commits) out += `[\`${c}\`]: ${repoUrl}/commit/${c}\n`; + return out; + }Also applies to: 98-102, 358-390
460-469: Non-atomic write risks data loss; add backup + atomic renameWrite to a temp file, make a timestamped backup if file exists, then atomic rename. Update imports accordingly.
@@ -import { readFileSync, writeFileSync, existsSync } from 'fs'; +import { readFileSync, writeFileSync, existsSync, copyFileSync, renameSync } from 'fs'; @@ async writeChangelog(content: string, workingDir: string): Promise<void> { @@ - try { - writeFileSync(changelogPath, content, 'utf8'); + try { + const ts = new Date().toISOString().replace(/[:.]/g, '-'); + const tmpPath = `${changelogPath}.tmp-${ts}`; + const backupPath = `${changelogPath}.bak-${ts}`; + if (existsSync(changelogPath)) { + copyFileSync(changelogPath, backupPath); + } + writeFileSync(tmpPath, content, 'utf8'); + renameSync(tmpPath, changelogPath); logger.info(`Changelog written to: ${changelogPath}`); } catch (error) { throw new ChangelogError(`Failed to write changelog: ${error}`); }Also applies to: 15-16
🧹 Nitpick comments (7)
src/core/generator/KeepChangelogGenerator.ts (7)
239-258: Reuse parser.extractVersions to avoid regex duplicationYou already have a robust extractor in ChangelogParser. Delegate to it for consistency and fewer edge cases.
- extractDocumentedVersions(changelog?: string): Set<string> { - const versions = new Set<string>(); - if (!changelog) { - return versions; - } - // Match version headers: ## [1.0.0] - 2023-01-01 or ## [Unreleased] - const versionRegex = /^##\s*\[([^\]]+)\]/gm; - let match; - while ((match = versionRegex.exec(changelog)) !== null) { - if (match[1]) { - versions.add(match[1]); - } - } - logger.debug(`Found ${versions.size} documented versions`, Array.from(versions)); - return versions; - } + async extractDocumentedVersions(changelog?: string): Promise<Set<string>> { + if (!changelog) return new Set<string>(); + const parser = new (await import('./ChangelogParser.js')).default(); + const versions = parser.extractVersions(changelog); + logger.debug(`Found ${versions.size} documented versions`, Array.from(versions)); + return versions; + }And update the caller:
- const documentedVersions = this.extractDocumentedVersions(existingChangelog); + const documentedVersions = await this.extractDocumentedVersions(existingChangelog);
595-610: Redundant 'no-conversion' switch case removed — good; drop it from the union type tooType still lists 'no-conversion' though default handles it.
- | 'no-conversion', + // 'no-conversion' handled by default
811-845: Always-true condition; simplify insertion logicThe condition
foundUnreleased || !foundUnreleasedis tautological. Insert before the first version encountered (or just below Unreleased).@@ - // If we've passed unreleased, this is where we insert - if (foundUnreleased || !foundUnreleased) { - insertionIndex = i; - break; - } + // Insert before the first version (below Unreleased if present) + insertionIndex = i; + break;
650-700: Normalize trailing newline in appendToNonMagicrChangelog()Ensure single trailing newline to match generate() behavior.
- return result.join('\n'); + return `${result.join('\n').trimEnd()}\n`;
332-338: Guard optional commits arrayIf Change.commits can be undefined, this throws. Use optional chaining.
- if (this.options.includeCommitLinks && change.commits.length > 0) { + if (this.options.includeCommitLinks && change.commits?.length > 0) {
861-862: Remove leftover placeholder commentDead comment; drop for cleanliness.
- // ...existing code...
434-455: Pre-release ordering nuancecompareVersions ignores pre-release tags; 1.0.0-beta will sort equal to 1.0.0. If you care about semver precedence, incorporate pre-release comparison.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (1)
src/core/generator/KeepChangelogGenerator.ts(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/core/generator/KeepChangelogGenerator.ts (2)
src/types/interfaces.ts (1)
ChangelogEntry(140-151)src/core/generator/ChangelogParser.ts (2)
ChangelogParser(13-393)isMagicrGenerated(203-206)
🔇 Additional comments (4)
src/core/generator/KeepChangelogGenerator.ts (4)
83-87: Unreleased separation looks good; keep consistency for released entriesYou added a spacer after Unreleased. Do the same for released sections to avoid merged blocks.
@@ - for (const entry of releasedEntries) { - content += this.formatEntry(entry); - } + for (const entry of releasedEntries) { + content += this.formatEntry(entry) + '\n'; + }
103-104: Consistent single trailing newline — LGTM
472-479: Avoid repeated dynamic imports; initialize parser onceHoist to a top‑level import or cache a single instance on the class for use across methods. Minor perf/complexity cleanup.
27-28: Append-only option is defined but never honored in generate()Wire the flag to short‑circuit into append‑only path when an existing changelog is present.
@@ const existingChangelog = await this.loadExistingChangelog(workingDir); - // Handle the changelog generation with proper merging logic - const finalEntries = await this.processEntriesWithExistingChangelog( - entries, - existingChangelog, - workingDir - ); + // Append-only short‑circuit + if (this.options.appendOnly && existingChangelog) { + return await this.generateAppendOnly(entries, existingChangelog, workingDir); + } + + // Handle the changelog generation with proper merging logic + const finalEntries = await this.processEntriesWithExistingChangelog( + entries, + existingChangelog, + workingDir + );
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
README.md (1)
115-124: Avoid showing API keys via CLI args (shell history leak).Document a safer alternative (env var or interactive prompt) to prevent leaking secrets in shell history.
Add a caution note after the block:
+> [!CAUTION] +> Passing API keys via CLI flags may store them in shell history. Prefer environment variables or interactive prompts: +> +> ```bash +> export MAGICR_API_KEY=your-api-key +> npx magicr --provider anthropic +> ```src/core/generator/KeepChangelogGenerator.ts (2)
333-351: Commit/PR/issue “links” are emitted as bare brackets, not clickable.Inline-link them when repo URL is known; otherwise fall back to code-formatted hash/IDs.
Apply this diff:
- if (this.options.includeCommitLinks && change.commits.length > 0) { - // Only include first commit to avoid clutter - const firstCommit = change.commits[0]; - if (firstCommit?.hash) { - links.push(`[\`${firstCommit.hash.substring(0, 7)}\`]`); - } - } + if (this.options.includeCommitLinks && change.commits.length > 0) { + const firstCommit = change.commits[0]; + const baseUrl = this.getRepositoryUrl(); + if (firstCommit?.hash) { + const short = firstCommit.hash.substring(0, 7); + links.push(baseUrl ? `[\`${short}\`](${baseUrl}/commit/${firstCommit.hash})` : `\`${short}\``); + } + } @@ - if (this.options.includePRLinks && change.pr) { - links.push(`[#${change.pr}]`); - } + if (this.options.includePRLinks && change.pr) { + const baseUrl = this.getRepositoryUrl(); + links.push(baseUrl ? `[#${change.pr}](${baseUrl}/pull/${change.pr})` : `#${change.pr}`); + } @@ - if (this.options.includeIssueLinks && change.issues && change.issues.length > 0) { - links.push(...change.issues.map(issue => `[#${issue}]`)); - } + if (this.options.includeIssueLinks && change.issues && change.issues.length > 0) { + const baseUrl = this.getRepositoryUrl(); + links.push( + ...change.issues.map(issue => + baseUrl ? `[#${issue}](${baseUrl}/issues/${issue})` : `#${issue}` + ) + ); + }
15-16: Add missing fs imports for atomic write.Needed by the previous change.
Apply this diff:
-import { readFileSync, writeFileSync, existsSync } from 'fs'; +import { readFileSync, writeFileSync, existsSync, renameSync, copyFileSync } from 'fs';CHANGELOG.md (1)
12-12: Add v0.1.1-beta release section to CHANGELOG.mdCHANGELOG.md currently lacks a '0.1.1-beta' release section (rg output: "Missing v0.1.1-beta section"). Add a "## [0.1.1-beta]" entry with date and release notes or convert the existing "## [Unreleased]" block to that release before publishing.
Location: CHANGELOG.md (around line 12).
🧹 Nitpick comments (8)
README.md (2)
11-14: Consolidate duplicate Demo sections.You added a Demo GIF at Lines 11-14 but there’s another “😎 Demo” section at Lines 69-72 claiming “Coming soon!”. Keep only one to avoid conflicting messaging.
Apply this diff to remove the second section:
@@ -## 😎 Demo - -> 📹 Coming soon! In the meantime, try it out in your project. -Also applies to: 69-72
30-31: Update the beta version in the Important notice.README still says v0.1.0-beta, while this PR is v0.1.1-beta. Align the text.
Apply this diff:
-> This project is currently in beta (v0.1.0-beta). While fully functional, expect occasional improvements and refinements. Report any issues you encounter - your feedback helps make it better! 🙏 +> This project is currently in beta (v0.1.1-beta). While fully functional, expect occasional improvements and refinements. Report any issues you encounter - your feedback helps make it better! 🙏CHANGELOG.md (1)
36-38: Deduplicate repeated tokens in a single entry.Example shows “([#4], [#4])”. Collapse duplicates during generation to avoid noisy output.
src/core/generator/KeepChangelogGenerator.ts (5)
461-471: Use atomic write with backup to avoid partial writes/corruption.Write to a temp file, optionally back up existing, then rename.
Apply this diff (and update imports per next comment):
async writeChangelog(content: string, workingDir: string): Promise<void> { const filename = this.config.changelog?.filename ?? 'CHANGELOG.md'; const changelogPath = path.join(workingDir, filename); try { - writeFileSync(changelogPath, content, 'utf8'); + const tmpPath = `${changelogPath}.tmp`; + const bakPath = `${changelogPath}.${Date.now()}.bak`; + if (existsSync(changelogPath)) { + copyFileSync(changelogPath, bakPath); + } + writeFileSync(tmpPath, content, 'utf8'); + renameSync(tmpPath, changelogPath); logger.info(`Changelog written to: ${changelogPath}`); } catch (error) { throw new ChangelogError(`Failed to write changelog: ${error}`); } }
812-836: Always-true condition; simplify insertion logic.This block inserts at the first non-Unreleased version; the condition is redundant.
Apply this diff:
- // If we've passed unreleased, this is where we insert - if (foundUnreleased || !foundUnreleased) { - insertionIndex = i; - break; - } + insertionIndex = i; + break;
476-479: Avoid mixed import styles for ChangelogParser.Use the existing static import instead of dynamic imports here to reduce overhead and keep consistency.
Apply this diff:
- static async isMagicrGenerated(content: string): Promise<boolean> { - const parser = new (await import('./ChangelogParser.js')).default(); + static async isMagicrGenerated(content: string): Promise<boolean> { + const parser = new ChangelogParser(); return parser.isMagicrGenerated(content); }- // Create parser instance for this method - const parser = new (await import('./ChangelogParser.js')).default(); + // Reuse static import + const parser = new ChangelogParser();Also applies to: 740-743
438-456: SemVer comparison ignores pre-release identifiers.For correct ordering of pre-releases (e.g., 1.0.0-beta < 1.0.0), consider using a semver library or parsing pre-release/build metadata.
240-259: Leverage parser.extractVersions to avoid duplication.You re-implement version extraction; reuse ChangelogParser.extractVersions for consistency and fewer regex divergences.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
.github/assets/banner.pngis excluded by!**/*.png.github/assets/demo.gifis excluded by!**/*.gif
📒 Files selected for processing (3)
CHANGELOG.md(5 hunks)README.md(1 hunks)src/core/generator/KeepChangelogGenerator.ts(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/core/generator/KeepChangelogGenerator.ts (2)
src/types/interfaces.ts (1)
ChangelogEntry(140-151)src/core/generator/ChangelogParser.ts (2)
ChangelogParser(13-393)isMagicrGenerated(203-206)
🪛 markdownlint-cli2 (0.18.1)
CHANGELOG.md
40-40: Bare URL used
(MD034, no-bare-urls)
🔇 Additional comments (3)
src/core/generator/KeepChangelogGenerator.ts (3)
95-97: Add a blank line between formatted released entries.Prevents merged sections and improves readability.
Apply this diff:
- for (const entry of releasedEntries) { - content += this.formatEntry(entry); - } + for (const entry of releasedEntries) { + content += this.formatEntry(entry) + '\n'; + }
72-80: appendOnly flag is declared but not honored in generate().If options.appendOnly is true and a changelog exists, branch to generateAppendOnly().
Apply this diff:
const existingChangelog = await this.loadExistingChangelog(workingDir); - // Handle the changelog generation with proper merging logic + // Append-only path preserves existing content with minimal edits + if (this.options.appendOnly && existingChangelog) { + return await this.generateAppendOnly(entries, existingChangelog, workingDir); + } + + // Handle normal generation with merging logic const finalEntries = await this.processEntriesWithExistingChangelog( entries, existingChangelog, workingDir );
166-184: Converted Unreleased content is preserved again, duplicating changes.When converting Unreleased to a version (Scenario 1), the preserve loop re-adds the old Unreleased. Track conversion and skip preserving Unreleased.
Apply this diff:
- const finalEntries: ChangelogEntry[] = []; + const finalEntries: ChangelogEntry[] = []; + let convertedUnreleased = false; @@ - finalEntries.push(convertedEntry); + finalEntries.push(convertedEntry); + convertedUnreleased = true; @@ - if (!isDuplicate) { + if (!isDuplicate) { // Only preserve if it has content if (this.hasChanges(existingEntry)) { - logger.debug(`Preserving existing entry: ${existingEntry.version}`); - finalEntries.push(existingEntry); + if (existingEntry.version === 'Unreleased' && convertedUnreleased) { + logger.debug('Skipping existing Unreleased after conversion'); + } else { + logger.debug(`Preserving existing entry: ${existingEntry.version}`); + finalEntries.push(existingEntry); + }Also applies to: 207-225
Summary by CodeRabbit