diff --git a/.github/actions/bump-version/action.yml b/.github/actions/bump-version/action.yml new file mode 100644 index 000000000..03cb236cd --- /dev/null +++ b/.github/actions/bump-version/action.yml @@ -0,0 +1,42 @@ +name: "Bump version" +inputs: + github-token: + description: GitHub token for authentication + required: true + version: + description: Optional version so action can skip checking separately +runs: + using: "composite" + steps: + - name: Get release draft version and set environment variable + if: "${{ inputs.version == '' }}" + run: | + VERSION_DETAILS=gh release -R ${{ github.repository }} ls -L 1 --json isDraft,tagName + echo "PKG_VERSION=$VERSION_DETAILS" >> "$GITHUB_ENV" + env: + GH_TOKEN: ${{ inputs.github-token }} + - name: Set environment variable to version + if: "${{ inputs.version != '' }}" + run: echo "PKG_VERSION=${{ inputs.version }}" >> "$GITHUB_ENV" + - name: Bump version of package + id: bump-version + run: | + PARSED_VERSION=node .\scripts\bump-version.js $PKG_VERSION + npm version $PARSED_VERSION --commit-hooks false --git-tag-version false + echo "PKG_VERSION=$PARSED_VERSION" >> "$GITHUB_OUTPUT" + - name: Create pull request with the changes + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ inputs.github-token }} + commit-message: Update files containing the version of the package + branch: bump-version + title: Update version for the next release (${{ steps.bump-version.outputs.PKG_VERSION }}) + body: | + _This PR is auto-generated._ + + - updates package version to `"${{ steps.bump-version.outputs.PKG_VERSION }}"` + + > [!IMPORTANT] + > + > Every time the main branch is updated and release drafter bumps the version of the new release draft, this PR is auto-updated! + labels: skip-changelog diff --git a/.github/workflows/create-version-bump-pr.yml b/.github/workflows/create-version-bump-pr.yml new file mode 100644 index 000000000..b589f3c0c --- /dev/null +++ b/.github/workflows/create-version-bump-pr.yml @@ -0,0 +1,16 @@ +name: Create Version Bump PR +on: + workflow_dispatch: + +jobs: + update_version: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Bump version in package files and create PR + uses: ./.github/actions/bump-version + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 20f2d83f4..26c8bd43c 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -8,9 +8,27 @@ on: jobs: update_release_draft: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - - uses: release-drafter/release-drafter@v6 + - name: Draft release + id: draft-release + uses: release-drafter/release-drafter@v6 with: config-name: release-draft-template.yml env: GITHUB_TOKEN: ${{ secrets.RELEASE_DRAFTER_TOKEN }} + - name: Get status of version bump PR + id: pr-status + run: | + PR_NOT_OPEN=gh pr -R ${{ github.repository }} view bump-version --json closed --jq '.closed' || echo true + echo "PR_NOT_OPEN=$PR_NOT_OPEN" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@v4 + if: steps.pr-status.outputs.PR_NOT_OPEN == 'false' + - name: Update PR with version bump + if: steps.pr-status.outputs.PR_NOT_OPEN == 'false' + uses: ./.github/actions/bump-version + with: + version: ${{ steps.draft-release.outputs.resolved_version }} + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/bump-version.js b/scripts/bump-version.js new file mode 100644 index 000000000..e84230f04 --- /dev/null +++ b/scripts/bump-version.js @@ -0,0 +1,60 @@ +import { argv, stdout } from "node:process"; +import { writeFileSync } from "node:fs"; + +if (argv.length !== 3) { + throw new Error("expected one command line argument", { + cause: argv.slice(2), + }); +} + +/** @type {string | { isDraft: boolean; tagName: string }[]} */ +const arrayWithLastReleaseOrVersion = JSON.parse(argv[2]); + +const pkgVersion = (function () { + if (typeof arrayWithLastReleaseOrVersion === "string") { + return arrayWithLastReleaseOrVersion; + } + + if ( + !Array.isArray(arrayWithLastReleaseOrVersion) || + arrayWithLastReleaseOrVersion.length !== 1 + ) { + throw new Error("expected an array with one element", { + cause: arrayWithLastReleaseOrVersion, + }); + } + + const lastRelease = arrayWithLastReleaseOrVersion[0]; + const { isDraft, tagName } = lastRelease; + + if ( + Object.keys(lastRelease).length !== 2 || + typeof isDraft !== "boolean" || + typeof tagName !== "string" + ) { + throw new Error( + 'expected an object with keys "isDraft" as boolean and "tagName" as string', + { cause: lastRelease }, + ); + } + + if (!isDraft) { + throw new Error( + "expected a draft release to be present as the first element in the releases list", + ); + } + + // remove the "v" from "vX.X.X" + return tagName.substring(1); +})(); + +const pkgVersionFileContents = `export const PACKAGE_VERSION = "${pkgVersion}";\n`; + +const pkgVersionFilePath = new URL( + "../src/package-version.ts", + import.meta.url, +); + +writeFileSync(pkgVersionFilePath, pkgVersionFileContents); + +stdout.write(pkgVersion);