Publish #35
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 | |
| # Handles two scenarios: | |
| # 1. Dev publish: auto-triggered after "Build and Test" succeeds on master | |
| # 2. PR prerelease: manually triggered by Liz for trusted testers | |
| # | |
| # Manual trigger (Liz uses this via gh CLI): | |
| # gh workflow run publish.yml \ | |
| # --repo dexie/Dexie.js \ | |
| # --ref <branch-name> \ | |
| # -f mode=pr \ | |
| # -f pr_number=2253 \ | |
| # -f packages=dexie-cloud-addon \ | |
| # -f requester_discord_id=321912613246337024 \ | |
| # -f build_number=1 | |
| on: | |
| workflow_run: | |
| workflows: ["Build and Test"] | |
| types: [completed] | |
| branches: [master] | |
| workflow_dispatch: | |
| inputs: | |
| mode: | |
| description: 'Publish mode' | |
| required: true | |
| default: 'pr' | |
| type: choice | |
| options: | |
| - pr | |
| - dev | |
| pr_number: | |
| description: 'PR number (required for mode=pr, e.g. 2253)' | |
| required: false | |
| type: string | |
| packages: | |
| description: 'Packages to publish (comma-separated or "all")' | |
| required: false | |
| default: 'all' | |
| type: string | |
| build_number: | |
| description: 'Build number suffix for PR mode (increment if re-running)' | |
| required: false | |
| default: '1' | |
| type: string | |
| requester_discord_id: | |
| description: 'Discord user ID of the person who requested the prerelease' | |
| required: false | |
| type: string | |
| jobs: | |
| publish: | |
| name: Publish to npm | |
| runs-on: ubuntu-latest | |
| # For workflow_run trigger: only run if tests passed | |
| # For workflow_dispatch (manual PR prereleases): always run | |
| if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' | |
| permissions: | |
| contents: read | |
| id-token: write # required for OIDC Trusted Publisher | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.14.0 | |
| registry-url: https://registry.npmjs.org | |
| # npm 11.5.1+ required for OIDC Trusted Publisher | |
| - name: Upgrade npm | |
| run: npm install -g npm@11.5.1 | |
| - name: Install dependencies | |
| run: pnpm install | |
| # Build in dependency order: | |
| # 1. dexie (no workspace deps) | |
| # 2. dexie-cloud-common, dexie-svelte-query (no workspace deps) | |
| # 3. y-dexie (→ dexie) | |
| # 4. dexie-export-import (→ dexie) | |
| # 5. dexie-react-hooks (→ dexie, y-dexie) | |
| # 6. dexie-cloud-addon (→ dexie, dexie-cloud-common, y-dexie) | |
| # Note: dexie-observable and dexie-syncable are excluded (planned deprecation) | |
| - name: Build dexie | |
| run: pnpm run build | |
| - name: Build dexie-cloud-common | |
| working-directory: libs/dexie-cloud-common | |
| run: pnpm run build | |
| - name: Build dexie-svelte-query | |
| working-directory: libs/dexie-svelte-query | |
| run: pnpm run build | |
| - name: Build y-dexie | |
| working-directory: addons/y-dexie | |
| run: pnpm run build | |
| - name: Build dexie-export-import | |
| working-directory: addons/dexie-export-import | |
| run: pnpm run build | |
| - name: Build dexie-react-hooks | |
| working-directory: libs/dexie-react-hooks | |
| run: pnpm run build | |
| - name: Build dexie-cloud-addon | |
| working-directory: addons/dexie-cloud | |
| run: pnpm run build | |
| - name: Publish packages | |
| id: publish | |
| run: | | |
| # Determine mode: push to master = dev, workflow_dispatch = use input | |
| if [ "${{ github.event_name }}" = "workflow_run" ]; then | |
| MODE="dev" | |
| else | |
| MODE="${{ inputs.mode }}" | |
| fi | |
| # Validate pr_number is provided when mode=pr | |
| if [ "$MODE" = "pr" ] && [ -z "${{ inputs.pr_number }}" ]; then | |
| echo "Error: pr_number is required when mode=pr" | |
| exit 1 | |
| fi | |
| PR="${{ inputs.pr_number }}" | |
| BUILD="${{ inputs.build_number }}" | |
| PACKAGES="${{ inputs.packages }}" | |
| [ -z "$PACKAGES" ] && PACKAGES="all" | |
| PUBLISHED_VERSIONS="" | |
| PUBLISHED_COUNT=0 | |
| publish_pkg() { | |
| local PKG_NAME="$1" | |
| local PKG_DIR="$2" | |
| # Check if this package should be published | |
| local SHOULD_PUBLISH=false | |
| if [ "$PACKAGES" = "all" ]; then | |
| SHOULD_PUBLISH=true | |
| else | |
| IFS=',' read -ra PKG_LIST <<< "$PACKAGES" | |
| for p in "${PKG_LIST[@]}"; do | |
| if [ "$(echo $p | tr -d ' ')" = "$PKG_NAME" ]; then | |
| SHOULD_PUBLISH=true | |
| break | |
| fi | |
| done | |
| fi | |
| if [ "$SHOULD_PUBLISH" = "false" ]; then | |
| echo "Skipping $PKG_NAME (not in requested packages)" | |
| return | |
| fi | |
| local BASE_VERSION=$(node -p "require('./${PKG_DIR}/package.json').version") | |
| if [ "$MODE" = "dev" ]; then | |
| # Dev: publish as-is if not already published | |
| # Use exit code to distinguish "not found" from real errors | |
| if npm show "${PKG_NAME}@${BASE_VERSION}" version >/dev/null 2>&1; then | |
| echo "${PKG_NAME}@${BASE_VERSION} already published, skipping." | |
| return | |
| fi | |
| local PUBLISH_VERSION="$BASE_VERSION" | |
| local TAG="dev" | |
| else | |
| # PR prerelease: append -prNNN.K suffix | |
| local PUBLISH_VERSION="${BASE_VERSION}-pr${PR}.${BUILD}" | |
| local TAG="pr${PR}" | |
| # Temporarily patch version | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('./${PKG_DIR}/package.json', 'utf8')); | |
| pkg.version = '${PUBLISH_VERSION}'; | |
| fs.writeFileSync('./${PKG_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| fi | |
| echo "Publishing ${PKG_NAME}@${PUBLISH_VERSION} with tag ${TAG}..." | |
| (cd "${PKG_DIR}" && pnpm publish --tag "${TAG}" --provenance --no-git-checks) | |
| # Restore original version after PR publish | |
| if [ "$MODE" = "pr" ]; then | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('./${PKG_DIR}/package.json', 'utf8')); | |
| pkg.version = '${BASE_VERSION}'; | |
| fs.writeFileSync('./${PKG_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| fi | |
| PUBLISHED_VERSIONS="${PUBLISHED_VERSIONS}\n- \`npm install ${PKG_NAME}@${PUBLISH_VERSION}\`" | |
| PUBLISHED_COUNT=$((PUBLISHED_COUNT + 1)) | |
| } | |
| publish_pkg "dexie" "." | |
| publish_pkg "dexie-cloud-common" "libs/dexie-cloud-common" | |
| publish_pkg "dexie-react-hooks" "libs/dexie-react-hooks" | |
| publish_pkg "dexie-svelte-query" "libs/dexie-svelte-query" | |
| publish_pkg "dexie-export-import" "addons/dexie-export-import" | |
| publish_pkg "y-dexie" "addons/y-dexie" | |
| publish_pkg "dexie-cloud-addon" "addons/dexie-cloud" | |
| # Fail fast if no packages were published (likely a typo in inputs.packages) | |
| if [ "$PUBLISHED_COUNT" -eq 0 ] && [ "$PACKAGES" != "all" ]; then | |
| echo "::error::No packages matched inputs.packages='${PACKAGES}'. Valid names: dexie, dexie-cloud-common, dexie-react-hooks, dexie-svelte-query, dexie-export-import, y-dexie, dexie-cloud-addon" | |
| exit 1 | |
| fi | |
| echo "mode=$MODE" >> $GITHUB_OUTPUT | |
| echo "published_versions<<EOF" >> $GITHUB_OUTPUT | |
| echo -e "$PUBLISHED_VERSIONS" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Notify via Discord (PR mode only) | |
| if: always() && github.event_name == 'workflow_dispatch' && inputs.mode == 'pr' | |
| env: | |
| DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_NPM_RELEASES }} | |
| run: | | |
| PR="${{ inputs.pr_number }}" | |
| REQUESTER="${{ inputs.requester_discord_id }}" | |
| OUTCOME="${{ steps.publish.outcome }}" | |
| BRANCH="${GITHUB_REF_NAME}" | |
| RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| if [ "$OUTCOME" = "success" ]; then | |
| MENTION="" | |
| if [ -n "$REQUESTER" ]; then | |
| MENTION=" (requested by <@${REQUESTER}>)" | |
| fi | |
| VERSIONS="${{ steps.publish.outputs.published_versions }}" | |
| TITLE="✅ PR #${PR} prerelease published${MENTION}" | |
| BODY="Branch: \`${BRANCH}\`\n\n${VERSIONS}\n\n[View run](${RUN_URL})" | |
| else | |
| TITLE="❌ PR #${PR} prerelease FAILED" | |
| BODY="[View run](${RUN_URL})" | |
| fi | |
| # Build JSON payload with python to safely handle special characters | |
| python3 -c " | |
| import json, os, sys | |
| title = os.environ.get('MSG_TITLE', '') | |
| body = os.environ.get('MSG_BODY', '') | |
| content = title + '\n\n' + body | |
| print(json.dumps({'content': content})) | |
| " MSG_TITLE="$TITLE" MSG_BODY="$BODY" | \ | |
| curl -s -X POST "$DISCORD_WEBHOOK" \ | |
| -H "Content-Type: application/json" \ | |
| -d @- |