mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 10:46:26 +01:00
feat(release): add human-gated beta channel with stable soak enforcement (#11008)
> Follow-up to #11006 (merged): rebased onto master and ready for review. ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The release subsystem now publishes canary (every master push), nightly (scheduled, smoke-gated, added in #11006), and stable (manual) > - There is still no human-approved release-candidate lane between nightly and stable, and nothing enforces that a stable actually soaked anywhere before shipping > - Betas need a real approval gate, and stables need a soak policy that is data, not prose > - This pull request adds the beta channel: a manual promotion of a chosen nightly behind the `npm-beta` environment gate, re-smoked after publish, plus a stable preflight that enforces a 3-day beta soak with a written-justification bypass > - The benefit is a complete canary → nightly → beta → stable train where every stable shipped as a beta first, and emergencies leave a written trace ## Linked Issues or Issue Description **Subsystem affected** Release automation: `scripts/release.sh`, `scripts/release-lib.sh`, `.github/workflows/release.yml`, `.github/workflows/docker.yml`, `.github/workflows/release-smoke.yml`. **Problem or motivation** After #11006 the project has canary and nightly prerelease lanes, but no release-candidate lane. Stable promotion has no enforced soak: any ref can ship as stable directly. There is no approval boundary for a broader-audience prerelease, and no structured way to record why an emergency release skipped validation. **Proposed solution** Add a `beta` channel: a manual dispatch that promotes a chosen nightly's source commit, publishes behind the `npm-beta` GitHub environment (required reviewers are the gate), re-smokes the published beta, and tags `beta/vX`. Enforce in the stable path that the source commit shipped as a beta at least 3 days earlier (measured from the beta's npm publish time), with a `skip_soak_justification` input as the recorded emergency bypass. **Alternatives considered** Codifying the soak policy in docs only. Rejected: an unenforced policy decays; the preflight makes the policy executable while the justification input keeps the emergency path usable and auditable. ## What Changed - `scripts/release.sh` + `scripts/release-lib.sh`: `beta` channel — requires HEAD to carry a `nightly/v*` tag, publishes the package set as `YYYY.MDD.P-beta.N` under dist-tag `beta`, tags `beta/vYYYY.MDD.P-beta.N` - `.github/workflows/release.yml`: - `channel: beta` dispatch path: `select_beta` resolves the newest (or an explicit `source_version`) nightly and fails loudly on selection problems; `publish_beta` runs behind the `npm-beta` environment, pushes the tag, and dispatches `docker.yml`; `smoke_beta` re-runs the release smoke suite against the exact published beta version - stable path: new `preflight_stable` job enforces the 3-day beta soak from the beta's npm publish time; `skip_soak_justification` bypasses with the reason echoed into the job summary; dry runs report without blocking - `.github/workflows/docker.yml`: `beta/v*` tags publish `:beta` on both images, with exact version stamping - `.github/workflows/release-smoke.yml`: `beta` added to the dispatch choice list - Docs: `CHANNELS.md` beta entries; `RELEASING.md` beta lane, soak gate, and failure playbook; `RELEASE-AUTOMATION-SETUP.md` `npm-beta` environment setup, including the warning to create the environment before the first beta dispatch (GitHub auto-creates unprotected environments on first reference) - Tests: beta version-counting coverage in `scripts/release-registry-versions.test.mjs`; beta identity and nightly-tag guard coverage in `scripts/__tests__/release-dry-run-notes.test.mjs` ## Verification - `node --test` on the two touched suites: 17 pass, including the 3 new beta tests - `bash -n` on both shell scripts and YAML parse of all three workflows - After merge, in order: create the `npm-beta` environment, dispatch `channel: beta` with `dry_run: true` to preview, then a real promotion of a published nightly through the approval gate, then a stable dry-run against a young beta to see the soak gate report ## Risks - If the `npm-beta` environment does not exist when the first beta dispatch runs, GitHub creates it with no protection rules and the beta publishes without approval. Mitigated by documentation and by creating the environment before merge (operator step) - Until the first beta exists, every stable dispatch requires `skip_soak_justification`. This is deliberate — the first beta ships immediately after this merges — but it is a behavior change to the stable dispatch - The soak clock reads the beta's npm publish time from the registry; a registry outage makes the preflight fall back to requiring justification (fail-closed) ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) in Claude Code, with extended thinking and full tool use (repository exploration, local test execution, live registry and git verification). All code, tests, and docs in this PR were model-authored under human direction. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green (pending — will confirm before merge) - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups (pending — will confirm before merge) - [x] I will address all Greptile and reviewer comments before requesting merge
This commit is contained in:
1 parent
459469638e
commit
8f7b8b3fda
13 files changed
+514
-43
No files matched your search
@@ -7,6 +7,7 @@ on:
|
||||
tags:
|
||||
- "v*"
|
||||
- "nightly/v*"
|
||||
- "beta/v*"
|
||||
# Release workflows push lane tags with GITHUB_TOKEN, and GitHub suppresses
|
||||
# push-triggered runs for those, so release.yml dispatches this workflow at
|
||||
# the new tag ref instead. The tag mapping below keys off github.ref either
|
||||
@@ -52,6 +53,9 @@ jobs:
|
||||
# instead of describing drift from the nearest stable tag.
|
||||
version="${GITHUB_REF#refs/tags/nightly/v}"
|
||||
;;
|
||||
refs/tags/beta/v*)
|
||||
version="${GITHUB_REF#refs/tags/beta/v}"
|
||||
;;
|
||||
*)
|
||||
version="$(git describe --tags --match 'v*' --long --dirty 2>/dev/null || true)"
|
||||
;;
|
||||
@@ -157,6 +161,7 @@ jobs:
|
||||
tags: |
|
||||
type=raw,value=canary,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=nightly,enable=${{ startsWith(github.ref, 'refs/tags/nightly/v') }}
|
||||
type=raw,value=beta,enable=${{ startsWith(github.ref, 'refs/tags/beta/v') }}
|
||||
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
@@ -223,6 +228,9 @@ jobs:
|
||||
# instead of describing drift from the nearest stable tag.
|
||||
version="${GITHUB_REF#refs/tags/nightly/v}"
|
||||
;;
|
||||
refs/tags/beta/v*)
|
||||
version="${GITHUB_REF#refs/tags/beta/v}"
|
||||
;;
|
||||
*)
|
||||
version="$(git describe --tags --match 'v*' --long --dirty 2>/dev/null || true)"
|
||||
;;
|
||||
@@ -330,6 +338,7 @@ jobs:
|
||||
tags: |
|
||||
type=raw,value=canary,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=nightly,enable=${{ startsWith(github.ref, 'refs/tags/nightly/v') }}
|
||||
type=raw,value=beta,enable=${{ startsWith(github.ref, 'refs/tags/beta/v') }}
|
||||
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
@@ -11,6 +11,7 @@ on:
|
||||
options:
|
||||
- canary
|
||||
- nightly
|
||||
- beta
|
||||
- latest
|
||||
host_port:
|
||||
description: Host port for the Docker smoke container
|
||||
|
||||
@@ -15,6 +15,7 @@ on:
|
||||
type: choice
|
||||
options:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
default: stable
|
||||
source_ref:
|
||||
@@ -27,7 +28,11 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
source_version:
|
||||
description: (nightly) Explicit canary version to promote, for example 2026.806.0-canary.7. Leave empty to select the newest canary on master.
|
||||
description: For nightly, the explicit canary version to promote (empty selects the newest canary on master). For beta, the explicit nightly version to promote (empty selects the newest nightly on master).
|
||||
required: false
|
||||
type: string
|
||||
skip_soak_justification:
|
||||
description: (stable) Written justification for publishing a stable whose source has not soaked as a beta for 3 days. Leave empty for normal releases.
|
||||
required: false
|
||||
type: string
|
||||
dry_run:
|
||||
@@ -314,17 +319,306 @@ jobs:
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
gh workflow run docker.yml --ref "refs/tags/${tag}" --repo "$GITHUB_REPOSITORY"
|
||||
|
||||
# ----- Beta lane --------------------------------------------------------
|
||||
# Beta is a manual, human-approved promotion of a nightly. Unlike the
|
||||
# scheduled nightly lane, a beta dispatch is explicit operator intent, so
|
||||
# selection problems fail the run loudly instead of skipping quietly. The
|
||||
# publish runs behind the npm-beta environment, whose required reviewers
|
||||
# are the approval gate.
|
||||
|
||||
select_beta:
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'beta'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
sha: ${{ steps.select.outputs.sha }}
|
||||
nightly_version: ${{ steps.select.outputs.nightly_version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Select beta candidate
|
||||
id: select
|
||||
env:
|
||||
EXPLICIT_NIGHTLY_VERSION: ${{ inputs.source_version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch origin --tags --prune --quiet
|
||||
|
||||
if [ -n "${EXPLICIT_NIGHTLY_VERSION:-}" ]; then
|
||||
tag="nightly/v${EXPLICIT_NIGHTLY_VERSION}"
|
||||
sha="$(git rev-list -n 1 "$tag" 2>/dev/null || true)"
|
||||
if [ -z "$sha" ]; then
|
||||
echo "Error: tag $tag does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Newest nightly-tagged commit on master.
|
||||
sha="$(grep -m1 -F \
|
||||
-f <(git for-each-ref 'refs/tags/nightly/v*' --format='%(objectname)') \
|
||||
<(git rev-list origin/master -n 2000) || true)"
|
||||
|
||||
if [ -z "$sha" ]; then
|
||||
echo "Error: no nightly/v* tag found on the last 2000 commits of master. Publish a nightly first, or pass source_version." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag="$(git tag --points-at "$sha" | grep '^nightly/v' | sort -V | tail -1)"
|
||||
fi
|
||||
|
||||
nightly_version="${tag#nightly/v}"
|
||||
|
||||
existing_beta="$(git tag --points-at "$sha" | grep '^beta/v' | head -1 || true)"
|
||||
if [ -n "$existing_beta" ]; then
|
||||
echo "Error: candidate $sha (nightly $nightly_version) already shipped as $existing_beta." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
||||
echo "nightly_version=$nightly_version" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## Beta candidate"
|
||||
echo ""
|
||||
echo "- Source SHA: \`$sha\`"
|
||||
echo "- Source nightly: \`$nightly_version\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
publish_beta:
|
||||
needs: select_beta
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
environment: npm-beta
|
||||
# Serialize beta publishes so two dispatches cannot race to the same next
|
||||
# -beta.N version; release.sh additionally refuses to double-publish a
|
||||
# commit that already carries a beta tag.
|
||||
concurrency:
|
||||
group: release-publish-beta
|
||||
cancel-in-progress: false
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
actions: write
|
||||
outputs:
|
||||
beta_version: ${{ steps.result.outputs.beta_version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ needs.select_beta.outputs.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 9.15.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Validate release package manifest
|
||||
run: node ./scripts/release-package-map.mjs check
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Restore tracked install-time changes
|
||||
run: git checkout -- pnpm-lock.yaml
|
||||
|
||||
- name: Configure git author
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Publish beta
|
||||
env:
|
||||
GITHUB_ACTIONS: "true"
|
||||
run: |
|
||||
args=(beta --skip-verify)
|
||||
if [ "${{ inputs.dry_run }}" = "true" ]; then
|
||||
args+=(--dry-run)
|
||||
fi
|
||||
./scripts/release.sh "${args[@]}"
|
||||
|
||||
- name: Dump npm debug logs
|
||||
if: failure()
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
for f in "$HOME"/.npm/_logs/*.log; do
|
||||
echo "===== $f ====="
|
||||
tail -n 300 "$f" | sed -E \
|
||||
-e 's#((authorization|_authToken|_auth|node_auth_token|npm_token)"?[[:space:]]*[:=][[:space:]]*"?)(Bearer[[:space:]]+)?[^",[:space:]]+#\1***REDACTED***#Ig'
|
||||
done
|
||||
|
||||
- name: Push beta tag
|
||||
if: ${{ !inputs.dry_run }}
|
||||
run: |
|
||||
tag="$(git tag --points-at HEAD | grep '^beta/v' | head -1)"
|
||||
if [ -z "$tag" ]; then
|
||||
echo "Error: no beta tag points at HEAD after release." >&2
|
||||
exit 1
|
||||
fi
|
||||
git push origin "refs/tags/${tag}"
|
||||
|
||||
# Tag pushes made with GITHUB_TOKEN do not fire docker.yml's tag
|
||||
# trigger (GitHub suppresses workflow runs caused by GITHUB_TOKEN
|
||||
# pushes), so dispatch the image build at the new tag explicitly.
|
||||
- name: Build Docker images for the beta tag
|
||||
id: result
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
tag="$(git tag --points-at HEAD | grep '^beta/v' | head -1)"
|
||||
echo "beta_version=${tag#beta/v}" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## Beta published"
|
||||
echo ""
|
||||
echo "- Source SHA: \`${{ needs.select_beta.outputs.sha }}\`"
|
||||
echo "- Source nightly: \`${{ needs.select_beta.outputs.nightly_version }}\`"
|
||||
echo "- Published beta: \`${tag#beta/v}\`"
|
||||
echo "- Docker build dispatched at \`${tag}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
gh workflow run docker.yml --ref "refs/tags/${tag}" --repo "$GITHUB_REPOSITORY"
|
||||
|
||||
# Post-publish verification: run the release smoke suite against the exact
|
||||
# beta version that was just published.
|
||||
smoke_beta:
|
||||
needs: publish_beta
|
||||
if: ${{ !inputs.dry_run }}
|
||||
uses: ./.github/workflows/release-smoke.yml
|
||||
with:
|
||||
paperclip_version: ${{ needs.publish_beta.outputs.beta_version }}
|
||||
artifact_name: beta-release-smoke
|
||||
|
||||
# ----- Stable lane ------------------------------------------------------
|
||||
|
||||
# Stable releases promote a soaked beta. The preflight enforces that the
|
||||
# source commit shipped as a beta at least 3 days ago (measured from the
|
||||
# npm publish time of that beta version), unless a written justification
|
||||
# is provided. Dry runs report soak state without blocking.
|
||||
# Resolves source_ref to an immutable commit exactly once; every downstream
|
||||
# stable job consumes that SHA. Otherwise a branch or movable tag that
|
||||
# advances mid-run could be soak-checked at one commit and verified or
|
||||
# published at another.
|
||||
preflight_stable:
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'stable'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
sha: ${{ steps.soak.outputs.sha }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check beta soak
|
||||
id: soak
|
||||
env:
|
||||
SOURCE_REF: ${{ inputs.source_ref }}
|
||||
JUSTIFICATION: ${{ inputs.skip_soak_justification }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch origin --tags --prune --quiet
|
||||
|
||||
sha="$(git rev-parse --verify "${SOURCE_REF}^{commit}" 2>/dev/null || true)"
|
||||
if [ -z "$sha" ]; then
|
||||
git fetch origin "$SOURCE_REF" --quiet || true
|
||||
sha="$(git rev-parse --verify "FETCH_HEAD^{commit}" 2>/dev/null || true)"
|
||||
fi
|
||||
if [ -z "$sha" ]; then
|
||||
echo "Error: could not resolve source_ref '$SOURCE_REF' to a commit." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## Stable source pinned"
|
||||
echo ""
|
||||
echo "- source_ref: \`${SOURCE_REF}\` -> \`$sha\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
fail_or_justify() {
|
||||
if [ -n "${JUSTIFICATION:-}" ]; then
|
||||
{
|
||||
echo "## Stable soak gate bypassed"
|
||||
echo ""
|
||||
echo "$1"
|
||||
echo ""
|
||||
echo "Justification: ${JUSTIFICATION}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "::warning::Soak gate bypassed: $1"
|
||||
return 0
|
||||
fi
|
||||
if [ "${DRY_RUN}" = "true" ]; then
|
||||
echo "::warning::Soak gate would block a real release: $1"
|
||||
{
|
||||
echo "## Stable soak gate (dry run)"
|
||||
echo ""
|
||||
echo "A real release would be blocked: $1"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
return 0
|
||||
fi
|
||||
echo "Error: $1" >&2
|
||||
echo "Pass skip_soak_justification with a written reason to release anyway." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
beta_tag="$(git tag --points-at "$sha" | grep '^beta/v' | sort -V | tail -1 || true)"
|
||||
if [ -z "$beta_tag" ]; then
|
||||
fail_or_justify "source commit $sha never shipped as a beta (no beta/v* tag)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
beta_version="${beta_tag#beta/v}"
|
||||
publish_time="$(npm view "paperclipai@${beta_version}" time --json 2>/dev/null \
|
||||
| node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{const t=JSON.parse(d);process.stdout.write(typeof t === "string" ? t : (t[process.argv[1]] ?? ""))})' "$beta_version" || true)"
|
||||
if [ -z "$publish_time" ]; then
|
||||
fail_or_justify "could not determine the npm publish time of beta ${beta_version}."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
age_seconds="$(node -e 'process.stdout.write(String(Math.floor((Date.now() - Date.parse(process.argv[1])) / 1000)))' "$publish_time")"
|
||||
min_seconds=$((3 * 24 * 60 * 60))
|
||||
age_days="$(node -e 'process.stdout.write((Number(process.argv[1]) / 86400).toFixed(1))' "$age_seconds")"
|
||||
|
||||
if [ "$age_seconds" -lt "$min_seconds" ]; then
|
||||
fail_or_justify "beta ${beta_version} has only soaked ${age_days} days (minimum is 3)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
{
|
||||
echo "## Stable soak gate passed"
|
||||
echo ""
|
||||
echo "- Source beta: \`${beta_version}\`"
|
||||
echo "- Soak time: ${age_days} days"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
verify_stable:
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'stable'
|
||||
needs: preflight_stable
|
||||
uses: ./.github/workflows/release-verify.yml
|
||||
with:
|
||||
ref: ${{ inputs.source_ref }}
|
||||
ref: ${{ needs.preflight_stable.outputs.sha }}
|
||||
|
||||
preview_stable:
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'stable' && inputs.dry_run
|
||||
needs: verify_stable
|
||||
needs: [preflight_stable, verify_stable]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
@@ -335,7 +629,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.source_ref }}
|
||||
ref: ${{ needs.preflight_stable.outputs.sha }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
@@ -366,7 +660,7 @@ jobs:
|
||||
|
||||
publish_stable:
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'stable' && !inputs.dry_run
|
||||
needs: verify_stable
|
||||
needs: [preflight_stable, verify_stable]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
environment: npm-stable
|
||||
@@ -380,7 +674,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.source_ref }}
|
||||
ref: ${{ needs.preflight_stable.outputs.sha }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
Reference in new issue
Block a user