Skip to content

Release Packages

Release Packages #82

name: Release Packages
on:
workflow_run:
workflows: ["Tag on Merge"]
types: [completed]
permissions:
id-token: write
contents: write
env:
CI: 1
UPDATE: 1
jobs:
publish:
environment: npm-release
runs-on: ubuntu-latest
env:
tags: ""
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch all tags
run: git fetch --tags
- name: Find all new package tags on this commit
id: find_tags
shell: bash
run: |
TAGS=$(git tag --points-at HEAD | grep -E '^@instructure.ai/.+@([0-9]+\.[0-9]+\.[0-9]+)$' || true)
# Output for conditionals in later steps
echo "tags<<EOF" >> "$GITHUB_OUTPUT"
echo "$TAGS" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
# Also set env var for shell scripts in later steps
{
echo "tags<<EOF"
echo "$TAGS"
echo "EOF"
} >> "$GITHUB_ENV"
if [[ -z "$TAGS" ]]; then
echo "No new matching tags found on this commit."
fi
# Only set up Node/pnpm + caching if we actually have tags to release
- name: Install pnpm
if: steps.find_tags.outputs.tags != ''
run: npm install -g pnpm
- uses: actions/setup-node@v4
if: steps.find_tags.outputs.tags != ''
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
cache-dependency-path: |
pnpm-lock.yaml
**/pnpm-lock.yaml
# Pre-create the ACTUAL pnpm store so post-job cache save never errors
- name: Ensure pnpm store exists for caching
if: steps.find_tags.outputs.tags != ''
shell: bash
run: |
STORE="$(pnpm store path)"
echo "pnpm store path: $STORE"
mkdir -p "$STORE"
- name: Release each package tag
if: steps.find_tags.outputs.tags != ''
env:
tags: ${{ env.tags }} # comes from $GITHUB_ENV we set above
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail
IFS=$'\n'
for TAG in $tags; do
PKG_NAME=$(echo "$TAG" | sed -E 's/@instructure.ai\/([^@]+)@.*/\1/')
echo "Processing release for tag: $TAG (package: $PKG_NAME)"
# Remove deprecated always-auth from any pre-existing .npmrc
if [ -f "$HOME/.npmrc" ]; then
sed -i.bak '/always-auth/d' "$HOME/.npmrc"
fi
# Install deps
pnpm install --frozen-lockfile
# Enable vite-node loader using the register() API
export NODE_OPTIONS="--import=${{ github.workspace }}/plugins/vite-node.plugin.loader.mjs"
# Only build & test if package is in /packages (skip apps and shared-configs)
if [ -d "packages/$PKG_NAME" ] && [ "$PKG_NAME" != "shared-configs" ]; then
echo "Building package: $PKG_NAME (in /packages)"
pnpm test package "$PKG_NAME"
pnpm build package "$PKG_NAME"
else
echo "Skipping build/test for $PKG_NAME (not in /packages or is shared-configs)."
fi
# Publish (only for /packages, skip apps and shared-configs)
if [ -d "packages/$PKG_NAME" ] && [ "$PKG_NAME" != "shared-configs" ]; then
PKG_JSON_PATH="packages/$PKG_NAME/package.json"
if [ -f "$PKG_JSON_PATH" ]; then
JSON_PATH="$PKG_JSON_PATH"
else
JSON_PATH=""
fi
TARBALL=$(find "./packages/$PKG_NAME/src" -name '*.tgz' -type f | head -n 1)
if [ -z "$TARBALL" ]; then
echo "Error: No .tgz tarball found for $PKG_NAME in ./packages/$PKG_NAME/src/"
continue
fi
if [ -n "$JSON_PATH" ]; then
ACCESS_PUBLIC=$(jq -r '.publishConfig.access // empty' "$JSON_PATH")
PRIVATE=$(jq -r '.private // false' "$JSON_PATH")
if [ "$PRIVATE" = "true" ]; then
echo "Skipping publish for $PKG_NAME because it is marked private."
elif [ "$ACCESS_PUBLIC" = "public" ]; then
echo "Publishing $PKG_NAME as public via npm (OIDC)…"
npm publish "$TARBALL" --access public --provenance
elif [ -n "$ACCESS_PUBLIC" ] && [ "$ACCESS_PUBLIC" != "public" ]; then
echo "Skipping publish for $PKG_NAME because publishConfig.access is '$ACCESS_PUBLIC'."
else
echo "Publishing $PKG_NAME via npm (no --access public)…"
npm publish "$TARBALL" --provenance
fi
else
echo "package.json not found for $PKG_NAME in packages, publishing without access check."
npm publish "$TARBALL" --provenance
fi
else
echo "Skipping publish for $PKG_NAME (not in /packages or is shared-configs)."
fi
# Create GitHub release (attach asset only for /packages)
if [ "$PKG_NAME" = "shared-configs" ]; then
gh release create "$TAG" --title "$TAG" --generate-notes
elif [ -d "packages/$PKG_NAME" ]; then
gh release create "$TAG" ./packages/$PKG_NAME/src/*.tgz --title "$TAG" --generate-notes
else
gh release create "$TAG" --title "$TAG" --generate-notes
fi
done
- name: Trigger nutritionfacts auto-release
if: steps.find_tags.outputs.tags != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAGS: ${{ steps.find_tags.outputs.tags }}
shell: bash
run: |
set -euo pipefail
IFS=$'\n'
for TAG in $TAGS; do
# Extract the package name from the tag (e.g., @instructure.ai/[email protected] => foo)
NAME=$(echo "$TAG" | sed -E 's/@instructure.ai\/([^@]+)@.*/\1/')
EVENT_TYPE="${NAME}_release"
# Build the required JSON payload and POST it
PAYLOAD=$(jq -n \
--arg et "$EVENT_TYPE" \
--arg tag "$TAG" \
'{event_type: $et, client_payload: {tag: $tag}}')
echo "Dispatching event: $EVENT_TYPE with payload: $PAYLOAD"
echo "$PAYLOAD" | gh api repos/${{ github.repository }}/dispatches \
--method POST \
-H "Accept: application/vnd.github+json" \
--input -
done