Skip to content

Commit f5abff8

Browse files
ci: block major changesets
1 parent 4462bc1 commit f5abff8

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Major release validation
2+
3+
on:
4+
merge_group:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
check:
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }}-major-release-validation
14+
cancel-in-progress: true
15+
16+
name: "Major release validation"
17+
runs-on: ubuntu-24.04-arm
18+
steps:
19+
- name: Checkout Repo
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 100
23+
24+
# Find all the changesets that were modified or added in this pull request
25+
- uses: dorny/paths-filter@v3
26+
id: filters
27+
with:
28+
list-files: "json"
29+
predicate-quantifier: "every"
30+
filters: |
31+
updated_changesets:
32+
- added|modified: '.changeset/*.md'
33+
- added|modified: '!.changeset/README.md'
34+
35+
- name: Install Dependencies
36+
if: steps.filters.outputs.updated_changesets == 'true'
37+
uses: ./.github/actions/install-dependencies
38+
39+
# Validate that the pull request is marked with the appropriate label if any of the modified or added changesets have major releases
40+
- name: Validate major changes
41+
if: steps.filters.outputs.updated_changesets == 'true'
42+
run: node -r esbuild-register tools/deployments/validate-major-changes.ts
43+
with:
44+
GITHUB_TOKEN: ${{ github.token }}
45+
CHANGES: ${{ steps.filters.outputs.updated_changesets_files }}
46+
LABEL: "breaking-change"
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { readFileSync } from "node:fs";
2+
import core from "@actions/core";
3+
import { context, getOctokit } from "@actions/github";
4+
import parseChangesetFile from "@changesets/parse";
5+
6+
if (require.main === module) {
7+
main().catch((err) => {
8+
console.error("Unexpected error:", err);
9+
process.exit(1);
10+
});
11+
}
12+
13+
async function main() {
14+
const majorChangesets = getMajorChangesets();
15+
const breakingChangeLabel = core.getInput("label", { required: true });
16+
const hasBreakingChangeLabel = await hasLabel(breakingChangeLabel);
17+
18+
if (majorChangesets.length === 0) {
19+
if (hasBreakingChangeLabel) {
20+
core.error(
21+
`This PR is marked with the "${breakingChangeLabel}" but does not contain any major release changesets.`
22+
);
23+
process.exit(1);
24+
}
25+
} else {
26+
if (!hasBreakingChangeLabel) {
27+
core.error(
28+
`This PR contains the following changesets that have major releases, but is not marked with the "${breakingChangeLabel}" label`
29+
);
30+
majorChangesets.forEach((changeset) => {
31+
core.info(
32+
` - "${changeset.file}": ${changeset.summary.split("\n")[0]}`
33+
);
34+
});
35+
process.exit(1);
36+
}
37+
}
38+
}
39+
40+
/**
41+
* Returns an array of parsed changesets that contain at least one major release
42+
*/
43+
export function getMajorChangesets() {
44+
const files = JSON.parse(
45+
core.getInput("changes", { required: true })
46+
) as string[];
47+
48+
const changesets = files.map((file) => ({
49+
file,
50+
...parseChangesetFile(readFileSync(file, "utf-8")),
51+
}));
52+
return changesets.filter((parsed) =>
53+
parsed.releases.some((r) => r.type === "major")
54+
);
55+
}
56+
57+
/**
58+
* Returns true of the current issue/pr has the given label
59+
*/
60+
export async function hasLabel(label: string) {
61+
const token = core.getInput("github_token", { required: true });
62+
63+
const octokit = getOctokit(token);
64+
const issue = await octokit.rest.issues.get({
65+
...context.repo,
66+
issue_number: context.issue.number,
67+
});
68+
return !!issue.data.labels.find(
69+
(l) => (typeof l !== "string" && l.name === label) || l === label
70+
);
71+
}

0 commit comments

Comments
 (0)