diff --git a/docs/help/testing.md b/docs/help/testing.md index f86373d78c0..f187ff094ef 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -112,7 +112,13 @@ runs the same lanes before release approval. live Telegram QA lane with that installed package as the SUT Gateway. - Defaults to `OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC=openclaw@beta`. - Uses the same Telegram env credentials or Convex credential source as - `pnpm openclaw qa telegram`. + `pnpm openclaw qa telegram`. For CI/release automation, set + `OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE=convex` plus + `OPENCLAW_QA_CONVEX_SITE_URL` and the role secret. If + `OPENCLAW_QA_CONVEX_SITE_URL` and a Convex role secret are present in CI, + the Docker wrapper selects Convex automatically. + - `OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE=ci|maintainer` overrides the shared + `OPENCLAW_QA_CREDENTIAL_ROLE` for this lane only. - `pnpm test:docker:bundled-channel-deps` - Packs and installs the current OpenClaw build in Docker, starts the Gateway with OpenAI configured, then enables bundled channel/plugins via config diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index 3df429a052f..2131c195297 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -88,9 +88,11 @@ OpenClaw has three public release lanes: `node --import tsx scripts/openclaw-npm-postpublish-verify.ts YYYY.M.D` (or the matching beta/correction version) to verify the published registry install path in a fresh temp prefix -- After a beta publish, run `OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC=openclaw@YYYY.M.D-beta.N pnpm test:docker:npm-telegram-live` +- After a beta publish, run `OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC=openclaw@YYYY.M.D-beta.N OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE=convex OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE=ci pnpm test:docker:npm-telegram-live` to verify installed-package onboarding, Telegram setup, and real Telegram E2E - against the published npm package. + against the published npm package using the shared leased Telegram credential + pool. Local maintainer one-offs may omit the Convex vars and pass the three + `OPENCLAW_QA_TELEGRAM_*` env credentials directly. - Maintainer release automation now uses preflight-then-promote: - real npm publish must pass a successful npm `preflight_run_id` - the real npm publish must be dispatched from the same `main` or diff --git a/scripts/e2e/npm-telegram-live-docker.sh b/scripts/e2e/npm-telegram-live-docker.sh index 7d9a320fd91..4969773cb92 100755 --- a/scripts/e2e/npm-telegram-live-docker.sh +++ b/scripts/e2e/npm-telegram-live-docker.sh @@ -9,6 +9,32 @@ DOCKER_TARGET="${OPENCLAW_NPM_TELEGRAM_DOCKER_TARGET:-build}" PACKAGE_SPEC="${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC:-openclaw@beta}" OUTPUT_DIR="${OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR:-.artifacts/qa-e2e/npm-telegram-live}" +resolve_credential_source() { + if [ -n "${OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE:-}" ]; then + printf "%s" "$OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE" + return 0 + fi + if [ -n "${OPENCLAW_QA_CREDENTIAL_SOURCE:-}" ]; then + printf "%s" "$OPENCLAW_QA_CREDENTIAL_SOURCE" + return 0 + fi + if [ -n "${CI:-}" ] && [ -n "${OPENCLAW_QA_CONVEX_SITE_URL:-}" ]; then + if [ -n "${OPENCLAW_QA_CONVEX_SECRET_CI:-}" ] || [ -n "${OPENCLAW_QA_CONVEX_SECRET_MAINTAINER:-}" ]; then + printf "convex" + fi + fi +} + +resolve_credential_role() { + if [ -n "${OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE:-}" ]; then + printf "%s" "$OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE" + return 0 + fi + if [ -n "${OPENCLAW_QA_CREDENTIAL_ROLE:-}" ]; then + printf "%s" "$OPENCLAW_QA_CREDENTIAL_ROLE" + fi +} + validate_openclaw_package_spec() { 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 @@ -24,6 +50,8 @@ docker_e2e_build_or_reuse "$IMAGE_NAME" npm-telegram-live "$ROOT_DIR/scripts/e2e mkdir -p "$ROOT_DIR/.artifacts/qa-e2e" run_log="$(mktemp "${TMPDIR:-/tmp}/openclaw-npm-telegram-live.XXXXXX")" +credential_source="$(resolve_credential_source)" +credential_role="$(resolve_credential_role)" docker_env=( -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 @@ -39,6 +67,13 @@ forward_env_if_set() { fi } +if [ -n "$credential_source" ]; then + docker_env+=(-e OPENCLAW_QA_CREDENTIAL_SOURCE="$credential_source") +fi +if [ -n "$credential_role" ]; then + docker_env+=(-e OPENCLAW_QA_CREDENTIAL_ROLE="$credential_role") +fi + for key in \ OPENAI_API_KEY \ ANTHROPIC_API_KEY \ @@ -50,8 +85,6 @@ for key in \ OPENCLAW_QA_TELEGRAM_GROUP_ID \ OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN \ OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN \ - OPENCLAW_QA_CREDENTIAL_SOURCE \ - OPENCLAW_QA_CREDENTIAL_ROLE \ OPENCLAW_QA_CONVEX_SITE_URL \ OPENCLAW_QA_CONVEX_SECRET_CI \ OPENCLAW_QA_CONVEX_SECRET_MAINTAINER \ diff --git a/scripts/e2e/npm-telegram-live-runner.ts b/scripts/e2e/npm-telegram-live-runner.ts index a01c3768796..4c0e02dce4b 100644 --- a/scripts/e2e/npm-telegram-live-runner.ts +++ b/scripts/e2e/npm-telegram-live-runner.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { pathToFileURL } from "node:url"; import { runTelegramQaLive } from "../../extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime.ts"; import { formatErrorMessage } from "../../src/infra/errors.ts"; @@ -17,6 +18,14 @@ function splitCsv(value: string | undefined) { .filter((entry) => entry.length > 0); } +function resolveCredentialSource(env: NodeJS.ProcessEnv) { + return env.OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE ?? env.OPENCLAW_QA_CREDENTIAL_SOURCE; +} + +function resolveCredentialRole(env: NodeJS.ProcessEnv) { + return env.OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE ?? env.OPENCLAW_QA_CREDENTIAL_ROLE; +} + async function resolveTrustedOpenClawCommand(rawCommand: string) { if (!path.isAbsolute(rawCommand)) { throw new Error("OPENCLAW_NPM_TELEGRAM_SUT_COMMAND must be an absolute path."); @@ -63,8 +72,8 @@ async function main() { fastMode: parseBoolean(process.env.OPENCLAW_NPM_TELEGRAM_FAST), scenarioIds: splitCsv(process.env.OPENCLAW_NPM_TELEGRAM_SCENARIOS), sutAccountId: process.env.OPENCLAW_NPM_TELEGRAM_SUT_ACCOUNT, - credentialSource: process.env.OPENCLAW_QA_CREDENTIAL_SOURCE, - credentialRole: process.env.OPENCLAW_QA_CREDENTIAL_ROLE, + credentialSource: resolveCredentialSource(process.env), + credentialRole: resolveCredentialRole(process.env), }); process.stdout.write(`NPM Telegram QA report: ${result.reportPath}\n`); @@ -78,7 +87,14 @@ async function main() { } } -main().catch((error) => { - process.stderr.write(`npm telegram live e2e failed: ${formatErrorMessage(error)}\n`); - process.exitCode = 1; -}); +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`npm telegram live e2e failed: ${formatErrorMessage(error)}\n`); + process.exitCode = 1; + }); +} + +export const __testing = { + resolveCredentialRole, + resolveCredentialSource, +}; diff --git a/test/scripts/npm-telegram-live.test.ts b/test/scripts/npm-telegram-live.test.ts new file mode 100644 index 00000000000..2ad08facb85 --- /dev/null +++ b/test/scripts/npm-telegram-live.test.ts @@ -0,0 +1,42 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; +import { __testing } from "../../scripts/e2e/npm-telegram-live-runner.ts"; + +const DOCKER_SCRIPT_PATH = "scripts/e2e/npm-telegram-live-docker.sh"; + +describe("npm Telegram live Docker E2E", () => { + it("supports npm-specific Convex credential aliases", () => { + const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8"); + + expect(script).toContain("OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE"); + expect(script).toContain("OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE"); + expect(script).toContain('docker_env+=(-e OPENCLAW_QA_CREDENTIAL_SOURCE="$credential_source")'); + expect(script).toContain('docker_env+=(-e OPENCLAW_QA_CREDENTIAL_ROLE="$credential_role")'); + }); + + it("defaults CI runs to Convex when broker credentials are present", () => { + const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8"); + + expect(script).toContain( + 'if [ -n "${CI:-}" ] && [ -n "${OPENCLAW_QA_CONVEX_SITE_URL:-}" ]; then', + ); + expect(script).toContain("OPENCLAW_QA_CONVEX_SECRET_CI"); + expect(script).toContain("OPENCLAW_QA_CONVEX_SECRET_MAINTAINER"); + expect(script).toContain('printf "convex"'); + }); + + it("lets npm-specific credential aliases override shared QA env", () => { + expect( + __testing.resolveCredentialSource({ + OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE: "convex", + OPENCLAW_QA_CREDENTIAL_SOURCE: "env", + }), + ).toBe("convex"); + expect( + __testing.resolveCredentialRole({ + OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE: "ci", + OPENCLAW_QA_CREDENTIAL_ROLE: "maintainer", + }), + ).toBe("ci"); + }); +});