Skip to content

ci: global changelog generator script #5328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 6, 2025
Merged

Conversation

Rajdeepc
Copy link
Contributor

@Rajdeepc Rajdeepc commented Apr 4, 2025

Description

This PR introduces a dual changelog generation setup in our monorepo:

✅ Per-package changelogs via @changesets/changelog-github

✅ Custom global changelog appended to the root CHANGELOG.md using a Yarn 4-compatible ESM script

Changes

  • Configured .changeset/config.json to use @changesets/changelog-github for individual packages.

  • Added a custom ESM script (scripts/add-global-changelog.js) to:

    • Read newly generated changelogs from the .changeset output

    • Format and append only feat: and fix: commits to the root CHANGELOG.md

    • Automatically add diff links (e.g., 1.4.0)

Updated package.json scripts to include:

"changeset-publish": "yarn prepublishOnly && yarn changeset version && yarn changelog:global && ......."
"changelog:global": "node scripts/add-global-changelog.mjs"

Related issue(s)

  • SWC-837

Motivation and context

Since Changesets doesn’t support a global changelog out of the box, this is a custom script to bring that adds the experience back.

How has this been tested?

DO NOT COMMIT OR PUSH ANYTHING FROM TESTING

  • Patch version changelog

    1. Pull down this PR
    2. Run yarn prepublishOnly && yarn changelog:global
    3. Review the output to match all available snapshots (patch)
    4. Check the version number is 1.6.1
  • Minor version changelog

    1. Revert the changelog changes from the previous test
    2. Run yarn changeset
    3. Select sp-alert-banner package
    4. Select minor version
    5. Enter changeset message: - **Fixed**: Updated default styles for better contrast [#9999](https://github.com/adobe/spectrum-web-components/pull/9999)
    6. Run yarn changelog:global
    7. Review the output to match all available snapshots (minor and patch)
    8. Check the version number is 1.7.0
  • Major version changelog

    1. Revert the changelog changes from the previous test, not the changeset
    2. Run yarn changeset
    3. Select sp-textfield package
    4. Select major version
    5. Enter changeset message: - **Added**: Added hidden-labelattribute, for use with assistive technologies. (Before:/ After:)
    6. Run yarn changelog:global
    7. Review the output to match all available snapshots (major, minor and patch)
    8. Check the version number is 2.0.0

UNSTAGE ALL CHANGES AND CHANGESETS

Screenshots (if appropriate)

Screenshot 2025-05-12 at 2 01 07 PM Screenshot 2025-05-12 at 2 15 02 PM Screenshot 2025-05-12 at 2 16 25 PM

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Chore (minor updates related to the tooling or maintenance of the repository, does not impact compiled assets)

Checklist

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • If my change required a change to the documentation, I have updated the documentation in this pull request.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices

Best practices

This repository uses conventional commit syntax for each commit message; note that the GitHub UI does not use this by default so be cautious when accepting suggested changes. Avoid the "Update branch" button on the pull request and opt instead for rebasing your branch against main.

@Rajdeepc Rajdeepc self-assigned this Apr 4, 2025
Copy link

changeset-bot bot commented Apr 4, 2025

⚠️ No Changeset found

Latest commit: 23ed137

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@Rajdeepc Rajdeepc added the WIP label Apr 4, 2025
Copy link

github-actions bot commented Apr 4, 2025

Branch preview

Review the following VRT differences

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

Copy link

github-actions bot commented Apr 4, 2025

Tachometer results

Currently, no packages are changed by this PR...

Copy link
Contributor

@castastrophe castastrophe left a comment

Choose a reason for hiding this comment

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

If this is an urgent automation, I think this approach can work as a temporary fix with a few hardening updates to how it's written but long-term we need to be handling this from the Changesets tooling (https://github.com/changesets/changesets/blob/main/docs/modifying-changelog-format.md#writing-changelog-formatting-functions)

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoUrl = 'https://github.com/adobe/spectrum-web-components';

const pkg = JSON.parse(
Copy link
Contributor

Choose a reason for hiding this comment

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

A best practice to keep code scoped is to wrap them in a main function and call the function at the end of the file. I think it's a good idea to maintain that best practice here that we see in our other scripts as well.

const pkg = JSON.parse(
fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8')
);
const newVersion = pkg.version;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can skip this abstraction since newVersion is only used in the following line. We also need a check for if the package doesn't load to throw that warning. You're assuming here that pkg.version exists.

Comment on lines 27 to 31
const prevTag = execSync('git tag --sort=-creatordate')
.toString()
.split('\n')
.filter(Boolean)
.find((tag) => tag !== newTag);
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs more robust failure captures. I recommend if you're going to use exec for this, separate the command execution (exec is notoriously flaky in node scripts so you need to account for it failing) from the string parsing. Check that exec returned a string and then run the split, etc.

Comment on lines 33 to 35
if (!prevTag) {
console.error('No previous tag found.');
process.exit(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm trying to think about what information I would need to debug this error. At the age of this project, there's no change that there aren't previous tags to be found so maybe we want this error to tell us why the exec command couldn't return a value we were expecting? Maybe this should log the git tag command output?

process.exit(1);
}

const date = new Date().toISOString().split('T')[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const date = new Date().toISOString().split('T')[0];
const date = new Date().toLocaleDateString('en-CA', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
}));

This should return the result you're wanting without having to do inline array parsing (which can sometimes lead to invalid results or fail when the array isn't in the format we're expecting).

}

const date = new Date().toISOString().split('T')[0];
const compareUrl = `${repoUrl}/compare/${prevTag}...${newTag}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is prevTag allowed to be a pre-tag or is there a requirement that prevTag must be a semver version? It seems like it must be one of the semver releases (not the betas for example) so maybe we can add a comment to that effect?

Comment on lines 40 to 42
const commitLogs = execSync(`git log ${prevTag}..HEAD --pretty=format:"%s|%h"`)
.toString()
.trim();
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like it's returning the commit logs but not the changelog content. Is that what we're wanting to add to the global changelog? It seems like the commit history is less useful now that we've migrated to changesets.

Copy link
Contributor Author

@Rajdeepc Rajdeepc Apr 7, 2025

Choose a reason for hiding this comment

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

This is a great point where I would want everyone's opinion on. I don't think only a summary of the change is sufficient for the users to check what changes went along. I want to keep the CHANGELOG to follow the same pattern as we were doing during lerna which I feel the users still wants.


// Skip if nothing relevant
if (!features.length && !fixes.length) {
console.log('🚫 No new feat() or fix() commits to add.');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
console.log('🚫 No new feat() or fix() commits to add.');
console.log('🚫 No new feat() or fix() commits to add.');

Since no new features or fixes isn't necessarily a failure of the script, should we format this more like a success message that it ran successfully but with no changes?

Comment on lines 52 to 63
commits.forEach(({ message, hash }) => {
const typeMatch = message.match(/^(feat|fix)\(([^)]+)\):\s*(.+)/i);
if (typeMatch) {
const [, type, scope, description] = typeMatch;
const entry = `- **${scope}**: ${description} ([\`${hash}\`](${repoUrl}/commit/${hash}))`;
if (type === 'feat') {
features.push(entry);
} else if (type === 'fix') {
fixes.push(entry);
}
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can serve a temporary fix but we should really be using the changesets tooling to create this content from it's new source (which is not the commit messages): https://github.com/changesets/changesets/blob/main/docs/modifying-changelog-format.md#writing-changelog-formatting-functions

I think the challenge with this as a sustainable approach is that less and less useful data is present in commit messages and the real value for customers now lives in the changesets files.


fs.writeFileSync(
changelogPath,
`${newEntry.trim()}\n\n${existingChangelog}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't this push the # Change log heading down to below the latest update?

@Rajdeepc
Copy link
Contributor Author

Rajdeepc commented Apr 14, 2025

If this is an urgent automation, I think this approach can work as a temporary fix with a few hardening updates to how it's written but long-term we need to be handling this from the Changesets tooling (https://github.com/changesets/changesets/blob/main/docs/modifying-changelog-format.md#writing-changelog-formatting-functions)

Thanks for the info, I deliberately added a custom script and didn't opt for built-in changeset tooling since it doesn' expose any API to create a global CHANGELOG which is the focus of this PR. There has been some discussions around this in the community but this is not something its on a priority radar for the team

@Rajdeepc Rajdeepc changed the title ci: updated global changelog ci: global changelog generator script Apr 17, 2025
@blunteshwar
Copy link
Contributor

ling since it doesn' expose any API to create a global CHANGELOG which is the focus of this PR. There has been some discussions around this in the community but this is not something its on a priority radar for the team

I agree, I think the custom script approach here is quite pragmatic. While Changesets does a great job generating per-package changelogs, it doesn’t currently offer a built-in mechanism or public API to aggregate them into a single global changelog file. This script effectively fills that gap without waiting on upstream support.

Relying solely on Changesets tooling would either limit us to fragmented changelogs or require non-trivial workarounds. The custom solution gives us immediate value, full control over formatting, and keeps things transparent for consumers of the monorepo.

Until Changesets supports this natively, this seems like a solid and scalable workaround.

@Rajdeepc Rajdeepc marked this pull request as ready for review April 24, 2025 07:21
@Rajdeepc Rajdeepc requested a review from a team as a code owner April 24, 2025 07:21
@Rajdeepc Rajdeepc mentioned this pull request May 2, 2025
11 tasks
package.json Outdated
@@ -23,7 +23,8 @@
"build:types": "wireit",
"build:watch": "wireit",
"changeset-snapshot-publish": "yarn prepublishOnly && yarn changeset version --snapshot && yarn lint:versions --fix && yarn update-version && yarn changeset publish --no-git-tag --tag snapshot",
"changeset-publish": "yarn prepublishOnly && yarn changeset version && yarn install && yarn lint:versions --fix && yarn update-version && yarn changeset publish --no-git-tag && yarn push-to-remote && yarn create-git-tag && yarn postpublish",
"changeset-publish": "yarn prepublishOnly && yarn changeset version && yarn changelog:global && yarn install && yarn lint:versions --fix && yarn update-version && yarn changeset publish --no-git-tag && yarn push-to-remote && yarn create-git-tag && yarn postpublish",
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn’t yarn changelog:global be executed before yarn changeset version? This is because yarn changelog:global reads from changeset files located in the .changeset directory. However, after running yarn changeset version, all the changesets are removed/deleted, and the changelogs are populated. Consequently, yarn changelog:global will no longer be able to read the changeset files in the .changeset directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're absolutely right — excellent observation.

Comment on lines 13 to 22
@import url("@spectrum-web-components/styles/tokens/global-vars.css");
@import url("@spectrum-web-components/styles/tokens/spectrum/global-vars.css");
@import url("@spectrum-web-components/styles/tokens/spectrum/custom-vars.css");
@import url("@spectrum-web-components/styles/tokens/spectrum/system-theme-bridge.css");
@import url("@spectrum-web-components/styles/src/spectrum-heading.css");
@import url("@spectrum-web-components/styles/src/spectrum-body.css");
@import url("@spectrum-web-components/styles/src/spectrum-code.css");
@import url("@spectrum-web-components/opacity-checkerboard/src/spectrum-opacity-checkerboard.css");
@import url("./inline-alert.css");
@import url("./fonts.css");
Copy link
Contributor

@blunteshwar blunteshwar May 12, 2025

Choose a reason for hiding this comment

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

nit
Why did we add this url here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These files came in from the main.

@Rajdeepc Rajdeepc force-pushed the rajdeep/changelog-global branch from 8380afc to d7fad41 Compare May 12, 2025 13:40
@castastrophe castastrophe force-pushed the rajdeep/changelog-global branch from d7fad41 to 39c3832 Compare May 12, 2025 17:03
Rajdeep Chandra and others added 3 commits May 19, 2025 12:10
* ci: re-arranged order of execution of global addtion of changelog script

* chore: fix logic for this to work before changeset version

* chore: add snapshot back

---------

Co-authored-by: Rajdeep Chandra <rajdeepchandra@Rajdeeps-MacBook-Pro-2.local>
Co-authored-by: Piyush Vashisht <piyush17303@iiitd.ac.in>
@caseyisonit caseyisonit dismissed castastrophe’s stale review May 22, 2025 15:05

Dismissing as @castastrophe is out of town and don't want to block the updated changes from being reviewed and merged

Copy link
Contributor

@rubencarvalho rubencarvalho left a comment

Choose a reason for hiding this comment

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

Thanks for this - looks great! 🚀 I just left a few comments

process.exit(1);
}

if (!gitTag) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We are already throwing above (on line 73), so this seems redundant?

@castastrophe castastrophe enabled auto-merge (squash) June 6, 2025 07:54
@castastrophe castastrophe merged commit f5f0e81 into main Jun 6, 2025
23 of 24 checks passed
@castastrophe castastrophe deleted the rajdeep/changelog-global branch June 6, 2025 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants