build: make npm release tag configurable

This commit is contained in:
Peter Steinberger
2026-04-02 19:05:27 +01:00
parent bcd61e54e1
commit 209535b7c7
10 changed files with 203 additions and 96 deletions

View File

@@ -17,7 +17,7 @@ Use this skill for release and publish-time workflow. Keep ordinary development
## Keep release channel naming aligned
- `stable`: tagged releases only, published to npm `latest` and then mirrored onto npm `beta` unless `beta` already points at a newer prerelease
- `stable`: tagged releases only, published to npm `beta` by default; operators may target npm `latest` explicitly or promote later
- `beta`: prerelease tags like `vYYYY.M.D-beta.N`, with npm dist-tag `beta`
- Prefer `-beta.N`; do not mint new `-1` or `-2` beta suffixes
- `dev`: moving head on `main`
@@ -234,21 +234,27 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
commit, and rerun all relevant preflights from scratch before continuing.
Never reuse old preflight results after the commit changes.
14. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for
the real publish and pass the successful npm `preflight_run_id`.
the real publish, choose `npm_dist_tag` (`beta` default, `latest` only when
you intentionally want direct stable publish), and pass the successful npm
`preflight_run_id`.
15. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
16. Start
16. If the stable release was published to `beta`, start
`.github/workflows/openclaw-npm-promote-beta.yml` with the exact stable
version after beta validation passes, then verify `latest` now points at
that version.
17. Start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
for the real publish with the successful private mac `preflight_run_id` and
wait for success.
17. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
18. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
and `.dSYM.zip` artifacts to the existing GitHub release in
`openclaw/openclaw`.
18. For stable releases, download `macos-appcast-<tag>` from the successful
19. For stable releases, download `macos-appcast-<tag>` from the successful
private mac run, update `appcast.xml` on `main`, and verify the feed.
19. For beta releases, publish the mac assets but expect no shared production
20. For beta releases, publish the mac assets but expect no shared production
`appcast.xml` artifact and do not update the shared production feed unless a
separate beta feed exists.
20. After publish, verify npm and the attached release artifacts.
21. After publish, verify npm and the attached release artifacts.
## GHSA advisory work

View File

@@ -0,0 +1,89 @@
name: OpenClaw NPM Promote Beta
on:
workflow_dispatch:
inputs:
version:
description: Stable version currently on npm beta to promote to latest (for example 2026.4.2 or 2026.4.2-1)
required: true
type: string
concurrency:
group: openclaw-npm-promote-beta-${{ inputs.version }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
jobs:
promote_beta_to_latest:
runs-on: ubuntu-latest
environment: npm-release
permissions:
contents: read
steps:
- name: Validate version input format
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_VERSION}" =~ ^[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]; then
echo "Invalid stable release version format: ${RELEASE_VERSION}" >&2
exit 1
fi
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
install-deps: "false"
- name: Validate npm dist-tags
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
beta_version="$(npm view openclaw dist-tags.beta)"
latest_version="$(npm view openclaw dist-tags.latest)"
echo "Current beta dist-tag: ${beta_version}"
echo "Current latest dist-tag: ${latest_version}"
if [[ "${beta_version}" != "${RELEASE_VERSION}" ]]; then
echo "npm beta points at ${beta_version}, expected ${RELEASE_VERSION}." >&2
exit 1
fi
if ! npm view "openclaw@${RELEASE_VERSION}" version >/dev/null 2>&1; then
echo "openclaw@${RELEASE_VERSION} is not published on npm." >&2
exit 1
fi
- name: Promote beta to latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
RELEASE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
userconfig="$(mktemp)"
trap 'rm -f "${userconfig}"' EXIT
chmod 0600 "${userconfig}"
printf '%s\n' "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > "${userconfig}"
NPM_CONFIG_USERCONFIG="${userconfig}" npm whoami >/dev/null
NPM_CONFIG_USERCONFIG="${userconfig}" \
npm dist-tag add "openclaw@${RELEASE_VERSION}" latest
promoted_latest="$(npm view openclaw dist-tags.latest)"
if [[ "${promoted_latest}" != "${RELEASE_VERSION}" ]]; then
echo "npm latest points at ${promoted_latest}, expected ${RELEASE_VERSION} after promotion." >&2
exit 1
fi
echo "Promoted openclaw@${RELEASE_VERSION} from beta to latest."

View File

@@ -16,9 +16,17 @@ on:
description: Existing successful preflight workflow run id to promote without rebuilding
required: false
type: string
npm_dist_tag:
description: npm dist-tag to publish to for stable releases
required: true
default: beta
type: choice
options:
- beta
- latest
concurrency:
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}', inputs.tag, inputs.npm_dist_tag) || github.ref }}
cancel-in-progress: false
env:
@@ -36,12 +44,17 @@ jobs:
- name: Validate tag input format
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
echo "Invalid release tag format: ${RELEASE_TAG}"
exit 1
fi
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
echo "Beta prerelease tags must publish to npm dist-tag beta."
exit 1
fi
- name: Forbid preflight artifact promotion on validation-only runs
if: ${{ inputs.preflight_only && inputs.preflight_run_id != '' }}
@@ -98,6 +111,7 @@ jobs:
OPENCLAW_NPM_RELEASE_SKIP_PACK_CHECK: "1"
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAIN_REF: origin/main
OPENCLAW_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
RELEASE_SHA=$(git rev-parse HEAD)
@@ -115,6 +129,7 @@ jobs:
env:
OPENCLAW_PREPACK_PREPARED: "1"
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
PACK_JSON="$(npm pack --json)"
@@ -131,6 +146,7 @@ jobs:
cp "$PACK_PATH" "$ARTIFACT_DIR/"
printf '%s\n' "$RELEASE_TAG" > "$ARTIFACT_DIR/release-tag.txt"
printf '%s\n' "$RELEASE_SHA" > "$ARTIFACT_DIR/release-sha.txt"
printf '%s\n' "$RELEASE_NPM_DIST_TAG" > "$ARTIFACT_DIR/release-npm-dist-tag.txt"
echo "dir=$ARTIFACT_DIR" >> "$GITHUB_OUTPUT"
- name: Upload prepared npm publish bundle
@@ -180,12 +196,17 @@ jobs:
- name: Validate tag input format
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
echo "Invalid release tag format: ${RELEASE_TAG}"
exit 1
fi
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
echo "Beta prerelease tags must publish to npm dist-tag beta."
exit 1
fi
- name: Checkout
uses: actions/checkout@v6
@@ -249,18 +270,21 @@ jobs:
- name: Verify prepared tarball provenance
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)"
TAG_FILE="preflight-tarball/release-tag.txt"
SHA_FILE="preflight-tarball/release-sha.txt"
if [[ ! -f "$TAG_FILE" || ! -f "$SHA_FILE" ]]; then
NPM_DIST_TAG_FILE="preflight-tarball/release-npm-dist-tag.txt"
if [[ ! -f "$TAG_FILE" || ! -f "$SHA_FILE" || ! -f "$NPM_DIST_TAG_FILE" ]]; then
echo "Prepared preflight metadata is missing." >&2
ls -la preflight-tarball >&2 || true
exit 1
fi
ARTIFACT_RELEASE_TAG="$(tr -d '\r\n' < "$TAG_FILE")"
ARTIFACT_RELEASE_SHA="$(tr -d '\r\n' < "$SHA_FILE")"
ARTIFACT_RELEASE_NPM_DIST_TAG="$(tr -d '\r\n' < "$NPM_DIST_TAG_FILE")"
if [[ "$ARTIFACT_RELEASE_TAG" != "$RELEASE_TAG" ]]; then
echo "Prepared preflight tag mismatch: expected $RELEASE_TAG, got $ARTIFACT_RELEASE_TAG" >&2
exit 1
@@ -269,6 +293,10 @@ jobs:
echo "Prepared preflight SHA mismatch: expected $EXPECTED_RELEASE_SHA, got $ARTIFACT_RELEASE_SHA" >&2
exit 1
fi
if [[ "$ARTIFACT_RELEASE_NPM_DIST_TAG" != "$RELEASE_NPM_DIST_TAG" ]]; then
echo "Prepared preflight npm dist-tag mismatch: expected $RELEASE_NPM_DIST_TAG, got $ARTIFACT_RELEASE_NPM_DIST_TAG" >&2
exit 1
fi
- name: Resolve publish tarball
id: publish_tarball
@@ -284,9 +312,8 @@ jobs:
- name: Publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
OPENCLAW_PREPACK_PREPARED: "1"
OPENCLAW_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
publish_target="${{ steps.publish_tarball.outputs.path }}"

View File

@@ -301,9 +301,10 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
- `latest` = stable
- `beta` = early build for testing
We ship builds to **beta**, test them, and once a build is solid we **promote
that same version to `latest`**. That's why beta and stable can point at the
**same version**.
Usually, a stable release lands on **beta** first, then an explicit
promotion step moves that same version to `latest`. Maintainers can also
publish straight to `latest` when needed. That's why beta and stable can
point at the **same version** after promotion.
See what changed:
[https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
@@ -313,7 +314,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
</Accordion>
<Accordion title="How do I install the beta version and what is the difference between beta and dev?">
**Beta** is the npm dist-tag `beta` (may match `latest`).
**Beta** is the npm dist-tag `beta` (may match `latest` after promotion).
**Dev** is the moving head of `main` (git); when published, it uses the npm dist-tag `dev`.
One-liners (macOS/Linux):

View File

@@ -18,8 +18,11 @@ OpenClaw ships three update channels:
The `main` branch is for experimentation and active development. It may contain
incomplete features or breaking changes. Do not use it for production gateways.
We ship builds to **beta**, test them, then **promote a vetted build to `latest`**
without changing the version number -- dist-tags are the source of truth for npm installs.
We usually ship stable builds to **beta** first, test them there, then run an
explicit promotion step that moves the vetted build to `latest` without
changing the version number. Maintainers can also publish a stable release
directly to `latest` when needed. Dist-tags are the source of truth for npm
installs.
## Switching channels
@@ -109,7 +112,7 @@ source (config, git tag, git branch, or default).
- Keep tags immutable: never move or reuse a tag.
- npm dist-tags remain the source of truth for npm installs:
- `latest` -> stable
- `beta` -> candidate build
- `beta` -> candidate build or beta-first stable build
- `dev` -> main snapshot (optional)
## macOS app availability

View File

@@ -10,7 +10,7 @@ read_when:
OpenClaw has three public release lanes:
- stable: tagged releases that publish to npm `latest` and mirror the same version onto `beta` unless `beta` already points at a newer prerelease
- stable: tagged releases that publish to npm `beta` by default, or to npm `latest` when explicitly requested
- beta: prerelease tags that publish to npm `beta`
- dev: the moving head of `main`
@@ -23,9 +23,9 @@ OpenClaw has three public release lanes:
- Beta prerelease version: `YYYY.M.D-beta.N`
- Git tag: `vYYYY.M.D-beta.N`
- Do not zero-pad month or day
- `latest` means the current stable npm release
- `beta` means the current beta install target, which may point to either the active prerelease or the latest promoted stable build
- Stable and stable correction releases publish to npm `latest` and also retag npm `beta` to that same non-beta version after promotion, unless `beta` already points at a newer prerelease
- `latest` means the current promoted stable npm release
- `beta` means the current beta install target
- Stable and stable correction releases publish to npm `beta` by default; release operators can target `latest` explicitly, or promote a vetted beta build later
- Every OpenClaw release ships the npm package and macOS app together
## Release cadence
@@ -49,6 +49,9 @@ OpenClaw has three public release lanes:
install path in a fresh temp prefix
- Maintainer release automation now uses preflight-then-promote:
- real npm publish must pass a successful npm `preflight_run_id`
- stable npm releases default to `beta`
- stable npm publish can target `latest` explicitly via workflow input
- stable npm promotion from `beta` to `latest` is still available as a separate manual workflow step
- public `macOS Release` is validation-only
- real private mac publish must pass successful private mac
`preflight_run_id` and `validate_run_id`
@@ -75,6 +78,7 @@ OpenClaw has three public release lanes:
## Public references
- [`.github/workflows/openclaw-npm-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-release.yml)
- [`.github/workflows/openclaw-npm-promote-beta.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-promote-beta.yml)
- [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts)
- [`scripts/package-mac-dist.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-dist.sh)
- [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)

View File

@@ -2,7 +2,8 @@ import { execFileSync } from "node:child_process";
import { mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { parseReleaseVersion, resolveNpmPublishPlan } from "../openclaw-npm-release-check.ts";
import { parseReleaseVersion } from "../openclaw-npm-release-check.ts";
import { resolveNpmPublishPlan } from "./npm-publish-plan.mjs";
export type PluginPackageJson = {
name?: string;

View File

@@ -18,30 +18,21 @@ if [[ -n "${publish_target}" && -f "${publish_target}" ]]; then
fi
package_version="$(node -p "require('./package.json').version")"
current_beta_version="$(npm view openclaw dist-tags.beta 2>/dev/null || true)"
mapfile -t publish_plan < <(
PACKAGE_VERSION="${package_version}" CURRENT_BETA_VERSION="${current_beta_version}" node --import tsx --input-type=module <<'EOF'
import {
resolveNpmDistTagMirrorAuth,
resolveNpmPublishPlan,
} from "./scripts/openclaw-npm-release-check.ts";
PACKAGE_VERSION="${package_version}" REQUESTED_PUBLISH_TAG="${OPENCLAW_NPM_PUBLISH_TAG:-}" \
node --import tsx --input-type=module <<'EOF'
import { resolveNpmPublishPlan } from "./scripts/openclaw-npm-release-check.ts";
const plan = resolveNpmPublishPlan(
process.env.PACKAGE_VERSION ?? "",
process.env.CURRENT_BETA_VERSION,
);
const auth = resolveNpmDistTagMirrorAuth();
const requestedPublishTag =
process.env.REQUESTED_PUBLISH_TAG === "latest" ? "latest" : "beta";
const plan = resolveNpmPublishPlan(process.env.PACKAGE_VERSION ?? "", undefined, requestedPublishTag);
console.log(plan.channel);
console.log(plan.publishTag);
console.log(plan.mirrorDistTags.join(","));
console.log(auth.source);
EOF
)
release_channel="${publish_plan[0]}"
publish_tag="${publish_plan[1]}"
mirror_dist_tags_csv="${publish_plan[2]:-}"
mirror_auth_source="${publish_plan[3]:-none}"
publish_cmd=(npm publish)
if [[ -n "${publish_target}" ]]; then
publish_cmd+=("${publish_target}")
@@ -49,58 +40,15 @@ fi
publish_cmd+=(--access public --tag "${publish_tag}" --provenance)
echo "Resolved package version: ${package_version}"
echo "Current beta dist-tag: ${current_beta_version:-<missing>}"
echo "Resolved release channel: ${release_channel}"
echo "Resolved publish tag: ${publish_tag}"
echo "Resolved mirror dist-tags: ${mirror_dist_tags_csv:-<none>}"
echo "Publish auth: GitHub OIDC trusted publishing"
echo "Mirror dist-tag auth source: ${mirror_auth_source}"
if [[ -n "${publish_target}" ]]; then
echo "Resolved publish target: ${publish_target}"
fi
mirror_auth_token=""
case "${mirror_auth_source}" in
node-auth-token)
mirror_auth_token="${NODE_AUTH_TOKEN:-}"
;;
npm-token)
mirror_auth_token="${NPM_TOKEN:-}"
;;
esac
if [[ -n "${mirror_dist_tags_csv}" && -z "${mirror_auth_token}" ]]; then
echo "npm dist-tag mirroring requires explicit npm auth via NODE_AUTH_TOKEN or NPM_TOKEN." >&2
echo "Refusing publish before npm latest/beta promotion can diverge." >&2
exit 1
fi
if [[ -n "${mirror_dist_tags_csv}" ]]; then
mirror_userconfig="$(mktemp)"
trap 'rm -f "${mirror_userconfig}"' EXIT
chmod 0600 "${mirror_userconfig}"
printf '%s\n' "//registry.npmjs.org/:_authToken=${mirror_auth_token}" > "${mirror_userconfig}"
echo "Validating npm auth for dist-tag mirroring"
if ! NPM_CONFIG_USERCONFIG="${mirror_userconfig}" npm whoami >/dev/null; then
echo "npm dist-tag auth is invalid; refusing publish before latest/beta diverge." >&2
echo "Rotate or replace NODE_AUTH_TOKEN/NPM_TOKEN, then rerun the release workflow." >&2
exit 1
fi
fi
printf 'Publish command:'
printf ' %q' "${publish_cmd[@]}"
printf '\n'
"${publish_cmd[@]}"
if [[ -n "${mirror_dist_tags_csv}" ]]; then
IFS=',' read -r -a mirror_dist_tags <<< "${mirror_dist_tags_csv}"
for dist_tag in "${mirror_dist_tags[@]}"; do
[[ -n "${dist_tag}" ]] || continue
echo "Mirroring openclaw@${package_version} onto dist-tag ${dist_tag}"
NPM_CONFIG_USERCONFIG="${mirror_userconfig}" \
npm dist-tag add "openclaw@${package_version}" "${dist_tag}"
done
fi

View File

@@ -8,7 +8,6 @@ import {
compareReleaseVersions as compareReleaseVersionsBase,
resolveNpmDistTagMirrorAuth as resolveNpmDistTagMirrorAuthBase,
parseReleaseVersion as parseReleaseVersionBase,
resolveNpmPublishPlan as resolveNpmPublishPlanBase,
} from "./lib/npm-publish-plan.mjs";
type PackageJson = {
@@ -82,9 +81,32 @@ export function compareReleaseVersions(left: string, right: string): number | nu
export function resolveNpmPublishPlan(
version: string,
currentBetaVersion?: string | null,
_currentBetaVersion?: string | null,
requestedPublishTag?: "latest" | "beta" | null,
): NpmPublishPlan {
return resolveNpmPublishPlanBase(version, currentBetaVersion) as NpmPublishPlan;
const parsedVersion = parseReleaseVersion(version);
if (parsedVersion === null) {
throw new Error(`Unsupported release version "${version}".`);
}
const publishTag = requestedPublishTag?.trim() === "latest" ? "latest" : "beta";
if (parsedVersion.channel === "beta") {
if (publishTag !== "beta") {
throw new Error("Beta prereleases must publish to the beta dist-tag.");
}
return {
channel: "beta",
publishTag: "beta",
mirrorDistTags: [],
};
}
return {
channel: "stable",
publishTag,
mirrorDistTags: [],
};
}
export function resolveNpmDistTagMirrorAuth(params?: {

View File

@@ -85,37 +85,43 @@ describe("resolveNpmPublishPlan", () => {
});
});
it("publishes stable releases to latest and mirrors beta", () => {
it("publishes stable releases to beta first", () => {
expect(resolveNpmPublishPlan("2026.3.29")).toEqual({
channel: "stable",
publishTag: "latest",
mirrorDistTags: ["beta"],
publishTag: "beta",
mirrorDistTags: [],
});
});
it("mirrors beta for stable correction releases too", () => {
it("publishes stable correction releases to beta first too", () => {
expect(resolveNpmPublishPlan("2026.3.29-2")).toEqual({
channel: "stable",
publishTag: "latest",
mirrorDistTags: ["beta"],
publishTag: "beta",
mirrorDistTags: [],
});
});
it("does not mirror beta when beta already points at a newer prerelease", () => {
expect(resolveNpmPublishPlan("2026.3.29", "2026.4.1-beta.1")).toEqual({
it("can publish stable releases directly to latest when requested", () => {
expect(resolveNpmPublishPlan("2026.3.29", undefined, "latest")).toEqual({
channel: "stable",
publishTag: "latest",
mirrorDistTags: [],
});
});
it("still mirrors beta when beta points at the same release line", () => {
expect(resolveNpmPublishPlan("2026.3.29", "2026.3.29-beta.2")).toEqual({
it("ignores current beta dist-tag state for stable publishes", () => {
expect(resolveNpmPublishPlan("2026.3.29", "2026.4.1-beta.1")).toEqual({
channel: "stable",
publishTag: "latest",
mirrorDistTags: ["beta"],
publishTag: "beta",
mirrorDistTags: [],
});
});
it("rejects publishing beta prereleases to latest", () => {
expect(() => resolveNpmPublishPlan("2026.3.29-beta.2", undefined, "latest")).toThrow(
"Beta prereleases must publish to the beta dist-tag.",
);
});
});
describe("resolveNpmDistTagMirrorAuth", () => {