Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lucky-coats-post.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cadamsdev/lazy-changesets": fix
---

Fix show breaking changes first in the changelog and release notes
176 changes: 175 additions & 1 deletion src/version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
parseChangesetFile,
getHighestReleaseType,
bumpVersion,
generateChangelog,
version,
type ChangesetReleaseType
} from './version.js';
Expand Down Expand Up @@ -821,6 +822,179 @@ Bug fix`;
await version({ dryRun: false, install: true });

expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Unsupported package manager'));
});
});
});

describe('generateChangelog', () => {
test('should generate changelog with breaking changes first', () => {
const changesetContents = [
`---
"@test/package": feat!
---
Breaking API change`,
`---
"@test/package": fix
---
Bug fix`,
];

const result = generateChangelog('@test/package', '2.0.0', changesetContents);

expect(result).toContain('## 2.0.0');
expect(result).toContain('⚠️ Breaking Changes');
expect(result.indexOf('⚠️ Breaking Changes')).toBeLessThan(result.indexOf('🐛 fix'));
expect(result).toContain('- Breaking API change');
expect(result).toContain('### 🐛 fix');
expect(result).toContain('- Bug fix');
});

test('should generate changelog with only breaking changes', () => {
const changesetContents = [
`---
"@test/package": feat!
---
Breaking API change`,
];

const result = generateChangelog('@test/package', '2.0.0', changesetContents);

expect(result).toContain('## 2.0.0');
expect(result).toContain('⚠️ Breaking Changes');
expect(result).toContain('- Breaking API change');
expect(result).not.toContain('###');
});

test('should generate changelog with only non-breaking changes', () => {
const changesetContents = [
`---
"@test/package": feat
---
New feature`,
`---
"@test/package": fix
---
Bug fix`,
];

const result = generateChangelog('@test/package', '1.1.0', changesetContents);

expect(result).toContain('## 1.1.0');
expect(result).not.toContain('⚠️ Breaking Changes');
expect(result).toContain('### 🚀 feat');
expect(result).toContain('- New feature');
expect(result).toContain('### 🐛 fix');
expect(result).toContain('- Bug fix');
});

test('should handle multiple breaking changes', () => {
const changesetContents = [
`---
"@test/package": feat!
---
Breaking API change 1`,
`---
"@test/package": fix!
---
Breaking API change 2`,
`---
"@test/package": feat
---
New feature`,
];

const result = generateChangelog('@test/package', '2.0.0', changesetContents);

expect(result).toContain('⚠️ Breaking Changes');
expect(result).toContain('- Breaking API change 1');
expect(result).toContain('- Breaking API change 2');
expect(result).toContain('### 🚀 feat');
expect(result).toContain('- New feature');
});

test('should filter changesets by package name', () => {
const changesetContents = [
`---
"@test/package": feat!
---
Breaking change for test package`,
`---
"@other/package": feat!
---
Breaking change for other package`,
];

const result = generateChangelog('@test/package', '2.0.0', changesetContents);

expect(result).toContain('- Breaking change for test package');
expect(result).not.toContain('Breaking change for other package');
});

test('should handle empty changeset contents', () => {
const result = generateChangelog('@test/package', '1.0.0', []);

expect(result).toContain('## 1.0.0');
expect(result).toContain('No changes recorded');
});

test('should handle changesets without matching package', () => {
const changesetContents = [
`---
"@other/package": feat
---
Feature for other package`,
];

const result = generateChangelog('@test/package', '1.0.0', changesetContents);

expect(result).toContain('## 1.0.0');
expect(result).toContain('No changes recorded');
});

test('should handle changesets with malformed frontmatter', () => {
const changesetContents = [
`No frontmatter here`,
`---
"@test/package": feat
---
Valid changeset`,
];

const result = generateChangelog('@test/package', '1.1.0', changesetContents);

expect(result).toContain('### 🚀 feat');
expect(result).toContain('- Valid changeset');
});

test('should maintain type order after breaking changes', () => {
const changesetContents = [
`---
"@test/package": chore!
---
Breaking chore`,
`---
"@test/package": feat
---
Feature`,
`---
"@test/package": fix
---
Fix`,
`---
"@test/package": docs
---
Documentation`,
];

const result = generateChangelog('@test/package', '2.0.0', changesetContents);

const breakingIndex = result.indexOf('⚠️ Breaking Changes');
const featIndex = result.indexOf('### 🚀 feat');
const fixIndex = result.indexOf('### 🐛 fix');
const docsIndex = result.indexOf('### 📚 docs');

expect(breakingIndex).toBeLessThan(featIndex);
expect(featIndex).toBeLessThan(fixIndex);
expect(fixIndex).toBeLessThan(docsIndex);
});
});
});
20 changes: 17 additions & 3 deletions src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function generateChangelog(packageName: string, version: string, changese
let changelog = `## ${version}\n\n`;

const typeGroups: Map<string, string[]> = new Map();
const breakingChanges: string[] = [];

for (const content of changesetContents) {
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
Expand All @@ -93,15 +94,28 @@ export function generateChangelog(packageName: string, version: string, changese
const match = line.match(/^"([^"]+)":\s*(\w+)(!?)/);
if (match && match[1] === packageName) {
const changesetType = match[2];
const isBreaking = match[3] === '!';

const existing = typeGroups.get(changesetType) || [];
typeGroups.set(changesetType, [...existing, message]);
if (isBreaking) {
breakingChanges.push(message);
} else {
const existing = typeGroups.get(changesetType) || [];
typeGroups.set(changesetType, [...existing, message]);
}
break;
}
}
}

if (typeGroups.size === 0) {
if (breakingChanges.length > 0) {
changelog += `⚠️ Breaking Changes\n`;
for (const msg of breakingChanges) {
changelog += `- ${msg}\n`;
}
changelog += '\n';
}

if (typeGroups.size === 0 && breakingChanges.length === 0) {
return changelog + 'No changes recorded.\n';
}

Expand Down