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
CI / Shell Format Check (push) Waiting to run
CI / Check Ruby (3.4) (push) Waiting to run
CI / CI Config (push) Waiting to run
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Blocked by required conditions
CI / Build on Node ${{ matrix.node }} (push) Blocked by required conditions
CI / Style Check (push) Waiting to run
CI / Generate Assets (push) Waiting to run
CI / Check Python (3.14) (push) Waiting to run
CI / Check Python (3.9) (push) Waiting to run
CI / Build Docs (push) Waiting to run
CI / Code Scan Action (push) Waiting to run
CI / Site tests (push) Waiting to run
CI / webui tests (push) Waiting to run
CI / Run Integration Tests (push) Waiting to run
CI / Run Smoke Tests (push) Waiting to run
CI / Go Tests (push) Waiting to run
CI / Share Test (push) Waiting to run
CI / Redteam (Production API) (push) Waiting to run
CI / Redteam (Staging API) (push) Waiting to run
CI / GitHub Actions Lint (push) Waiting to run
CI / Check Ruby (3.0) (push) Waiting to run
release-please / release-please (push) Waiting to run
release-please / build (push) Blocked by required conditions
release-please / publish-npm (push) Blocked by required conditions
release-please / publish-npm-backfill (push) Waiting to run
release-please / docker (push) Blocked by required conditions
release-please / publish-code-scan-action (push) Blocked by required conditions
release-please / attest-code-scan-action (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
424 lines
17 KiB
YAML
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
|