Skip to content

Commit 814b506

Browse files
authored
chore(ci): add v10 release automation (#20056)
* ci: update v10 version and release workflows, update changelog to not prompt * chore: convert to dry run tag
1 parent 6568a33 commit 814b506

File tree

3 files changed

+200
-21
lines changed

3 files changed

+200
-21
lines changed

.github/workflows/v10-release.yml

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,62 @@ jobs:
3333
- name: Run Continuous Integration checks
3434
run: yarn ci-check
3535

36-
- name: Publish packages under the `next` dist tag
36+
- name: Publish packages under the `community` dist tag
3737
run:
38-
yarn lerna publish from-package --dist-tag next --no-verify-access
39-
--yes
38+
yarn lerna publish from-package --dist-tag community
39+
--no-verify-access --yes
4040
env:
4141
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
4242

4343
- name: Prepare artifacts for release
4444
run: |
4545
zip -r --junk-paths carbon-elements.sketchplugin.zip packages/sketch/carbon-elements.sketchplugin
4646
47+
- name: Get latest v10 tag
48+
id: calculate-tag
49+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
50+
with:
51+
script: |
52+
const { execSync } = require('child_process');
53+
const semver = require('semver');
54+
55+
// Get all tags starting with v10
56+
const output = execSync("git tag --list 'v10.*.*'", { encoding: 'utf8' });
57+
const tags = output.trim().split('\n').filter(Boolean);
58+
59+
if (tags.length === 0) {
60+
core.setFailed('No v10 tags found.');
61+
return;
62+
}
63+
64+
// Sort with semver
65+
const sorted = tags.sort((a, b) => semver.rcompare(a, b));
66+
const latest = sorted[0];
67+
68+
core.info(`Latest v10 tag: ${latest}`);
69+
core.setOutput('latestTag', latest);
70+
4771
- name: Create Release
4872
id: create_release
4973
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
5074
with:
5175
script: |
76+
// Generate the changelog
77+
const {
78+
exitCode,
79+
stdout,
80+
stderr
81+
} = await exec.getExecOutput('./packages/cli/bin/carbon-cli.js', ['changelog', '${{ steps.calculate-tag.outputs.latestTag }}..${{ context.ref }}', '--noPrompt'], options);
82+
83+
// Changelog command outputs the generated changelog to stdout
84+
core.info(`${stdout}`);
85+
5286
github.rest.repos.createRelease({
5387
tag_name: context.ref,
5488
name: context.ref,
89+
body: stdout
5590
draft: false,
56-
prerelease: true,
91+
make_latest: false,
5792
owner: context.repo.owner,
5893
repo: context.repo.repo,
5994
});

.github/workflows/v10-version.yml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
name: v10 - Version
2+
3+
on:
4+
push:
5+
branches:
6+
- v10
7+
8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
version:
14+
runs-on: ubuntu-latest
15+
if: github.repository == 'carbon-design-system/carbon'
16+
timeout-minutes: 60
17+
steps:
18+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
19+
with:
20+
fetch-depth: '0'
21+
fetch-tags: true
22+
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
23+
- name: Use Node.js version from .nvmrc
24+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
25+
with:
26+
node-version-file: '.nvmrc'
27+
registry-url: 'https://registry.npmjs.org'
28+
- name: Install dependencies
29+
run: yarn install --immutable --immutable-cache --check-cache
30+
- name: Build project
31+
run: yarn build
32+
- name: Run Continuous Integration checks
33+
run: yarn ci-check
34+
35+
- name: Check if commit is a release commit
36+
id: check-commit
37+
uses: actions/github-script@v7
38+
with:
39+
script: |
40+
const commitMessage = context.payload.head_commit?.message || '';
41+
core.info(`Latest commit message: "${commitMessage}"`);
42+
const isReleaseCommit = commitMessage.includes('chore(release): v10.');
43+
core.setOutput('is_release_commit', isReleaseCommit);
44+
45+
- name: Bump package versions if commit is not a release commit
46+
if: steps.check-commit.outputs.is_release_commit != 'true'
47+
run: |
48+
yarn lerna version prerelease --force-publish --no-push --no-git-tag-version --preid community --yes
49+
50+
- name: Get latest v10 tag and determine next appropriate tag
51+
id: calculate-tag
52+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
53+
with:
54+
script: |
55+
const { execSync } = require('child_process');
56+
const semver = require('semver');
57+
58+
// Get all tags starting with v10
59+
const output = execSync("git tag --list 'v10.*.*'", { encoding: 'utf8' });
60+
const tags = output.trim().split('\n').filter(Boolean);
61+
62+
if (tags.length === 0) {
63+
core.setFailed('No v10 tags found.');
64+
return;
65+
}
66+
67+
// Sort with semver
68+
const sorted = tags.sort((a, b) => semver.rcompare(a, b));
69+
const latest = sorted[0];
70+
71+
// The third argument to semver.inc(version, release, identifier)
72+
// is crucial. 'prerelease' tells semver to increment the final
73+
// numeric part of the prerelease tag. 'community' is passed as the
74+
// prerelease identifier to continue the correct sequence.
75+
// This ensures v10.60.3-community.1 is bumped to
76+
// v10.60.3-community.2, for example.
77+
const next = semver.inc(latest, 'prerelease', 'community');
78+
79+
// semver.inc() returns the version without the 'v' prefix. Here
80+
// we add it back to ensure the final tag is prefixed with 'v'.
81+
const nextTag = `v${next}`;
82+
83+
core.info(`Latest v10 tag: ${latest}`);
84+
core.info(`Next v10 tag: ${next}`);
85+
core.setOutput('latestTag', latest);
86+
core.setOutput('nextTag', next);
87+
88+
# TODO dry run this as a test to see what we'd have
89+
- name: If commit is a release commit, tag it
90+
if: steps.check-commit.outputs.is_release_commit == 'true'
91+
run: |
92+
echo ${{ steps.calculate-tag.outputs.nextTag }} ${{ steps.calculate-tag.outputs.nextTag }}
93+
# run: |
94+
# git tag -a ${{ steps.calculate-tag.outputs.nextTag }} -m '${{ steps.calculate-tag.outputs.nextTag }}' && git push upstream ${{ steps.calculate-tag.outputs.nextTag }}
95+
96+
- name: Generate token
97+
# Do not generate token for release commits
98+
if: steps.check-commit.outputs.is_release_commit != 'true'
99+
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a #v2.1.0
100+
id: generate_token
101+
with:
102+
app_id: ${{ secrets.APP_ID }}
103+
private_key: ${{ secrets.APP_PRIVATE_KEY }}
104+
105+
- name: Create Pull Request
106+
# Do not create PR for release commits
107+
if: steps.check-commit.outputs.is_release_commit != 'true'
108+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
109+
with:
110+
branch: 'release/${{ steps.calculate-tag.outputs.nextTag }}'
111+
commit-message:
112+
'chore(release): ${{ steps.calculate-tag.outputs.nextTag }}'
113+
delete-branch: true
114+
base: 'v10'
115+
title: 'chore(release): ${{ steps.calculate-tag.outputs.nextTag }}'
116+
token: ${{ steps.generate_token.outputs.token }}
117+
body: |
118+
Automated release PR for ${{ steps.calculate-tag.outputs.nextTag }}
119+
120+
**Checklist**
121+
122+
- [ ] Verify package version bumps are accurate
123+
- [ ] Verify CI passes as expected

packages/cli/src/commands/changelog.js

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,31 +23,45 @@ const logger = createLogger('changelog');
2323
* v10.5.1..master or v10.5.0..v10.5.1
2424
* @returns {void}
2525
*/
26-
async function changelog({ range }) {
27-
displayBanner();
28-
29-
logger.start('Fetching latest git information from upstream');
26+
async function changelog({ range, noPrompt = false }) {
27+
if (!noPrompt) {
28+
displayBanner();
29+
logger.start('Fetching latest git information from upstream');
30+
}
3031
await fetchLatestFromUpstream();
31-
logger.stop();
32+
if (!noPrompt) {
33+
logger.stop();
34+
}
3235

33-
logger.start('Getting a list of all packages in the project');
36+
if (!noPrompt) {
37+
logger.start('Getting a list of all packages in the project');
38+
}
3439
const packages = await getPackages();
35-
logger.stop();
40+
if (!noPrompt) {
41+
logger.stop();
42+
}
3643

3744
const [lastTag, tag] = range.split('..');
38-
logger.start(`Generating a changelog for range: ${range}`);
45+
if (!noPrompt) {
46+
logger.start(`Generating a changelog for range: ${range}`);
47+
}
3948
const changelog = await generate(packages, lastTag, tag);
40-
logger.stop();
49+
if (!noPrompt) {
50+
logger.stop();
51+
}
4152

42-
const { copy } = await prompt([
43-
{
44-
type: 'confirm',
45-
name: 'copy',
46-
message: 'Would you like to copy the changelog to your clipboard?',
47-
},
48-
]);
53+
let response;
54+
if (!noPrompt) {
55+
response = await prompt([
56+
{
57+
type: 'confirm',
58+
name: 'copy',
59+
message: 'Would you like to copy the changelog to your clipboard?',
60+
},
61+
]);
62+
}
4963

50-
if (copy) {
64+
if (response?.copy) {
5165
clipboard.writeSync(changelog);
5266
console.log('Done!');
5367
} else {
@@ -63,6 +77,13 @@ module.exports = {
6377
describe: 'the git tag range to generate a changelog for',
6478
type: 'string',
6579
});
80+
yargs.options({
81+
n: {
82+
alias: 'noPrompt',
83+
describe: 'disables copy to clipboard prompt',
84+
type: 'boolean',
85+
},
86+
});
6687
},
6788
handler: changelog,
6889
};

0 commit comments

Comments
 (0)