diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml deleted file mode 100644 index 78fbc1c..0000000 --- a/.github/workflows/_build.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Build and publish container image - -on: - workflow_call: - inputs: - docker_tags: - type: string - required: true - description: Docker tags configuration for docker/metadata-action (multiline string) - -permissions: - contents: read - packages: write - -env: - GHCR_IMAGE: ghcr.io/${{ github.repository }} - -jobs: - build-image: - name: Build and publish container image - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Docker meta - id: meta - uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 - with: - images: | - ${{ env.GHCR_IMAGE }} - tags: ${{ inputs.docker_tags }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - - - name: Login to GitHub Container Registry - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push multi-platform image - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 - with: - platforms: linux/amd64,linux/arm64 - context: ./ - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - push: true - cache-from: type=gha - cache-to: type=gha,mode=min,ignore-error=true diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 0000000..6407e71 --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,15 @@ +name: actionlint + +on: + pull_request: + push: + branches: [main] + +permissions: {} + +jobs: + # Check name: `actionlint / actionlint` + actionlint: + uses: Aam-Digital/.github/.github/workflows/actionlint.yml@v1 + permissions: + contents: read diff --git a/.github/workflows/on-push.yml b/.github/workflows/on-push.yml index 8611b86..932198b 100644 --- a/.github/workflows/on-push.yml +++ b/.github/workflows/on-push.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-node@v7 with: - node-version-file: .node-version + node-version-file: .nvmrc - name: Install dependencies run: npm ci @@ -39,25 +39,42 @@ jobs: - name: Run e2e tests run: npm run test:e2e + # Check name: `Create Release / release`. Auto release mode: every releasable push to main becomes a full release. release: name: Create Release if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest needs: - test + uses: Aam-Digital/.github/.github/workflows/release.yml@v1 permissions: - contents: write - issues: write - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v7 + contents: read # everything that writes uses the GH_TOKEN PAT (its releases trigger the image build) + secrets: + token: ${{ secrets.GH_TOKEN }} - - uses: actions/setup-node@v7 - with: - node-version-file: .node-version + # Edge build of every main push (see Aam-Digital/.github): version `X.Y.Z-edge.N` (the last release with the patch + # number bumped, plus the commits since), no tag and no GitHub release. + edge-version: + if: github.ref == 'refs/heads/main' + needs: + - test + uses: Aam-Digital/.github/.github/workflows/edge-version.yml@v1 + permissions: + contents: read - - name: Semantic Release - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - run: npx semantic-release + # Check names: `edge-build / build ()`, `edge-build / merge`. Image `X.Y.Z-edge.N`, plus `edge` if this is still the newest main commit. + # A newer push cancels an older edge build, so that `edge` never moves back. + edge-build: + needs: edge-version + concurrency: + group: edge-build + cancel-in-progress: true + uses: Aam-Digital/.github/.github/workflows/docker-image.yml@v1 + permissions: + contents: read + packages: write + with: + images: ghcr.io/aam-digital/platform-admin-services + version: ${{ needs.edge-version.outputs.version }} + tags: ${{ needs.edge-version.outputs.is-head == 'true' && 'edge' || '' }} + # package.json's version is what the app reports to Sentry + prepare: npm version "$VERSION" --no-git-tag-version --allow-same-version diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index 912d613..a8fda51 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -2,15 +2,34 @@ name: On Semantic Release (build & publish image) on: release: - types: [released] + types: [published] # created with the GH_TOKEN PAT, releases by the GITHUB_TOKEN trigger no workflows + +permissions: {} jobs: + # Check names: `build / build (linux/amd64)`, `build / build (linux/arm64)`, `build / merge` build: - uses: ./.github/workflows/_build.yml - with: - docker_tags: | - type=raw,value=${{ github.event.release.tag_name }} - type=raw,value=latest + uses: Aam-Digital/.github/.github/workflows/docker-image.yml@v1 permissions: contents: read packages: write + with: + images: ghcr.io/aam-digital/platform-admin-services + # Tag `` (e.g. `1.7.7`, also the `org.opencontainers.image.version` label); + # full releases also `stable` and `latest` + version: ${{ github.event.release.tag_name }} + stable: ${{ !github.event.release.prerelease }} + # package.json's version is what the app reports to Sentry + prepare: npm version "$VERSION" --no-git-tag-version --allow-same-version + + # After a full release, the edge images (`*-edge.*`) of the finished cycle are no longer needed. The image `edge` + # points to is kept. + prune-edge-images: + if: ${{ !github.event.release.prerelease }} + needs: build + uses: Aam-Digital/.github/.github/workflows/image-cleanup.yml@v1 + permissions: + actions: write + packages: write + with: + tags: "*-edge.*" diff --git a/.github/workflows/pr-cleanup.yml b/.github/workflows/pr-cleanup.yml deleted file mode 100644 index 2f04d95..0000000 --- a/.github/workflows/pr-cleanup.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: On PR Closed (cleanup pre-release image) - -on: - pull_request: - types: [closed] - -permissions: - contents: read - -jobs: - cleanup: - name: Delete pr- package version - runs-on: ubuntu-latest - steps: - - name: Delete GHCR image tagged pr-${{ github.event.number }} - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - set -euo pipefail - TAG="pr-${{ github.event.number }}" - OWNER="${{ github.repository_owner }}" - PACKAGE="${{ github.event.repository.name }}" - - version_ids=$(gh api "/orgs/$OWNER/packages/container/$PACKAGE/versions" --paginate \ - --jq ".[] | select(.metadata.container.tags | index(\"$TAG\")) | .id") - - if [ -z "$version_ids" ]; then - echo "No package version tagged $TAG found, nothing to delete" - exit 0 - fi - - for id in $version_ids; do - echo "Deleting package version $id (tag $TAG)" - gh api --method DELETE "/orgs/$OWNER/packages/container/$PACKAGE/versions/$id" - done diff --git a/.github/workflows/pr-closed.yml b/.github/workflows/pr-closed.yml new file mode 100644 index 0000000..e678549 --- /dev/null +++ b/.github/workflows/pr-closed.yml @@ -0,0 +1,19 @@ +name: On PR closed (delete pre-release image) + +on: + pull_request: + types: [closed] + +permissions: {} + +jobs: + # Check name: `cleanup-image / cleanup` + cleanup-image: + # PRs from forks have no image + if: github.event.pull_request.head.repo.full_name == github.repository + uses: Aam-Digital/.github/.github/workflows/image-cleanup.yml@v1 + permissions: + actions: write # cancel a still running PR image build, which would otherwise push pr- after the cleanup + packages: write # the package is linked to this repository, so the job token can delete its versions + with: + build-workflows: pr-release.yml diff --git a/.github/workflows/pr-release.yml b/.github/workflows/pr-release.yml index 83627f9..eea2058 100644 --- a/.github/workflows/pr-release.yml +++ b/.github/workflows/pr-release.yml @@ -3,6 +3,9 @@ name: On PR (build & publish pre-release image) on: pull_request: +permissions: + contents: read + jobs: test: name: Test @@ -13,7 +16,7 @@ jobs: - uses: actions/setup-node@v7 with: - node-version-file: .node-version + node-version-file: .nvmrc - name: Install dependencies run: npm ci @@ -27,12 +30,19 @@ jobs: - name: Run e2e tests run: npm run test:e2e + # Check names: `build / build (linux/amd64)`, `build / merge` build: needs: test - uses: ./.github/workflows/_build.yml - with: - docker_tags: | - type=raw,value=pr-${{ github.event.number }} + uses: Aam-Digital/.github/.github/workflows/docker-image.yml@v1 permissions: contents: read packages: write + with: + images: ghcr.io/aam-digital/platform-admin-services + tags: pr-${{ github.event.number }} + platforms: linux/amd64 + # PRs from forks cannot push; only check that the image builds + push: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + # package.json's version is what the app reports to Sentry. npm only accepts semver, hence `0.0.0-pr.`; + # the image's version label is its tag `pr-`. + prepare: npm version "0.0.0-pr.${{ github.event.number }}" --no-git-tag-version --allow-same-version diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml new file mode 100644 index 0000000..5eb2adc --- /dev/null +++ b/.github/workflows/pr-title-lint.yml @@ -0,0 +1,16 @@ +name: PR title lint + +on: + # pull_request_target so that it also runs for PRs from forks. Safe here: the shared workflow never checks out PR code + # and only reads the PR title from the event payload. + pull_request_target: # zizmor: ignore[dangerous-triggers] never checks out PR code, only reads the PR title from the event payload + types: [opened, edited, synchronize, reopened] + +permissions: {} + +jobs: + # Check name: `pr-title / Validate PR title` + pr-title: + uses: Aam-Digital/.github/.github/workflows/pr-title-lint.yml@v1 + permissions: + pull-requests: read diff --git a/.node-version b/.node-version deleted file mode 100644 index 54c6511..0000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -v24 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..a43a5b9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.22.0 diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..765f069 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,15 @@ +{ + "tagFormat": "${version}", + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/github", + { + "successComment": false, + "failComment": false, + "labels": false + } + ] + ] +} diff --git a/renovate.json b/renovate.json index 10ddfb9..93f1cbe 100644 --- a/renovate.json +++ b/renovate.json @@ -1,95 +1,10 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - "group:allNonMajor", - ":maintainLockFilesMonthly" - ], - "enabledManagers": [ - "npm", - "dockerfile", - "docker-compose", - "github-actions" - ], - "timezone": "Etc/UTC", - "schedule": [ - "* * * * 0" - ], - "dependencyDashboard": false, - "prConcurrentLimit": 5, - "prHourlyLimit": 2, - "labels": [ - "dependencies" - ], - "vulnerabilityAlerts": { - "enabled": true, - "schedule": [ - "at any time" - ], - "labels": [ - "dependencies", - "security" - ] - }, - "osvVulnerabilityAlerts": true, - "reviewers": [ - "sleidig", - "Abhinegi2" - ], - "rangeStrategy": "bump", - "constraints": { - "npm": ">=11.0.0" - }, - "packageRules": [ - { - "matchManagers": [ - "npm" - ], - "rangeStrategy": "bump" - }, - { - "matchUpdateTypes": [ - "major" - ], - "draftPR": true, - "labels": [ - "dependencies" - ] - }, - { - "matchDepTypes": [ - "dependencies", - "devDependencies" - ], - "matchUpdateTypes": [ - "minor", - "patch" - ], - "groupName": "weekly-non-major-updates", - "automerge": false - }, - { - "matchManagers": [ - "dockerfile", - "docker-compose" - ], - "matchUpdateTypes": [ - "minor", - "patch" - ], - "groupName": "docker-base-image", - "automerge": false - }, - { - "matchManagers": [ - "github-actions" - ], - "matchUpdateTypes": [ - "minor", - "patch" - ], - "groupName": "github-actions", - "automerge": false - } - ] -} \ No newline at end of file + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>Aam-Digital/.github//renovate/default", + "local>Aam-Digital/.github//renovate/node", + "local>Aam-Digital/.github//renovate/docker" + ], + "enabledManagers": ["npm", "nvm", "dockerfile", "docker-compose", "github-actions"], + "additionalReviewers": ["Abhinegi2"] +} diff --git a/src/instrument.ts b/src/instrument.ts index 75cfb7c..302e5ba 100644 --- a/src/instrument.ts +++ b/src/instrument.ts @@ -1,11 +1,14 @@ import * as Sentry from "@sentry/nestjs"; import { nodeProfilingIntegration } from "@sentry/profiling-node"; +import { version } from "../package.json"; /** * Sentry must be initialised before anything else is imported. * This file is imported at the very top of main.ts. */ Sentry.init({ + // set by CI for each image build (release, edge or `0.0.0-pr.`) + release: version, dsn: process.env.SENTRY_DSN, environment: process.env.SENTRY_ENVIRONMENT ?? "development", integrations: [nodeProfilingIntegration()], diff --git a/tsconfig.json b/tsconfig.json index 399b42b..bfdfc30 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, "target": "ES2021", "lib": ["ESNext"], "sourceMap": true,