diff --git a/.github/actions/get-prerelease/action.yml b/.github/actions/get-prerelease/action.yml new file mode 100644 index 0000000..ce7acdc --- /dev/null +++ b/.github/actions/get-prerelease/action.yml @@ -0,0 +1,30 @@ +name: Return a boolean indicating if the version contains prerelease identifiers + +# +# Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not. +# +# TODO: Remove once the common repo is public. +# + +inputs: + version: + required: true + +outputs: + prerelease: + value: ${{ steps.get_prerelease.outputs.PRERELEASE }} + +runs: + using: composite + + steps: + - id: get_prerelease + shell: bash + run: | + if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then + echo "PRERELEASE=true" >> $GITHUB_OUTPUT + else + echo "PRERELEASE=false" >> $GITHUB_OUTPUT + fi + env: + VERSION: ${{ inputs.version }} diff --git a/.github/actions/get-release-notes/action.yml b/.github/actions/get-release-notes/action.yml new file mode 100644 index 0000000..4c3219b --- /dev/null +++ b/.github/actions/get-release-notes/action.yml @@ -0,0 +1,42 @@ +name: Return the release notes extracted from the body of the PR associated with the release. + +# +# Returns the release notes from the content of a pull request linked to a release branch. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc. +# +# TODO: Remove once the common repo is public. +# +inputs: + version: + required: true + repo_name: + required: false + repo_owner: + required: true + token: + required: true + +outputs: + release-notes: + value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }} + +runs: + using: composite + + steps: + - uses: actions/github-script@v7 + id: get_release_notes + with: + result-encoding: string + script: | + const { data: pulls } = await github.rest.pulls.list({ + owner: process.env.REPO_OWNER, + repo: process.env.REPO_NAME, + state: 'all', + head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`, + }); + core.setOutput('RELEASE_NOTES', pulls[0].body); + env: + GITHUB_TOKEN: ${{ inputs.token }} + REPO_OWNER: ${{ inputs.repo_owner }} + REPO_NAME: ${{ inputs.repo_name }} + VERSION: ${{ inputs.version }} \ No newline at end of file diff --git a/.github/actions/get-version/action.yml b/.github/actions/get-version/action.yml new file mode 100644 index 0000000..8e0b132 --- /dev/null +++ b/.github/actions/get-version/action.yml @@ -0,0 +1,26 @@ +name: Return the version extracted from the branch name + +# +# Returns the version from the .version file. +# +# TODO: Remove once the common repo is public. +# + +inputs: + working-directory: + default: './' + +outputs: + version: + value: ${{ steps.get_version.outputs.VERSION }} + +runs: + using: composite + + steps: + - id: get_version + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + VERSION=$(head -1 .version) + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/actions/npm-publish/action.yml b/.github/actions/npm-publish/action.yml new file mode 100644 index 0000000..45c87d1 --- /dev/null +++ b/.github/actions/npm-publish/action.yml @@ -0,0 +1,52 @@ +name: Publish release to npm + +inputs: + node-version: + required: true + npm-token: + required: true + version: + required: true + require-build: + default: true + release-directory: + default: './' + +runs: + using: composite + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + shell: bash + run: npm ci --include=dev + + - name: Build package + if: inputs.require-build == 'true' + shell: bash + run: npm run build + + - name: Publish release to NPM + shell: bash + working-directory: ${{ inputs.release-directory }} + run: | + if [[ "${VERSION}" == *"beta"* ]]; then + TAG="beta" + elif [[ "${VERSION}" == *"alpha"* ]]; then + TAG="alpha" + else + TAG="latest" + fi + npm publish --provenance --tag $TAG + env: + NODE_AUTH_TOKEN: ${{ inputs.npm-token }} + VERSION: ${{ inputs.version }} \ No newline at end of file diff --git a/.github/actions/release-create/action.yml b/.github/actions/release-create/action.yml new file mode 100644 index 0000000..6a2bf80 --- /dev/null +++ b/.github/actions/release-create/action.yml @@ -0,0 +1,47 @@ +name: Create a GitHub release + +# +# Creates a GitHub release with the given version. +# +# TODO: Remove once the common repo is public. +# + +inputs: + token: + required: true + files: + required: false + name: + required: true + body: + required: true + tag: + required: true + commit: + required: true + draft: + default: false + required: false + prerelease: + default: false + required: false + fail_on_unmatched_files: + default: true + required: false + +runs: + using: composite + + steps: + - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + with: + body: ${{ inputs.body }} + name: ${{ inputs.name }} + tag_name: ${{ inputs.tag }} + target_commitish: ${{ inputs.commit }} + draft: ${{ inputs.draft }} + prerelease: ${{ inputs.prerelease }} + fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} + files: ${{ inputs.files }} + env: + GITHUB_TOKEN: ${{ inputs.token }} diff --git a/.github/actions/tag-exists/action.yml b/.github/actions/tag-exists/action.yml new file mode 100644 index 0000000..b5fbdb7 --- /dev/null +++ b/.github/actions/tag-exists/action.yml @@ -0,0 +1,36 @@ +name: Return a boolean indicating if a tag already exists for the repository + +# +# Returns a simple true/false boolean indicating whether the tag exists or not. +# +# TODO: Remove once the common repo is public. +# + +inputs: + token: + required: true + tag: + required: true + +outputs: + exists: + description: 'Whether the tag exists or not' + value: ${{ steps.tag-exists.outputs.EXISTS }} + +runs: + using: composite + + steps: + - id: tag-exists + shell: bash + run: | + GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" + http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}") + if [ "$http_status_code" -ne "404" ] ; then + echo "EXISTS=true" >> $GITHUB_OUTPUT + else + echo "EXISTS=false" >> $GITHUB_OUTPUT + fi + env: + TAG_NAME: ${{ inputs.tag }} + GITHUB_TOKEN: ${{ inputs.token }} diff --git a/.github/workflows/npm-release.yml b/.github/workflows/npm-release.yml new file mode 100644 index 0000000..248ceef --- /dev/null +++ b/.github/workflows/npm-release.yml @@ -0,0 +1,83 @@ +name: Create npm and GitHub Release + +on: + workflow_call: + inputs: + node-version: + required: true + type: string + require-build: + default: true + type: string + release-directory: + default: './' + type: string + secrets: + github-token: + required: true + npm-token: + required: true + +jobs: + release: + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) + runs-on: ubuntu-latest + environment: release + + steps: + # Checkout the code + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Get the version from the branch name + - id: get_version + uses: ./get-version + with: + working-directory: ${{ inputs.release-directory }} + + + # Get the prerelease flag from the branch name + - id: get_prerelease + uses: ./get-prerelease + with: + version: ${{ steps.get_version.outputs.version }} + + # Get the release notes + - id: get_release_notes + uses: ./get-release-notes + with: + token: ${{ secrets.github-token }} + version: ${{ steps.get_version.outputs.version }} + repo_owner: ${{ github.repository_owner }} + repo_name: ${{ github.event.repository.name }} + + # Check if the tag already exists + - id: tag_exists + uses: ./tag-exists + with: + tag: ${{ steps.get_version.outputs.version }} + token: ${{ secrets.github-token }} + + # If the tag already exists, exit with an error + - if: steps.tag_exists.outputs.exists == 'true' + run: exit 1 + + # Publish the release to our package manager + - uses: ./npm-publish + with: + node-version: ${{ inputs.node-version }} + require-build: ${{ inputs.require-build }} + version: ${{ steps.get_version.outputs.version }} + npm-token: ${{ secrets.npm-token }} + release-directory: ${{ inputs.release-directory }} + + # Create a release for the tag + - uses: ./release-create + with: + token: ${{ secrets.github-token }} + name: ${{ steps.get_version.outputs.version }} + body: ${{ steps.get_release_notes.outputs.release-notes }} + tag: ${{ steps.get_version.outputs.version }} + commit: ${{ github.sha }} + prerelease: ${{ steps.get_prerelease.outputs.prerelease }} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d109574..4d69bea 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,111 +1,22 @@ name: Publish Release on: + pull_request: + types: + - closed workflow_dispatch: - inputs: - branch: - description: The branch to release from - required: true - default: main - version: - description: The version being published. This should be a valid semver version, such as `1.0.0`. - required: true - default: "" - type: string - dry-run: - type: boolean - description: Perform a publishing dry run. This will not publish the release, but will validate the release and log the commands that would be run. - default: false permissions: contents: read - id-token: write # For publishing to NPM with provenance. Allows developers to run `npm audit signatures` and verify release signature of SDK. @see https://github.blog/2023-04-19-introducing-npm-package-provenance/ - -env: - NODE_VERSION: 18 - NODE_ENV: development + id-token: write # For publishing to npm using --provenance jobs: - configure: - name: Validate input parameters - runs-on: ubuntu-latest - - outputs: - vtag: ${{ steps.vtag.outputs.vtag }} # The fully constructed release tag to use for publishing - dry-run: ${{ steps.dry-run.outputs.dry-run }} # The dry-run flag to use for publishing, if applicable - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.branch }} - - # Configure for dry-run, if applicable. @see https://docs.npmjs.com/cli/v9/commands/npm-publish#dry-run - - id: dry-run - if: ${{ github.event.inputs.dry-run == 'true' }} - name: Configure for `--dry-run` - run: | - echo "dry-run=--dry-run" >> $GITHUB_ENV - echo "dry-run=--dry-run" >> $GITHUB_OUTPUT - - # Build the tag string from package.json version and release suffix. Produces something like `1.0.0-beta.1` for a beta, or `1.0.0` for a stable release. - - name: Build tag - id: vtag - run: | - PACKAGE_VERSION="${{ github.event.inputs.version }}" - echo "vtag=${PACKAGE_VERSION}" >> $GITHUB_ENV - echo "vtag=${PACKAGE_VERSION}" >> $GITHUB_OUTPUT - - # Ensure tag does not already exist. - - name: Validate version - uses: actions/github-script@v7 - env: - vtag: ${{ env.vtag }} - with: - script: | - const releaseMeta = github.rest.repos.listReleases.endpoint.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - }); - - const releases = await github.paginate(releaseMeta); - - for (const release of releases) { - if (release.name === process.env.vtag) { - throw new Error(`${process.env.vtag} already exists`); - } - } - - console.log(`${process.env.vtag} does not exist. Proceeding with release.`) - - publish-npm: - needs: configure - - name: Publish to NPM - runs-on: ubuntu-latest - environment: "release" - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.branch }} - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: npm - registry-url: "https://registry.npmjs.org" - - - name: Install dependencies - run: npm ci - - - run: npm run build - - - name: Publish release to NPM - run: npm publish --provenance --tag ${{ needs.configure.outputs.vtag }} ${{ needs.configure.outputs.dry-run }} --workspace=packages/express-oauth2-jwt-bearer - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + release: + uses: ./.github/workflows/npm-release.yml + with: + node-version: 18 + require-build: true + release-directory: './packages/express-oauth2-jwt-bearer' + secrets: + npm-token: ${{ secrets.NPM_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/packages/express-oauth2-jwt-bearer/.shiprc b/packages/express-oauth2-jwt-bearer/.shiprc index 495532b..c3629fa 100644 --- a/packages/express-oauth2-jwt-bearer/.shiprc +++ b/packages/express-oauth2-jwt-bearer/.shiprc @@ -1,3 +1,6 @@ { + "files": { + ".version": [] + }, "postbump": "npm install --prefix=../.. && npm run docs --prefix=../.." } diff --git a/packages/express-oauth2-jwt-bearer/.version b/packages/express-oauth2-jwt-bearer/.version new file mode 100644 index 0000000..05f629f --- /dev/null +++ b/packages/express-oauth2-jwt-bearer/.version @@ -0,0 +1 @@ +v1.6.0 \ No newline at end of file