Intro
For quite some time, I wanted to try to automate releasing NPM packages with GitHub Actions.
I already had tests running in CI/CD. If the branch is main and all tests are passed, the desired outcome is to automatically publish NPM package and update changelog.
Workflow file
name: Tests on: push: branches: [ main ] pull_request: branches: [ main ] workflow_dispatch: branches: [ main ] repository_dispatch: types: [semantic-release] # cancel previous in progress actions concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true permissions: # important for npm provenance contents: read pages: write id-token: write issues: write pull-requests: write jobs: # install all packages and run unit tests installtest: name: installtest timeout-minutes: 20 runs-on: ubuntu-22.04 container: image: mcr.microsoft.com/playwright:v1.45.0-jammy permissions: contents: write # to be able to publish a GitHub release issues: write # to be able to comment on released issues pull-requests: write # to be able to comment on released pull requests id-token: write # to enable use of OIDC for npm provenance steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - uses: actions/cache/restore@v4 id: cache-node-modules with: path: | ./node_modules key: modules-1-${{ hashFiles('package-lock.json') }} - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' # put playwright executable to node_modules run: npm clean-install && npx cross-env HOME=/root PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install chromium firefox webkit - name: Unit tests run: npm run test:unit - name: Run codacy-coverage-reporter uses: codacy/codacy-coverage-reporter-action@v1.3.0 continue-on-error: true with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} coverage-reports: coverage-reports/lcov.info - name: Cache dependencies id: cache uses: actions/cache/save@v4 if: steps.cache-node-modules.outputs.cache-hit != 'true' with: path: | ./node_modules key: modules-1-${{ hashFiles('package-lock.json') }} # build all packages and cache it build: name: build needs: [installtest] timeout-minutes: 20 runs-on: ubuntu-22.04 container: image: mcr.microsoft.com/playwright:v1.45.0-jammy permissions: contents: write # to be able to publish a GitHub release issues: write # to be able to comment on released issues pull-requests: write # to be able to comment on released pull requests id-token: write # to enable use of OIDC for npm provenance steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/cache/restore@v4 id: cache-node-modules with: path: | ./node_modules key: modules-1-${{ hashFiles('package-lock.json') }} - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' run: npm clean-install && npx cross-env HOME=/root PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install chromium firefox webkit - name: build packages run: npm run build:packages - name: Cache dependencies id: cache uses: actions/cache/save@v4 if: always() with: path: | ./node_modules ./dist ./packages/example-react/dist ./packages/example-react/package.json ./packages/example-nextjs14/package.json ./packages/example-nextjs14/.next ./packages/example-nextjs15/package.json ./packages/example-nextjs15/.next key: modules-2-${{ github.sha }} # playwright tests testint: needs: [build] name: testint runs-on: ubuntu-22.04 timeout-minutes: 30 container: image: mcr.microsoft.com/playwright:v1.45.0-jammy strategy: fail-fast: false matrix: # using 2 workers shardIndex: [1, 2] shardTotal: [2] steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/cache/restore@v4 id: cache with: # reusing cache from build step path: | ./node_modules ./dist ./packages/example-react/dist ./packages/example-react/package.json ./packages/example-nextjs14/package.json ./packages/example-nextjs14/.next ./packages/example-nextjs15/package.json ./packages/example-nextjs15/.next key: modules-2-${{ github.sha }} - name: Run Playwright tests run: | npm run start:ci & \ npx wait-on http://localhost:3000 && \ npx wait-on http://localhost:3001 && \ npm run test:int:ci -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - uses: actions/upload-artifact@v4 if: always() with: name: blob-report-${{ matrix.shardIndex }} path: blob-report retention-days: 5 # publish to npm release: name: release needs: [testint] if: github.ref == 'refs/heads/main' timeout-minutes: 20 runs-on: ubuntu-22.04 container: image: mcr.microsoft.com/playwright:v1.45.0-jammy permissions: contents: write # to be able to publish a GitHub release issues: write # to be able to comment on released issues pull-requests: write # to be able to comment on released pull requests id-token: write # to enable use of OIDC for npm provenance steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/setup-node@v4 with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' - uses: actions/cache/restore@v4 id: cache with: path: | ./node_modules ./dist ./packages/example-react/dist ./packages/example-react/package.json ./packages/example-nextjs14/package.json ./packages/example-nextjs14/.next ./packages/example-nextjs15/package.json ./packages/example-nextjs15/.next key: modules-2-${{ github.head_ref }} - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies run: npm audit signatures - name: git config run: git config --global --add safe.directory /__w/state-in-url/state-in-url - name: Initialize Git user run: | git config --global user.email "github-release-bot@example.com" git config --global user.name "Release Workflow" - name: Initialise the NPM config run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Release env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release Result looks like this
There are also many small details like adding secrets like NPM_TOKEN to repository, scripts in package.json and so on.
Tools
Had to use multiple tools to achieve it, Github Actions obviously, wireit to run npm scripts with dependencies, commits with commitizen (needed to update version and to mark breaking changes in Changelog), husky for pre-commit hooks, and semantic-release package.
Pro and cons
- The biggest issue is that cache in GitHub actions is pretty slow, thinking about using
Dockerto speed things up. - Most of the benefits from such complex setup will be visible if you use at least 3 workers for tests. If the project is small, maybe not worth the effort.
Links
Full workflow
Official docs for reference



Top comments (0)