Files
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

424 lines
17 KiB
YAML

name: release-please
run-name: promptfoo release by @${{ github.actor }}
on:
push:
branches:
- main
workflow_dispatch:
inputs:
tag_name:
description: 'Optional existing release tag to publish to npm (e.g., 0.121.10)'
required: false
type: string
permissions:
contents: read
concurrency:
group: release-please-${{ github.ref }}
cancel-in-progress: false
jobs:
release-please:
if: github.event_name != 'workflow_dispatch' || inputs.tag_name == ''
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
code_scan_action_release_created: ${{ steps.release.outputs['code-scan-action--release_created'] }}
code_scan_action_tag_name: ${{ steps.release.outputs['code-scan-action--tag_name'] }}
code_scan_action_version: ${{ steps.release.outputs['code-scan-action--version'] }}
steps:
# Use GitHub App token so the created PR triggers CI workflows
# (PRs created with GITHUB_TOKEN don't trigger workflows to prevent loops).
# Token inherits the App installation's granted permissions. Previous
# attempt to narrow via `permission-*` inputs failed with 422 because
# the PROMPTFOOBOT installation does not grant all of them (release-please
# PR #8796 landed green, then main broke on the next release-please run).
# If we want to narrow here, first widen the App installation permissions
# in the GitHub App settings, then re-add the `permission-*` inputs.
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
id: app-token
with:
app-id: ${{ vars.PROMPTFOOBOT_APP_ID }}
private-key: ${{ secrets.PROMPTFOOBOT_APP_PRIVATE_KEY }}
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
token: ${{ steps.app-token.outputs.token }}
build:
# release-please can create a release, then still fail while opening the
# next release PR. Preserve artifact publication when the release outputs
# prove a release was already created.
if: >-
${{
always() &&
!cancelled() &&
(
needs.release-please.outputs.release_created == 'true' ||
needs.release-please.outputs.code_scan_action_release_created == 'true'
)
}}
runs-on: ubuntu-latest
timeout-minutes: 10
needs: release-please
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
# Node 24 ships with npm 11.6.2 which has a lockfile compatibility bug (npm/cli#8669)
- run: npm install -g npm@11.11.0
- run: npm ci
- run: npm test
publish-npm:
if: >-
${{
always() &&
!cancelled() &&
needs.release-please.outputs.release_created == 'true' &&
needs.build.result == 'success'
}}
needs: [build, release-please]
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
# Node 24 ships with npm 11.6.2 which has a lockfile compatibility bug (npm/cli#8669)
- run: npm install -g npm@11.11.0
- run: npm ci
# Empty NODE_AUTH_TOKEN so npm uses OIDC trusted publishing instead of the
# placeholder token actions/setup-node writes into .npmrc (actions/setup-node#1440).
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ''
PROMPTFOO_POSTHOG_KEY: ${{ secrets.PROMPTFOO_POSTHOG_KEY }}
publish-npm-backfill:
# npm trusted publishing is bound to this workflow filename on npmjs.com,
# so manual backfills must stay inside release-please.yml.
if: github.event_name == 'workflow_dispatch' && inputs.tag_name != ''
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
env:
TAG_NAME: ${{ inputs.tag_name }}
steps:
- name: Validate release tag
run: |
if [[ ! "$TAG_NAME" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][A-Za-z0-9.-]+)?$ ]]; then
echo "::error::TAG_NAME '$TAG_NAME' is empty or invalid"
exit 1
fi
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: refs/tags/${{ inputs.tag_name }}
- name: Verify package version matches tag
run: |
package_version="$(node -p "require('./package.json').version")"
if [[ "$package_version" != "$TAG_NAME" ]]; then
echo "::error::package.json version '$package_version' does not match tag '$TAG_NAME'"
exit 1
fi
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
# Node 24 ships with npm 11.6.2 which has a lockfile compatibility bug (npm/cli#8669)
- run: npm install -g npm@11.11.0
- run: npm ci
- name: Publish package
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ''
PROMPTFOO_POSTHOG_KEY: ${{ secrets.PROMPTFOO_POSTHOG_KEY }}
docker:
if: >-
${{
always() &&
!cancelled() &&
needs.publish-npm.result == 'success' &&
needs.release-please.outputs.release_created == 'true'
}}
needs: [publish-npm, release-please]
permissions:
contents: read
packages: write
id-token: write
attestations: write
uses: ./.github/workflows/docker.yml
with:
tag_name: ${{ needs.release-please.outputs.tag_name }}
publish-code-scan-action:
if: >-
${{
always() &&
!cancelled() &&
needs.release-please.outputs.code_scan_action_release_created == 'true' &&
needs.build.result == 'success'
}}
runs-on: ubuntu-latest
timeout-minutes: 15
# Gate on `build` so root test failures block the mirror — code-scan-action
# imports shared source from ../../src, so broken root code means broken action.
# `publish-npm` is in `needs` for ordering only, not gating — see the
# npm-availability check below for the rationale.
needs: [release-please, build, publish-npm]
permissions:
contents: read
env:
# Plain release-artifact files mirrored into promptfoo/code-scan-action.
# `dist/` (a directory) and `.release-source.json` (generated below, not
# copied from source) are handled separately in each step.
MIRROR_ARTIFACT_FILES: |-
action.yml
README.md
CHANGELOG.md
steps:
# Token is scoped to the foreign repo (code-scan-action) via owner/
# repositories. Do NOT add `permission-*` inputs here until the App
# installation is confirmed to grant every requested permission — see
# the matching note on the release-please job above.
- name: Create app token for code-scan-action mirror
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
id: mirror-token
with:
app-id: ${{ vars.PROMPTFOOBOT_APP_ID }}
private-key: ${{ secrets.PROMPTFOOBOT_APP_PRIVATE_KEY }}
owner: promptfoo
repositories: code-scan-action
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
# Node 24 ships with npm 11.6.2 which has a lockfile compatibility bug (npm/cli#8669)
- run: npm install -g npm@11.11.0
# The action's runtime install is pinned to this commit's root package.json
# version, inlined into dist/ at build time. Refuse to mirror a release whose
# pinned scanner version never reached npm (e.g. a failed publish-npm awaiting
# backfill) — otherwise every consumer scan fails at install time with ETARGET
# (no matching version found).
# The registry is the ground truth (a publish-npm job result would wrongly
# block a version that was backfilled later); `needs: publish-npm` guarantees
# a same-run publish has finished before this check runs, and the retries
# absorb registry read-after-write lag for a just-published version.
- name: Verify pinned promptfoo version is published to npm
run: |
set -euo pipefail
version="$(node -p "require('./package.json').version")"
for attempt in 1 2 3 4 5; do
if npm view "promptfoo@${version}" version; then
exit 0
fi
if [[ "$attempt" -lt 5 ]]; then
echo "promptfoo@${version} not visible on npm (attempt ${attempt}/5); retrying in 30s"
sleep 30
fi
done
echo "::error::promptfoo@${version} is not published to npm; the mirrored action would fail at install time"
exit 1
# Install root deps first; code-scan-action imports shared source from ../../src
- run: npm ci
- name: Install Code Scan Action dependencies
working-directory: code-scan-action
run: npm ci
- name: Type Check Code Scan Action
working-directory: code-scan-action
run: npm run tsc
- name: Build Code Scan Action
working-directory: code-scan-action
run: npm run build
# Companion to the npm-availability gate above: that step validates the version
# read from package.json, this one asserts the built bundle actually embeds the
# same pin — so a future refactor of the pin source in main.ts cannot desync
# the gate from what ships.
- name: Verify built dist embeds the published scanner pin
run: |
set -euo pipefail
version="$(node -p "require('./package.json').version")"
count="$(grep -Fc "\"${version}\"" code-scan-action/dist/index.js || true)"
echo "dist/index.js embeds \"${version}\" ${count} time(s)"
if [[ "$count" -eq 0 ]]; then
echo "::error::dist/index.js does not embed promptfoo@${version}; the runtime pin has desynced from the npm-availability gate"
exit 1
fi
- name: Prepare mirror release payload
env:
CODE_SCAN_ACTION_VERSION: ${{ needs.release-please.outputs.code_scan_action_version }}
SOURCE_SHA: ${{ github.sha }}
SOURCE_TAG: ${{ needs.release-please.outputs.code_scan_action_tag_name }}
run: |
set -euo pipefail
export_dir="$RUNNER_TEMP/code-scan-action-export"
rm -rf "$export_dir"
mkdir -p "$export_dir"
mapfile -t mirror_artifact_files <<< "$MIRROR_ARTIFACT_FILES"
for file in "${mirror_artifact_files[@]}"; do
cp "code-scan-action/$file" "$export_dir/$file"
done
cp -R code-scan-action/dist "$export_dir/dist"
cat > "$export_dir/.release-source.json" <<JSON
{
"repository": "${{ github.repository }}",
"sourceSha": "$SOURCE_SHA",
"sourceTag": "$SOURCE_TAG",
"packagePath": "code-scan-action",
"version": "$CODE_SCAN_ACTION_VERSION"
}
JSON
# Hand the executable payload (dist/ plus the action.yml that selects the
# entrypoint) to attest-code-scan-action — see that job for why attestation
# is isolated from this one.
- name: Upload release payload for attestation
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: code-scan-action-release-payload
if-no-files-found: error
path: |
${{ runner.temp }}/code-scan-action-export/dist
${{ runner.temp }}/code-scan-action-export/action.yml
- name: Open mirror release PR
env:
GH_TOKEN: ${{ steps.mirror-token.outputs.token }}
CODE_SCAN_ACTION_VERSION: ${{ needs.release-please.outputs.code_scan_action_version }}
SOURCE_SHA: ${{ github.sha }}
SOURCE_TAG: ${{ needs.release-please.outputs.code_scan_action_tag_name }}
run: |
set -euo pipefail
branch="release/code-scan-action-v${CODE_SCAN_ACTION_VERSION}"
export_dir="$RUNNER_TEMP/code-scan-action-export"
mirror_dir="$RUNNER_TEMP/code-scan-action-mirror"
# Keep the app token out of the remote URL and out of .git/config;
# attach it per-command via `-c http.extraheader=...` and disable git
# hooks so nothing executes in the token's env. GitHub's Git smart
# HTTP endpoint expects Basic auth.
basic_auth="$(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 | tr -d '\n')"
auth_header="http.https://github.com/.extraheader=AUTHORIZATION: basic ${basic_auth}"
rm -rf "$mirror_dir"
git -c "$auth_header" -c core.hooksPath=/dev/null clone \
"https://github.com/promptfoo/code-scan-action.git" "$mirror_dir"
cd "$mirror_dir"
git switch -C "$branch" origin/main
# Replace only the generated release-artifact paths. The mirror repo
# owns every other path (.github/, renovate.json, AGENTS.md, ...);
# syncing the whole tree with `rsync --delete` silently deleted those
# and broke the mirror's validate-release-pr check.
rm -rf dist
cp -R "$export_dir/dist" dist
mapfile -t mirror_artifact_files <<< "$MIRROR_ARTIFACT_FILES"
for file in "${mirror_artifact_files[@]}"; do
cp "$export_dir/$file" "$file"
done
cp "$export_dir/.release-source.json" .release-source.json
git add "${mirror_artifact_files[@]}" dist .release-source.json
if git diff --cached --quiet; then
echo "No mirror changes to publish for code-scan-action v${CODE_SCAN_ACTION_VERSION}"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git -c core.hooksPath=/dev/null \
commit -m "Release code-scan-action v${CODE_SCAN_ACTION_VERSION}"
git -c "$auth_header" -c core.hooksPath=/dev/null \
push --force-with-lease origin "HEAD:$branch"
pr_body=$(cat <<BODY
Automated release mirror for \`@promptfoo/code-scan-action\` v${CODE_SCAN_ACTION_VERSION}.
Source: promptfoo/promptfoo@${SOURCE_SHA}
Source tag: ${SOURCE_TAG}
This PR is generated from the monorepo release workflow. The mirror repository validation workflow rebuilds from \`.release-source.json\` and checks that the generated artifacts match.
BODY
)
existing_pr="$(gh pr list --repo promptfoo/code-scan-action --head "$branch" --base main --state open --json number --jq '.[0].number // empty')"
if [[ -n "$existing_pr" ]]; then
gh pr edit "$existing_pr" --repo promptfoo/code-scan-action --title "Release code-scan-action v${CODE_SCAN_ACTION_VERSION}" --body "$pr_body"
else
gh pr create --repo promptfoo/code-scan-action --base main --head "$branch" --title "Release code-scan-action v${CODE_SCAN_ACTION_VERSION}" --body "$pr_body"
fi
attest-code-scan-action:
# Signed build provenance for the exact bytes mirrored into
# promptfoo/code-scan-action — both the dist/ bundle and the action.yml that
# selects the entrypoint — so consumers can verify the committed artifacts came
# from this workflow without re-running the build:
# gh attestation verify dist/index.js --repo promptfoo/promptfoo
# gh attestation verify action.yml --repo promptfoo/promptfoo
# Kept as a separate job so id-token/attestations permissions are confined to
# steps that never install packages or execute repository code — a compromised
# dependency lifecycle script in the publish job must not be able to mint OIDC
# tokens or sign attestations as this repository. Default `needs` gating (run
# only when the publish job succeeded) is exactly the behavior wanted here.
needs: [publish-code-scan-action]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
id-token: write
attestations: write
steps:
- name: Download release payload
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: code-scan-action-release-payload
path: ${{ runner.temp }}/code-scan-action-attest
- name: Attest build provenance for mirrored artifacts
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
with:
subject-path: |
${{ runner.temp }}/code-scan-action-attest/dist/*
${{ runner.temp }}/code-scan-action-attest/action.yml