mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
ci: speed up full release validation
This commit is contained in:
@@ -134,8 +134,10 @@ workflow ref input; choose the trusted harness by choosing the workflow run ref.
|
||||
Use `release_profile=minimum|stable|full` to control live/provider breadth:
|
||||
`minimum` keeps the fastest OpenAI/core release-critical set, `stable` adds the
|
||||
stable provider/backend set, and `full` adds the broad advisory provider/media
|
||||
matrix. The parent verifier job appends slowest-job tables for child runs; rerun
|
||||
only that verifier after a child rerun turns green.
|
||||
matrix. Do not make `full` faster by silently dropping suites; optimize setup,
|
||||
artifact reuse, and sharding instead. The parent verifier job appends
|
||||
slowest-job tables for child runs; rerun only that verifier after a child rerun
|
||||
turns green.
|
||||
|
||||
If a full run is already active on a newer `origin/main`, prefer watching that
|
||||
run over dispatching a duplicate. If you accidentally dispatch a stale duplicate,
|
||||
@@ -211,8 +213,17 @@ gh workflow run openclaw-release-checks.yml \
|
||||
Release-check rerun groups are `all`, `install-smoke`, `cross-os`, `live-e2e`,
|
||||
`package`, `qa`, `qa-parity`, and `qa-live`.
|
||||
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the selected
|
||||
ref once as `release-package-under-test` and passes that artifact into both
|
||||
release-path Docker live/E2E checks and Package Acceptance.
|
||||
ref once as `release-package-under-test` and passes that artifact into cross-OS
|
||||
release checks, release-path Docker live/E2E checks, and Package Acceptance.
|
||||
When `Full Release Validation` dispatches release checks, it passes the requested
|
||||
branch/tag plus an `expected_sha` so branch/tag refs resolve through the fast
|
||||
remote-ref path while the package and QA jobs still validate the exact SHA.
|
||||
|
||||
The release Docker path intentionally shards the plugin/runtime tail. The
|
||||
workflow uses `plugins-runtime-plugins`, `plugins-runtime-services`, and
|
||||
`plugins-runtime-install-a` through `plugins-runtime-install-d`; aggregate
|
||||
aliases such as `plugins-runtime-core`, `plugins-runtime`, and
|
||||
`plugins-integrations` remain for manual reruns.
|
||||
|
||||
The release QA parity box is internally split into candidate and baseline lane
|
||||
jobs, followed by a report job that downloads both artifacts and runs
|
||||
@@ -272,12 +283,15 @@ Useful knobs:
|
||||
- blank `live_model_providers`: run the full live-model provider matrix.
|
||||
|
||||
Release-path Docker chunks are currently `core`, `package-update-openai`,
|
||||
`package-update-anthropic`, `package-update-core`, `plugins-runtime-core`,
|
||||
`package-update-anthropic`, `package-update-core`,
|
||||
`plugins-runtime-plugins`, `plugins-runtime-services`,
|
||||
`plugins-runtime-install-a`, `plugins-runtime-install-b`,
|
||||
`plugins-runtime-install-c`, `plugins-runtime-install-d`,
|
||||
`bundled-channels-core`, `bundled-channels-update-a`,
|
||||
`bundled-channels-update-b`, and `bundled-channels-contracts`. The aggregate
|
||||
`bundled-channels` chunk remains valid for manual one-shot reruns, but release
|
||||
checks use the split chunks.
|
||||
`bundled-channels`, `plugins-runtime-core`, `plugins-runtime`, and
|
||||
`plugins-integrations` chunks remain valid for manual one-shot reruns, but
|
||||
release checks use the split chunks.
|
||||
|
||||
When live suites are enabled, the workflow shards broad native `pnpm test:live`
|
||||
coverage through `scripts/test-live-shard.mjs` instead of one serial `live-all`
|
||||
@@ -360,18 +374,22 @@ image. Release-path normal mode fans out into smaller Docker chunk jobs:
|
||||
- `package-update-openai`
|
||||
- `package-update-anthropic`
|
||||
- `package-update-core`
|
||||
- `plugins-runtime-core`
|
||||
- `plugins-runtime-plugins`
|
||||
- `plugins-runtime-services`
|
||||
- `plugins-runtime-install-a`
|
||||
- `plugins-runtime-install-b`
|
||||
- `plugins-runtime-install-c`
|
||||
- `plugins-runtime-install-d`
|
||||
- `bundled-channels`
|
||||
|
||||
OpenWebUI is folded into `plugins-runtime-core` for full release-path coverage
|
||||
and keeps a standalone `openwebui` chunk only for OpenWebUI-only dispatches.
|
||||
The legacy `package-update`, `plugins-runtime`, and `plugins-integrations`
|
||||
chunks still work as aggregate aliases for manual reruns, but the release
|
||||
workflow uses the split chunks so provider installer checks, plugin runtime
|
||||
checks, bundled plugin install/uninstall shards, and bundled-channel checks can
|
||||
run on separate machines. The bundled-channel runtime-dependency coverage
|
||||
OpenWebUI is folded into `plugins-runtime-services` for full release-path
|
||||
coverage and keeps a standalone `openwebui` chunk only for OpenWebUI-only
|
||||
dispatches. The legacy `package-update`, `plugins-runtime-core`,
|
||||
`plugins-runtime`, and `plugins-integrations` chunks still work as aggregate
|
||||
aliases for manual reruns, but the release workflow uses the split chunks so
|
||||
provider installer checks, plugin runtime checks, bundled plugin
|
||||
install/uninstall shards, and bundled-channel checks can run on separate
|
||||
machines. The bundled-channel runtime-dependency coverage
|
||||
inside `bundled-channels`
|
||||
uses the split `bundled-channel-*` and `bundled-channel-update-*` lanes rather
|
||||
than the serial `bundled-channel-deps` lane, so failures produce cheap targeted
|
||||
|
||||
17
.github/workflows/full-release-validation.yml
vendored
17
.github/workflows/full-release-validation.yml
vendored
@@ -96,17 +96,23 @@ jobs:
|
||||
outputs:
|
||||
sha: ${{ steps.resolve.outputs.sha }}
|
||||
steps:
|
||||
- name: Checkout target ref
|
||||
- name: Checkout trusted workflow helper
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref_name }}
|
||||
path: workflow
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Resolve target SHA
|
||||
id: resolve
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
run: |
|
||||
bash workflow/scripts/github/resolve-openclaw-ref.sh \
|
||||
--ref "$TARGET_REF" \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Summarize target
|
||||
env:
|
||||
@@ -324,7 +330,8 @@ jobs:
|
||||
fi
|
||||
|
||||
dispatch_and_wait openclaw-release-checks.yml \
|
||||
-f ref="$TARGET_SHA" \
|
||||
-f ref="$TARGET_REF" \
|
||||
-f expected_sha="$TARGET_SHA" \
|
||||
-f provider="$PROVIDER" \
|
||||
-f mode="$MODE" \
|
||||
-f release_profile="$RELEASE_PROFILE" \
|
||||
|
||||
@@ -51,6 +51,31 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_artifact_name:
|
||||
description: Optional current-run artifact name containing the candidate OpenClaw tarball
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_artifact_run_id:
|
||||
description: Optional workflow run id for candidate_artifact_name
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_file_name:
|
||||
description: Optional candidate tarball file name inside candidate_artifact_name
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_version:
|
||||
description: Optional candidate OpenClaw package version
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_source_sha:
|
||||
description: Optional source SHA used to build the candidate tarball
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
@@ -90,6 +115,31 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_artifact_name:
|
||||
description: Optional current-run artifact name containing the candidate OpenClaw tarball
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_artifact_run_id:
|
||||
description: Optional workflow run id for candidate_artifact_name
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_file_name:
|
||||
description: Optional candidate tarball file name inside candidate_artifact_name
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_version:
|
||||
description: Optional candidate OpenClaw package version
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_source_sha:
|
||||
description: Optional source SHA used to build the candidate tarball
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
@@ -119,7 +169,7 @@ env:
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
outputs:
|
||||
baseline_file_name: ${{ steps.baseline_metadata.outputs.file_name }}
|
||||
baseline_spec: ${{ steps.baseline.outputs.value }}
|
||||
@@ -260,6 +310,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout public source ref
|
||||
if: inputs.candidate_artifact_name == ''
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.OPENCLAW_REPOSITORY }}
|
||||
@@ -280,9 +331,10 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
cache-dependency-path: source/pnpm-lock.yaml
|
||||
cache-dependency-path: ${{ inputs.candidate_artifact_name == '' && 'source/pnpm-lock.yaml' || 'workflow/pnpm-lock.yaml' }}
|
||||
|
||||
- name: Build candidate artifact once
|
||||
if: inputs.candidate_artifact_name == ''
|
||||
env:
|
||||
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare
|
||||
run: |
|
||||
@@ -291,6 +343,52 @@ jobs:
|
||||
--source-dir source \
|
||||
--output-dir "${OUTPUT_DIR}"
|
||||
|
||||
- name: Download provided candidate artifact
|
||||
if: inputs.candidate_artifact_name != ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.candidate_artifact_name }}
|
||||
run-id: ${{ inputs.candidate_artifact_run_id || github.run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package
|
||||
|
||||
- name: Capture provided candidate artifact metadata
|
||||
if: inputs.candidate_artifact_name != ''
|
||||
env:
|
||||
PACKAGE_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package
|
||||
INPUT_CANDIDATE_FILE_NAME: ${{ inputs.candidate_file_name }}
|
||||
INPUT_CANDIDATE_VERSION: ${{ inputs.candidate_version }}
|
||||
INPUT_CANDIDATE_SOURCE_SHA: ${{ inputs.candidate_source_sha }}
|
||||
CANDIDATE_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/candidate.json
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const packageDir = process.env.PACKAGE_DIR;
|
||||
const requestedFileName = process.env.INPUT_CANDIDATE_FILE_NAME.trim();
|
||||
const files = fs.readdirSync(packageDir).filter((file) => file.endsWith(".tgz"));
|
||||
const candidateFileName = requestedFileName || (files.length === 1 ? files[0] : "");
|
||||
if (!candidateFileName) {
|
||||
throw new Error(`Expected exactly one candidate .tgz in ${packageDir}; found ${files.length}.`);
|
||||
}
|
||||
if (!fs.existsSync(path.join(packageDir, candidateFileName))) {
|
||||
throw new Error(`Provided candidate artifact does not contain ${candidateFileName}.`);
|
||||
}
|
||||
const candidateVersion = process.env.INPUT_CANDIDATE_VERSION.trim();
|
||||
if (!candidateVersion) {
|
||||
throw new Error("candidate_version is required when candidate_artifact_name is provided.");
|
||||
}
|
||||
const sourceSha = process.env.INPUT_CANDIDATE_SOURCE_SHA.trim();
|
||||
if (!/^[0-9a-f]{40}$/iu.test(sourceSha)) {
|
||||
throw new Error("candidate_source_sha must be a full commit SHA when candidate_artifact_name is provided.");
|
||||
}
|
||||
fs.writeFileSync(
|
||||
process.env.CANDIDATE_JSON,
|
||||
`${JSON.stringify({ candidateFileName, candidateVersion, sourceSha }, null, 2)}\n`,
|
||||
);
|
||||
NODE
|
||||
|
||||
- name: Resolve baseline package spec
|
||||
if: ${{ inputs.mode != 'fresh' }}
|
||||
id: baseline
|
||||
|
||||
@@ -457,15 +457,24 @@ jobs:
|
||||
- chunk_id: package-update-core
|
||||
label: package/update core
|
||||
timeout_minutes: 120
|
||||
- chunk_id: plugins-runtime-core
|
||||
label: plugins/runtime core
|
||||
timeout_minutes: 180
|
||||
- chunk_id: plugins-runtime-plugins
|
||||
label: plugins/runtime plugins
|
||||
timeout_minutes: 120
|
||||
- chunk_id: plugins-runtime-services
|
||||
label: plugins/runtime services
|
||||
timeout_minutes: 120
|
||||
- chunk_id: plugins-runtime-install-a
|
||||
label: plugins/runtime install A
|
||||
timeout_minutes: 180
|
||||
timeout_minutes: 120
|
||||
- chunk_id: plugins-runtime-install-b
|
||||
label: plugins/runtime install B
|
||||
timeout_minutes: 180
|
||||
timeout_minutes: 120
|
||||
- chunk_id: plugins-runtime-install-c
|
||||
label: plugins/runtime install C
|
||||
timeout_minutes: 120
|
||||
- chunk_id: plugins-runtime-install-d
|
||||
label: plugins/runtime install D
|
||||
timeout_minutes: 120
|
||||
- chunk_id: bundled-channels-core
|
||||
label: bundled channels core
|
||||
timeout_minutes: 90
|
||||
|
||||
93
.github/workflows/openclaw-release-checks.yml
vendored
93
.github/workflows/openclaw-release-checks.yml
vendored
@@ -7,6 +7,11 @@ on:
|
||||
description: Branch, tag, or full commit SHA to validate
|
||||
required: true
|
||||
type: string
|
||||
expected_sha:
|
||||
description: Optional full SHA that ref must resolve to
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
provider:
|
||||
description: Provider lane for cross-OS onboarding and the end-to-end agent turn
|
||||
required: false
|
||||
@@ -86,24 +91,54 @@ jobs:
|
||||
- name: Validate ref input
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${RELEASE_REF// }" ]] || [[ "${RELEASE_REF}" == -* ]]; then
|
||||
echo "Expected a branch, tag, or full commit SHA; got: ${RELEASE_REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${EXPECTED_SHA// }" ]] && [[ ! "${EXPECTED_SHA}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "Expected expected_sha to be a full commit SHA; got: ${EXPECTED_SHA}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout selected ref
|
||||
- name: Checkout trusted workflow helper
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
path: workflow
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Fast-resolve selected ref
|
||||
id: fast_ref
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
run: |
|
||||
bash workflow/scripts/github/resolve-openclaw-ref.sh \
|
||||
--ref "$RELEASE_REF" \
|
||||
--expected-sha "$EXPECTED_SHA" \
|
||||
--fallback-ok \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout selected ref for reachability fallback
|
||||
if: steps.fast_ref.outputs.fallback == 'true'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
path: source
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve checked-out SHA
|
||||
id: ref
|
||||
- name: Resolve checked-out fallback SHA
|
||||
if: steps.fast_ref.outputs.fallback == 'true'
|
||||
id: fallback_ref
|
||||
working-directory: source
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate selected ref belongs to this repository
|
||||
if: steps.fast_ref.outputs.fallback == 'true'
|
||||
working-directory: source
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
run: |
|
||||
@@ -124,6 +159,29 @@ jobs:
|
||||
echo "Secret-bearing release checks only run repository-owned branch/tag history, not arbitrary unreferenced commits." >&2
|
||||
exit 1
|
||||
|
||||
- name: Finalize resolved SHA
|
||||
id: ref
|
||||
env:
|
||||
FAST_SHA: ${{ steps.fast_ref.outputs.sha }}
|
||||
FALLBACK_SHA: ${{ steps.fallback_ref.outputs.sha }}
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
USED_FALLBACK: ${{ steps.fast_ref.outputs.fallback }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
selected_sha="$FAST_SHA"
|
||||
if [[ "$USED_FALLBACK" == "true" ]]; then
|
||||
selected_sha="$FALLBACK_SHA"
|
||||
fi
|
||||
if [[ -z "$selected_sha" ]]; then
|
||||
echo "Failed to resolve selected ref SHA." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${EXPECTED_SHA// }" ]] && [[ "${selected_sha,,}" != "${EXPECTED_SHA,,}" ]]; then
|
||||
echo "Ref resolved to ${selected_sha}, expected ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "sha=${selected_sha,,}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Capture selected inputs
|
||||
id: inputs
|
||||
env:
|
||||
@@ -146,6 +204,7 @@ jobs:
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
RELEASE_SHA: ${{ steps.ref.outputs.sha }}
|
||||
RELEASE_REF_FAST_PATH: ${{ steps.fast_ref.outputs.fast }}
|
||||
RELEASE_PROVIDER: ${{ inputs.provider }}
|
||||
RELEASE_MODE: ${{ inputs.mode }}
|
||||
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
||||
@@ -156,6 +215,7 @@ jobs:
|
||||
echo
|
||||
echo "- Requested ref: \`${RELEASE_REF}\`"
|
||||
echo "- Validated SHA: \`${RELEASE_SHA}\`"
|
||||
echo "- Ref resolution fast path: \`${RELEASE_REF_FAST_PATH}\`"
|
||||
echo "- Cross-OS provider: \`${RELEASE_PROVIDER}\`"
|
||||
echo "- Cross-OS mode: \`${RELEASE_MODE}\`"
|
||||
echo "- Release profile: \`${RELEASE_PROFILE}\`"
|
||||
@@ -166,7 +226,7 @@ jobs:
|
||||
prepare_release_package:
|
||||
name: Prepare release package artifact
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","live-e2e","package"]'), needs.resolve_target.outputs.rerun_group)
|
||||
if: contains(fromJSON('["all","cross-os","live-e2e","package"]'), needs.resolve_target.outputs.rerun_group)
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
@@ -175,6 +235,7 @@ jobs:
|
||||
artifact_name: ${{ steps.artifact.outputs.name }}
|
||||
package_sha256: ${{ steps.package.outputs.sha256 }}
|
||||
package_version: ${{ steps.package.outputs.package_version }}
|
||||
source_sha: ${{ steps.package.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout trusted workflow ref
|
||||
uses: actions/checkout@v6
|
||||
@@ -198,7 +259,7 @@ jobs:
|
||||
id: package
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE_REF: ${{ needs.resolve_target.outputs.ref }}
|
||||
PACKAGE_REF: ${{ needs.resolve_target.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node scripts/resolve-openclaw-package-candidate.mjs \
|
||||
@@ -210,6 +271,8 @@ jobs:
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
digest="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).sha256")"
|
||||
version="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).version")"
|
||||
source_sha="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).packageSourceSha")"
|
||||
echo "source_sha=$source_sha" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## Release package artifact"
|
||||
echo
|
||||
@@ -217,6 +280,7 @@ jobs:
|
||||
echo "- Package ref: \`$PACKAGE_REF\`"
|
||||
echo "- SHA-256: \`$digest\`"
|
||||
echo "- Version: \`$version\`"
|
||||
echo "- Source SHA: \`$source_sha\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload release package artifact
|
||||
@@ -234,11 +298,11 @@ jobs:
|
||||
contents: read
|
||||
uses: ./.github/workflows/install-smoke.yml
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
ref: ${{ needs.resolve_target.outputs.sha }}
|
||||
run_bun_global_install_smoke: true
|
||||
|
||||
cross_os_release_checks:
|
||||
needs: [resolve_target]
|
||||
needs: [resolve_target, prepare_release_package]
|
||||
if: contains(fromJSON('["all","cross-os"]'), needs.resolve_target.outputs.rerun_group)
|
||||
permissions: read-all
|
||||
uses: ./.github/workflows/openclaw-cross-os-release-checks-reusable.yml
|
||||
@@ -246,6 +310,11 @@ jobs:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
provider: ${{ needs.resolve_target.outputs.provider }}
|
||||
mode: ${{ needs.resolve_target.outputs.mode }}
|
||||
candidate_artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
|
||||
candidate_artifact_run_id: ${{ github.run_id }}
|
||||
candidate_file_name: openclaw-current.tgz
|
||||
candidate_version: ${{ needs.prepare_release_package.outputs.package_version }}
|
||||
candidate_source_sha: ${{ needs.prepare_release_package.outputs.source_sha }}
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
@@ -264,7 +333,7 @@ jobs:
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
ref: ${{ needs.resolve_target.outputs.sha }}
|
||||
include_repo_e2e: true
|
||||
include_release_path_suites: true
|
||||
include_openwebui: ${{ needs.resolve_target.outputs.release_profile != 'minimum' }}
|
||||
@@ -419,7 +488,7 @@ jobs:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
ref: ${{ needs.resolve_target.outputs.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -487,7 +556,7 @@ jobs:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
ref: ${{ needs.resolve_target.outputs.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -543,7 +612,7 @@ jobs:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
ref: ${{ needs.resolve_target.outputs.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -622,7 +691,7 @@ jobs:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
ref: ${{ needs.resolve_target.outputs.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
|
||||
File diff suppressed because one or more lines are too long
166
scripts/github/resolve-openclaw-ref.sh
Executable file
166
scripts/github/resolve-openclaw-ref.sh
Executable file
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REMOTE_URL="${OPENCLAW_REF_REMOTE:-https://github.com/openclaw/openclaw.git}"
|
||||
REF=""
|
||||
EXPECTED_SHA=""
|
||||
FALLBACK_OK=0
|
||||
GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-}"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: resolve-openclaw-ref.sh --ref <ref> [--expected-sha <sha>] [--fallback-ok] [--github-output <file>]
|
||||
|
||||
Fast-resolves OpenClaw branch and tag refs with git ls-remote. Full commit SHAs
|
||||
are returned as fallback refs so callers can decide whether to run deeper
|
||||
reachability validation.
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--ref)
|
||||
REF="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--expected-sha)
|
||||
EXPECTED_SHA="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--fallback-ok)
|
||||
FALLBACK_OK=1
|
||||
shift
|
||||
;;
|
||||
--github-output)
|
||||
GITHUB_OUTPUT_FILE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
trim() {
|
||||
local value="$1"
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
write_output() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
if [[ -n "$GITHUB_OUTPUT_FILE" ]]; then
|
||||
printf '%s=%s\n' "$key" "$value" >> "$GITHUB_OUTPUT_FILE"
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_unique_remote_ref() {
|
||||
local refspec
|
||||
local -a matches=()
|
||||
for refspec in "$@"; do
|
||||
[[ -n "$refspec" ]] || continue
|
||||
mapfile -t matches < <(
|
||||
git ls-remote "$REMOTE_URL" "$refspec" | awk '{print $1}' | awk '!seen[$0]++'
|
||||
)
|
||||
if [[ "${#matches[@]}" -eq 0 ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ "${#matches[@]}" -ne 1 ]]; then
|
||||
return 2
|
||||
fi
|
||||
printf '%s\n' "${matches[0]}"
|
||||
return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
REF="$(trim "$REF")"
|
||||
EXPECTED_SHA="$(trim "$EXPECTED_SHA")"
|
||||
if [[ -z "$REF" ]] || [[ "$REF" == -* ]]; then
|
||||
echo "Expected a branch, tag, or full commit SHA; got: ${REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$EXPECTED_SHA" ]] && [[ ! "$EXPECTED_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "Expected --expected-sha to be a full commit SHA; got: ${EXPECTED_SHA}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$REF" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
if [[ -n "$EXPECTED_SHA" ]] && [[ "${REF,,}" != "${EXPECTED_SHA,,}" ]]; then
|
||||
echo "Ref SHA ${REF} does not match expected SHA ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
write_output sha "${REF,,}"
|
||||
write_output ref_kind sha
|
||||
write_output fast false
|
||||
write_output fallback true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
declare -a matches=()
|
||||
if [[ "$REF" == refs/heads/* ]]; then
|
||||
mapfile -t matches < <(resolve_unique_remote_ref "$REF" || true)
|
||||
elif [[ "$REF" == refs/tags/* ]]; then
|
||||
mapfile -t matches < <(resolve_unique_remote_ref "${REF}^{}" "$REF" || true)
|
||||
elif [[ "$REF" == refs/* ]]; then
|
||||
mapfile -t matches < <(resolve_unique_remote_ref "$REF" || true)
|
||||
else
|
||||
mapfile -t branch_matches < <(resolve_unique_remote_ref "refs/heads/${REF}" || true)
|
||||
mapfile -t tag_matches < <(resolve_unique_remote_ref "refs/tags/${REF}^{}" "refs/tags/${REF}" || true)
|
||||
match_count=$(( ${#branch_matches[@]} + ${#tag_matches[@]} ))
|
||||
if [[ "$match_count" -eq 1 ]]; then
|
||||
if [[ "${#branch_matches[@]}" -eq 1 ]]; then
|
||||
matches=("${branch_matches[0]}")
|
||||
ref_kind=branch
|
||||
else
|
||||
matches=("${tag_matches[0]}")
|
||||
ref_kind=tag
|
||||
fi
|
||||
elif [[ "$match_count" -gt 1 ]]; then
|
||||
echo "Ref resolved ambiguously as both branch and tag: ${REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${#matches[@]}" -eq 1 ]]; then
|
||||
resolved="${matches[0],,}"
|
||||
if [[ -n "$EXPECTED_SHA" ]] && [[ "$resolved" != "${EXPECTED_SHA,,}" ]]; then
|
||||
echo "Ref ${REF} resolved to ${resolved}, expected ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${ref_kind:-}" ]]; then
|
||||
if [[ "$REF" == refs/tags/* ]]; then
|
||||
ref_kind=tag
|
||||
elif [[ "$REF" == refs/heads/* ]]; then
|
||||
ref_kind=branch
|
||||
else
|
||||
ref_kind=ref
|
||||
fi
|
||||
fi
|
||||
write_output sha "$resolved"
|
||||
write_output ref_kind "$ref_kind"
|
||||
write_output fast true
|
||||
write_output fallback false
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$FALLBACK_OK" -eq 1 ]]; then
|
||||
write_output sha "$EXPECTED_SHA"
|
||||
write_output ref_kind unknown
|
||||
write_output fast false
|
||||
write_output fallback true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Failed to resolve OpenClaw ref: ${REF}" >&2
|
||||
exit 1
|
||||
@@ -418,11 +418,14 @@ const releasePathPluginRuntimeLanes = [
|
||||
),
|
||||
];
|
||||
|
||||
const releasePathPluginRuntimeCoreLanes = [
|
||||
const releasePathPluginRuntimePluginLanes = [
|
||||
lane("plugins", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugins", {
|
||||
resources: ["npm", "service"],
|
||||
weight: 6,
|
||||
}),
|
||||
];
|
||||
|
||||
const releasePathPluginRuntimeServiceLanes = [
|
||||
serviceLane(
|
||||
"cron-mcp-cleanup",
|
||||
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:cron-mcp-cleanup",
|
||||
@@ -438,6 +441,11 @@ const releasePathPluginRuntimeCoreLanes = [
|
||||
),
|
||||
];
|
||||
|
||||
const releasePathPluginRuntimeCoreLanes = [
|
||||
...releasePathPluginRuntimePluginLanes,
|
||||
...releasePathPluginRuntimeServiceLanes,
|
||||
];
|
||||
|
||||
const releasePathBundledChannelLanes = [
|
||||
npmLane("plugin-update", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugin-update"),
|
||||
...bundledScenarioLanes,
|
||||
@@ -508,9 +516,12 @@ const primaryReleasePathChunks = {
|
||||
"package-update-openai": releasePathPackageInstallOpenAiLanes,
|
||||
"package-update-anthropic": releasePathPackageInstallAnthropicLanes,
|
||||
"package-update-core": releasePathPackageUpdateCoreLanes,
|
||||
"plugins-runtime-core": releasePathPluginRuntimeCoreLanes,
|
||||
"plugins-runtime-install-a": bundledPluginInstallUninstallLanes.slice(0, 4),
|
||||
"plugins-runtime-install-b": bundledPluginInstallUninstallLanes.slice(4),
|
||||
"plugins-runtime-plugins": releasePathPluginRuntimePluginLanes,
|
||||
"plugins-runtime-services": releasePathPluginRuntimeServiceLanes,
|
||||
"plugins-runtime-install-a": bundledPluginInstallUninstallLanes.slice(0, 2),
|
||||
"plugins-runtime-install-b": bundledPluginInstallUninstallLanes.slice(2, 4),
|
||||
"plugins-runtime-install-c": bundledPluginInstallUninstallLanes.slice(4, 6),
|
||||
"plugins-runtime-install-d": bundledPluginInstallUninstallLanes.slice(6),
|
||||
"bundled-channels-core": [releasePathBundledChannelLanes[0], ...bundledChannelSmokeLanes],
|
||||
"bundled-channels-update-a": [bundledChannelUpdateLanes[0], bundledChannelUpdateLanes[4]],
|
||||
"bundled-channels-update-discord": [bundledChannelUpdateLanes[1]],
|
||||
@@ -529,6 +540,7 @@ const legacyReleasePathChunks = {
|
||||
...releasePathPackageInstallAnthropicLanes,
|
||||
...releasePathPackageUpdateCoreLanes,
|
||||
],
|
||||
"plugins-runtime-core": releasePathPluginRuntimeCoreLanes,
|
||||
"plugins-runtime": releasePathPluginRuntimeLanes,
|
||||
"plugins-integrations": [...releasePathPluginRuntimeLanes, ...releasePathBundledChannelLanes],
|
||||
"bundled-channels": releasePathBundledChannelLanes,
|
||||
@@ -560,7 +572,8 @@ export function releasePathChunkLanes(chunk, options = {}) {
|
||||
return options.includeOpenWebUI ? [openWebUILane()] : [];
|
||||
}
|
||||
if (
|
||||
(chunk !== "plugins-runtime-core" &&
|
||||
(chunk !== "plugins-runtime-services" &&
|
||||
chunk !== "plugins-runtime-core" &&
|
||||
chunk !== "plugins-runtime" &&
|
||||
chunk !== "plugins-integrations") ||
|
||||
!options.includeOpenWebUI
|
||||
|
||||
@@ -155,7 +155,7 @@ export function resolveRunnerMatrix(params) {
|
||||
{
|
||||
os_id: "ubuntu",
|
||||
display_name: "Linux",
|
||||
runner: pick(params.ubuntuRunner, params.varUbuntuRunner, "ubuntu-latest"),
|
||||
runner: pick(params.ubuntuRunner, params.varUbuntuRunner, "blacksmith-8vcpu-ubuntu-2404"),
|
||||
artifact_name: "linux",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -89,10 +89,15 @@ describe("scripts/lib/docker-e2e-plan", () => {
|
||||
profile: RELEASE_PATH_PROFILE,
|
||||
releaseChunk: "package-update-core",
|
||||
});
|
||||
const pluginsRuntimeCore = planFor({
|
||||
const pluginsRuntimePlugins = planFor({
|
||||
includeOpenWebUI: true,
|
||||
profile: RELEASE_PATH_PROFILE,
|
||||
releaseChunk: "plugins-runtime-core",
|
||||
releaseChunk: "plugins-runtime-plugins",
|
||||
});
|
||||
const pluginsRuntimeServices = planFor({
|
||||
includeOpenWebUI: true,
|
||||
profile: RELEASE_PATH_PROFILE,
|
||||
releaseChunk: "plugins-runtime-services",
|
||||
});
|
||||
const pluginsRuntimeInstallA = planFor({
|
||||
includeOpenWebUI: true,
|
||||
@@ -104,6 +109,16 @@ describe("scripts/lib/docker-e2e-plan", () => {
|
||||
profile: RELEASE_PATH_PROFILE,
|
||||
releaseChunk: "plugins-runtime-install-b",
|
||||
});
|
||||
const pluginsRuntimeInstallC = planFor({
|
||||
includeOpenWebUI: true,
|
||||
profile: RELEASE_PATH_PROFILE,
|
||||
releaseChunk: "plugins-runtime-install-c",
|
||||
});
|
||||
const pluginsRuntimeInstallD = planFor({
|
||||
includeOpenWebUI: true,
|
||||
profile: RELEASE_PATH_PROFILE,
|
||||
releaseChunk: "plugins-runtime-install-d",
|
||||
});
|
||||
const bundledChannelsCore = planFor({
|
||||
includeOpenWebUI: true,
|
||||
profile: RELEASE_PATH_PROFILE,
|
||||
@@ -139,26 +154,28 @@ describe("scripts/lib/docker-e2e-plan", () => {
|
||||
"doctor-switch",
|
||||
"update-channel-switch",
|
||||
]);
|
||||
expect(pluginsRuntimeCore.lanes.map((lane) => lane.name)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"plugins",
|
||||
"cron-mcp-cleanup",
|
||||
"openai-web-search-minimal",
|
||||
"openwebui",
|
||||
]),
|
||||
);
|
||||
expect(pluginsRuntimeCore.lanes.map((lane) => lane.name)).not.toContain(
|
||||
expect(pluginsRuntimePlugins.lanes.map((lane) => lane.name)).toEqual(["plugins"]);
|
||||
expect(pluginsRuntimeServices.lanes.map((lane) => lane.name)).toEqual([
|
||||
"cron-mcp-cleanup",
|
||||
"openai-web-search-minimal",
|
||||
"openwebui",
|
||||
]);
|
||||
expect(pluginsRuntimePlugins.lanes.map((lane) => lane.name)).not.toContain(
|
||||
"bundled-plugin-install-uninstall-0",
|
||||
);
|
||||
expect(pluginsRuntimeInstallA.lanes.map((lane) => lane.name)).toEqual([
|
||||
"bundled-plugin-install-uninstall-0",
|
||||
"bundled-plugin-install-uninstall-1",
|
||||
]);
|
||||
expect(pluginsRuntimeInstallB.lanes.map((lane) => lane.name)).toEqual([
|
||||
"bundled-plugin-install-uninstall-2",
|
||||
"bundled-plugin-install-uninstall-3",
|
||||
]);
|
||||
expect(pluginsRuntimeInstallB.lanes.map((lane) => lane.name)).toEqual([
|
||||
expect(pluginsRuntimeInstallC.lanes.map((lane) => lane.name)).toEqual([
|
||||
"bundled-plugin-install-uninstall-4",
|
||||
"bundled-plugin-install-uninstall-5",
|
||||
]);
|
||||
expect(pluginsRuntimeInstallD.lanes.map((lane) => lane.name)).toEqual([
|
||||
"bundled-plugin-install-uninstall-6",
|
||||
"bundled-plugin-install-uninstall-7",
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user