mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
feat: support alpha releases
This commit is contained in:
2
.github/workflows/docker-release.yml
vendored
2
.github/workflows/docker-release.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
RELEASE_TAG: ${{ inputs.tag }}
|
RELEASE_TAG: ${{ inputs.tag }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-beta\.[1-9][0-9]*)?$ ]]; then
|
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-(alpha|beta)\.[1-9][0-9]*)?$ ]]; then
|
||||||
echo "Invalid release tag: ${RELEASE_TAG}"
|
echo "Invalid release tag: ${RELEASE_TAG}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
4
.github/workflows/macos-release.yml
vendored
4
.github/workflows/macos-release.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: Existing release tag to validate for macOS release handoff (for example v2026.3.22 or v2026.3.22-beta.1)
|
description: Existing release tag to validate for macOS release handoff (for example v2026.3.22, v2026.3.22-alpha.1, or v2026.3.22-beta.1)
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
preflight_only:
|
preflight_only:
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
RELEASE_TAG: ${{ inputs.tag }}
|
RELEASE_TAG: ${{ inputs.tag }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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
|
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
|
||||||
echo "Invalid release tag format: ${RELEASE_TAG}"
|
echo "Invalid release tag format: ${RELEASE_TAG}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
4
.github/workflows/npm-telegram-beta-e2e.yml
vendored
4
.github/workflows/npm-telegram-beta-e2e.yml
vendored
@@ -152,8 +152,8 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then
|
if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then
|
||||||
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
|
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(alpha|beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-(alpha|beta)\.[1-9][0-9]*)?)$ ]]; then
|
||||||
echo "package_spec must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
|
echo "package_spec must be openclaw@alpha, openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
15
.github/workflows/openclaw-npm-release.yml
vendored
15
.github/workflows/openclaw-npm-release.yml
vendored
@@ -17,11 +17,12 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
npm_dist_tag:
|
npm_dist_tag:
|
||||||
description: npm dist-tag to publish to for stable releases
|
description: npm dist-tag to publish to
|
||||||
required: true
|
required: true
|
||||||
default: beta
|
default: beta
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
|
- alpha
|
||||||
- beta
|
- beta
|
||||||
- latest
|
- latest
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ jobs:
|
|||||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
if [[ ! "${RELEASE_REF}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]] && [[ ! "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
if [[ ! "${RELEASE_REF}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]] && [[ ! "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||||
echo "Invalid release ref format: ${RELEASE_REF}"
|
echo "Invalid release ref format: ${RELEASE_REF}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -62,6 +63,10 @@ jobs:
|
|||||||
echo "Full commit SHA input is only supported for validation-only preflight runs."
|
echo "Full commit SHA input is only supported for validation-only preflight runs."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if [[ "${RELEASE_REF}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" != "alpha" ]]; then
|
||||||
|
echo "Alpha prerelease tags must publish to npm dist-tag alpha."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
if [[ "${RELEASE_REF}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
if [[ "${RELEASE_REF}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
||||||
echo "Beta prerelease tags must publish to npm dist-tag beta."
|
echo "Beta prerelease tags must publish to npm dist-tag beta."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -294,10 +299,14 @@ jobs:
|
|||||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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
|
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
|
||||||
echo "Invalid release tag format: ${RELEASE_TAG}"
|
echo "Invalid release tag format: ${RELEASE_TAG}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" != "alpha" ]]; then
|
||||||
|
echo "Alpha prerelease tags must publish to npm dist-tag alpha."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
||||||
echo "Beta prerelease tags must publish to npm dist-tag beta."
|
echo "Beta prerelease tags must publish to npm dist-tag beta."
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: Release tag to publish, for example v2026.5.1-beta.1
|
description: Release tag to publish, for example v2026.5.1-alpha.1 or v2026.5.1-beta.1
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
preflight_run_id:
|
preflight_run_id:
|
||||||
@@ -17,6 +17,7 @@ on:
|
|||||||
default: beta
|
default: beta
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
|
- alpha
|
||||||
- beta
|
- beta
|
||||||
- latest
|
- latest
|
||||||
plugin_publish_scope:
|
plugin_publish_scope:
|
||||||
@@ -69,10 +70,14 @@ jobs:
|
|||||||
WORKFLOW_REF: ${{ github.ref }}
|
WORKFLOW_REF: ${{ github.ref }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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
|
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
|
||||||
echo "Invalid release tag: ${RELEASE_TAG}" >&2
|
echo "Invalid release tag: ${RELEASE_TAG}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" != "alpha" ]]; then
|
||||||
|
echo "Alpha prerelease tags must publish OpenClaw to npm dist-tag alpha." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
||||||
echo "Beta prerelease tags must publish OpenClaw to npm dist-tag beta." >&2
|
echo "Beta prerelease tags must publish OpenClaw to npm dist-tag beta." >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ Use `Package Acceptance` when the question is "does this installable OpenClaw pa
|
|||||||
|
|
||||||
### Candidate sources
|
### Candidate sources
|
||||||
|
|
||||||
- `source=npm` accepts only `openclaw@beta`, `openclaw@latest`, or an exact OpenClaw release version such as `openclaw@2026.4.27-beta.2`. Use this for published beta/stable acceptance.
|
- `source=npm` accepts only `openclaw@alpha`, `openclaw@beta`, `openclaw@latest`, or an exact OpenClaw release version such as `openclaw@2026.4.27-beta.2`. Use this for published prerelease/stable acceptance.
|
||||||
- `source=ref` packs a trusted `package_ref` branch, tag, or full commit SHA. The resolver fetches OpenClaw branches/tags, verifies the selected commit is reachable from repository branch history or a release tag, installs deps in a detached worktree, and packs it with `scripts/package-openclaw-for-docker.mjs`.
|
- `source=ref` packs a trusted `package_ref` branch, tag, or full commit SHA. The resolver fetches OpenClaw branches/tags, verifies the selected commit is reachable from repository branch history or a release tag, installs deps in a detached worktree, and packs it with `scripts/package-openclaw-for-docker.mjs`.
|
||||||
- `source=url` downloads an HTTPS `.tgz`; `package_sha256` is required.
|
- `source=url` downloads an HTTPS `.tgz`; `package_sha256` is required.
|
||||||
- `source=artifact` downloads one `.tgz` from `artifact_run_id` and `artifact_name`; `package_sha256` is optional but should be supplied for externally shared artifacts.
|
- `source=artifact` downloads one `.tgz` from `artifact_run_id` and `artifact_name`; `package_sha256` is optional but should be supplied for externally shared artifacts.
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ read_when:
|
|||||||
- Looking for version naming and cadence
|
- Looking for version naming and cadence
|
||||||
---
|
---
|
||||||
|
|
||||||
OpenClaw has three public release lanes:
|
OpenClaw has four public release lanes:
|
||||||
|
|
||||||
- stable: tagged releases that publish to npm `beta` by default, or to npm `latest` when explicitly requested
|
- stable: tagged releases that publish to npm `beta` by default, or to npm `latest` when explicitly requested
|
||||||
|
- alpha: prerelease tags that publish to npm `alpha`
|
||||||
- beta: prerelease tags that publish to npm `beta`
|
- beta: prerelease tags that publish to npm `beta`
|
||||||
- dev: the moving head of `main`
|
- dev: the moving head of `main`
|
||||||
|
|
||||||
@@ -19,10 +20,13 @@ OpenClaw has three public release lanes:
|
|||||||
- Git tag: `vYYYY.M.D`
|
- Git tag: `vYYYY.M.D`
|
||||||
- Stable correction release version: `YYYY.M.D-N`
|
- Stable correction release version: `YYYY.M.D-N`
|
||||||
- Git tag: `vYYYY.M.D-N`
|
- Git tag: `vYYYY.M.D-N`
|
||||||
|
- Alpha prerelease version: `YYYY.M.D-alpha.N`
|
||||||
|
- Git tag: `vYYYY.M.D-alpha.N`
|
||||||
- Beta prerelease version: `YYYY.M.D-beta.N`
|
- Beta prerelease version: `YYYY.M.D-beta.N`
|
||||||
- Git tag: `vYYYY.M.D-beta.N`
|
- Git tag: `vYYYY.M.D-beta.N`
|
||||||
- Do not zero-pad month or day
|
- Do not zero-pad month or day
|
||||||
- `latest` means the current promoted stable npm release
|
- `latest` means the current promoted stable npm release
|
||||||
|
- `alpha` means the current alpha install target
|
||||||
- `beta` means the current beta install target
|
- `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
|
- 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 stable OpenClaw release ships the npm package and macOS app together;
|
- Every stable OpenClaw release ships the npm package and macOS app together;
|
||||||
@@ -75,14 +79,15 @@ the maintainer-only release runbook.
|
|||||||
file, lane, workflow job, package profile, provider, or model allowlist that
|
file, lane, workflow job, package profile, provider, or model allowlist that
|
||||||
proves the fix. Rerun the full umbrella only when the changed surface makes
|
proves the fix. Rerun the full umbrella only when the changed surface makes
|
||||||
prior evidence stale.
|
prior evidence stale.
|
||||||
9. For beta, tag `vYYYY.M.D-beta.N`, then run `OpenClaw Release Publish` from
|
9. For alpha or beta, tag `vYYYY.M.D-alpha.N` or `vYYYY.M.D-beta.N`, then run `OpenClaw Release Publish` from
|
||||||
the matching `release/YYYY.M.D` branch. It verifies `pnpm plugins:sync:check`,
|
the matching `release/YYYY.M.D` branch. It verifies `pnpm plugins:sync:check`,
|
||||||
publishes all publishable plugin packages to npm first, publishes the same
|
publishes all publishable plugin packages to npm first, publishes the same
|
||||||
set to ClawHub second, and then promotes the prepared OpenClaw npm preflight
|
set to ClawHub second, and then promotes the prepared OpenClaw npm preflight
|
||||||
artifact with dist-tag `beta`. After publish, run post-publish package
|
artifact with the matching dist-tag. After publish, run post-publish package
|
||||||
acceptance against the published `openclaw@YYYY.M.D-beta.N` or `openclaw@beta`
|
acceptance against the published `openclaw@YYYY.M.D-alpha.N`, `openclaw@alpha`,
|
||||||
package. If a pushed or published beta needs a fix, cut the next `-beta.N`;
|
`openclaw@YYYY.M.D-beta.N`, or `openclaw@beta` package. If a pushed or
|
||||||
do not delete or rewrite the old beta.
|
published prerelease needs a fix, cut the next matching prerelease number;
|
||||||
|
do not delete or rewrite the old prerelease.
|
||||||
10. For stable, continue only after the vetted beta or release candidate has the
|
10. For stable, continue only after the vetted beta or release candidate has the
|
||||||
required validation evidence. Stable npm publish also goes through
|
required validation evidence. Stable npm publish also goes through
|
||||||
`OpenClaw Release Publish`, reusing the successful preflight artifact via
|
`OpenClaw Release Publish`, reusing the successful preflight artifact via
|
||||||
@@ -124,7 +129,7 @@ the maintainer-only release runbook.
|
|||||||
`gh workflow run full-release-validation.yml --ref main -f ref=release/YYYY.M.D`
|
`gh workflow run full-release-validation.yml --ref main -f ref=release/YYYY.M.D`
|
||||||
- Run the manual `Package Acceptance` workflow when you want side-channel proof
|
- Run the manual `Package Acceptance` workflow when you want side-channel proof
|
||||||
for a package candidate while release work continues. Use `source=npm` for
|
for a package candidate while release work continues. Use `source=npm` for
|
||||||
`openclaw@beta`, `openclaw@latest`, or an exact release version; `source=ref`
|
`openclaw@alpha`, `openclaw@beta`, `openclaw@latest`, or an exact release version; `source=ref`
|
||||||
to pack a trusted `package_ref` branch/tag/SHA with the current
|
to pack a trusted `package_ref` branch/tag/SHA with the current
|
||||||
`workflow_ref` harness; `source=url` for an HTTPS tarball with a required
|
`workflow_ref` harness; `source=url` for an HTTPS tarball with a required
|
||||||
SHA-256; or `source=artifact` for a tarball uploaded by another GitHub
|
SHA-256; or `source=artifact` for a tarball uploaded by another GitHub
|
||||||
@@ -548,6 +553,16 @@ gh workflow run openclaw-release-publish.yml \
|
|||||||
-f npm_dist_tag=beta
|
-f npm_dist_tag=beta
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alpha publish example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh workflow run openclaw-release-publish.yml \
|
||||||
|
--ref release/YYYY.M.D \
|
||||||
|
-f tag=vYYYY.M.D-alpha.N \
|
||||||
|
-f preflight_run_id=<successful-openclaw-npm-preflight-run-id> \
|
||||||
|
-f npm_dist_tag=alpha
|
||||||
|
```
|
||||||
|
|
||||||
Stable publish to the default beta dist-tag:
|
Stable publish to the default beta dist-tag:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -579,7 +594,7 @@ OpenClaw package must not be published.
|
|||||||
`OpenClaw NPM Release` accepts these operator-controlled inputs:
|
`OpenClaw NPM Release` accepts these operator-controlled inputs:
|
||||||
|
|
||||||
- `tag`: required release tag such as `v2026.4.2`, `v2026.4.2-1`, or
|
- `tag`: required release tag such as `v2026.4.2`, `v2026.4.2-1`, or
|
||||||
`v2026.4.2-beta.1`; when `preflight_only=true`, it may also be the current
|
`v2026.4.2-alpha.1` or `v2026.4.2-beta.1`; when `preflight_only=true`, it may also be the current
|
||||||
full 40-character workflow-branch commit SHA for validation-only preflight
|
full 40-character workflow-branch commit SHA for validation-only preflight
|
||||||
- `preflight_only`: `true` for validation/build/package only, `false` for the
|
- `preflight_only`: `true` for validation/build/package only, `false` for the
|
||||||
real publish path
|
real publish path
|
||||||
@@ -609,6 +624,7 @@ OpenClaw package must not be published.
|
|||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
- Stable and correction tags may publish to either `beta` or `latest`
|
- Stable and correction tags may publish to either `beta` or `latest`
|
||||||
|
- Alpha prerelease tags may publish only to `alpha`
|
||||||
- Beta prerelease tags may publish only to `beta`
|
- Beta prerelease tags may publish only to `beta`
|
||||||
- For `OpenClaw NPM Release`, full commit SHA input is allowed only when
|
- For `OpenClaw NPM Release`, full commit SHA input is allowed only when
|
||||||
`preflight_only=true`
|
`preflight_only=true`
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ rm -f "$SUMMARY_JSON" "$CONFIG_COVERAGE_JSON"
|
|||||||
|
|
||||||
validate_baseline_package_spec() {
|
validate_baseline_package_spec() {
|
||||||
local spec="$1"
|
local spec="$1"
|
||||||
if [[ "$spec" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
|
if [[ "$spec" =~ ^openclaw@(alpha|beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-(alpha|beta)\.[1-9][0-9]*)?)$ ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
echo "OPENCLAW_UPGRADE_SURVIVOR_BASELINE must be openclaw@latest, openclaw@beta, an exact OpenClaw release version, or a bare release version; got: $spec" >&2
|
echo "OPENCLAW_UPGRADE_SURVIVOR_BASELINE must be openclaw@latest, openclaw@beta, openclaw@alpha, an exact OpenClaw release version, or a bare release version; got: $spec" >&2
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,12 +98,12 @@ normalize_baseline() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
case "$baseline_version" in
|
case "$baseline_version" in
|
||||||
latest | beta)
|
latest | beta | alpha)
|
||||||
baseline_version=""
|
baseline_version=""
|
||||||
baseline_version_expected="0"
|
baseline_version_expected="0"
|
||||||
;;
|
;;
|
||||||
dev | main | "")
|
dev | main | "")
|
||||||
echo "OPENCLAW_UPGRADE_SURVIVOR_BASELINE must be openclaw@latest, openclaw@beta, openclaw@<version>, or a bare version" >&2
|
echo "OPENCLAW_UPGRADE_SURVIVOR_BASELINE must be openclaw@latest, openclaw@beta, openclaw@alpha, openclaw@<version>, or a bare version" >&2
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ resolve_credential_role() {
|
|||||||
|
|
||||||
validate_openclaw_package_spec() {
|
validate_openclaw_package_spec() {
|
||||||
local spec="$1"
|
local spec="$1"
|
||||||
if [[ "$spec" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
|
if [[ "$spec" =~ ^openclaw@(alpha|beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-(alpha|beta)\.[1-9][0-9]*)?)$ ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
echo "OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: $spec" >&2
|
echo "OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC must be openclaw@alpha, openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: $spec" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ OUTPUT_DIR="${OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR:-.artifacts/qa-e2e/npm-telegram-r
|
|||||||
|
|
||||||
validate_openclaw_package_spec() {
|
validate_openclaw_package_spec() {
|
||||||
local spec="$1"
|
local spec="$1"
|
||||||
if [[ "$spec" =~ ^openclaw@(main|beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
|
if [[ "$spec" =~ ^openclaw@(main|alpha|beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-(alpha|beta)\.[1-9][0-9]*)?)$ ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
echo "OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC must be openclaw@main, openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: $spec" >&2
|
echo "OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC must be openclaw@main, openclaw@alpha, openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: $spec" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,12 +91,14 @@ export function normalizeUpgradeSurvivorBaselineSpec(raw) {
|
|||||||
}
|
}
|
||||||
const spec = value.startsWith("openclaw@") ? value : `openclaw@${value}`;
|
const spec = value.startsWith("openclaw@") ? value : `openclaw@${value}`;
|
||||||
if (
|
if (
|
||||||
!/^openclaw@(?:beta|latest|[0-9]{4}\.[0-9]+\.[0-9]+(?:-(?:[0-9]+|beta\.[0-9]+))?)$/u.test(spec)
|
!/^openclaw@(?:alpha|beta|latest|[0-9]{4}\.[0-9]+\.[0-9]+(?:-(?:[0-9]+|alpha\.[0-9]+|beta\.[0-9]+))?)$/u.test(
|
||||||
|
spec,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`invalid published upgrade survivor baseline: ${JSON.stringify(
|
`invalid published upgrade survivor baseline: ${JSON.stringify(
|
||||||
value,
|
value,
|
||||||
)}. Expected openclaw@latest, openclaw@beta, or openclaw@YYYY.M.D.`,
|
)}. Expected openclaw@latest, openclaw@beta, openclaw@alpha, or openclaw@YYYY.M.D.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return spec;
|
return spec;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const IOS_VERSION_XCCONFIG_FILE = "apps/ios/Config/Version.xcconfig";
|
|||||||
const IOS_RELEASE_NOTES_FILE = "apps/ios/fastlane/metadata/en-US/release_notes.txt";
|
const IOS_RELEASE_NOTES_FILE = "apps/ios/fastlane/metadata/en-US/release_notes.txt";
|
||||||
|
|
||||||
const PINNED_IOS_VERSION_PATTERN = /^(\d{4}\.\d{1,2}\.\d{1,2})$/u;
|
const PINNED_IOS_VERSION_PATTERN = /^(\d{4}\.\d{1,2}\.\d{1,2})$/u;
|
||||||
const GATEWAY_VERSION_PATTERN = /^(\d{4}\.\d{1,2}\.\d{1,2})(?:-(?:beta\.\d+|\d+))?$/u;
|
const GATEWAY_VERSION_PATTERN = /^(\d{4}\.\d{1,2}\.\d{1,2})(?:-(?:alpha\.\d+|beta\.\d+|\d+))?$/u;
|
||||||
|
|
||||||
type IosVersionManifest = {
|
type IosVersionManifest = {
|
||||||
version: string;
|
version: string;
|
||||||
@@ -52,7 +52,7 @@ export function normalizeGatewayVersionToPinnedIosVersion(rawVersion: string): s
|
|||||||
const match = GATEWAY_VERSION_PATTERN.exec(trimmed);
|
const match = GATEWAY_VERSION_PATTERN.exec(trimmed);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid gateway version '${rawVersion}'. Expected YYYY.M.D, YYYY.M.D-beta.N, or YYYY.M.D-N.`,
|
`Invalid gateway version '${rawVersion}'. Expected YYYY.M.D, YYYY.M.D-alpha.N, YYYY.M.D-beta.N, or YYYY.M.D-N.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
const STABLE_VERSION_REGEX = /^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<day>[1-9]\d?)$/;
|
const STABLE_VERSION_REGEX = /^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<day>[1-9]\d?)$/;
|
||||||
|
const ALPHA_VERSION_REGEX =
|
||||||
|
/^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<day>[1-9]\d?)-alpha\.(?<alpha>[1-9]\d*)$/;
|
||||||
const BETA_VERSION_REGEX =
|
const BETA_VERSION_REGEX =
|
||||||
/^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<day>[1-9]\d?)-beta\.(?<beta>[1-9]\d*)$/;
|
/^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<day>[1-9]\d?)-beta\.(?<beta>[1-9]\d*)$/;
|
||||||
const CORRECTION_VERSION_REGEX =
|
const CORRECTION_VERSION_REGEX =
|
||||||
@@ -8,10 +10,11 @@ const CORRECTION_VERSION_REGEX =
|
|||||||
* @typedef {object} ParsedReleaseVersion
|
* @typedef {object} ParsedReleaseVersion
|
||||||
* @property {string} version
|
* @property {string} version
|
||||||
* @property {string} baseVersion
|
* @property {string} baseVersion
|
||||||
* @property {"stable" | "beta"} channel
|
* @property {"stable" | "alpha" | "beta"} channel
|
||||||
* @property {number} year
|
* @property {number} year
|
||||||
* @property {number} month
|
* @property {number} month
|
||||||
* @property {number} day
|
* @property {number} day
|
||||||
|
* @property {number | undefined} [alphaNumber]
|
||||||
* @property {number | undefined} [betaNumber]
|
* @property {number | undefined} [betaNumber]
|
||||||
* @property {number | undefined} [correctionNumber]
|
* @property {number | undefined} [correctionNumber]
|
||||||
* @property {Date} date
|
* @property {Date} date
|
||||||
@@ -19,9 +22,9 @@ const CORRECTION_VERSION_REGEX =
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} NpmPublishPlan
|
* @typedef {object} NpmPublishPlan
|
||||||
* @property {"stable" | "beta"} channel
|
* @property {"stable" | "alpha" | "beta"} channel
|
||||||
* @property {"latest" | "beta"} publishTag
|
* @property {"latest" | "alpha" | "beta"} publishTag
|
||||||
* @property {("latest" | "beta")[]} mirrorDistTags
|
* @property {("latest" | "alpha" | "beta")[]} mirrorDistTags
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,13 +40,14 @@ const CORRECTION_VERSION_REGEX =
|
|||||||
/**
|
/**
|
||||||
* @param {string} version
|
* @param {string} version
|
||||||
* @param {Record<string, string | undefined>} groups
|
* @param {Record<string, string | undefined>} groups
|
||||||
* @param {"stable" | "beta"} channel
|
* @param {"stable" | "alpha" | "beta"} channel
|
||||||
* @returns {ParsedReleaseVersion | null}
|
* @returns {ParsedReleaseVersion | null}
|
||||||
*/
|
*/
|
||||||
function parseDateParts(version, groups, channel) {
|
function parseDateParts(version, groups, channel) {
|
||||||
const year = Number.parseInt(groups.year ?? "", 10);
|
const year = Number.parseInt(groups.year ?? "", 10);
|
||||||
const month = Number.parseInt(groups.month ?? "", 10);
|
const month = Number.parseInt(groups.month ?? "", 10);
|
||||||
const day = Number.parseInt(groups.day ?? "", 10);
|
const day = Number.parseInt(groups.day ?? "", 10);
|
||||||
|
const alphaNumber = channel === "alpha" ? Number.parseInt(groups.alpha ?? "", 10) : undefined;
|
||||||
const betaNumber = channel === "beta" ? Number.parseInt(groups.beta ?? "", 10) : undefined;
|
const betaNumber = channel === "beta" ? Number.parseInt(groups.beta ?? "", 10) : undefined;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -60,6 +64,9 @@ function parseDateParts(version, groups, channel) {
|
|||||||
if (channel === "beta" && (!Number.isInteger(betaNumber) || (betaNumber ?? 0) < 1)) {
|
if (channel === "beta" && (!Number.isInteger(betaNumber) || (betaNumber ?? 0) < 1)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (channel === "alpha" && (!Number.isInteger(alphaNumber) || (alphaNumber ?? 0) < 1)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const date = new Date(Date.UTC(year, month - 1, day));
|
const date = new Date(Date.UTC(year, month - 1, day));
|
||||||
if (
|
if (
|
||||||
@@ -77,6 +84,7 @@ function parseDateParts(version, groups, channel) {
|
|||||||
year,
|
year,
|
||||||
month,
|
month,
|
||||||
day,
|
day,
|
||||||
|
alphaNumber,
|
||||||
betaNumber,
|
betaNumber,
|
||||||
date,
|
date,
|
||||||
};
|
};
|
||||||
@@ -97,6 +105,11 @@ export function parseReleaseVersion(version) {
|
|||||||
return parseDateParts(trimmed, stableMatch.groups, "stable");
|
return parseDateParts(trimmed, stableMatch.groups, "stable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const alphaMatch = ALPHA_VERSION_REGEX.exec(trimmed);
|
||||||
|
if (alphaMatch?.groups) {
|
||||||
|
return parseDateParts(trimmed, alphaMatch.groups, "alpha");
|
||||||
|
}
|
||||||
|
|
||||||
const betaMatch = BETA_VERSION_REGEX.exec(trimmed);
|
const betaMatch = BETA_VERSION_REGEX.exec(trimmed);
|
||||||
if (betaMatch?.groups) {
|
if (betaMatch?.groups) {
|
||||||
return parseDateParts(trimmed, betaMatch.groups, "beta");
|
return parseDateParts(trimmed, betaMatch.groups, "beta");
|
||||||
@@ -137,7 +150,12 @@ export function compareReleaseVersions(left, right) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsedLeft.channel !== parsedRight.channel) {
|
if (parsedLeft.channel !== parsedRight.channel) {
|
||||||
return parsedLeft.channel === "stable" ? 1 : -1;
|
const rank = { alpha: 0, beta: 1, stable: 2 };
|
||||||
|
return Math.sign(rank[parsedLeft.channel] - rank[parsedRight.channel]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedLeft.channel === "alpha" && parsedRight.channel === "alpha") {
|
||||||
|
return Math.sign((parsedLeft.alphaNumber ?? 0) - (parsedRight.alphaNumber ?? 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedLeft.channel === "beta" && parsedRight.channel === "beta") {
|
if (parsedLeft.channel === "beta" && parsedRight.channel === "beta") {
|
||||||
@@ -165,6 +183,13 @@ export function resolveNpmPublishPlan(version, currentBetaVersion) {
|
|||||||
mirrorDistTags: [],
|
mirrorDistTags: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (parsedVersion.channel === "alpha") {
|
||||||
|
return {
|
||||||
|
channel: "alpha",
|
||||||
|
publishTag: "alpha",
|
||||||
|
mirrorDistTags: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const normalizedCurrentBeta = currentBetaVersion?.trim();
|
const normalizedCurrentBeta = currentBetaVersion?.trim();
|
||||||
if (normalizedCurrentBeta) {
|
if (normalizedCurrentBeta) {
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export type PublishablePluginPackage = {
|
|||||||
packageDir: string;
|
packageDir: string;
|
||||||
packageName: string;
|
packageName: string;
|
||||||
version: string;
|
version: string;
|
||||||
channel: "stable" | "beta";
|
channel: "stable" | "alpha" | "beta";
|
||||||
publishTag: "latest" | "beta";
|
publishTag: "latest" | "alpha" | "beta";
|
||||||
};
|
};
|
||||||
|
|
||||||
type PluginReleasePlanItem = PublishablePluginPackage & {
|
type PluginReleasePlanItem = PublishablePluginPackage & {
|
||||||
@@ -154,7 +154,12 @@ export function collectClawHubPublishablePluginPackages(
|
|||||||
packageName,
|
packageName,
|
||||||
version,
|
version,
|
||||||
channel: parsedVersion.channel,
|
channel: parsedVersion.channel,
|
||||||
publishTag: parsedVersion.channel === "beta" ? "beta" : "latest",
|
publishTag:
|
||||||
|
parsedVersion.channel === "alpha"
|
||||||
|
? "alpha"
|
||||||
|
: parsedVersion.channel === "beta"
|
||||||
|
? "beta"
|
||||||
|
: "latest",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ export type PublishablePluginPackage = {
|
|||||||
packageDir: string;
|
packageDir: string;
|
||||||
packageName: string;
|
packageName: string;
|
||||||
version: string;
|
version: string;
|
||||||
channel: "stable" | "beta";
|
channel: "stable" | "alpha" | "beta";
|
||||||
publishTag: "latest" | "beta";
|
publishTag: "latest" | "alpha" | "beta";
|
||||||
installNpmSpec?: string;
|
installNpmSpec?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ export function resolvePublishablePluginVersion(params: {
|
|||||||
const parsedVersion = parseReleaseVersion(version);
|
const parsedVersion = parseReleaseVersion(version);
|
||||||
if (parsedVersion === null) {
|
if (parsedVersion === null) {
|
||||||
params.validationErrors.push(
|
params.validationErrors.push(
|
||||||
`${params.extensionId}: package.json version must match YYYY.M.D, YYYY.M.D-N, or YYYY.M.D-beta.N; found "${version}".`,
|
`${params.extensionId}: package.json version must match YYYY.M.D, YYYY.M.D-N, YYYY.M.D-alpha.N, or YYYY.M.D-beta.N; found "${version}".`,
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,7 @@ export function collectPublishablePluginPackageErrors(
|
|||||||
errors.push("package.json version must be non-empty.");
|
errors.push("package.json version must be non-empty.");
|
||||||
} else if (parseReleaseVersion(packageVersion) === null) {
|
} else if (parseReleaseVersion(packageVersion) === null) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`package.json version must match YYYY.M.D, YYYY.M.D-N, or YYYY.M.D-beta.N; found "${packageVersion}".`,
|
`package.json version must match YYYY.M.D, YYYY.M.D-N, YYYY.M.D-alpha.N, or YYYY.M.D-beta.N; found "${packageVersion}".`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!Array.isArray(extensions) || extensions.length === 0) {
|
if (!Array.isArray(extensions) || extensions.length === 0) {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ type TelegramQaSummary = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const OPENCLAW_PACKAGE_SPEC_RE =
|
const OPENCLAW_PACKAGE_SPEC_RE =
|
||||||
/^openclaw@(main|beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$/u;
|
/^openclaw@(main|alpha|beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-(alpha|beta)\.[1-9][0-9]*)?)$/u;
|
||||||
|
|
||||||
const REQUIRED_TELEGRAM_ENV = [
|
const REQUIRED_TELEGRAM_ENV = [
|
||||||
"OPENCLAW_QA_TELEGRAM_GROUP_ID",
|
"OPENCLAW_QA_TELEGRAM_GROUP_ID",
|
||||||
@@ -75,7 +75,7 @@ const REQUIRED_TELEGRAM_ENV = [
|
|||||||
export function validateOpenClawPackageSpec(spec: string) {
|
export function validateOpenClawPackageSpec(spec: string) {
|
||||||
if (!OPENCLAW_PACKAGE_SPEC_RE.test(spec)) {
|
if (!OPENCLAW_PACKAGE_SPEC_RE.test(spec)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Package spec must be openclaw@main, openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${spec}`,
|
`Package spec must be openclaw@main, openclaw@alpha, openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${spec}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return spec;
|
return spec;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ ZIP_NAME=$(basename "$ZIP")
|
|||||||
ZIP_BASE="${ZIP_NAME%.zip}"
|
ZIP_BASE="${ZIP_NAME%.zip}"
|
||||||
VERSION=${SPARKLE_RELEASE_VERSION:-}
|
VERSION=${SPARKLE_RELEASE_VERSION:-}
|
||||||
if [[ -z "$VERSION" ]]; then
|
if [[ -z "$VERSION" ]]; then
|
||||||
# Accept legacy calver suffixes like -1 and prerelease forms like -beta.1 / .beta.1.
|
# Accept legacy calver suffixes like -1 and prerelease forms like -alpha.1 / -beta.1 / .beta.1.
|
||||||
if [[ "$ZIP_NAME" =~ ^OpenClaw-([0-9]+(\.[0-9]+){1,2}([-.][0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?)\.zip$ ]]; then
|
if [[ "$ZIP_NAME" =~ ^OpenClaw-([0-9]+(\.[0-9]+){1,2}([-.][0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?)\.zip$ ]]; then
|
||||||
VERSION="${BASH_REMATCH[1]}"
|
VERSION="${BASH_REMATCH[1]}"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ function parseBooleanEnv(name, fallback) {
|
|||||||
|
|
||||||
export function looksLikeReleaseVersionRef(ref) {
|
export function looksLikeReleaseVersionRef(ref) {
|
||||||
const trimmed = normalizeRequestedRef(ref);
|
const trimmed = normalizeRequestedRef(ref);
|
||||||
return /^v?[0-9]{4}\.[0-9]+\.[0-9]+(?:-(?:[1-9][0-9]*)|[-.](?:beta|rc)[-.]?[0-9]+)?$/iu.test(
|
return /^v?[0-9]{4}\.[0-9]+\.[0-9]+(?:-(?:[1-9][0-9]*)|[-.](?:alpha|beta|rc)[-.]?[0-9]+)?$/iu.test(
|
||||||
trimmed,
|
trimmed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ mapfile -t publish_plan < <(
|
|||||||
import { resolveNpmPublishPlan } from "./scripts/openclaw-npm-release-check.ts";
|
import { resolveNpmPublishPlan } from "./scripts/openclaw-npm-release-check.ts";
|
||||||
|
|
||||||
const requestedPublishTag =
|
const requestedPublishTag =
|
||||||
process.env.REQUESTED_PUBLISH_TAG === "latest" ? "latest" : "beta";
|
process.env.REQUESTED_PUBLISH_TAG === "latest"
|
||||||
|
? "latest"
|
||||||
|
: process.env.REQUESTED_PUBLISH_TAG === "alpha"
|
||||||
|
? "alpha"
|
||||||
|
: "beta";
|
||||||
const plan = resolveNpmPublishPlan(process.env.PACKAGE_VERSION ?? "", undefined, requestedPublishTag);
|
const plan = resolveNpmPublishPlan(process.env.PACKAGE_VERSION ?? "", undefined, requestedPublishTag);
|
||||||
console.log(plan.channel);
|
console.log(plan.channel);
|
||||||
console.log(plan.publishTag);
|
console.log(plan.publishTag);
|
||||||
|
|||||||
@@ -32,10 +32,11 @@ type PackageJson = {
|
|||||||
export type ParsedReleaseVersion = {
|
export type ParsedReleaseVersion = {
|
||||||
version: string;
|
version: string;
|
||||||
baseVersion: string;
|
baseVersion: string;
|
||||||
channel: "stable" | "beta";
|
channel: "stable" | "alpha" | "beta";
|
||||||
year: number;
|
year: number;
|
||||||
month: number;
|
month: number;
|
||||||
day: number;
|
day: number;
|
||||||
|
alphaNumber?: number;
|
||||||
betaNumber?: number;
|
betaNumber?: number;
|
||||||
correctionNumber?: number;
|
correctionNumber?: number;
|
||||||
date: Date;
|
date: Date;
|
||||||
@@ -45,15 +46,15 @@ export type ParsedReleaseTag = {
|
|||||||
version: string;
|
version: string;
|
||||||
packageVersion: string;
|
packageVersion: string;
|
||||||
baseVersion: string;
|
baseVersion: string;
|
||||||
channel: "stable" | "beta";
|
channel: "stable" | "alpha" | "beta";
|
||||||
correctionNumber?: number;
|
correctionNumber?: number;
|
||||||
date: Date;
|
date: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NpmPublishPlan = {
|
export type NpmPublishPlan = {
|
||||||
channel: "stable" | "beta";
|
channel: "stable" | "alpha" | "beta";
|
||||||
publishTag: "latest" | "beta";
|
publishTag: "latest" | "alpha" | "beta";
|
||||||
mirrorDistTags: ("latest" | "beta")[];
|
mirrorDistTags: ("latest" | "alpha" | "beta")[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NpmDistTagMirrorAuth = {
|
export type NpmDistTagMirrorAuth = {
|
||||||
@@ -193,14 +194,30 @@ export function compareReleaseVersions(left: string, right: string): number | nu
|
|||||||
export function resolveNpmPublishPlan(
|
export function resolveNpmPublishPlan(
|
||||||
version: string,
|
version: string,
|
||||||
_currentBetaVersion?: string | null,
|
_currentBetaVersion?: string | null,
|
||||||
requestedPublishTag?: "latest" | "beta" | null,
|
requestedPublishTag?: "latest" | "alpha" | "beta" | null,
|
||||||
): NpmPublishPlan {
|
): NpmPublishPlan {
|
||||||
const parsedVersion = parseReleaseVersion(version);
|
const parsedVersion = parseReleaseVersion(version);
|
||||||
if (parsedVersion === null) {
|
if (parsedVersion === null) {
|
||||||
throw new Error(`Unsupported release version "${version}".`);
|
throw new Error(`Unsupported release version "${version}".`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishTag = requestedPublishTag?.trim() === "latest" ? "latest" : "beta";
|
const publishTag =
|
||||||
|
requestedPublishTag?.trim() === "latest"
|
||||||
|
? "latest"
|
||||||
|
: requestedPublishTag?.trim() === "alpha"
|
||||||
|
? "alpha"
|
||||||
|
: "beta";
|
||||||
|
|
||||||
|
if (parsedVersion.channel === "alpha") {
|
||||||
|
if (publishTag !== "alpha") {
|
||||||
|
throw new Error("Alpha prereleases must publish to the alpha dist-tag.");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
channel: "alpha",
|
||||||
|
publishTag: "alpha",
|
||||||
|
mirrorDistTags: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (parsedVersion.channel === "beta") {
|
if (parsedVersion.channel === "beta") {
|
||||||
if (publishTag !== "beta") {
|
if (publishTag !== "beta") {
|
||||||
@@ -336,7 +353,7 @@ export function collectReleaseTagErrors(params: {
|
|||||||
const parsedVersion = parseReleaseVersion(packageVersion);
|
const parsedVersion = parseReleaseVersion(packageVersion);
|
||||||
if (parsedVersion === null) {
|
if (parsedVersion === null) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`package.json version must match YYYY.M.D, YYYY.M.D-N, or YYYY.M.D-beta.N; found "${packageVersion || "<missing>"}".`,
|
`package.json version must match YYYY.M.D, YYYY.M.D-N, YYYY.M.D-alpha.N, or YYYY.M.D-beta.N; found "${packageVersion || "<missing>"}".`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +365,7 @@ export function collectReleaseTagErrors(params: {
|
|||||||
const parsedTag = parseReleaseTagVersion(tagVersion);
|
const parsedTag = parseReleaseTagVersion(tagVersion);
|
||||||
if (parsedTag === null) {
|
if (parsedTag === null) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`Release tag must match vYYYY.M.D, vYYYY.M.D-beta.N, or fallback correction tag vYYYY.M.D-N; found "${releaseTag || "<missing>"}".`,
|
`Release tag must match vYYYY.M.D, vYYYY.M.D-alpha.N, vYYYY.M.D-beta.N, or fallback correction tag vYYYY.M.D-N; found "${releaseTag || "<missing>"}".`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { fileURLToPath } from "node:url";
|
|||||||
const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
const DEFAULT_OUTPUT_NAME = "openclaw-current.tgz";
|
const DEFAULT_OUTPUT_NAME = "openclaw-current.tgz";
|
||||||
export const OPENCLAW_PACKAGE_SPEC_RE =
|
export const OPENCLAW_PACKAGE_SPEC_RE =
|
||||||
/^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$/u;
|
/^openclaw@(alpha|beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-(alpha|beta)\.[1-9][0-9]*)?)$/u;
|
||||||
|
|
||||||
function usage() {
|
function usage() {
|
||||||
return `Usage: node scripts/resolve-openclaw-package-candidate.mjs --source <ref|npm|url|artifact> --output-dir <dir> [options]
|
return `Usage: node scripts/resolve-openclaw-package-candidate.mjs --source <ref|npm|url|artifact> --output-dir <dir> [options]
|
||||||
@@ -82,7 +82,7 @@ export function parseArgs(argv) {
|
|||||||
export function validateOpenClawPackageSpec(spec) {
|
export function validateOpenClawPackageSpec(spec) {
|
||||||
if (!OPENCLAW_PACKAGE_SPEC_RE.test(spec)) {
|
if (!OPENCLAW_PACKAGE_SPEC_RE.test(spec)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`package_spec must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${spec}`,
|
`package_spec must be openclaw@alpha, openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${spec}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ describe("shouldRequireNpmDistTagMirrorAuth", () => {
|
|||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("publishes alpha prereleases without dist-tag mirroring", () => {
|
||||||
|
const plan = resolveNpmPublishPlan("2026.4.1-alpha.1");
|
||||||
|
|
||||||
|
expect(plan).toEqual({
|
||||||
|
channel: "alpha",
|
||||||
|
publishTag: "alpha",
|
||||||
|
mirrorDistTags: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("does not require auth when a publish already has npm auth", () => {
|
it("does not require auth when a publish already has npm auth", () => {
|
||||||
const plan = resolveNpmPublishPlan("2026.4.1");
|
const plan = resolveNpmPublishPlan("2026.4.1");
|
||||||
const auth = resolveNpmDistTagMirrorAuth({ npmToken: "token" });
|
const auth = resolveNpmDistTagMirrorAuth({ npmToken: "token" });
|
||||||
|
|||||||
@@ -54,6 +54,18 @@ describe("parseReleaseVersion", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("parses alpha CalVer releases", () => {
|
||||||
|
expect(parseReleaseVersion("2026.3.10-alpha.2")).toMatchObject({
|
||||||
|
version: "2026.3.10-alpha.2",
|
||||||
|
baseVersion: "2026.3.10",
|
||||||
|
channel: "alpha",
|
||||||
|
year: 2026,
|
||||||
|
month: 3,
|
||||||
|
day: 10,
|
||||||
|
alphaNumber: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("parses stable correction releases", () => {
|
it("parses stable correction releases", () => {
|
||||||
expect(parseReleaseVersion("2026.3.10-1")).toMatchObject({
|
expect(parseReleaseVersion("2026.3.10-1")).toMatchObject({
|
||||||
version: "2026.3.10-1",
|
version: "2026.3.10-1",
|
||||||
@@ -101,6 +113,14 @@ describe("resolveNpmPublishPlan", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("publishes alpha prereleases to alpha only", () => {
|
||||||
|
expect(resolveNpmPublishPlan("2026.3.29-alpha.2", undefined, "alpha")).toEqual({
|
||||||
|
channel: "alpha",
|
||||||
|
publishTag: "alpha",
|
||||||
|
mirrorDistTags: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("publishes stable releases to beta first", () => {
|
it("publishes stable releases to beta first", () => {
|
||||||
expect(resolveNpmPublishPlan("2026.3.29")).toEqual({
|
expect(resolveNpmPublishPlan("2026.3.29")).toEqual({
|
||||||
channel: "stable",
|
channel: "stable",
|
||||||
@@ -138,6 +158,15 @@ describe("resolveNpmPublishPlan", () => {
|
|||||||
"Beta prereleases must publish to the beta dist-tag.",
|
"Beta prereleases must publish to the beta dist-tag.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects publishing alpha prereleases to beta or latest", () => {
|
||||||
|
expect(() => resolveNpmPublishPlan("2026.3.29-alpha.2")).toThrow(
|
||||||
|
"Alpha prereleases must publish to the alpha dist-tag.",
|
||||||
|
);
|
||||||
|
expect(() => resolveNpmPublishPlan("2026.3.29-alpha.2", undefined, "latest")).toThrow(
|
||||||
|
"Alpha prereleases must publish to the alpha dist-tag.",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("resolveNpmDistTagMirrorAuth", () => {
|
describe("resolveNpmDistTagMirrorAuth", () => {
|
||||||
@@ -205,6 +234,10 @@ describe("compareReleaseVersions", () => {
|
|||||||
expect(compareReleaseVersions("2026.3.29", "2026.3.29-beta.2")).toBe(1);
|
expect(compareReleaseVersions("2026.3.29", "2026.3.29-beta.2")).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("orders alpha before beta on the same day", () => {
|
||||||
|
expect(compareReleaseVersions("2026.3.29-alpha.2", "2026.3.29-beta.1")).toBe(-1);
|
||||||
|
});
|
||||||
|
|
||||||
it("treats a newer beta day as newer than an older stable day", () => {
|
it("treats a newer beta day as newer than an older stable day", () => {
|
||||||
expect(compareReleaseVersions("2026.4.1-beta.1", "2026.3.29")).toBe(1);
|
expect(compareReleaseVersions("2026.4.1-beta.1", "2026.3.29")).toBe(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ describe("collectPublishablePluginPackageErrors", () => {
|
|||||||
'package name must start with "@openclaw/"; found "broken".',
|
'package name must start with "@openclaw/"; found "broken".',
|
||||||
"package.json private must not be true.",
|
"package.json private must not be true.",
|
||||||
`package.json repository.url must be "${OPENCLAW_PLUGIN_NPM_REPOSITORY_URL}" so npm provenance can validate GitHub trusted publishing; found "<missing>".`,
|
`package.json repository.url must be "${OPENCLAW_PLUGIN_NPM_REPOSITORY_URL}" so npm provenance can validate GitHub trusted publishing; found "<missing>".`,
|
||||||
'package.json version must match YYYY.M.D, YYYY.M.D-N, or YYYY.M.D-beta.N; found "latest".',
|
'package.json version must match YYYY.M.D, YYYY.M.D-N, YYYY.M.D-alpha.N, or YYYY.M.D-beta.N; found "latest".',
|
||||||
"openclaw.extensions must contain only non-empty strings.",
|
"openclaw.extensions must contain only non-empty strings.",
|
||||||
"openclaw.install.npmSpec must be a non-empty string for publishable plugins.",
|
"openclaw.install.npmSpec must be a non-empty string for publishable plugins.",
|
||||||
]);
|
]);
|
||||||
@@ -314,6 +314,37 @@ describe("collectPublishablePluginPackages", () => {
|
|||||||
}),
|
}),
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("publishes alpha plugin packages to the alpha dist-tag", () => {
|
||||||
|
const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-");
|
||||||
|
mkdirSync(join(repoDir, "extensions", "demo-plugin"), { recursive: true });
|
||||||
|
writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), {
|
||||||
|
name: "@openclaw/demo-plugin",
|
||||||
|
version: "2026.4.10-alpha.1",
|
||||||
|
repository: {
|
||||||
|
type: "git",
|
||||||
|
url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL,
|
||||||
|
},
|
||||||
|
openclaw: {
|
||||||
|
extensions: ["./index.ts"],
|
||||||
|
install: {
|
||||||
|
npmSpec: "@openclaw/demo-plugin",
|
||||||
|
},
|
||||||
|
release: {
|
||||||
|
publishToNpm: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(collectPublishablePluginPackages(repoDir)).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
channel: "alpha",
|
||||||
|
packageName: "@openclaw/demo-plugin",
|
||||||
|
publishTag: "alpha",
|
||||||
|
version: "2026.4.10-alpha.1",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("resolveSelectedPublishablePluginPackages", () => {
|
describe("resolveSelectedPublishablePluginPackages", () => {
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ describe("gateway version normalization", () => {
|
|||||||
expect(normalizeGatewayVersionToPinnedIosVersion("2026.4.6-beta.2")).toBe("2026.4.6");
|
expect(normalizeGatewayVersionToPinnedIosVersion("2026.4.6-beta.2")).toBe("2026.4.6");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("strips alpha suffixes when pinning from gateway version", () => {
|
||||||
|
expect(normalizeGatewayVersionToPinnedIosVersion("2026.4.6-alpha.2")).toBe("2026.4.6");
|
||||||
|
});
|
||||||
|
|
||||||
it("strips fallback correction suffixes when pinning from gateway version", () => {
|
it("strips fallback correction suffixes when pinning from gateway version", () => {
|
||||||
expect(normalizeGatewayVersionToPinnedIosVersion("2026.4.6-3")).toBe("2026.4.6");
|
expect(normalizeGatewayVersionToPinnedIosVersion("2026.4.6-3")).toBe("2026.4.6");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -233,6 +233,8 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
|||||||
expect(looksLikeReleaseVersionRef("2026.4.5")).toBe(true);
|
expect(looksLikeReleaseVersionRef("2026.4.5")).toBe(true);
|
||||||
expect(looksLikeReleaseVersionRef("refs/tags/v2026.4.5-beta.1")).toBe(true);
|
expect(looksLikeReleaseVersionRef("refs/tags/v2026.4.5-beta.1")).toBe(true);
|
||||||
expect(looksLikeReleaseVersionRef("v2026.4.5-beta.1")).toBe(true);
|
expect(looksLikeReleaseVersionRef("v2026.4.5-beta.1")).toBe(true);
|
||||||
|
expect(looksLikeReleaseVersionRef("refs/tags/v2026.4.5-alpha.1")).toBe(true);
|
||||||
|
expect(looksLikeReleaseVersionRef("v2026.4.5-alpha.1")).toBe(true);
|
||||||
expect(looksLikeReleaseVersionRef("v2026.4.7-1")).toBe(true);
|
expect(looksLikeReleaseVersionRef("v2026.4.7-1")).toBe(true);
|
||||||
expect(looksLikeReleaseVersionRef("main")).toBe(false);
|
expect(looksLikeReleaseVersionRef("main")).toBe(false);
|
||||||
expect(looksLikeReleaseVersionRef("codex/cross-os-release-checks")).toBe(false);
|
expect(looksLikeReleaseVersionRef("codex/cross-os-release-checks")).toBe(false);
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ describe("package artifact reuse", () => {
|
|||||||
expect(scheduler).toContain('["OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS",');
|
expect(scheduler).toContain('["OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS",');
|
||||||
expect(packageJson).toContain("OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1");
|
expect(packageJson).toContain("OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE=1");
|
||||||
expect(publishedUpgradeSurvivor).toContain("validate_baseline_package_spec");
|
expect(publishedUpgradeSurvivor).toContain("validate_baseline_package_spec");
|
||||||
expect(publishedUpgradeSurvivor).toContain("openclaw@(beta|latest|");
|
expect(publishedUpgradeSurvivor).toContain("openclaw@(alpha|beta|latest|");
|
||||||
expect(publishedUpgradeSurvivor).toContain("plugin_deps_cleanup_plugin_dirs");
|
expect(publishedUpgradeSurvivor).toContain("plugin_deps_cleanup_plugin_dirs");
|
||||||
expect(publishedUpgradeSurvivor).toContain('"$(package_root)/extensions/$plugin"');
|
expect(publishedUpgradeSurvivor).toContain('"$(package_root)/extensions/$plugin"');
|
||||||
expect(publishedUpgradeSurvivor).toContain("probe_gateway_endpoint");
|
expect(publishedUpgradeSurvivor).toContain("probe_gateway_endpoint");
|
||||||
@@ -623,7 +623,7 @@ describe("package artifact reuse", () => {
|
|||||||
});
|
});
|
||||||
expectTextToIncludeAll(validateStep.run, [
|
expectTextToIncludeAll(validateStep.run, [
|
||||||
'if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then',
|
'if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then',
|
||||||
"package_spec must be openclaw@beta",
|
"package_spec must be openclaw@alpha",
|
||||||
]);
|
]);
|
||||||
expectTextToIncludeAll(runStep.run, [
|
expectTextToIncludeAll(runStep.run, [
|
||||||
'export OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ="${package_tgzs[0]}"',
|
'export OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ="${package_tgzs[0]}"',
|
||||||
|
|||||||
@@ -11,25 +11,27 @@ import {
|
|||||||
describe("resolve-openclaw-package-candidate", () => {
|
describe("resolve-openclaw-package-candidate", () => {
|
||||||
it("accepts only OpenClaw release package specs for npm candidates", () => {
|
it("accepts only OpenClaw release package specs for npm candidates", () => {
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@beta")).not.toThrow();
|
expect(() => validateOpenClawPackageSpec("openclaw@beta")).not.toThrow();
|
||||||
|
expect(() => validateOpenClawPackageSpec("openclaw@alpha")).not.toThrow();
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@latest")).not.toThrow();
|
expect(() => validateOpenClawPackageSpec("openclaw@latest")).not.toThrow();
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@2026.4.27")).not.toThrow();
|
expect(() => validateOpenClawPackageSpec("openclaw@2026.4.27")).not.toThrow();
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@2026.4.27-1")).not.toThrow();
|
expect(() => validateOpenClawPackageSpec("openclaw@2026.4.27-1")).not.toThrow();
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@2026.4.27-beta.2")).not.toThrow();
|
expect(() => validateOpenClawPackageSpec("openclaw@2026.4.27-beta.2")).not.toThrow();
|
||||||
|
expect(() => validateOpenClawPackageSpec("openclaw@2026.4.27-alpha.2")).not.toThrow();
|
||||||
|
|
||||||
expect(() => validateOpenClawPackageSpec("@evil/openclaw@1.0.0")).toThrow(
|
expect(() => validateOpenClawPackageSpec("@evil/openclaw@1.0.0")).toThrow(
|
||||||
"package_spec must be openclaw@beta",
|
"package_spec must be openclaw@alpha",
|
||||||
);
|
);
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@canary")).toThrow(
|
expect(() => validateOpenClawPackageSpec("openclaw@canary")).toThrow(
|
||||||
"package_spec must be openclaw@beta",
|
"package_spec must be openclaw@alpha",
|
||||||
);
|
);
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@2026.04.27")).toThrow(
|
expect(() => validateOpenClawPackageSpec("openclaw@2026.04.27")).toThrow(
|
||||||
"package_spec must be openclaw@beta",
|
"package_spec must be openclaw@alpha",
|
||||||
);
|
);
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@npm:other-package")).toThrow(
|
expect(() => validateOpenClawPackageSpec("openclaw@npm:other-package")).toThrow(
|
||||||
"package_spec must be openclaw@beta",
|
"package_spec must be openclaw@alpha",
|
||||||
);
|
);
|
||||||
expect(() => validateOpenClawPackageSpec("openclaw@file:../other-package.tgz")).toThrow(
|
expect(() => validateOpenClawPackageSpec("openclaw@file:../other-package.tgz")).toThrow(
|
||||||
"package_spec must be openclaw@beta",
|
"package_spec must be openclaw@alpha",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,16 @@ const FIXTURE_PATH = path.resolve(TEST_DIR, "../fixtures/telegram-qa-summary-rtt
|
|||||||
describe("RTT harness", () => {
|
describe("RTT harness", () => {
|
||||||
it("validates OpenClaw package specs", () => {
|
it("validates OpenClaw package specs", () => {
|
||||||
expect(validateOpenClawPackageSpec("openclaw@main")).toBe("openclaw@main");
|
expect(validateOpenClawPackageSpec("openclaw@main")).toBe("openclaw@main");
|
||||||
|
expect(validateOpenClawPackageSpec("openclaw@alpha")).toBe("openclaw@alpha");
|
||||||
expect(validateOpenClawPackageSpec("openclaw@beta")).toBe("openclaw@beta");
|
expect(validateOpenClawPackageSpec("openclaw@beta")).toBe("openclaw@beta");
|
||||||
expect(validateOpenClawPackageSpec("openclaw@latest")).toBe("openclaw@latest");
|
expect(validateOpenClawPackageSpec("openclaw@latest")).toBe("openclaw@latest");
|
||||||
expect(validateOpenClawPackageSpec("openclaw@2026.4.30")).toBe("openclaw@2026.4.30");
|
expect(validateOpenClawPackageSpec("openclaw@2026.4.30")).toBe("openclaw@2026.4.30");
|
||||||
expect(validateOpenClawPackageSpec("openclaw@2026.4.30-beta.2")).toBe(
|
expect(validateOpenClawPackageSpec("openclaw@2026.4.30-beta.2")).toBe(
|
||||||
"openclaw@2026.4.30-beta.2",
|
"openclaw@2026.4.30-beta.2",
|
||||||
);
|
);
|
||||||
|
expect(validateOpenClawPackageSpec("openclaw@2026.4.30-alpha.2")).toBe(
|
||||||
|
"openclaw@2026.4.30-alpha.2",
|
||||||
|
);
|
||||||
|
|
||||||
expect(() => validateOpenClawPackageSpec("@openclaw/openclaw@beta")).toThrow(
|
expect(() => validateOpenClawPackageSpec("@openclaw/openclaw@beta")).toThrow(
|
||||||
/Package spec must be/,
|
/Package spec must be/,
|
||||||
|
|||||||
Reference in New Issue
Block a user