Skip to content
Closed
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
46 changes: 46 additions & 0 deletions .github/workflows/major-release-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Major release validation

on:
merge_group:
pull_request:

permissions:
contents: read

jobs:
check:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }}-major-release-validation
cancel-in-progress: true

name: "Major release validation"
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 100

# Find all the changesets that were modified or added in this pull request
- uses: dorny/paths-filter@v3
id: filters
with:
list-files: "json"
predicate-quantifier: "every"
filters: |
updated_changesets:
- added|modified: '.changeset/*.md'
- added|modified: '!.changeset/README.md'

- name: Install Dependencies
if: steps.filters.outputs.updated_changesets == 'true'
uses: ./.github/actions/install-dependencies

# Validate that the pull request is marked with the appropriate label if any of the modified or added changesets have major releases
- name: Validate major changes
if: steps.filters.outputs.updated_changesets == 'true'
run: node -r esbuild-register tools/deployments/validate-major-changes.ts
with:
GITHUB_TOKEN: ${{ github.token }}
CHANGES: ${{ steps.filters.outputs.updated_changesets_files }}
LABEL: "breaking-change"
71 changes: 71 additions & 0 deletions tools/deployments/validate-major-changesets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { readFileSync } from "node:fs";
import core from "@actions/core";
import { context, getOctokit } from "@actions/github";
import parseChangesetFile from "@changesets/parse";

if (require.main === module) {
main().catch((err) => {
console.error("Unexpected error:", err);
process.exit(1);
});
}

async function main() {
const majorChangesets = getMajorChangesets();
const breakingChangeLabel = core.getInput("label", { required: true });
const hasBreakingChangeLabel = await hasLabel(breakingChangeLabel);

if (majorChangesets.length === 0) {
if (hasBreakingChangeLabel) {
core.error(
`This PR is marked with the "${breakingChangeLabel}" but does not contain any major release changesets.`
);
process.exit(1);
}
} else {
if (!hasBreakingChangeLabel) {
core.error(
`This PR contains the following changesets that have major releases, but is not marked with the "${breakingChangeLabel}" label`
);
majorChangesets.forEach((changeset) => {
core.info(
` - "${changeset.file}": ${changeset.summary.split("\n")[0]}`
);
});
process.exit(1);
}
}
}

/**
* Returns an array of parsed changesets that contain at least one major release
*/
export function getMajorChangesets() {
const files = JSON.parse(
core.getInput("changes", { required: true })
) as string[];

const changesets = files.map((file) => ({
file,
...parseChangesetFile(readFileSync(file, "utf-8")),
}));
return changesets.filter((parsed) =>
parsed.releases.some((r) => r.type === "major")
);
}

/**
* Returns true of the current issue/pr has the given label
*/
export async function hasLabel(label: string) {
const token = core.getInput("github_token", { required: true });

const octokit = getOctokit(token);
const issue = await octokit.rest.issues.get({
...context.repo,
issue_number: context.issue.number,
});
return !!issue.data.labels.find(
(l) => (typeof l !== "string" && l.name === label) || l === label
);
}
Loading