Publish to NPM #149
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish to NPM | |
| # This workflow publishes packages to npm. We use a single workflow file because | |
| # npm Trusted Publishing currently supports only a single workflow file. | |
| # | |
| # Stable release flow (manual): | |
| # 1. Locally: `bun run release <package>` — bumps version, writes CHANGELOG, commits, tags. | |
| # 2. `git push --follow-tags origin main` | |
| # 3. GitHub → Actions → "Publish to NPM" → Run workflow → publish_type: release, package: <package> | |
| # The workflow reads the version from the package's package.json, builds, and publishes. | |
| # A pre-release version (containing a hyphen, e.g. 1.1.0-rc.0) is published to the `next` | |
| # dist-tag; a stable version is published to `latest`. | |
| # | |
| # Nightly pre-release flow (automatic): | |
| # Runs at 03:00 UTC. If there were commits in the last 25h, every package is bumped to a | |
| # timestamped pre-release version and published to the `dev` dist-tag. | |
| on: | |
| schedule: | |
| - cron: '0 3 * * *' # Nightly at 03:00 UTC | |
| workflow_dispatch: | |
| inputs: | |
| publish_type: | |
| description: 'Type of publish' | |
| required: true | |
| type: choice | |
| options: | |
| - release | |
| - nightly | |
| default: release | |
| package: | |
| description: 'Package to publish (release requires a single package; nightly accepts "all")' | |
| required: false | |
| type: choice | |
| options: | |
| - all | |
| - braintree-plugin | |
| - elasticsearch-plugin | |
| - mollie-plugin | |
| - punchout-gateway-plugin | |
| - sentry-plugin | |
| - stellate-plugin | |
| - stripe-plugin | |
| - pub-sub-plugin | |
| default: all | |
| dry-run: | |
| description: 'Dry run (no actual publish)' | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| setup: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| publish_type: ${{ steps.determine.outputs.publish_type }} | |
| steps: | |
| - name: Determine publish type | |
| id: determine | |
| run: | | |
| if [ "${{ github.event_name }}" == "schedule" ]; then | |
| echo "publish_type=nightly" >> $GITHUB_OUTPUT | |
| else | |
| echo "publish_type=${{ inputs.publish_type }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Validate release inputs | |
| if: github.event_name == 'workflow_dispatch' && inputs.publish_type == 'release' | |
| run: | | |
| if [ -z "${{ inputs.package }}" ] || [ "${{ inputs.package }}" == "all" ]; then | |
| echo "::error::A stable release requires a single package — 'all' is not allowed. Select one package." | |
| exit 1 | |
| fi | |
| publish: | |
| needs: setup | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # For nightly builds: check if there were commits in the last 25 hours | |
| # (25h instead of 24h to account for cron schedule jitter) | |
| - name: Check for new commits | |
| id: commit_check | |
| if: needs.setup.outputs.publish_type == 'nightly' | |
| run: | | |
| COMMITS=$(git rev-list --count --since="25 hours" HEAD) | |
| echo "Commits found in last 25h: $COMMITS" | |
| if [ "$COMMITS" -eq 0 ]; then | |
| echo "should_publish=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "should_publish=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Skip publish (no new commits) | |
| if: needs.setup.outputs.publish_type == 'nightly' && steps.commit_check.outputs.should_publish == 'false' | |
| run: echo "No new commits in last 25 hours – skipping nightly publish." | |
| # Gate the remaining steps: run for a release, or for a nightly that has new commits. | |
| - name: Should run | |
| id: should_run | |
| run: | | |
| if [ "${{ needs.setup.outputs.publish_type }}" == "release" ] || \ | |
| [ "${{ steps.commit_check.outputs.should_publish }}" == "true" ]; then | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Setup Node.js | |
| if: steps.should_run.outputs.run == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24.x' | |
| registry-url: 'https://registry.npmjs.org' | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version: 1.3.10 | |
| - name: Update npm | |
| if: steps.should_run.outputs.run == 'true' | |
| run: npm install -g npm@11 | |
| - name: Install dependencies | |
| if: steps.should_run.outputs.run == 'true' | |
| run: bun install --frozen-lockfile | |
| - name: Build | |
| if: steps.should_run.outputs.run == 'true' | |
| run: bun run build | |
| # ── Stable release ─────────────────────────────────────────────────────────── | |
| # The version is read from package.json (already bumped & tagged by `bun run release` | |
| # and pushed to main). Nothing is committed back; npm provenance attests the published | |
| # version against the pushed release commit. | |
| - name: Resolve release version | |
| id: release | |
| if: needs.setup.outputs.publish_type == 'release' | |
| run: | | |
| PKG="${{ inputs.package }}" | |
| VERSION=$(node -p "require('./packages/$PKG/package.json').version") | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| if [[ "$VERSION" =~ - ]]; then | |
| echo "dist_tag=next" >> $GITHUB_OUTPUT | |
| echo "Publishing @vendure-community/$PKG@$VERSION (pre-release → next)" | |
| else | |
| echo "dist_tag=latest" >> $GITHUB_OUTPUT | |
| echo "Publishing @vendure-community/$PKG@$VERSION (stable → latest)" | |
| fi | |
| - name: Publish to NPM (release) | |
| if: needs.setup.outputs.publish_type == 'release' | |
| run: | | |
| cd packages/${{ inputs.package }} | |
| ARGS="--access public --provenance --tag ${{ steps.release.outputs.dist_tag }}" | |
| if [ "${{ inputs.dry-run }}" == "true" ]; then | |
| ARGS="$ARGS --dry-run" | |
| fi | |
| npm publish $ARGS | |
| # ── Nightly pre-release ────────────────────────────────────────────────────── | |
| - name: Get current date | |
| if: needs.setup.outputs.publish_type == 'nightly' && steps.commit_check.outputs.should_publish == 'true' | |
| id: date | |
| run: echo "date=$(date +'%Y%m%d%H%M')" >> $GITHUB_OUTPUT | |
| - name: Configure Git | |
| if: needs.setup.outputs.publish_type == 'nightly' && steps.commit_check.outputs.should_publish == 'true' | |
| run: | | |
| git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git config --global user.name "github-actions[bot]" | |
| - name: Bump pre-release versions | |
| if: needs.setup.outputs.publish_type == 'nightly' && steps.commit_check.outputs.should_publish == 'true' | |
| run: | | |
| if [ "${{ inputs.package }}" != "" ] && [ "${{ inputs.package }}" != "all" ]; then | |
| SCOPE="--scope @vendure-community/${{ inputs.package }}" | |
| else | |
| SCOPE="--no-private" | |
| fi | |
| npx lerna exec $SCOPE -- \ | |
| 'NEW=$(node -p "const v=require(\"./package.json\").version.replace(/-.*/,\"\").split(\".\");v[2]++;v.join(\".\")") && \ | |
| npm version "${NEW}-dev.${{ steps.date.outputs.date }}" --no-git-tag-version' | |
| # The version bump is committed (but never pushed) so that npm provenance | |
| # attestation links to a commit whose package.json matches the published version. | |
| - name: Commit version changes (nightly) | |
| if: needs.setup.outputs.publish_type == 'nightly' && steps.commit_check.outputs.should_publish == 'true' | |
| run: | | |
| git add packages/*/package.json | |
| git commit -m "chore: Bump version for nightly pre-release" | |
| - name: Publish to NPM (nightly) | |
| if: needs.setup.outputs.publish_type == 'nightly' && steps.commit_check.outputs.should_publish == 'true' | |
| run: | | |
| if [ "${{ inputs.package }}" != "" ] && [ "${{ inputs.package }}" != "all" ]; then | |
| SCOPE="--scope @vendure-community/${{ inputs.package }}" | |
| else | |
| SCOPE="--no-private" | |
| fi | |
| ARGS="--access public --provenance --tag dev" | |
| if [ "${{ inputs.dry-run }}" = "true" ]; then | |
| ARGS="$ARGS --dry-run" | |
| fi | |
| npx lerna exec $SCOPE -- npm publish $ARGS |