Skip to content

Publish

Publish #22

Workflow file for this run

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 @-