name: Publish to npm on: push: branches: - main permissions: id-token: write # OIDC for npm trusted publishing + provenance contents: write # for tag + GitHub Release creation # Release is driven by the head commit message. Supported forms: # release: 2.1.0 -> apex-grid (core) only — back-compat # release: apex-grid@2.1.0 -> core # release: apex-grid-enterprise@0.1.0 -> enterprise # release: apex-grid@2.1.0 apex-grid-enterprise@0.1.0 -> both, on their own version lines # A pre-release version (containing a hyphen, e.g. 2.2.0-beta.1) publishes under the # `next` dist-tag; otherwise `latest`. Each package is verified, published with # provenance from its own dist/, git-tagged, and given a GitHub Release. jobs: publish: runs-on: ubuntu-latest if: startsWith(github.event.head_commit.message, 'release:') steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Parse the release commit into newline-separated entries: # |||| - name: Parse release commit id: parse env: MSG: ${{ github.event.head_commit.message }} run: | set -euo pipefail # Map an npm package name to its workspace dir (case, not an associative # array, so it runs the same on bash 3.x and 5.x). dir_of() { case "$1" in apex-grid) echo packages/core;; apex-grid-enterprise) echo packages/enterprise;; *) echo "";; esac } HEAD_LINE=$(printf '%s\n' "$MSG" | head -1) BODY=$(printf '%s' "${HEAD_LINE#release:}" | xargs || true) # strip prefix + trim RELEASES="" npm_tag_for() { case "$1" in *-*) echo next;; *) echo latest;; esac; } if [[ "$BODY" =~ ^[0-9]+\.[0-9]+\.[0-9]+[A-Za-z0-9.+-]*$ ]]; then # Bare version -> core, legacy git tag form vX.Y.Z. RELEASES="packages/core|apex-grid|$BODY|v$BODY|$(npm_tag_for "$BODY")" else for tok in $BODY; do name=${tok%@*}; version=${tok#*@} dir=$(dir_of "$name") if [[ -z "$dir" ]]; then echo "❌ Unknown package '$name'"; exit 1; fi if [[ "$tok" != *@* || -z "$version" ]]; then echo "❌ Expected @, got '$tok'"; exit 1; fi if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+[A-Za-z0-9.+-]*$ ]]; then echo "❌ Bad version '$version'"; exit 1; fi RELEASES+="${dir}|${name}|${version}|${name}@${version}|$(npm_tag_for "$version")"$'\n' done fi RELEASES=$(printf '%s' "$RELEASES" | sed '/^$/d') if [[ -z "$RELEASES" ]]; then echo "❌ Commit must match 'release: X.Y.Z' or 'release: @ ...'"; exit 1; fi echo "Planned releases:"; printf '%s\n' "$RELEASES" { echo "releases<> "$GITHUB_OUTPUT" # Fail fast if a requested version doesn't match the package's package.json, # BEFORE we spend time installing/building/publishing anything. - name: Verify source versions env: RELEASES: ${{ steps.parse.outputs.releases }} run: | set -euo pipefail while IFS='|' read -r dir name version gittag npmtag; do [[ -z "$dir" ]] && continue pv=$(node -p "require('./$dir/package.json').version") if [[ "$pv" != "$version" ]]; then echo "❌ $name: $dir/package.json version ($pv) does not match requested ($version)"; exit 1 fi echo "✅ $name source version $pv" done < <(printf '%s\n' "$RELEASES") - uses: actions/setup-node@v4 with: node-version: '20' registry-url: https://registry.npmjs.org/ cache: 'npm' - run: npm install -g npm@latest - run: npm ci - name: Install Playwright browsers run: npx playwright install --with-deps chromium # Build before Test: the enterprise unit tests import `apex-grid/internal`, # which resolves to core's built dist/ (gitignored, so absent in a fresh # checkout). Building first ensures that artifact exists. - name: Build run: npm run build - name: Test run: npm test - name: Publish, tag, and release each package env: RELEASES: ${{ steps.parse.outputs.releases }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" while IFS='|' read -r dir name version gittag npmtag; do [[ -z "$dir" ]] && continue echo "::group::Release $name@$version ($npmtag)" # The built dist/ is the publishable manifest — confirm the build injected the version. dist_v=$(node -p "require('./$dir/dist/package.json').version") if [[ "$dist_v" != "$version" ]]; then echo "❌ $name: dist version ($dist_v) does not match expected ($version). Build did not inject the version."; exit 1 fi ( cd "$dir/dist" && npm publish --provenance --tag "$npmtag" ) echo "✅ Published $name@$version to npm (dist-tag: $npmtag)" if git rev-parse "$gittag" >/dev/null 2>&1; then echo "Tag $gittag already exists, skipping" else git tag -a "$gittag" -m "Release $gittag" git push origin "$gittag" fi if gh release view "$gittag" >/dev/null 2>&1; then echo "Release $gittag already exists, skipping" else notes=$(printf '%s\n\n%s' "\`$name@$version\` published to npm with provenance." "See the commit history for changes since the previous release.") gh release create "$gittag" --title "$gittag" --notes "$notes" fi echo "::endgroup::" done < <(printf '%s\n' "$RELEASES")