mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 10:21:34 +00:00
Align Telegram artifact reuse and packaged Docker validation with current GitHub Actions and runtime state contracts. Refresh affected regression fixtures and isolate test state.
608 lines
25 KiB
YAML
608 lines
25 KiB
YAML
name: NPM Telegram Beta E2E
|
|
|
|
run-name: ${{ inputs.dispatch_id != '' && format('NPM Telegram Beta E2E {0}', inputs.dispatch_id) || 'NPM Telegram Beta E2E' }}
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
package_spec:
|
|
description: Published OpenClaw package spec to test when no artifact is supplied
|
|
required: true
|
|
default: openclaw@beta
|
|
type: string
|
|
package_label:
|
|
description: Optional display label for an artifact-backed package candidate
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_name:
|
|
description: Advanced package-under-test artifact name; leave blank for registry install
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_id:
|
|
description: Immutable GitHub artifact id for package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_digest:
|
|
description: GitHub artifact service SHA-256 digest without the sha256 prefix
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_sha256:
|
|
description: Expected SHA-256 for the OpenClaw package tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_run_id:
|
|
description: Producer run id containing package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_run_attempt:
|
|
description: Producer run attempt containing package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_file_name:
|
|
description: Exact OpenClaw tarball filename inside package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_source_sha:
|
|
description: Exact source commit recorded in the package tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_version:
|
|
description: Exact OpenClaw package version
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
harness_ref:
|
|
description: Source ref for the private QA harness; defaults to the dispatched workflow ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
provider_mode:
|
|
description: QA provider mode
|
|
required: true
|
|
default: mock-openai
|
|
type: choice
|
|
options:
|
|
- mock-openai
|
|
- live-frontier
|
|
scenario:
|
|
description: Optional comma-separated Telegram scenario ids
|
|
required: false
|
|
type: string
|
|
advisory:
|
|
description: Treat package Telegram failures as advisory for the caller
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
dispatch_id:
|
|
description: Optional parent workflow dispatch identifier
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
workflow_call:
|
|
inputs:
|
|
advisory:
|
|
description: Treat package Telegram failures as advisory for the caller
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
package_spec:
|
|
description: Published OpenClaw package spec to test when no artifact is supplied
|
|
required: true
|
|
type: string
|
|
package_artifact_name:
|
|
description: Optional package-under-test artifact from the current or specified workflow run
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_digest:
|
|
description: GitHub artifact service SHA-256 digest without the sha256 prefix
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_run_id:
|
|
description: Producer run id containing package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_run_attempt:
|
|
description: Producer run attempt containing package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_id:
|
|
description: Immutable GitHub artifact id for package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_sha256:
|
|
description: Expected SHA-256 for the OpenClaw package tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_file_name:
|
|
description: Exact OpenClaw tarball filename inside package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_source_sha:
|
|
description: Exact source commit recorded in the package tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_version:
|
|
description: Exact OpenClaw package version
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_label:
|
|
description: Optional display label for an artifact-backed package candidate
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
harness_ref:
|
|
description: Source ref for the private QA harness; defaults to the called workflow ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
provider_mode:
|
|
description: QA provider mode
|
|
required: false
|
|
default: mock-openai
|
|
type: string
|
|
scenario:
|
|
description: Optional comma-separated Telegram scenario ids
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
dispatch_id:
|
|
description: Optional parent workflow dispatch identifier
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
secrets:
|
|
OPENAI_API_KEY:
|
|
required: false
|
|
OPENCLAW_QA_CONVEX_SITE_URL:
|
|
required: false
|
|
OPENCLAW_QA_CONVEX_SECRET_CI:
|
|
required: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: npm-telegram-beta-e2e-${{ github.run_id }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
NODE_VERSION: "24.15.0"
|
|
|
|
jobs:
|
|
run_package_telegram_e2e:
|
|
name: Run package Telegram E2E
|
|
runs-on: blacksmith-32vcpu-ubuntu-2404
|
|
continue-on-error: ${{ inputs.advisory }}
|
|
timeout-minutes: 60
|
|
environment: qa-live-shared
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
env:
|
|
DOCKER_BUILD_SUMMARY: "false"
|
|
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
|
steps:
|
|
- name: Checkout dispatch ref
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ inputs.harness_ref || github.sha }}
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
- name: Set up Blacksmith Docker Builder
|
|
uses: useblacksmith/setup-docker-builder@6ff44f8e5255f9d8aa31ef22f7e57a2d926b7da0 # v1
|
|
with:
|
|
max-cache-size-mb: 800000
|
|
|
|
- name: Build Docker E2E image
|
|
uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2
|
|
with:
|
|
context: .
|
|
file: ./scripts/e2e/Dockerfile
|
|
target: build
|
|
platforms: linux/amd64
|
|
tags: openclaw-docker-e2e:local
|
|
load: true
|
|
push: false
|
|
provenance: false
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Validate inputs and secrets
|
|
env:
|
|
PACKAGE_SPEC: ${{ inputs.package_spec }}
|
|
PACKAGE_ARTIFACT_DIGEST: ${{ inputs.package_artifact_digest || '' }}
|
|
PACKAGE_ARTIFACT_ID: ${{ inputs.package_artifact_id || '' }}
|
|
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || '' }}
|
|
PACKAGE_ARTIFACT_RUN_ATTEMPT: ${{ inputs.package_artifact_run_attempt || '' }}
|
|
PACKAGE_ARTIFACT_RUN_ID: ${{ inputs.package_artifact_run_id || '' }}
|
|
PACKAGE_FILE_NAME: ${{ inputs.package_file_name || '' }}
|
|
PACKAGE_SHA256: ${{ inputs.package_sha256 || '' }}
|
|
PACKAGE_SOURCE_SHA: ${{ inputs.package_source_sha || '' }}
|
|
PACKAGE_VERSION: ${{ inputs.package_version || '' }}
|
|
PROVIDER_MODE: ${{ inputs.provider_mode }}
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
|
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
artifact_tuple_present=0
|
|
for value in \
|
|
"$PACKAGE_ARTIFACT_DIGEST" \
|
|
"$PACKAGE_ARTIFACT_ID" \
|
|
"$PACKAGE_ARTIFACT_NAME" \
|
|
"$PACKAGE_ARTIFACT_RUN_ATTEMPT" \
|
|
"$PACKAGE_ARTIFACT_RUN_ID" \
|
|
"$PACKAGE_FILE_NAME" \
|
|
"$PACKAGE_SHA256" \
|
|
"$PACKAGE_SOURCE_SHA" \
|
|
"$PACKAGE_VERSION"; do
|
|
if [[ -n "${value// }" ]]; then
|
|
artifact_tuple_present=1
|
|
fi
|
|
done
|
|
if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then
|
|
if [[ "$artifact_tuple_present" == "1" ]]; then
|
|
echo "Artifact-backed Telegram E2E requires all artifact identity fields or none." >&2
|
|
exit 1
|
|
fi
|
|
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
|
|
exit 1
|
|
fi
|
|
elif [[ ! "$PACKAGE_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ ||
|
|
! "$PACKAGE_ARTIFACT_ID" =~ ^[1-9][0-9]*$ ||
|
|
! "$PACKAGE_ARTIFACT_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ||
|
|
! "$PACKAGE_ARTIFACT_RUN_ID" =~ ^[1-9][0-9]*$ ||
|
|
! "$PACKAGE_FILE_NAME" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*\.tgz$ ||
|
|
! "$PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ ||
|
|
! "$PACKAGE_SOURCE_SHA" =~ ^[0-9a-f]{40}$ ||
|
|
-z "${PACKAGE_VERSION// }" ]]; then
|
|
echo "Artifact-backed Telegram E2E requires the complete immutable artifact and package identity tuple." >&2
|
|
exit 1
|
|
fi
|
|
case "${PROVIDER_MODE}" in
|
|
mock-openai | live-frontier) ;;
|
|
*)
|
|
echo "provider_mode must be mock-openai or live-frontier; got: ${PROVIDER_MODE}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
require_var() {
|
|
local key="$1"
|
|
if [[ -z "${!key:-}" ]]; then
|
|
echo "Missing required ${key}." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
require_var OPENCLAW_QA_CONVEX_SITE_URL
|
|
require_var OPENCLAW_QA_CONVEX_SECRET_CI
|
|
if [[ "${PROVIDER_MODE}" == "live-frontier" ]]; then
|
|
require_var OPENAI_API_KEY
|
|
fi
|
|
|
|
- name: Validate package artifact identity
|
|
if: inputs.package_artifact_name != ''
|
|
env:
|
|
ARTIFACT_DIGEST: ${{ inputs.package_artifact_digest }}
|
|
ARTIFACT_ID: ${{ inputs.package_artifact_id }}
|
|
ARTIFACT_NAME: ${{ inputs.package_artifact_name }}
|
|
ARTIFACT_RUN_ATTEMPT: ${{ inputs.package_artifact_run_attempt }}
|
|
ARTIFACT_RUN_ID: ${{ inputs.package_artifact_run_id }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
artifact_json="$(gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}")"
|
|
jq -e \
|
|
--arg digest "sha256:${ARTIFACT_DIGEST}" \
|
|
--arg id "$ARTIFACT_ID" \
|
|
--arg name "$ARTIFACT_NAME" \
|
|
--arg run_id "$ARTIFACT_RUN_ID" \
|
|
'
|
|
(.id | tostring) == $id and
|
|
.name == $name and
|
|
.expired == false and
|
|
.digest == $digest and
|
|
(.workflow_run.id | tostring) == $run_id
|
|
' <<< "$artifact_json" >/dev/null || {
|
|
echo "Package Telegram artifact identity does not match the requested immutable tuple." >&2
|
|
exit 1
|
|
}
|
|
artifact_created_at="$(jq -er '.created_at | fromdateiso8601' <<< "$artifact_json")"
|
|
attempt_json="$(
|
|
gh api \
|
|
"repos/${GITHUB_REPOSITORY}/actions/runs/${ARTIFACT_RUN_ID}/attempts/${ARTIFACT_RUN_ATTEMPT}"
|
|
)"
|
|
jq -e \
|
|
--arg attempt "$ARTIFACT_RUN_ATTEMPT" \
|
|
--arg run_id "$ARTIFACT_RUN_ID" \
|
|
'(.id | tostring) == $run_id and
|
|
(.run_attempt | tostring) == $attempt' \
|
|
<<< "$attempt_json" >/dev/null || {
|
|
echo "Package Telegram artifact producer run attempt does not match the requested tuple." >&2
|
|
exit 1
|
|
}
|
|
attempt_started_at="$(jq -er '.run_started_at | fromdateiso8601' <<< "$attempt_json")"
|
|
if [[ "$ARTIFACT_RUN_ID" == "$GITHUB_RUN_ID" ]]; then
|
|
jq -e '(.status == "pending" or .status == "queued" or .status == "requested" or .status == "waiting" or .status == "in_progress") and .conclusion == null' \
|
|
<<< "$attempt_json" >/dev/null || {
|
|
echo "Current-run Package Telegram artifact is not from the active workflow attempt." >&2
|
|
exit 1
|
|
}
|
|
if (( artifact_created_at <= attempt_started_at )); then
|
|
echo "Package Telegram artifact predates the active producer run attempt." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
jq -e '.status == "completed" and .conclusion == "success"' \
|
|
<<< "$attempt_json" >/dev/null || {
|
|
echo "Completed Package Telegram artifact producer run attempt was not successful." >&2
|
|
exit 1
|
|
}
|
|
attempt_completed_at="$(jq -er '.updated_at | fromdateiso8601' <<< "$attempt_json")"
|
|
if (( artifact_created_at <= attempt_started_at || artifact_created_at > attempt_completed_at )); then
|
|
echo "Package Telegram artifact creation time is outside the declared producer run attempt." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
- name: Download package-under-test artifact
|
|
if: inputs.package_artifact_name != '' && inputs.package_artifact_run_id == github.run_id
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ inputs.package_artifact_id }}
|
|
path: .artifacts/telegram-package-under-test
|
|
run-id: ${{ inputs.package_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Download package-under-test artifact from release run
|
|
if: inputs.package_artifact_name != '' && inputs.package_artifact_run_id != github.run_id
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ inputs.package_artifact_id }}
|
|
path: .artifacts/telegram-package-under-test
|
|
run-id: ${{ inputs.package_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Run package Telegram E2E
|
|
id: run_lane
|
|
shell: bash
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
|
OPENCLAW_DOCKER_E2E_IMAGE: openclaw-docker-e2e:local
|
|
OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.package_spec }}
|
|
OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL: ${{ inputs.package_label }}
|
|
OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE: ${{ inputs.provider_mode }}
|
|
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE: convex
|
|
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE: ci
|
|
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
|
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
|
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "600000"
|
|
OPENCLAW_QA_SUITE_PROGRESS: "1"
|
|
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
|
INPUT_SCENARIO: ${{ inputs.scenario }}
|
|
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || '' }}
|
|
PACKAGE_FILE_NAME: ${{ inputs.package_file_name || '' }}
|
|
PACKAGE_SHA256: ${{ inputs.package_sha256 || '' }}
|
|
PACKAGE_SOURCE_SHA: ${{ inputs.package_source_sha || '' }}
|
|
PACKAGE_VERSION: ${{ inputs.package_version || '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
output_dir=".artifacts/qa-e2e/npm-telegram-beta-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
|
export OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR="${output_dir}"
|
|
|
|
append_telegram_summary() {
|
|
local status=$?
|
|
local report="${output_dir}/qa-suite-report.md"
|
|
if [[ -n "${GITHUB_STEP_SUMMARY:-}" && -f "${report}" ]]; then
|
|
{
|
|
echo "## Package Telegram E2E"
|
|
echo
|
|
echo "- Package: ${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL:-${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC}}"
|
|
echo "- Provider mode: ${OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE}"
|
|
echo
|
|
cat "${report}"
|
|
} >> "${GITHUB_STEP_SUMMARY}"
|
|
fi
|
|
return "${status}"
|
|
}
|
|
trap append_telegram_summary EXIT
|
|
|
|
if [[ -n "${PACKAGE_ARTIFACT_NAME// }" ]]; then
|
|
package_dir=".artifacts/telegram-package-under-test"
|
|
declared_package_tgz="${package_dir}/${PACKAGE_FILE_NAME}"
|
|
[[ -f "$declared_package_tgz" ]] || {
|
|
echo "Package Telegram artifact is missing the declared package tarball." >&2
|
|
exit 1
|
|
}
|
|
manifest="${package_dir}/preflight-manifest.json"
|
|
if [[ -f "${manifest}" ]]; then
|
|
package_tgz="$(
|
|
node - "${manifest}" "${package_dir}" <<'NODE'
|
|
const crypto = require("node:crypto");
|
|
const childProcess = require("node:child_process");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const [manifestPath, packageDir] = process.argv.slice(2);
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
const dependencyTarballs = Array.isArray(manifest.corePackageTarballs)
|
|
? manifest.corePackageTarballs
|
|
: manifest.corePackageTarballs === undefined && Array.isArray(manifest.dependencyTarballs)
|
|
? manifest.dependencyTarballs
|
|
: null;
|
|
if (!dependencyTarballs) {
|
|
throw new Error("package artifact manifest is missing dependency tarball metadata");
|
|
}
|
|
const entries = [
|
|
{
|
|
packageName: "openclaw",
|
|
packageVersion: manifest.packageVersion,
|
|
tarballName: manifest.tarballName,
|
|
tarballSha256: manifest.tarballSha256,
|
|
},
|
|
...dependencyTarballs,
|
|
];
|
|
const packageNames = new Set();
|
|
const tarballNames = new Set();
|
|
for (const entry of entries) {
|
|
if (
|
|
!entry.packageName ||
|
|
!entry.packageVersion ||
|
|
!entry.tarballName ||
|
|
!entry.tarballSha256 ||
|
|
entry.tarballName !== path.basename(entry.tarballName)
|
|
) {
|
|
throw new Error("package artifact manifest contains invalid tarball metadata");
|
|
}
|
|
if (packageNames.has(entry.packageName) || tarballNames.has(entry.tarballName)) {
|
|
throw new Error("package artifact manifest contains duplicate package metadata");
|
|
}
|
|
packageNames.add(entry.packageName);
|
|
tarballNames.add(entry.tarballName);
|
|
const tarballPath = path.join(packageDir, entry.tarballName);
|
|
if (!fs.existsSync(tarballPath)) {
|
|
throw new Error(`package artifact is missing ${entry.tarballName}`);
|
|
}
|
|
const digest = crypto.createHash("sha256").update(fs.readFileSync(tarballPath)).digest("hex");
|
|
if (digest !== entry.tarballSha256) {
|
|
throw new Error(`package artifact digest mismatch for ${entry.packageName}`);
|
|
}
|
|
const packageJson = JSON.parse(
|
|
childProcess.execFileSync("tar", ["-xOf", tarballPath, "package/package.json"], {
|
|
encoding: "utf8",
|
|
}),
|
|
);
|
|
if (packageJson.name !== entry.packageName || packageJson.version !== entry.packageVersion) {
|
|
throw new Error(`package artifact metadata mismatch for ${entry.packageName}`);
|
|
}
|
|
}
|
|
const artifactTarballs = fs
|
|
.readdirSync(packageDir, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".tgz"))
|
|
.map((entry) => entry.name);
|
|
if (
|
|
artifactTarballs.length !== tarballNames.size ||
|
|
artifactTarballs.some((name) => !tarballNames.has(name))
|
|
) {
|
|
throw new Error("package artifact tarball set does not match preflight manifest");
|
|
}
|
|
process.stdout.write(path.join(packageDir, manifest.tarballName));
|
|
NODE
|
|
)"
|
|
export OPENCLAW_NPM_TELEGRAM_PACKAGE_DIR="${package_dir}"
|
|
else
|
|
mapfile -t package_tgzs < <(find "${package_dir}" -type f -name "*.tgz" | sort)
|
|
if [[ "${#package_tgzs[@]}" -ne 1 ]]; then
|
|
echo "package artifact ${PACKAGE_ARTIFACT_NAME} without a preflight manifest must contain exactly one tgz; found ${#package_tgzs[@]}" >&2
|
|
exit 1
|
|
fi
|
|
package_tgz="${package_tgzs[0]}"
|
|
candidate_manifest="${package_dir}/package-candidate.json"
|
|
node - "${package_tgz}" "${candidate_manifest}" <<'NODE'
|
|
const crypto = require("node:crypto");
|
|
const childProcess = require("node:child_process");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const [tarballPath, manifestPath] = process.argv.slice(2);
|
|
const packageJson = JSON.parse(
|
|
childProcess.execFileSync("tar", ["-xOf", tarballPath, "package/package.json"], {
|
|
encoding: "utf8",
|
|
}),
|
|
);
|
|
if (packageJson.name !== "openclaw" || typeof packageJson.version !== "string") {
|
|
throw new Error("package artifact tgz is not a versioned openclaw package");
|
|
}
|
|
if (fs.existsSync(manifestPath)) {
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
const expectedTarballName = path.basename(manifest.tarball || "");
|
|
if (
|
|
manifest.name !== "openclaw" ||
|
|
manifest.version !== packageJson.version ||
|
|
expectedTarballName !== path.basename(tarballPath) ||
|
|
typeof manifest.sha256 !== "string"
|
|
) {
|
|
throw new Error("package candidate manifest does not match the OpenClaw tarball");
|
|
}
|
|
const digest = crypto.createHash("sha256").update(fs.readFileSync(tarballPath)).digest("hex");
|
|
if (digest !== manifest.sha256) {
|
|
throw new Error("package candidate digest mismatch");
|
|
}
|
|
}
|
|
NODE
|
|
fi
|
|
[[ "$(basename "$package_tgz")" == "$PACKAGE_FILE_NAME" ]] || {
|
|
echo "Package Telegram artifact tarball differs from package_file_name." >&2
|
|
exit 1
|
|
}
|
|
actual_package_sha256="$(sha256sum "$package_tgz" | awk '{print $1}')"
|
|
if [[ "$actual_package_sha256" != "$PACKAGE_SHA256" ]]; then
|
|
echo "Package Telegram artifact SHA-256 differs from package_sha256." >&2
|
|
exit 1
|
|
fi
|
|
actual_package_version="$(
|
|
tar -xOf "$package_tgz" package/package.json |
|
|
jq -er '.version | select(type == "string" and length > 0)'
|
|
)"
|
|
actual_package_source_sha="$(
|
|
tar -xOf "$package_tgz" package/dist/build-info.json |
|
|
jq -er '.commit | select(type == "string" and test("^[0-9a-f]{40}$"))'
|
|
)"
|
|
[[ "$actual_package_source_sha" == "$PACKAGE_SOURCE_SHA" &&
|
|
"$actual_package_version" == "$PACKAGE_VERSION" ]] || {
|
|
echo "Package Telegram artifact source SHA/version differs from the declared identity." >&2
|
|
exit 1
|
|
}
|
|
export OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ="${package_tgz}"
|
|
if [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
|
|
export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="$(basename "${package_tgz}")"
|
|
fi
|
|
elif [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
|
|
export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC}"
|
|
fi
|
|
|
|
if [[ -n "${INPUT_SCENARIO// }" ]]; then
|
|
export OPENCLAW_NPM_TELEGRAM_SCENARIOS="${INPUT_SCENARIO}"
|
|
fi
|
|
|
|
pnpm test:docker:npm-telegram-live
|
|
|
|
- name: Upload npm Telegram E2E artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: npm-telegram-beta-e2e-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: .artifacts/qa-e2e/
|
|
retention-days: 14
|
|
if-no-files-found: error
|