release: forward-port extended stable pipeline

This commit is contained in:
Dallin Romney
2026-07-13 02:09:50 -07:00
parent f079f2d758
commit ee8a29eec7
130 changed files with 29370 additions and 1596 deletions
+49 -3
View File
@@ -1,4 +1,5 @@
name: Full Release Validation
run-name: ${{ inputs.dispatch_correlation_id || github.workflow }}
on:
workflow_dispatch:
@@ -8,6 +9,16 @@ on:
required: true
default: main
type: string
dispatch_correlation_id:
description: Optional unique dispatch correlation used by release orchestration
required: false
default: ""
type: string
target_context_ref:
description: Optional canonical release branch or tag context for an exact-SHA target
required: false
default: ""
type: string
provider:
description: Provider lane for cross-OS onboarding and the end-to-end agent turn
required: false
@@ -1082,7 +1093,8 @@ jobs:
-f repeat=3 \
-f deep_profile=false \
-f live_openai_candidate=false \
-f fail_on_regression=true
-f fail_on_regression=true \
-f publish_reports=false
run_id=""
for _ in $(seq 1 60); do
@@ -1593,6 +1605,14 @@ jobs:
NPM_TELEGRAM_RUN_ID: ${{ needs.npm_telegram.outputs.run_id }}
PERFORMANCE_RUN_ID: ${{ needs.performance.outputs.run_id }}
PERFORMANCE_CONCLUSION: ${{ needs.performance.outputs.conclusion }}
PROVIDER: ${{ inputs.provider }}
MODE: ${{ inputs.mode }}
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
run: |
set -euo pipefail
manifest_dir="${RUNNER_TEMP}/full-release-validation"
@@ -1602,6 +1622,9 @@ jobs:
--arg runId "$GITHUB_RUN_ID" \
--arg runAttempt "$GITHUB_RUN_ATTEMPT" \
--arg workflowRef "$GITHUB_REF_NAME" \
--arg workflowSha "$GITHUB_SHA" \
--arg workflowFullRef "$GITHUB_REF" \
--arg workflowRefType "$GITHUB_REF_TYPE" \
--arg targetRef "$TARGET_REF" \
--arg targetSha "$TARGET_SHA" \
--arg releaseProfile "$RELEASE_PROFILE" \
@@ -1613,20 +1636,42 @@ jobs:
--arg npmTelegramRunId "$NPM_TELEGRAM_RUN_ID" \
--arg performanceRunId "$PERFORMANCE_RUN_ID" \
--arg performanceConclusion "$PERFORMANCE_CONCLUSION" \
--arg provider "$PROVIDER" \
--arg mode "$MODE" \
--arg targetContextRef "$TARGET_CONTEXT_REF" \
--arg liveSuiteFilter "$LIVE_SUITE_FILTER" \
--arg crossOsSuiteFilter "$CROSS_OS_SUITE_FILTER" \
--arg releasePackageSpec "$RELEASE_PACKAGE_SPEC" \
--arg packageAcceptancePackageSpec "$PACKAGE_ACCEPTANCE_PACKAGE_SPEC" \
--arg codexPluginSpec "$CODEX_PLUGIN_SPEC" \
'{
version: 2,
version: 3,
workflowName: $workflowName,
runId: $runId,
runAttempt: $runAttempt,
workflowRef: $workflowRef,
workflowSha: $workflowSha,
workflowFullRef: $workflowFullRef,
workflowRefType: $workflowRefType,
targetRef: $targetRef,
targetSha: $targetSha,
releaseProfile: $releaseProfile,
rerunGroup: $rerunGroup,
runReleaseSoak: $runReleaseSoak,
validationInputs: {
provider: $provider,
mode: $mode,
targetContextRef: $targetContextRef,
liveSuiteFilter: $liveSuiteFilter,
crossOsSuiteFilter: $crossOsSuiteFilter,
releasePackageSpec: $releasePackageSpec,
packageAcceptancePackageSpec: $packageAcceptancePackageSpec,
codexPluginSpec: $codexPluginSpec
},
controls: {
stableSoakRequired: ($releaseProfile == "stable" or $releaseProfile == "full"),
performanceBlocking: true
performanceBlocking: true,
performanceReportPublication: "artifact-only"
},
childRuns: {
normalCi: $normalCiRunId,
@@ -1656,3 +1701,4 @@ jobs:
name: full-release-validation-${{ github.run_id }}
path: ${{ runner.temp }}/full-release-validation
if-no-files-found: error
overwrite: true
+8 -2
View File
@@ -1,4 +1,5 @@
name: NPM Telegram Beta E2E
run-name: ${{ inputs.dispatch_correlation_id || github.workflow }}
on:
workflow_dispatch:
@@ -8,6 +9,11 @@ on:
required: true
default: openclaw@beta
type: string
dispatch_correlation_id:
description: Optional unique dispatch correlation used by release orchestration
required: false
default: ""
type: string
package_label:
description: Optional display label for an artifact-backed package candidate
required: false
@@ -161,8 +167,8 @@ jobs:
set -euo pipefail
if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; 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@alpha, openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(alpha|beta|extended-stable|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@alpha, openclaw@beta, openclaw@extended-stable, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
exit 1
fi
fi
+92 -38
View File
@@ -1,4 +1,5 @@
name: OpenClaw NPM Release
run-name: ${{ inputs.dispatch_correlation_id || github.workflow }}
on:
workflow_dispatch:
@@ -7,6 +8,11 @@ on:
description: Release tag to publish, or any full 40-character commit SHA for validation-only preflight (for example v2026.3.22 or 0123456789abcdef0123456789abcdef01234567)
required: true
type: string
dispatch_correlation_id:
description: Optional unique dispatch correlation used by release orchestration
required: false
default: ""
type: string
preflight_only:
description: Run validation/build only and skip the gated publish job
required: true
@@ -184,12 +190,46 @@ jobs:
OPENCLAW_LOCAL_CHECK: "0"
run: pnpm check:architecture
# Same-SHA re-runs restore the built outputs so a late preflight failure
# does not redo the build and Control UI build; every validation step
# still runs against the restored artifacts.
- name: Resolve preflight build cache key
id: preflight_cache_key
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Restore preflight build outputs
id: dist_build_cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
dist/
dist-runtime/
packages/*/dist/
extensions/*/src/host/**/.bundle.hash
extensions/*/src/host/**/*.bundle.js
key: ${{ runner.os }}-npm-preflight-dist-v1-${{ github.workflow_sha }}-${{ steps.preflight_cache_key.outputs.sha }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Build
if: steps.dist_build_cache.outputs.cache-hit != 'true'
run: pnpm build
- name: Build Control UI
if: steps.dist_build_cache.outputs.cache-hit != 'true'
run: pnpm ui:build
- name: Save preflight build outputs
if: steps.dist_build_cache.outputs.cache-hit != 'true'
continue-on-error: true
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
dist/
dist-runtime/
packages/*/dist/
extensions/*/src/host/**/.bundle.hash
extensions/*/src/host/**/*.bundle.js
key: ${{ steps.dist_build_cache.outputs.cache-primary-key }}
- name: Validate release metadata
if: ${{ inputs.preflight_run_id == '' }}
env:
@@ -273,6 +313,7 @@ jobs:
name: openclaw-plugin-npm-preflight-${{ inputs.tag }}
path: ${{ steps.plugin_npm_preflight.outputs.dir }}
if-no-files-found: error
overwrite: true
- name: Generate dependency release evidence
id: dependency_evidence
@@ -392,6 +433,7 @@ jobs:
packageVersion: process.env.PACKAGE_VERSION,
tarballName: process.env.TARBALL_NAME,
tarballSha256: process.env.TARBALL_SHA256,
dependencyTarballs: [],
dependencyEvidenceDir: "dependency-evidence",
dependencyEvidenceManifest: "dependency-evidence/dependency-evidence-manifest.json",
};
@@ -424,6 +466,7 @@ jobs:
name: openclaw-release-dependency-evidence-${{ inputs.tag }}
path: ${{ steps.dependency_evidence.outputs.dir }}
if-no-files-found: error
overwrite: true
- name: Upload dependency release evidence tag alias
if: ${{ steps.packed_tarball.outputs.release_tag != inputs.tag }}
@@ -432,6 +475,7 @@ jobs:
name: openclaw-release-dependency-evidence-${{ steps.packed_tarball.outputs.release_tag }}
path: ${{ steps.dependency_evidence.outputs.dir }}
if-no-files-found: error
overwrite: true
- name: Upload immutable prepared npm publish bundle
uses: actions/upload-artifact@v7
@@ -446,6 +490,7 @@ jobs:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: ${{ steps.packed_tarball.outputs.dir }}
if-no-files-found: error
overwrite: true
- name: Upload legacy prepared npm publish bundle tag alias
if: ${{ steps.packed_tarball.outputs.release_tag != inputs.tag }}
@@ -454,6 +499,7 @@ jobs:
name: openclaw-npm-preflight-${{ steps.packed_tarball.outputs.release_tag }}
path: ${{ steps.packed_tarball.outputs.dir }}
if-no-files-found: error
overwrite: true
validate_publish_request:
if: ${{ !inputs.preflight_only }}
@@ -480,18 +526,26 @@ jobs:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
WORKFLOW_REF: ${{ github.ref }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: |
set -euo pipefail
tideclaw_alpha_publish=false
extended_stable_publish=false
release_publish_workflow=false
if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" == "alpha" && "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
tideclaw_alpha_publish=true
fi
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" && "${WORKFLOW_REF}" == refs/heads/extended-stable/* ]]; then
extended_stable_publish=true
fi
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ "${tideclaw_alpha_publish}" != "true" ]] && [[ "${extended_stable_publish}" != "true" ]]; then
echo "Real publish runs must be dispatched from main, release/YYYY.M.PATCH, the exact validated extended-stable branch, or a Tideclaw alpha branch for alpha prereleases. Use preflight_only=true for other branch validation."
if [[ "${WORKFLOW_REF}" == "refs/heads/release-publish/${WORKFLOW_SHA}" ]]; then
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
if git merge-base --is-ancestor "${WORKFLOW_SHA}" origin/main; then
release_publish_workflow=true
fi
fi
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ "${tideclaw_alpha_publish}" != "true" ]] && [[ "${extended_stable_publish}" != "true" ]] && [[ "${release_publish_workflow}" != "true" ]]; then
echo "Real publish runs must be dispatched from main, release/YYYY.M.PATCH, an immutable release-publish/<workflow-sha> main ancestor, the exact validated extended-stable branch, or a Tideclaw alpha branch for alpha prereleases. Use preflight_only=true for other branch validation."
exit 1
fi
@@ -531,11 +585,8 @@ jobs:
echo "Real publish requires full_release_validation_run_id from a successful Full Release Validation run." >&2
exit 1
fi
elif [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" && ! "${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" =~ ^[1-9][0-9]*$ ]]; then
echo "Extended-stable publish requires a positive full_release_validation_run_attempt." >&2
exit 1
elif [[ -n "${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" && ! "${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" =~ ^[1-9][0-9]*$ ]]; then
echo "full_release_validation_run_attempt must be a positive integer when provided." >&2
elif [[ ! "${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" =~ ^[1-9][0-9]*$ ]]; then
echo "Real publish with full release validation requires a positive full_release_validation_run_attempt." >&2
exit 1
fi
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" && -z "${PLUGIN_NPM_RUN_ID// }" ]]; then
@@ -694,19 +745,21 @@ jobs:
set -euo pipefail
EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)"
export EXPECTED_RELEASE_SHA
if [[ -n "$FULL_RELEASE_VALIDATION_RUN_ATTEMPT" ]]; then
run_file="${RUNNER_TEMP}/full-release-validation-run.json"
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" > "$run_file"
jq '{workflowName: .name, headBranch: .head_branch, headSha: .head_sha, event, status, conclusion, url: .html_url}' "$run_file" | node scripts/openclaw-npm-extended-stable-release.mjs verify-run
run_attempt="$(jq -r '.run_attempt // ""' "$run_file")"
if [[ "$run_attempt" != "$FULL_RELEASE_VALIDATION_RUN_ATTEMPT" ]]; then
echo "Full Release Validation run ${FULL_RELEASE_VALIDATION_RUN_ID} attempt mismatch: expected ${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}, got ${run_attempt:-<missing>}." >&2
exit 1
fi
else
RUN_JSON="$(gh run view "$FULL_RELEASE_VALIDATION_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,headSha,event,status,conclusion,url)"
printf '%s' "$RUN_JSON" | node scripts/openclaw-npm-extended-stable-release.mjs verify-run
run_attempt=""
run_file="${RUNNER_TEMP}/full-release-validation-run.json"
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" > "$run_file"
jq '{
workflowName: .name,
headBranch: .head_branch,
headSha: .head_sha,
event,
status,
conclusion,
url: .html_url
}' "$run_file" | node scripts/openclaw-npm-extended-stable-release.mjs verify-run
run_attempt="$(jq -r '.run_attempt // ""' "$run_file")"
if [[ "$run_attempt" != "$FULL_RELEASE_VALIDATION_RUN_ATTEMPT" ]]; then
echo "Full Release Validation run ${FULL_RELEASE_VALIDATION_RUN_ID} attempt mismatch: expected ${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}, got ${run_attempt:-<missing>}." >&2
exit 1
fi
echo "attempt=$run_attempt" >> "$GITHUB_OUTPUT"
@@ -784,33 +837,27 @@ jobs:
FULL_RELEASE_VALIDATION_RUN_ATTEMPT: ${{ steps.full_run.outputs.attempt }}
run: |
set -euo pipefail
if [[ -n "$FULL_RELEASE_VALIDATION_RUN_ATTEMPT" ]]; then
artifact_name="full-release-validation-${FULL_RELEASE_VALIDATION_RUN_ID}-${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}"
artifacts_file="${RUNNER_TEMP}/full-validation-artifacts.json"
artifact_name="full-release-validation-${FULL_RELEASE_VALIDATION_RUN_ID}-${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}"
artifacts_file="${RUNNER_TEMP}/full-release-validation-artifacts.json"
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/artifacts?per_page=100" > "$artifacts_file"
jq --arg name "$artifact_name" '[.artifacts[] | select(.expired != true and .name == $name)]' "$artifacts_file" > "${RUNNER_TEMP}/full-validation-matches.json"
artifact_count="$(jq 'length' "${RUNNER_TEMP}/full-validation-matches.json")"
jq --arg name "$artifact_name" '[.artifacts[] | select(.expired != true and .name == $name)]' "$artifacts_file" > "${RUNNER_TEMP}/full-release-validation-matches.json"
artifact_count="$(jq 'length' "${RUNNER_TEMP}/full-release-validation-matches.json")"
if [[ "$artifact_count" != "1" ]]; then
echo "Expected exactly one immutable artifact named ${artifact_name}; found ${artifact_count}." >&2
exit 1
fi
artifact_id="$(jq -er '.[0].id' "${RUNNER_TEMP}/full-validation-matches.json")"
artifact_digest="$(jq -er '.[0].digest | strings' "${RUNNER_TEMP}/full-validation-matches.json")"
artifact_zip="${RUNNER_TEMP}/full-validation-${artifact_id}.zip"
artifact_id="$(jq -er '.[0].id' "${RUNNER_TEMP}/full-release-validation-matches.json")"
artifact_digest="$(jq -er '.[0].digest | strings' "${RUNNER_TEMP}/full-release-validation-matches.json")"
artifact_zip="${RUNNER_TEMP}/full-release-validation-${artifact_id}.zip"
gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}/zip" > "$artifact_zip"
actual_digest="sha256:$(sha256sum "$artifact_zip" | awk '{print $1}')"
if [[ "$actual_digest" != "$artifact_digest" ]]; then
echo "Immutable full validation artifact digest mismatch: expected ${artifact_digest}, got ${actual_digest}." >&2
echo "Immutable full release validation artifact digest mismatch: expected ${artifact_digest}, got ${actual_digest}." >&2
exit 1
fi
rm -rf full-release-validation
mkdir -p full-release-validation
unzip -q "$artifact_zip" -d full-release-validation
else
rm -rf full-release-validation
mkdir -p full-release-validation
gh run download "$FULL_RELEASE_VALIDATION_RUN_ID" --repo "$GITHUB_REPOSITORY" --name "full-release-validation-${FULL_RELEASE_VALIDATION_RUN_ID}" --dir full-release-validation
fi
unzip -q "$artifact_zip" -d full-release-validation
- name: Validate release tag and package metadata
if: ${{ inputs.preflight_run_id == '' }}
@@ -909,6 +956,7 @@ jobs:
export EXPECTED_RELEASE_SHA MANIFEST_FILE
node scripts/openclaw-npm-extended-stable-release.mjs verify-manifest
RERUN_GROUP="$(jq -r '.rerunGroup // ""' "$MANIFEST_FILE")"
RELEASE_PROFILE="$(jq -r '.releaseProfile // ""' "$MANIFEST_FILE")"
RUN_RELEASE_SOAK="$(jq -r '.runReleaseSoak // ""' "$MANIFEST_FILE")"
PERFORMANCE_BLOCKING="$(jq -r '.controls.performanceBlocking // false' "$MANIFEST_FILE")"
if [[ "$RERUN_GROUP" != "all" ]]; then
@@ -919,9 +967,15 @@ jobs:
echo "Full release validation manifest does not record blocking product performance evidence." >&2
exit 1
fi
if [[ "$RELEASE_TAG" != *"-alpha."* && "$RELEASE_TAG" != *"-beta."* && "$RUN_RELEASE_SOAK" != "true" ]]; then
echo "Stable releases require Full Release Validation with runReleaseSoak=true." >&2
exit 1
if [[ "$RELEASE_TAG" != *"-alpha."* && "$RELEASE_TAG" != *"-beta."* ]]; then
if [[ "$RELEASE_PROFILE" != "stable" && "$RELEASE_PROFILE" != "full" ]]; then
echo "Stable releases require stable or full validation; got $RELEASE_PROFILE." >&2
exit 1
fi
if [[ "$RUN_RELEASE_SOAK" != "true" ]]; then
echo "Stable releases require Full Release Validation with runReleaseSoak=true." >&2
exit 1
fi
fi
- name: Recheck npm release request
@@ -66,6 +66,21 @@ env:
KOVA_SCENARIO_TIMEOUT_MS: "300000"
jobs:
verify_artifact_only:
name: Verify artifact-only report mode
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_reports == false }}
runs-on: ubuntu-24.04
steps:
- name: Confirm report publication is disabled
env:
PUBLISH_REPORTS: ${{ inputs.publish_reports }}
run: |
set -euo pipefail
[[ "$PUBLISH_REPORTS" == "false" ]] || {
echo "Artifact-only performance validation requires publish_reports=false." >&2
exit 1
}
kova:
name: ${{ matrix.title }}
runs-on: blacksmith-16vcpu-ubuntu-2404
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+36 -60
View File
@@ -42,7 +42,7 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
CLAWHUB_REGISTRY: "https://clawhub.ai"
CLAWHUB_CLI_PACKAGE: "clawhub@0.21.0"
CLAWHUB_CLI_PACKAGE: "clawhub@0.23.1"
jobs:
preview_plugins_clawhub:
@@ -63,7 +63,7 @@ jobs:
missing_trusted_publisher_matrix: ${{ steps.plan.outputs.missing_trusted_publisher_matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
ref: ${{ github.ref }}
@@ -275,7 +275,7 @@ jobs:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
@@ -315,7 +315,7 @@ jobs:
plugin: ${{ fromJson(needs.preview_plugins_clawhub.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
ref: ${{ github.ref }}
@@ -364,7 +364,7 @@ jobs:
run: bash scripts/plugin-clawhub-publish.sh --pack "${PACKAGE_DIR}"
- name: Upload ClawHub package artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.plugin.artifactName }}
path: ${{ runner.temp }}/clawhub-package-artifact/*.tgz
@@ -387,7 +387,7 @@ jobs:
needs:
[preview_plugins_clawhub, pack_plugins_clawhub_artifacts, approve_plugins_clawhub_release]
if: always() && github.event_name == 'workflow_dispatch' && needs.preview_plugins_clawhub.outputs.has_candidates == 'true' && needs.pack_plugins_clawhub_artifacts.result == 'success' && (inputs.dry_run == true || needs.approve_plugins_clawhub_release.result == 'success')
uses: openclaw/clawhub/.github/workflows/package-publish.yml@9d49df109d4ad3dc8a6ecf05d26b39f46d294721
uses: openclaw/clawhub/.github/workflows/package-publish.yml@d8096dfc039e86ab942ddf9ef117d04849fd84c1
permissions:
actions: read
contents: read
@@ -402,6 +402,7 @@ jobs:
dry_run: ${{ inputs.dry_run }}
registry: https://clawhub.ai
site: https://clawhub.ai
family: ${{ contains(fromJson('["@openclaw/acpx","@openclaw/diffs","@openclaw/feishu","@openclaw/qqbot"]'), matrix.plugin.packageName) && 'bundle-plugin' || '' }}
tags: ${{ matrix.plugin.publishTag }}
source_repo: ${{ github.repository }}
source_commit: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
@@ -414,7 +415,9 @@ jobs:
needs: [preview_plugins_clawhub, publish_plugins_clawhub]
if: github.event_name == 'workflow_dispatch' && inputs.dry_run != true && needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
actions: read
contents: read
strategy:
fail-fast: false
@@ -422,6 +425,27 @@ jobs:
matrix:
plugin: ${{ fromJson(needs.preview_plugins_clawhub.outputs.matrix) }}
steps:
- name: Checkout verification tooling
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
ref: ${{ github.sha }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
install-bun: "false"
install-deps: "false"
use-actions-cache: "false"
- name: Download published package input
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: ${{ matrix.plugin.artifactName }}
path: ${{ runner.temp }}/clawhub-package-artifact
- name: Verify published ClawHub package
env:
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
@@ -430,57 +454,9 @@ jobs:
PACKAGE_TAG: ${{ matrix.plugin.publishTag }}
run: |
set -euo pipefail
node --input-type=module <<'EOF'
const registry = (process.env.CLAWHUB_REGISTRY ?? "https://clawhub.ai").replace(/\/+$/, "");
const packageName = process.env.PACKAGE_NAME;
const packageVersion = process.env.PACKAGE_VERSION;
const packageTag = process.env.PACKAGE_TAG;
if (!packageName || !packageVersion || !packageTag) {
throw new Error("Missing ClawHub package verification env.");
}
const encodedName = encodeURIComponent(packageName);
const encodedVersion = encodeURIComponent(packageVersion);
const detailUrl = `${registry}/api/v1/packages/${encodedName}`;
const versionUrl = `${detailUrl}/versions/${encodedVersion}`;
const artifactUrl = `${versionUrl}/artifact/download`;
async function fetchWithRetry(url, options = {}) {
let lastStatus = "unknown";
for (let attempt = 1; attempt <= 12; attempt += 1) {
try {
const response = await fetch(url, { redirect: "manual", ...options });
lastStatus = response.status;
if (response.status !== 429 && response.status < 500) {
return response;
}
} catch (error) {
lastStatus = error instanceof Error ? error.message : String(error);
}
await new Promise((resolve) => setTimeout(resolve, attempt * 5000));
}
throw new Error(`${url} did not stabilize; last status ${lastStatus}.`);
}
const detailResponse = await fetchWithRetry(detailUrl, {
headers: { accept: "application/json" },
});
if (!detailResponse.ok) {
throw new Error(`${detailUrl} returned HTTP ${detailResponse.status}.`);
}
const detail = await detailResponse.json();
const tags = detail?.package?.tags ?? {};
if (tags[packageTag] !== packageVersion) {
throw new Error(
`${packageName}: ClawHub tag ${packageTag} points to ${tags[packageTag] ?? "<missing>"}, expected ${packageVersion}.`,
);
}
const versionResponse = await fetchWithRetry(versionUrl);
if (!versionResponse.ok) {
throw new Error(`${versionUrl} returned HTTP ${versionResponse.status}.`);
}
const artifactResponse = await fetchWithRetry(artifactUrl, { method: "HEAD" });
if (artifactResponse.status < 200 || artifactResponse.status >= 400) {
throw new Error(`${artifactUrl} returned HTTP ${artifactResponse.status}.`);
}
console.log(`${packageName}@${packageVersion} verified on ClawHub.`);
EOF
node scripts/verify-clawhub-published-artifact.mjs \
--expected-artifact-dir "${RUNNER_TEMP}/clawhub-package-artifact" \
--package-name "${PACKAGE_NAME}" \
--package-version "${PACKAGE_VERSION}" \
--publish-tag "${PACKAGE_TAG}" \
--registry "${CLAWHUB_REGISTRY}"
File diff suppressed because it is too large Load Diff