name: Release Make on: workflow_call: secrets: ELEMENT_BOT_TOKEN: required: true GPG_PASSPHRASE: required: false GPG_PRIVATE_KEY: required: false inputs: final: description: Make final release required: true default: false type: boolean npm: description: Publish to npm type: boolean default: false gpg-fingerprint: description: Fingerprint of the GPG key to use for signing the git tag and assets, if any. type: string required: false asset-path: description: | The path to the asset you want to upload, if any. You can use glob patterns here. Will be GPG signed and an `.asc` file included in the release artifacts if `gpg-fingerprint` is set. Relative to `dir`. type: string required: false expected-asset-count: description: The number of expected assets, including signatures, excluding generated zip & tarball. type: number required: false dist-dir: description: The directory to release type: string default: "." version-dirs: description: Directories in which to update package.json `version` field type: string required: false outputs: npm-id: description: "The npm package@version string we published" value: ${{ jobs.npm.outputs.id }} permissions: {} jobs: checks: name: Sanity checks permissions: issues: read pull-requests: read uses: matrix-org/matrix-js-sdk/.github/workflows/release-checks.yml@develop # zizmor: ignore[unpinned-uses] release: name: Release runs-on: ubuntu-24.04 environment: Release needs: checks permissions: contents: write steps: - name: Load GPG key id: gpg if: inputs.gpg-fingerprint uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} fingerprint: ${{ inputs.gpg-fingerprint }} - name: Get draft release id: draft-release uses: cardinalby/git-get-release-action@5172c3a026600b1d459b117738c605fabc9e4e44 # 1.2.5 env: GITHUB_TOKEN: ${{ github.token }} with: draft: true latest: true - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: staging # We will be pushing to this branch and want the CI to run after we do so we cannot use the GITHUB_TOKEN token: ${{ secrets.ELEMENT_BOT_TOKEN }} fetch-depth: 0 persist-credentials: true - name: Get actions scripts uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: repository: matrix-org/matrix-js-sdk persist-credentials: false path: .action-repo sparse-checkout: | .github/actions scripts/release - name: Prepare variables id: prepare working-directory: ${{ inputs.dist-dir }} run: | echo "VERSION=$VERSION" >> $GITHUB_ENV HAS_DIST=0 jq -e .scripts.dist package.json >/dev/null 2>&1 && HAS_DIST=1 echo "has-dist-script=$HAS_DIST" >> $GITHUB_OUTPUT env: VERSION: ${{ steps.draft-release.outputs.tag_name }} - name: Finalise version if: inputs.final run: echo "VERSION=$(echo $VERSION | cut -d- -f1)" >> $GITHUB_ENV - name: Check version number not in use uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | const { VERSION } = process.env; github.rest.repos.getReleaseByTag({ owner: context.repo.owner, repo: context.repo.repo, tag: VERSION, }).then(() => { core.setFailed(`Version ${VERSION} already exists`); }).catch(() => { // This is fine, we expect there to not be any release with this version yet }); - name: Set up git run: | git config --global user.email "releases@riot.im" git config --global user.name "RiotRobot" - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: cache: "pnpm" node-version-file: ${{ inputs.dist-dir }}/package.json - name: Install dependencies run: "pnpm install --frozen-lockfile" - name: Handle develop dependencies working-directory: ${{ inputs.dist-dir }} run: | ret=0 cat package.json | jq -r '.dependencies | to_entries | .[] | "\(.key) \(.value)"' | grep '#develop$' | while read -r dep ; do IFS=" " PACKAGE=${dep[0]} VERSION=${dep[1]} echo "::warning title=Develop dependency found::$DEPENDENCY will be kept at $VERSION" pnpm add "$PACKAGE@$VERSION" --save-exact git add -u git commit -m "Keep $PACKAGE at $VERSION" done - name: Bump package.json versions run: | for DIR in $DIRS; do pnpm version -C "$DIR" --no-git-tag-version "${VERSION#v}" git add "$DIR"/package.json done env: DIRS: ${{ inputs.version-dirs || inputs.dist-dir }} - name: Add to CHANGELOG.md if: inputs.final run: | mv CHANGELOG.md CHANGELOG.md.old HEADER="Changes in [${VERSION#v}](https://github.com/${{ github.repository }}/releases/tag/$VERSION) ($(date '+%Y-%m-%d'))" { echo "$HEADER" printf '=%.0s' $(seq ${#HEADER}) echo "" echo "$RELEASE_NOTES" echo "" } > CHANGELOG.md cat CHANGELOG.md.old >> CHANGELOG.md rm CHANGELOG.md.old git add CHANGELOG.md env: RELEASE_NOTES: ${{ steps.draft-release.outputs.body }} - name: Commit changes run: git commit -m "$VERSION" - name: Build assets if: steps.prepare.outputs.has-dist-script == '1' working-directory: ${{ inputs.dist-dir }} run: DIST_VERSION="$VERSION" pnpm dist - name: Upload release assets & signatures if: inputs.asset-path uses: ./.action-repo/.github/actions/upload-release-assets with: gpg-fingerprint: ${{ inputs.gpg-fingerprint }} upload-url: ${{ steps.draft-release.outputs.upload_url }} asset-path: ${{ inputs.dist-dir }}/${{ inputs.asset-path }} - name: Create signed tag if: inputs.gpg-fingerprint run: | GIT_COMMITTER_EMAIL="$SIGNING_ID" GPG_TTY=$(tty) git tag -u "$SIGNING_ID" -m "Release $VERSION" "$VERSION" env: SIGNING_ID: ${{ steps.gpg.outputs.email }} - name: Generate & upload tarball signature if: inputs.gpg-fingerprint uses: ./.action-repo/.github/actions/sign-release-tarball with: gpg-fingerprint: ${{ inputs.gpg-fingerprint }} upload-url: ${{ steps.draft-release.outputs.upload_url }} # We defer pushing changes until after the release assets are built, # signed & uploaded to improve the atomicity of this action. - name: Push changes to staging run: | git push origin staging $TAG git reset --hard env: TAG: ${{ inputs.gpg-fingerprint && env.VERSION || '' }} - name: Validate tarball signature if: inputs.gpg-fingerprint run: | wget https://github.com/$GITHUB_REPOSITORY/archive/refs/tags/$VERSION.tar.gz gpg --verify "$VERSION.tar.gz.asc" "$VERSION.tar.gz" - name: Validate release has expected assets if: inputs.expected-asset-count uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 env: RELEASE_ID: ${{ steps.draft-release.outputs.id }} EXPECTED_ASSET_COUNT: ${{ inputs.expected-asset-count }} with: retries: 3 script: | const { RELEASE_ID: release_id, EXPECTED_ASSET_COUNT } = process.env; const { owner, repo } = context.repo; const { data: release } = await github.rest.repos.getRelease({ owner, repo, release_id, }); if (release.assets.length !== parseInt(EXPECTED_ASSET_COUNT, 10)) { core.setFailed(`Found ${release.assets.length} assets but expected ${EXPECTED_ASSET_COUNT}`); } - name: Merge to master if: inputs.final run: | git checkout master git merge -X theirs staging git push origin master - name: Publish release uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 env: RELEASE_ID: ${{ steps.draft-release.outputs.id }} FINAL: ${{ inputs.final }} with: retries: 3 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} script: | const { RELEASE_ID: release_id, RELEASE_NOTES, VERSION, FINAL } = process.env; const { owner, repo } = context.repo; const opts = { owner, repo, release_id, tag_name: VERSION, name: VERSION, draft: false, body: RELEASE_NOTES, }; if (FINAL == "true") { opts.prerelease = false; opts.make_latest = true; } github.rest.repos.updateRelease(opts); npm: name: Publish to npm needs: release if: inputs.npm uses: matrix-org/matrix-js-sdk/.github/workflows/release-npm.yml@develop # zizmor: ignore[unpinned-uses] with: dir: ${{ inputs.dist-dir }} permissions: contents: read id-token: write post-release: name: Post release steps needs: release runs-on: ubuntu-24.04 permissions: issues: write steps: - id: repository run: echo "REPO=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT - name: Advance release blocker labels uses: garganshu/github-label-updater@3770d15ebfed2fe2cb06a241047bc340f774a7d1 # v1.0.0 with: owner: ${{ github.repository_owner }} repo: ${{ steps.repository.outputs.REPO }} token: ${{ secrets.GITHUB_TOKEN }} filter-labels: X-Upcoming-Release-Blocker remove-labels: X-Upcoming-Release-Blocker add-labels: X-Release-Blocker # - name: Wait for master->develop gitflow merge # if: inputs.final # uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork # with: # ref: master # repo-token: ${{ secrets.GITHUB_TOKEN }} # wait-interval: 10 # check-name: merge # allowed-conclusions: success