fix(release): enforce unified Preview publication

Problem:
- Preview events for different versions could overlap and let an older npm canary publish last.
- CLI, Desktop, and npm child workflows still exposed standalone public Preview paths that bypassed the all-surface release marker.

Root cause:
- The Preview concurrency key included the version tag, while npm had no channel-level serialization.
- Child workflow dispatch inputs and resolvers treated Preview publication as independently recoverable.

Fix:
- Serialize every Preview event and npm dist-tag publisher, and require public CLI, Desktop, and npm Preview releases to come from release-preview.yml.
- Keep Stable/RC recovery and non-publishing Desktop signing checks available, document unified Preview recovery, and add regression coverage.

Verification:
- bash scripts/release-workflows.test.sh
- actionlint v1.7.7 on all release workflows
- git diff --check
This commit is contained in:
SivanCola committed 2026-07-31 23:53:55 +08:00
1 parent c54516a516
commit 413422feee
9 files changed
+152 -71

No files matched your search

+15 -16
View File
@@ -12,13 +12,11 @@ name: Release desktop
# release and mirrored to R2 (the updater reads R2 first, then the crash worker
# release gateway; stable desktop releases own GitHub's repository-wide "latest").
#
# The same build/sign/manifest pipeline serves two channels. An orchestrated
# `desktop-v*` tag (or manual stable recovery) publishes a GitHub release and
# moves R2's latest/ pointer. A manual `channel: preview` dispatch builds with
# -X main.channel=preview
# and uploads only to R2's preview/ pointer — no GitHub release, so Preview never
# shows on the releases page and never touches latest/. The legacy canary/
# pointer is mirrored for clients released before the channel rename.
# The same build/sign/manifest pipeline serves two channels. Approved
# orchestrators publish Stable or Preview, while manual dispatch supports
# Stable/RC recovery and non-publishing Preview signing checks. Preview advances
# only R2's preview/ pointer and never appears on the GitHub releases page. The
# legacy canary/ pointer is mirrored for clients released before the rename.
on:
workflow_dispatch:
inputs:
@@ -32,7 +30,7 @@ on:
required: false
type: string
base_version:
description: "preview: intended next stable version, e.g. 1.5.0"
description: "Preview signing checks: intended next stable version, e.g. 1.5.0"
required: false
type: string
production_signing_smoke:
@@ -70,7 +68,7 @@ on:
required: true
type: string
orchestrated:
description: "True only when called by release-stable.yml after approval"
description: "True only when called by an approved release orchestrator"
required: false
default: false
type: boolean
@@ -127,9 +125,12 @@ jobs:
id: release
env:
EVENT_NAME: ${{ github.event_name }}
IN_ORCHESTRATED: ${{ inputs.orchestrated }}
IN_CHANNEL: ${{ inputs.channel }}
IN_TAG: ${{ inputs.tag }}
IN_BASE_VERSION: ${{ inputs.base_version }}
IN_PRODUCTION_SIGNING_SMOKE: ${{ inputs.production_signing_smoke }}
IN_SIGNING_PREFLIGHT: ${{ inputs.signing_preflight }}
REF_NAME: ${{ github.ref_name }}
RUN_NUMBER: ${{ github.run_number }}
IN_PREVIEW_NUMBER: ${{ inputs.preview_number }}
@@ -186,9 +187,8 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
# Keep Preview fast, but do not make its public, formally signed binaries an
# unreviewed path. The `canary` environment must use the same required
# reviewers as `release`.
# Standalone Preview is limited to non-publishing signing checks, but still
# exercises the production policy behind the protected `canary` environment.
environment: ${{ needs.resolve.outputs.channel == 'preview' && 'canary' || 'release' }}
steps:
- env:
@@ -533,10 +533,9 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
# The stable caller has already passed the single GitHub release approval.
# Direct prereleases, manual stable recovery, and Preview all pass the
# appropriate release gate. The SignPath CI identity records each payload
# and installer approval after that GitHub gate.
# Approved orchestrators have already passed the matching GitHub environment.
# Direct prereleases and manual Stable recovery pass release-gate above. The
# SignPath CI identity records each payload and installer approval after it.
steps:
- uses: actions/checkout@v7
with:
+22 -18
View File
@@ -7,12 +7,12 @@ name: Release npm
# git tag npm-vX.Y.Z-rc.N # prerelease -> publishes under `next`
# git push origin <tag>
#
# A manual workflow_dispatch can publish either an opt-in canary from the current
# ref or recover an approved stable version from an existing tag. Canary publishes
# to the `canary` dist-tag (npm i reasonix@canary) and never moves
# `next`/`latest`. build.mjs decides the dist-tag: a `-canary.` build is `canary`,
# any other prerelease is `next`, and a stable version is `latest` (#5822 — the
# old "promote latest by hand" rule left it pinned at 0.53.2 and downgraded
# Manual workflow_dispatch recovers only an approved Stable version from an
# existing tag. Public canary publication is owned by release-preview.yml so its
# version remains aligned with CLI, Desktop, and the immutable release marker.
# build.mjs decides the dist-tag: a `-canary.` build is `canary`, any other
# prerelease is `next`, and a stable version is `latest` (#5822 — the old
# "promote latest by hand" rule left it pinned at 0.53.2 and downgraded
# `npm update -g` users). The verify step below asserts the tag actually landed.
on:
push:
@@ -20,15 +20,14 @@ on:
workflow_dispatch:
inputs:
channel:
description: "Publish channel"
description: "Standalone npm recovery channel"
required: true
default: canary
default: stable
type: choice
options:
- canary
- stable
base_version:
description: "Version to publish. Canary appends -canary.<run_number>; stable publishes exactly this version."
description: "Stable version to recover exactly"
required: true
type: string
tag:
@@ -59,7 +58,7 @@ on:
required: true
type: string
orchestrated:
description: "True only when called by release-stable.yml after approval"
description: "True only when called by an approved release orchestrator"
required: false
default: false
type: boolean
@@ -77,6 +76,12 @@ on:
permissions:
contents: read
concurrency:
# Serialize every publisher for one npm dist-tag. In particular, canary
# remains ordered even if another approved orchestrator is added later.
group: release-npm-${{ inputs.channel || 'next' }}
cancel-in-progress: false
jobs:
orchestration-guard:
name: verify approved orchestrator
@@ -109,7 +114,7 @@ jobs:
release-gate:
name: approve standalone npm release
if: ${{ !inputs.orchestrated && !(github.event_name == 'workflow_dispatch' && inputs.channel == 'canary') }}
if: ${{ !inputs.orchestrated }}
runs-on: ubuntu-latest
environment: release
steps:
@@ -120,7 +125,7 @@ jobs:
cache-guard:
name: cache hit guard
needs: [orchestration-guard, release-gate]
if: ${{ always() && !cancelled() && ((inputs.orchestrated && needs.orchestration-guard.result == 'success') || (!inputs.orchestrated && ((github.event_name == 'workflow_dispatch' && inputs.channel == 'canary') || needs.release-gate.result == 'success'))) }}
if: ${{ always() && !cancelled() && ((inputs.orchestrated && needs.orchestration-guard.result == 'success') || (!inputs.orchestrated && needs.release-gate.result == 'success')) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
@@ -137,9 +142,8 @@ jobs:
needs: cache-guard
if: ${{ always() && !cancelled() && needs.cache-guard.result == 'success' }}
runs-on: ubuntu-latest
# The stable caller has already passed the single GitHub release approval.
# Direct prereleases/manual stable recovery pass release-gate above; canaries
# run free.
# Orchestrated releases have already passed their GitHub environment
# approval. Direct prereleases and manual Stable recovery pass release-gate.
steps:
- uses: actions/checkout@v7
with:
@@ -152,8 +156,8 @@ jobs:
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
# Tag push uses the npm-v* tag. A canary dispatch synthesizes a
# <base>-canary.<run_number> version; a manual stable dispatch publishes
# Tag push uses the npm-v* tag. The Preview orchestrator synthesizes a
# <base>-canary.<preview_number> version; a manual Stable dispatch publishes
# the requested semver exactly. build.mjs strips the leading `npm-`/`v`.
- name: Resolve version
id: ver
+3 -1
View File
@@ -13,7 +13,9 @@ on:
type: string
concurrency:
group: preview-release-${{ inputs.tag }}
# Every Preview version advances the same public CLI, Desktop, and npm
# channels. Keep the whole all-surface event serialized across versions.
group: preview-release
cancel-in-progress: false
permissions:
+11 -13
View File
@@ -1,24 +1,22 @@
name: Release
# Native CLI binary line. Stable releases are called by release-stable.yml after
# its single GitHub `release`-environment approval. Preview uses immutable
# vX.Y.Z-preview.N tags, publishes a GitHub prerelease behind the same `canary`
# gate as Desktop Preview, and never moves Homebrew or GitHub Latest. Other
# standalone prereleases remain internal stable-channel checkpoints behind the
# `release` gate.
# Native CLI binary line. Stable and Preview releases are called by their
# approved all-surface orchestrators. Manual dispatch remains available only for
# Stable/RC recovery; standalone prereleases remain internal stable-channel
# checkpoints behind the `release` gate.
on:
workflow_dispatch:
inputs:
channel:
description: "Native CLI release channel"
description: "Standalone CLI recovery channel"
required: true
# Preserve existing tag-only recovery dispatches as Stable. Preview must
# be an explicit operator choice or arrive through the tag relay.
# Public Preview recovery reruns release-preview.yml so every surface and
# its immutable release-event marker stay aligned.
default: stable
type: choice
options: [stable, preview]
options: [stable]
tag:
description: "Existing protected CLI tag (Preview: v1.18.0-preview.1; Stable/RC: v1.18.0-rc.1)"
description: "Existing protected Stable/RC CLI tag (for example v1.18.0 or v1.18.0-rc.1)"
required: true
type: string
workflow_call:
@@ -41,7 +39,7 @@ on:
required: true
type: string
orchestrated:
description: "True only when called by release-stable.yml after approval"
description: "True only when called by an approved release orchestrator"
required: false
default: false
type: boolean
@@ -149,7 +147,7 @@ jobs:
needs: resolve
if: ${{ !inputs.orchestrated }}
runs-on: ubuntu-latest
environment: ${{ needs.resolve.outputs.channel == 'preview' && 'canary' || 'release' }}
environment: release
steps:
- env:
RELEASE_TAG: ${{ needs.resolve.outputs.tag }}
+6
View File
@@ -213,6 +213,12 @@ strict separation from repository writers is required.
npm, and Desktop tags to remain aligned on an ancestor of current `main-v2`,
then uses the same single approval and postflight. Never move or recreate the
published tags to pick up a workflow fix.
- Recover an interrupted Preview release by rerunning **Release preview** from
protected `main-v2` with the existing `vX.Y.Z-preview.N` tag. Do not dispatch
the CLI, Desktop, or npm publisher directly: those paths cannot create the
all-surface `release-event.json` marker and are blocked from public Preview
publication. Standalone Desktop Preview dispatch remains available only for
non-publishing SignPath preflight and production-signing smoke checks.
- Windows release signing uses SignPath trusted-build, origin verification, and
malware scanning. Keep the checked-in `windows-payload` and
`windows-installer-v2` artifact configurations synchronized with the matching
+76 -19
View File
@@ -35,6 +35,11 @@ grep -Fq "workflow_id: preview ? 'release-preview.yml' : 'release.yml'" \
"$repo_root/.github/workflows/release-cli-trigger.yml"
grep -Eq "preview\\\\\." "$repo_root/.github/workflows/release-cli-trigger.yml"
grep -Eq '^name: Release preview$' "$repo_root/.github/workflows/release-preview.yml"
grep -Eq '^ group: preview-release$' "$repo_root/.github/workflows/release-preview.yml"
if grep -Fq 'group: preview-release-${{ inputs.tag }}' "$repo_root/.github/workflows/release-preview.yml"; then
echo "Preview releases must serialize across versions" >&2
exit 1
fi
grep -Eq '^ environment: canary$' "$repo_root/.github/workflows/release-preview.yml"
grep -Eq '^ signpath-preflight:$' "$repo_root/.github/workflows/release-preview.yml"
grep -Eq 'signing_preflight: true' "$repo_root/.github/workflows/release-preview.yml"
@@ -57,11 +62,10 @@ for workflow in release.yml release-npm.yml release-desktop.yml; do
grep -Eq 'release-\{1\}\.yml' "$repo_root/.github/workflows/$workflow"
grep -Eq "needs\.cache-guard\.result == 'success'" "$repo_root/.github/workflows/$workflow"
done
grep -Eq 'options: \[stable, preview\]' "$repo_root/.github/workflows/release.yml"
grep -Eq 'options: \[stable\]' "$repo_root/.github/workflows/release.yml"
grep -A6 -E '^ channel:' "$repo_root/.github/workflows/release.yml" |
grep -Eq 'default: stable'
grep -Eq "needs\.resolve\.outputs\.channel == 'preview'.*'canary'.*'release'" \
"$repo_root/.github/workflows/release.yml"
grep -Eq '^ environment: release$' "$repo_root/.github/workflows/release.yml"
grep -Eq 'GORELEASER_CURRENT_TAG:.*needs\.resolve\.outputs\.tag' \
"$repo_root/.github/workflows/release.yml"
grep -Eq 'bash scripts/resolve-cli-release\.sh' "$repo_root/.github/workflows/release.yml"
@@ -98,6 +102,20 @@ if grep -Fq 'ref: ${{ inputs.approved_sha || inputs.tag || github.ref }}' \
fi
grep -Fq "group: release-desktop-\${{ (inputs.channel == 'preview' || inputs.channel == 'canary') && 'preview' || 'stable' }}" \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'IN_PRODUCTION_SIGNING_SMOKE:.*inputs\.production_signing_smoke' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Eq 'IN_SIGNING_PREFLIGHT:.*inputs\.signing_preflight' \
"$repo_root/.github/workflows/release-desktop.yml"
grep -Fq "group: release-npm-\${{ inputs.channel || 'next' }}" \
"$repo_root/.github/workflows/release-npm.yml"
sed -n '/^ workflow_dispatch:/,/^ workflow_call:/p' "$repo_root/.github/workflows/release-npm.yml" |
grep -Eq 'default: stable'
if sed -n '/^ workflow_dispatch:/,/^ workflow_call:/p' "$repo_root/.github/workflows/release-npm.yml" |
grep -Eq '^ - canary$'; then
echo "Standalone npm dispatch must not expose public Canary publication" >&2
exit 1
fi
grep -Fq 'if: ${{ !inputs.orchestrated }}' "$repo_root/.github/workflows/release-npm.yml"
grep -Eq 'signing-policy-slug: release-signing' "$repo_root/.github/workflows/release-desktop.yml"
if grep -Eq 'signing-policy-slug:.*test-signing' "$repo_root/.github/workflows/release-desktop.yml"; then
echo "public desktop workflow must not use the SignPath test certificate" >&2
@@ -1072,14 +1090,15 @@ grep -Eq '^tag=desktop-v1\.2\.3$' "$test_root/desktop-stable.out"
grep -Eq '^version=v1\.2\.3$' "$test_root/desktop-stable.out"
grep -Eq '^notes_version=v1\.2\.3$' "$test_root/desktop-stable.out"
EVENT_NAME=workflow_dispatch IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=42 \
EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 \
IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=999 IN_PREVIEW_NUMBER=42 \
GITHUB_OUTPUT="$test_root/desktop-preview.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^version=v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out"
grep -Eq '^tag=desktop-v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out"
grep -Eq '^channel=preview$' "$test_root/desktop-preview.out"
grep -Eq '^notes_version=v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out"
if EVENT_NAME=workflow_dispatch IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 \
if EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 \
IN_TAG=desktop-v1.3.0-preview.42 REF_NAME=main-v2 RUN_NUMBER=42 \
GITHUB_OUTPUT="$test_root/desktop-preview-tagged.out" \
bash "$repo_root/scripts/resolve-desktop-release.sh" \
@@ -1090,14 +1109,31 @@ fi
grep -Eq 'Desktop Preview versions are synthesized from protected main-v2' \
"$test_root/desktop-preview-tagged.log"
# Legacy automation may still dispatch `canary`; normalize it to Preview without
# exposing a third user-facing release channel.
EVENT_NAME=workflow_dispatch IN_CHANNEL=canary IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=43 \
# Legacy signing automation may still dispatch `canary`; normalize it to Preview
# without exposing a third public release path.
EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_SIGNING_PREFLIGHT=true \
IN_CHANNEL=canary IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=43 \
GITHUB_OUTPUT="$test_root/desktop-canary-compat.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^version=v1\.3\.0-preview\.43$' "$test_root/desktop-canary-compat.out"
grep -Eq '^tag=desktop-v1\.3\.0-preview\.43$' "$test_root/desktop-canary-compat.out"
grep -Eq '^channel=preview$' "$test_root/desktop-canary-compat.out"
EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_PRODUCTION_SIGNING_SMOKE=true \
IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=44 \
GITHUB_OUTPUT="$test_root/desktop-preview-smoke.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^version=v1\.3\.0-preview\.44$' "$test_root/desktop-preview-smoke.out"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \
IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=45 \
GITHUB_OUTPUT="$test_root/desktop-preview-direct.out" \
bash "$repo_root/scripts/resolve-desktop-release.sh" \
>"$test_root/desktop-preview-direct.log" 2>&1; then
echo "standalone public Desktop Preview unexpectedly passed" >&2
exit 1
fi
grep -Eq 'public Desktop Preview releases must be dispatched by release-preview.yml' \
"$test_root/desktop-preview-direct.log"
EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=desktop-v1.4.0-rc.1 RUN_NUMBER=50 \
GITHUB_OUTPUT="$test_root/desktop-rc.out" bash "$repo_root/scripts/resolve-desktop-release.sh"
grep -Eq '^prerelease=true$' "$test_root/desktop-rc.out"
@@ -1121,20 +1157,26 @@ if EVENT_NAME=workflow_dispatch IN_CHANNEL=stable IN_TAG='' REF_NAME=main-v2 RUN
fi
grep -Eq 'stable dispatch requires tag' "$test_root/desktop-missing-tag.log"
EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=v1.3.0-preview.42 \
GITHUB_OUTPUT="$test_root/cli-preview-push.out" bash "$repo_root/scripts/resolve-cli-release.sh"
grep -Eq '^tag=v1\.3\.0-preview\.42$' "$test_root/cli-preview-push.out"
grep -Eq '^version=1\.3\.0-preview\.42$' "$test_root/cli-preview-push.out"
grep -Eq '^base_version=1\.3\.0$' "$test_root/cli-preview-push.out"
grep -Eq '^notes_version=v1\.3\.0-preview\.42$' "$test_root/cli-preview-push.out"
grep -Eq '^channel=preview$' "$test_root/cli-preview-push.out"
grep -Eq '^prerelease=true$' "$test_root/cli-preview-push.out"
EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_TAG=v1.3.0-preview.42 \
REF_NAME=main-v2 GITHUB_OUTPUT="$test_root/cli-preview-call.out" \
bash "$repo_root/scripts/resolve-cli-release.sh"
grep -Eq '^tag=v1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out"
grep -Eq '^version=1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out"
grep -Eq '^base_version=1\.3\.0$' "$test_root/cli-preview-call.out"
grep -Eq '^notes_version=v1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out"
grep -Eq '^channel=preview$' "$test_root/cli-preview-call.out"
grep -Eq '^prerelease=true$' "$test_root/cli-preview-call.out"
EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \
IN_TAG=v1.3.0-preview.42 REF_NAME=main-v2 CALLER_REF=refs/heads/main-v2 \
CALLER_REF_PROTECTED=true GITHUB_OUTPUT="$test_root/cli-preview-dispatch.out" \
bash "$repo_root/scripts/resolve-cli-release.sh"
grep -Eq '^channel=preview$' "$test_root/cli-preview-dispatch.out"
bash "$repo_root/scripts/resolve-cli-release.sh" \
>"$test_root/cli-preview-dispatch.log" 2>&1; then
echo "standalone public CLI Preview unexpectedly passed" >&2
exit 1
fi
grep -Eq 'public CLI Preview releases must be dispatched by release-preview.yml' \
"$test_root/cli-preview-dispatch.log"
EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=v1.3.0-rc.1 \
GITHUB_OUTPUT="$test_root/cli-rc.out" bash "$repo_root/scripts/resolve-cli-release.sh"
@@ -1178,6 +1220,21 @@ EVENT_NAME=push IN_ORCHESTRATED=true IN_CHANNEL=stable IN_BASE_VERSION=1.5.0 \
bash "$repo_root/scripts/resolve-npm-release.sh"
grep -Eq '^arg=v1\.5\.0$' "$test_root/npm-stable.out"
EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=canary IN_BASE_VERSION=1.5.0 \
IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=999 IN_PREVIEW_NUMBER=42 \
GITHUB_OUTPUT="$test_root/npm-canary.out" bash "$repo_root/scripts/resolve-npm-release.sh"
grep -Eq '^arg=v1\.5\.0-canary\.42$' "$test_root/npm-canary.out"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=canary IN_BASE_VERSION=1.5.0 \
IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=52 GITHUB_OUTPUT="$test_root/npm-canary-direct.out" \
bash "$repo_root/scripts/resolve-npm-release.sh" \
>"$test_root/npm-canary-direct.log" 2>&1; then
echo "standalone public npm Canary unexpectedly passed" >&2
exit 1
fi
grep -Eq 'public npm Canary releases must be dispatched by release-preview.yml' \
"$test_root/npm-canary-direct.log"
if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=stable IN_BASE_VERSION=1.5.0 \
IN_TAG=npm-v1.5.1 REF_NAME=main-v2 RUN_NUMBER=52 GITHUB_OUTPUT="$test_root/npm-mismatch.out" \
bash "$repo_root/scripts/resolve-npm-release.sh" >"$test_root/npm-mismatch.log" 2>&1; then
+5
View File
@@ -66,6 +66,11 @@ if [ -n "$requested_channel" ]; then
fi
fi
if [ "$channel" = "preview" ] && [ "${IN_ORCHESTRATED:-false}" != "true" ]; then
echo "::error::public CLI Preview releases must be dispatched by release-preview.yml" >&2
exit 1
fi
{
echo "tag=$tag"
echo "version=$version"
+10 -4
View File
@@ -5,10 +5,10 @@
#
# stable: from a desktop-v* prerelease tag push, a manual dispatch with `tag`,
# or the stable release orchestrator's workflow_call input.
# preview: a manual dispatch with channel=preview (legacy canary accepted);
# version is synthesized from base_version + the monotonic run_number,
# tag is the matching immutable asset directory, and it is always a
# prerelease.
# preview: public publication requires the approved Preview orchestrator;
# standalone Preview is limited to non-publishing signing checks.
# Legacy canary input is accepted for those checks. The version uses
# the orchestrator ordinal or, for a signing check, the run number.
set -euo pipefail
stable_semver_re='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
@@ -16,6 +16,12 @@ release_semver_re='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-([0-9A-Za
preview_semver_re='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)-preview\.(0|[1-9][0-9]*)$'
if [ "${IN_CHANNEL:-stable}" = "preview" ] || [ "${IN_CHANNEL:-stable}" = "canary" ]; then
if [ "${IN_ORCHESTRATED:-false}" != "true" ] && \
[ "${IN_PRODUCTION_SIGNING_SMOKE:-false}" != "true" ] && \
[ "${IN_SIGNING_PREFLIGHT:-false}" != "true" ]; then
echo "::error::public Desktop Preview releases must be dispatched by release-preview.yml" >&2
exit 1
fi
if [ -n "${IN_TAG:-}" ]; then
echo "::error::Desktop Preview versions are synthesized from protected main-v2; tag must be empty" >&2
exit 1
+4
View File
@@ -15,6 +15,10 @@ if [ "${EVENT_NAME:-}" = "workflow_dispatch" ] || [ "${IN_ORCHESTRATED:-false}"
exit 1
fi
if [ "${IN_CHANNEL:-stable}" = "canary" ]; then
if [ "${IN_ORCHESTRATED:-false}" != "true" ]; then
echo "::error::public npm Canary releases must be dispatched by release-preview.yml" >&2
exit 1
fi
preview_number="${IN_PREVIEW_NUMBER:-${RUN_NUMBER:-}}"
if [[ ! "$preview_number" =~ ^(0|[1-9][0-9]*)$ ]]; then
echo "::error::canary preview number must be a non-negative integer, got: $preview_number" >&2