Skip to content

Publish PR pre-release #12

Publish PR pre-release

Publish PR pre-release #12

Workflow file for this run

# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
#
# Workflow to manually publish pre-release packages from a pull request.
# This is useful for testing changes before they are merged.
name: Publish PR pre-release
on:
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to publish'
required: true
type: number
publish_packages:
description: 'Publish packages to JSR/npm'
type: boolean
default: true
publish_docs:
description: 'Publish docs preview to Cloudflare'
type: boolean
default: true
jobs:
# ===========================================================================
# Get PR information
# ===========================================================================
get-pr-info:
runs-on: ubuntu-latest
outputs:
head_sha: ${{ steps.pr.outputs.head_sha }}
head_repo: ${{ steps.pr.outputs.head_repo }}
steps:
- name: Get PR info
id: pr
uses: actions/github-script@v7
with:
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: ${{ inputs.pr_number }}
});
if (pr.data.state !== 'open') {
core.setFailed(`PR #${{ inputs.pr_number }} is not open`);
return;
}
core.setOutput('head_sha', pr.data.head.sha);
core.setOutput('head_repo', pr.data.head.repo.full_name);
# ===========================================================================
# Determine version
# ===========================================================================
determine-version:
if: inputs.publish_packages
needs: [get-pr-info]
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
short_version: ${{ steps.version.outputs.short_version }}
steps:
- uses: actions/checkout@v4
with:
repository: ${{ needs.get-pr-info.outputs.head_repo }}
ref: ${{ needs.get-pr-info.outputs.head_sha }}
- uses: ./.github/actions/setup-mise
- id: version
uses: ./.github/actions/determine-version
with:
pr_number: ${{ inputs.pr_number }}
head_sha: ${{ needs.get-pr-info.outputs.head_sha }}
# ===========================================================================
# Build packages
# ===========================================================================
build-packages:
if: inputs.publish_packages
needs: [get-pr-info, determine-version]
runs-on: ubuntu-latest
outputs:
packages_table: ${{ steps.packages-table.outputs.table }}
packages_links: ${{ steps.packages-table.outputs.links }}
steps:
- uses: actions/checkout@v4
with:
repository: ${{ needs.get-pr-info.outputs.head_repo }}
ref: ${{ needs.get-pr-info.outputs.head_sha }}
- uses: ./.github/actions/setup-mise
- uses: ./.github/actions/determine-version
with:
pr_number: ${{ inputs.pr_number }}
head_sha: ${{ needs.get-pr-info.outputs.head_sha }}
- run: sudo npm install -g npm@latest && npm --version
- run: |
pnpm pack --recursive --filter='!./examples/**' --config.enable-pre-post-scripts=false
# Remove private packages and CLI package
for pkg in fedify-*.tgz; do
if tar -xOzf "$pkg" package/package.json | jq -e '.private == true' > /dev/null 2>&1; then
echo "Removing private package: $pkg"
rm "$pkg"
fi
done
rm fedify-cli-*.tgz
- uses: actions/upload-artifact@v4
with:
name: npm-packages
path: fedify-*.tgz
- name: Generate packages table
id: packages-table
run: |
set -e
VERSION="${{ needs.determine-version.outputs.version }}"
SHORT_VERSION="${{ needs.determine-version.outputs.short_version }}"
{
echo 'table<<EOFTABLE'
deno run -A scripts/generate_packages_table.ts --format=table "$VERSION" "$SHORT_VERSION"
echo 'EOFTABLE'
} >> $GITHUB_OUTPUT
{
echo 'links<<EOFLINKS'
deno run -A scripts/generate_packages_table.ts --format=links "$VERSION" "$SHORT_VERSION"
echo 'EOFLINKS'
} >> $GITHUB_OUTPUT
# ===========================================================================
# Publish to JSR
# ===========================================================================
publish-jsr:
if: inputs.publish_packages
needs: [get-pr-info, determine-version]
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
with:
repository: ${{ needs.get-pr-info.outputs.head_repo }}
ref: ${{ needs.get-pr-info.outputs.head_sha }}
- uses: ./.github/actions/setup-mise
- uses: ./.github/actions/determine-version
with:
pr_number: ${{ inputs.pr_number }}
head_sha: ${{ needs.get-pr-info.outputs.head_sha }}
# Don't know why, but the .gitignore list is not overridden by include list
# in deno.json:
- run: 'rm -f src/vocab/.gitignore || true'
working-directory: ${{ github.workspace }}/packages/fedify/
- name: Publish to JSR
run: |
set -ex
mise run codegen
max_attempts=5
attempt=1
until deno publish --allow-dirty --allow-slow-types; do
exit_code=$?
if [[ $attempt -ge $max_attempts ]]; then
echo "deno publish failed after $max_attempts attempts"
exit $exit_code
fi
echo "deno publish failed (attempt $attempt/$max_attempts), retrying in 30 seconds..."
sleep 30
((attempt++))
done
# ===========================================================================
# Publish to npm (trigger build.yaml)
# ===========================================================================
# Trigger build.yaml via workflow_dispatch to publish to npm.
# This is required because npm's trusted publishing (OIDC) validates
# the directly triggered workflow, not reusable workflows called via
# workflow_call. By triggering build.yaml directly, npm sees build.yaml
# as the entry point and validates against it.
publish-npm:
if: inputs.publish_packages
needs: [build-packages, publish-jsr]
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Trigger build.yaml workflow
uses: actions/github-script@v7
with:
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'build.yaml',
ref: 'main',
inputs: {
run_id: '${{ github.run_id }}',
tag: 'pr-${{ inputs.pr_number }}'
}
});
console.log('Triggered build.yaml workflow with run_id=${{ github.run_id }}, tag=pr-${{ inputs.pr_number }}');
- name: Wait for npm publish to complete
uses: actions/github-script@v7
with:
script: |
// Wait a bit for the workflow to start
await new Promise(resolve => setTimeout(resolve, 10000));
// Poll for the triggered workflow run
const maxAttempts = 30;
const pollInterval = 20000; // 20 seconds
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const runs = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'build.yaml',
event: 'workflow_dispatch',
per_page: 5
});
// Find the run that was triggered by this workflow
const matchingRun = runs.data.workflow_runs.find(run => {
// Check if it's recent (within last 5 minutes)
const runTime = new Date(run.created_at);
const now = new Date();
const diffMinutes = (now - runTime) / 1000 / 60;
return diffMinutes < 5;
});
if (matchingRun) {
console.log(`Found workflow run: ${matchingRun.html_url}`);
if (matchingRun.status === 'completed') {
if (matchingRun.conclusion === 'success') {
console.log('npm publish completed successfully');
return;
} else {
core.setFailed(`npm publish failed with conclusion: ${matchingRun.conclusion}`);
return;
}
}
console.log(`Attempt ${attempt}/${maxAttempts}: Workflow status is ${matchingRun.status}, waiting...`);
} else {
console.log(`Attempt ${attempt}/${maxAttempts}: Workflow run not found yet, waiting...`);
}
if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
}
core.setFailed('Timed out waiting for npm publish workflow to complete');
# ===========================================================================
# Publish docs preview
# ===========================================================================
publish-docs:
if: inputs.publish_docs
needs: [get-pr-info, determine-version]
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
deployment_url: ${{ steps.wrangler.outputs.deployment-url }}
steps:
- uses: actions/checkout@v4
with:
repository: ${{ needs.get-pr-info.outputs.head_repo }}
ref: ${{ needs.get-pr-info.outputs.head_sha }}
- uses: ./.github/actions/setup-mise
- run: |
set -ex
EXTRA_NAV_TEXT=Stable \
EXTRA_NAV_LINK="$STABLE_DOCS_URL" \
SITEMAP_HOSTNAME="$UNSTABLE_DOCS_URL" \
JSR_REF_VERSION=unstable \
pnpm run build
env:
PLAUSIBLE_DOMAIN: ${{ secrets.PLAUSIBLE_DOMAIN }}
STABLE_DOCS_URL: ${{ vars.STABLE_DOCS_URL }}
UNSTABLE_DOCS_URL: ${{ vars.UNSTABLE_DOCS_URL }}
working-directory: ${{ github.workspace }}/docs/
- id: wrangler
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
gitHubToken: ${{ github.token }}
command: >-
pages deploy .vitepress/dist
--project-name=${{ vars.CLOUDFLARE_PROJECT_NAME }}
--branch=pr-${{ inputs.pr_number }}
packageManager: pnpm
workingDirectory: ${{ github.workspace }}/docs/
# ===========================================================================
# Post comment on PR
# ===========================================================================
comment-on-pr:
needs: [get-pr-info, determine-version, build-packages, publish-npm, publish-docs]
if: always() && needs.get-pr-info.result == 'success' && (needs.publish-npm.result == 'success' || needs.publish-docs.result == 'success')
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Delete old comment
uses: thollander/actions-comment-pull-request@v3
with:
pr-number: ${{ inputs.pr_number }}
comment-tag: publish
mode: delete
- name: Post comment
uses: thollander/actions-comment-pull-request@v3
with:
pr-number: ${{ inputs.pr_number }}
comment-tag: publish
message: |
Pre-release has been published for this pull request:
${{ needs.publish-npm.result == 'success' && format('## Packages
{0}
{1}
', needs.build-packages.outputs.packages_table, needs.build-packages.outputs.packages_links) || '' }}${{ needs.publish-docs.result == 'success' && format('## Documentation
The docs for this pull request have been published:
<{0}>', needs.publish-docs.outputs.deployment_url) || '' }}
# cSpell: ignore npmjs thollander