Skip to content

Conversation

@BYK
Copy link
Member

@BYK BYK commented Nov 14, 2025

Replaces the milestone-based changelog generation with a system that uses GitHub's release.yml categories.

This change leverages GitHub's native release notes configuration for more flexible and powerful categorization of PRs based on labels and exclusion rules, moving away from the less flexible milestone-based approach.

Co-authored-by: burak.kaya <burak.kaya@sentry.io>
@cursor
Copy link
Contributor

cursor bot commented Nov 14, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

cursoragent and others added 3 commits November 14, 2025 23:54
Co-authored-by: burak.kaya <burak.kaya@sentry.io>
Co-authored-by: burak.kaya <burak.kaya@sentry.io>
* Refactor: Improve changelog generation and mock fs

This commit refactors the changelog generation logic to correctly handle category-level exclusions and improves the mocking of the `fs` module by using `jest.requireActual` for `readFileSync`. It also updates the `gcsAPI` tests to use the mocked `fs.existsSync`.

Co-authored-by: burak.kaya <burak.kaya@sentry.io>

* chore: Remove package-lock.json and regenerate yarn.lock

- Remove npm-generated package-lock.json file
- Regenerate yarn.lock with current dependencies
- Project uses yarn as the package manager

* fix: Resolve all TypeScript compilation and test failures

Fixed multiple TypeScript compilation errors and dependency issues:

**TypeScript Fixes:**
- Fixed github.ts: Changed Promise resolve callback to match expected signature
- Fixed awsLambdaLayerManager.ts:
  - Added Runtime import and cast CompatibleRuntimes to Runtime[]
  - Cast headers to Record<string, string> for fetch compatibility
- Fixed registry.ts: Added explicit RemoteArtifact type to mapLimit callback
- Fixed brew.ts: Added RemoteArtifact import and type annotation

**Configuration Fixes:**
- tsconfig.build.json: Added noImplicitUseStrict: false to override deprecated parent config option
- jest.config.js: Added transformIgnorePatterns for ESM modules (dot-prop, configstore)
- package.json: Pinned dot-prop to ^5.3.0 to avoid ESM-only version 10.x that breaks Jest

**Test Results:**
- All 38 test suites passing ✅
- 369 tests passing, 1 skipped (370 total)
- 28 snapshots passing

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Copy link
Member Author

@BYK BYK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great that you followed the existing changelog style but GitHub's automated release notes format is:


## <Category Title>

- <PR Title> by @<author-with> in [#<PR no>](<link to PR>)

So we need to align with that.

Comment on lines 299 to 306
// Check label exclusions
if (exclude.labels) {
for (const excludeLabel of exclude.labels) {
if (labels.includes(excludeLabel)) {
return true;
}
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be cleaner with exclude.labels.some(label => labels.includes(label)). Also we may wanna convert labels to a Set() for efficient look up if this is happenning frequently.

Comment on lines 308 to 313
// Check author exclusions
if (exclude.authors && author) {
if (exclude.authors.includes(author)) {
return true;
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like my previous comment: probably better to make exclude.authors a Set() right after parsing


/**
* Checks if a PR is excluded at the category level
* Category-level exclusions completely remove the PR from the changelog
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. A category level exclusion just removes the PR from that category. shouldExcludePR covers the case where a PR should be completely omitted from changelogs.


for (const category of config.changelog.categories) {
// Check if PR matches category labels
if (!category.labels || category.labels.length === 0) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's normalize this data right after parsing the config to either category.labels being undefined if it's an empty array or it is always an empty array if it is undefined in the actual config.

Comment on lines 337 to 351
let matchesCategory = false;
for (const categoryLabel of category.labels) {
if (categoryLabel === '*') {
matchesCategory = true;
break;
}
if (labels.includes(categoryLabel)) {
matchesCategory = true;
break;
}
}

if (!matchesCategory) {
continue;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to check if a PR matches a category? I mean if it is excluded, that's enough to NOT have it here. Would that not be more efficient?

Comment on lines 394 to 397
if (categoryLabel === '*') {
matchesCategory = true;
break;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think this logic is correct. Here's what the spec says:

Use * as a catch-all for pull requests that didn't match any of the previous categories.

That means we should check this last.

Comment on lines 480 to 483
// Apply category-level exclusions (these completely hide the PR)
if (isCategoryLevelExcluded(labels, author, releaseConfig)) {
continue;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned earlier, this logic is incorrect. This just hides the PR from a specific category, not necessarily from everything else.

}

// Match PR to category
const categoryTitle = matchPRToCategory(labels, releaseConfig);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to bake the isCategoryLevelExcluded logic into matchPRToCategory so it just returns whatever category it matches the PR to, taking the exclusions into account.

Comment on lines 514 to 515
// If we have both PR and author, add to category PRs list
// Otherwise, add to leftovers (e.g., PR without author, or commit without PR)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you should never have a PR without an author?

// XXX(BYK): This case should never happen in real life
throw new Error(`Cannot get information for milestone #${milestoneNum}`);
// Generate sections for each category
for (const categoryTitle of Object.keys(categories)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mighte be better to make categories a Map()

cursoragent and others added 2 commits November 17, 2025 13:13
Co-authored-by: burak.kaya <burak.kaya@sentry.io>
Co-authored-by: burak.kaya <burak.kaya@sentry.io>
Comment on lines 261 to 265
exclude?: {
labels?: Set<string>;
authors?: Set<string>;
};
categories?: NormalizedCategory[];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't make anything optional in normalized configs. Empty sets and arrays are easier.

Comment on lines 272 to 275
exclude?: {
labels?: Set<string>;
authors?: Set<string>;
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, don't make anything optional in normalized configs. Use empty sets when needed.

cursoragent and others added 2 commits November 17, 2025 14:03
Co-authored-by: burak.kaya <burak.kaya@sentry.io>
Co-authored-by: burak.kaya <burak.kaya@sentry.io>
'### Better drivetrain',
'',
'By: @alice (#123), @bob (#456)',
'- Upgraded the manifold (#123) by @alice in [#123](https://github.com/test-owner/test-repo/pull/123)',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should strip the PR no at the end of commit logs or PR titles when they appear like this: (#<PR_NO>)

This commit refactors the changelog exclusion logic to be more concise and removes unnecessary checks. It also updates the yarn.lock file to reflect the latest dependency versions.

Co-authored-by: burak.kaya <burak.kaya@sentry.io>
cursoragent and others added 5 commits November 24, 2025 22:39
Co-authored-by: burak.kaya <burak.kaya@sentry.io>
Co-authored-by: burak.kaya <burak.kaya@sentry.io>
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.13.1 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@3.13.1...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: Upgrade sentry/node to latest

* fix
@BYK BYK force-pushed the cursor/migrate-changelog-to-github-releases-51a3 branch from fbe8b24 to e02c495 Compare November 24, 2025 22:57
BYK added 3 commits November 24, 2025 23:06
The older Jest version had issues with strip-ansi ESM module resolution
when running on Node.js 22. Also added a resolution to pin strip-ansi
in @jest/reporters to the CJS-compatible version.
Rebase artifact left 'yarn' prefix on a line in the test file.
Set.intersection() is only available in Node 22+. Use a loop to check
for excluded labels instead.
@BYK BYK force-pushed the cursor/migrate-changelog-to-github-releases-51a3 branch from 0b29d66 to 0513aad Compare November 24, 2025 23:21
When formatting PR entries for the changelog, the PR number like '(#123)'
at the end of the title is now stripped since the PR link is already
included at the end of the entry.
@BYK BYK force-pushed the cursor/migrate-changelog-to-github-releases-51a3 branch from 0513aad to 79eaedc Compare November 24, 2025 23:23
@BYK BYK marked this pull request as ready for review November 24, 2025 23:25
Comment on lines 537 to -307
};
commits[hash] = commit;

if (!githubCommit) {
missing.push(commit);
}
if (!commit.milestone) {

if (!categoryTitle) {
leftovers.push(commit);
} else {
const milestone = milestones[commit.milestone] || {
prs: [] as PullRequest[],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: normalizeReleaseConfig() crashes with TypeError if config.changelog.categories is a non-array truthy value due to missing runtime type validation.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

The normalizeReleaseConfig() function reads YAML configuration without runtime schema validation. If config.changelog.categories is provided as a non-array truthy value (e.g., a string or an object) in the .github/release.yml file, the if (config.changelog.categories) check on line 336 passes. However, the subsequent call to .map() on line 337 will then throw a TypeError: config.changelog.categories.map is not a function, as map is not a method on non-array types. This unhandled error will crash the changelog generation process.

💡 Suggested Fix

Add runtime validation using Array.isArray(config.changelog.categories) before attempting to call .map() on line 337. Consider implementing a more robust schema validation for the entire ReleaseConfig object to prevent similar issues.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/utils/changelog.ts#L296-L307

Potential issue: The `normalizeReleaseConfig()` function reads YAML configuration
without runtime schema validation. If `config.changelog.categories` is provided as a
non-array truthy value (e.g., a string or an object) in the `.github/release.yml` file,
the `if (config.changelog.categories)` check on line 336 passes. However, the subsequent
call to `.map()` on line 337 will then throw a `TypeError:
config.changelog.categories.map is not a function`, as `map` is not a method on
non-array types. This unhandled error will crash the changelog generation process.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 3326459

@BYK BYK merged commit ab67cc1 into byk/feat/github-changelog-grouping Nov 24, 2025
7 checks passed
@BYK BYK deleted the cursor/migrate-changelog-to-github-releases-51a3 branch November 24, 2025 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants