|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" |
| 5 | +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)" |
| 6 | +DEFAULT_ROOT="/tmp/openclaw-weixin-cli-repro" |
| 7 | +if [[ -d /data/tmp || -w /data ]]; then |
| 8 | + DEFAULT_ROOT="/data/tmp/openclaw-weixin-cli-repro" |
| 9 | +fi |
| 10 | + |
| 11 | +STATE_ROOT="${OPENCLAW_WEIXIN_REPRO_ROOT:-$DEFAULT_ROOT}" |
| 12 | +PLUGIN_SPEC="${OPENCLAW_WEIXIN_PLUGIN_SPEC:-@tencent-weixin/openclaw-weixin@2.1.7}" |
| 13 | +PLUGIN_TGZ="" |
| 14 | +PAIRING_CHANNEL="feishu" |
| 15 | +PAIRING_CODE="BAH8YVB3" |
| 16 | +CHANNEL_ID="openclaw-weixin" |
| 17 | +TIMEOUT_ONBOARD=30 |
| 18 | +TIMEOUT_PAIRING=30 |
| 19 | +TIMEOUT_LOGIN=30 |
| 20 | +SKIP_BUILD=0 |
| 21 | + |
| 22 | +usage() { |
| 23 | + cat <<'USAGE' |
| 24 | +Usage: |
| 25 | + bash scripts/repro/verify-weixin-plugin-cli.sh [options] |
| 26 | +
|
| 27 | +Options: |
| 28 | + --repo <path> Repo/worktree to validate. Default: current repo root. |
| 29 | + --state-root <path> Temp run root. Default: /data/tmp/openclaw-weixin-cli-repro when available, else /tmp/openclaw-weixin-cli-repro. |
| 30 | + --plugin-spec <spec> npm spec for the plugin. Default: @tencent-weixin/[email protected] |
| 31 | + --plugin-tgz <path> Use a local plugin tarball instead of npm pack. |
| 32 | + --pairing-channel <id> Pairing channel for the approve smoke. Default: feishu |
| 33 | + --pairing-code <code> Pairing code for the approve smoke. Default: BAH8YVB3 |
| 34 | + --channel-id <id> Channel id for login smoke. Default: openclaw-weixin |
| 35 | + --timeout-onboard <sec> Timeout for onboard smoke. Default: 30 |
| 36 | + --timeout-pairing <sec> Timeout for pairing smoke. Default: 30 |
| 37 | + --timeout-login <sec> Timeout for channel login smoke. Default: 30 |
| 38 | + --skip-build Skip `pnpm build` before running probes. |
| 39 | + -h, --help Show this help. |
| 40 | +
|
| 41 | +Examples: |
| 42 | + bash scripts/repro/verify-weixin-plugin-cli.sh |
| 43 | + bash scripts/repro/verify-weixin-plugin-cli.sh --repo /data/worktrees/openclaw-main-weixin-repro --skip-build |
| 44 | + bash scripts/repro/verify-weixin-plugin-cli.sh --plugin-spec @tencent-weixin/[email protected] |
| 45 | + bash scripts/repro/verify-weixin-plugin-cli.sh --plugin-tgz /tmp/openclaw-weixin-bad.tgz |
| 46 | +USAGE |
| 47 | +} |
| 48 | + |
| 49 | +log() { |
| 50 | + local level="$1" |
| 51 | + shift |
| 52 | + printf '[weixin-repro][%s] %s\n' "$level" "$*" |
| 53 | +} |
| 54 | + |
| 55 | +fail() { |
| 56 | + log ERROR "$*" |
| 57 | + exit 1 |
| 58 | +} |
| 59 | + |
| 60 | +require_cmd() { |
| 61 | + command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1" |
| 62 | +} |
| 63 | + |
| 64 | +while [[ $# -gt 0 ]]; do |
| 65 | + case "$1" in |
| 66 | + --repo) |
| 67 | + REPO_ROOT="$2" |
| 68 | + shift 2 |
| 69 | + ;; |
| 70 | + --state-root) |
| 71 | + STATE_ROOT="$2" |
| 72 | + shift 2 |
| 73 | + ;; |
| 74 | + --plugin-spec) |
| 75 | + PLUGIN_SPEC="$2" |
| 76 | + shift 2 |
| 77 | + ;; |
| 78 | + --plugin-tgz) |
| 79 | + PLUGIN_TGZ="$2" |
| 80 | + shift 2 |
| 81 | + ;; |
| 82 | + --pairing-channel) |
| 83 | + PAIRING_CHANNEL="$2" |
| 84 | + shift 2 |
| 85 | + ;; |
| 86 | + --pairing-code) |
| 87 | + PAIRING_CODE="$2" |
| 88 | + shift 2 |
| 89 | + ;; |
| 90 | + --channel-id) |
| 91 | + CHANNEL_ID="$2" |
| 92 | + shift 2 |
| 93 | + ;; |
| 94 | + --timeout-onboard) |
| 95 | + TIMEOUT_ONBOARD="$2" |
| 96 | + shift 2 |
| 97 | + ;; |
| 98 | + --timeout-pairing) |
| 99 | + TIMEOUT_PAIRING="$2" |
| 100 | + shift 2 |
| 101 | + ;; |
| 102 | + --timeout-login) |
| 103 | + TIMEOUT_LOGIN="$2" |
| 104 | + shift 2 |
| 105 | + ;; |
| 106 | + --skip-build) |
| 107 | + SKIP_BUILD=1 |
| 108 | + shift |
| 109 | + ;; |
| 110 | + -h|--help) |
| 111 | + usage |
| 112 | + exit 0 |
| 113 | + ;; |
| 114 | + *) |
| 115 | + fail "unknown option: $1" |
| 116 | + ;; |
| 117 | + esac |
| 118 | +done |
| 119 | + |
| 120 | +require_cmd npm |
| 121 | +require_cmd pnpm |
| 122 | +require_cmd rg |
| 123 | +require_cmd script |
| 124 | +require_cmd tar |
| 125 | +require_cmd timeout |
| 126 | + |
| 127 | +[[ -d "$REPO_ROOT" ]] || fail "repo not found: $REPO_ROOT" |
| 128 | +[[ -f "$REPO_ROOT/package.json" ]] || fail "repo package.json not found: $REPO_ROOT/package.json" |
| 129 | +if [[ -n "$PLUGIN_TGZ" && ! -f "$PLUGIN_TGZ" ]]; then |
| 130 | + fail "plugin tarball not found: $PLUGIN_TGZ" |
| 131 | +fi |
| 132 | + |
| 133 | +RUN_ID="$(basename "$REPO_ROOT")-$(date +%Y%m%d-%H%M%S)" |
| 134 | +RUN_ROOT="$STATE_ROOT/$RUN_ID" |
| 135 | +STATE_DIR="$RUN_ROOT/state" |
| 136 | +PKG_DIR="$RUN_ROOT/pkg" |
| 137 | +PLUGIN_DIR="$STATE_DIR/extensions/openclaw-weixin" |
| 138 | +CONFIG_PATH="$STATE_DIR/openclaw.json" |
| 139 | + |
| 140 | +mkdir -p "$RUN_ROOT" "$STATE_DIR" "$PKG_DIR" "$PLUGIN_DIR" |
| 141 | + |
| 142 | +log INFO "repo=$REPO_ROOT" |
| 143 | +log INFO "run_root=$RUN_ROOT" |
| 144 | +if [[ -n "$PLUGIN_TGZ" ]]; then |
| 145 | + log INFO "plugin_source=tarball:$PLUGIN_TGZ" |
| 146 | +else |
| 147 | + log INFO "plugin_spec=$PLUGIN_SPEC" |
| 148 | +fi |
| 149 | + |
| 150 | +if [[ ! -d "$REPO_ROOT/node_modules" ]]; then |
| 151 | + log INFO "node_modules missing; running pnpm install" |
| 152 | + (cd "$REPO_ROOT" && pnpm install) |
| 153 | +fi |
| 154 | + |
| 155 | +if [[ "$SKIP_BUILD" -eq 0 ]]; then |
| 156 | + log INFO "running pnpm build" |
| 157 | + (cd "$REPO_ROOT" && pnpm build) |
| 158 | +else |
| 159 | + log INFO "skipping pnpm build" |
| 160 | +fi |
| 161 | + |
| 162 | +if [[ -n "$PLUGIN_TGZ" ]]; then |
| 163 | + log INFO "using plugin tarball: $PLUGIN_TGZ" |
| 164 | + tar -xzf "$PLUGIN_TGZ" -C "$PLUGIN_DIR" --strip-components=1 |
| 165 | +else |
| 166 | + log INFO "packing plugin via npm: $PLUGIN_SPEC" |
| 167 | + (cd "$PKG_DIR" && npm pack "$PLUGIN_SPEC" >/dev/null) |
| 168 | + PACKED_TGZ="$(find "$PKG_DIR" -maxdepth 1 -type f -name '*.tgz' | sort | tail -n 1)" |
| 169 | + [[ -n "$PACKED_TGZ" ]] || fail "npm pack did not produce a tarball" |
| 170 | + tar -xzf "$PACKED_TGZ" -C "$PLUGIN_DIR" --strip-components=1 |
| 171 | +fi |
| 172 | + |
| 173 | +log INFO "installing plugin runtime dependencies" |
| 174 | +(cd "$PLUGIN_DIR" && npm install --omit=dev) |
| 175 | + |
| 176 | +if ! rg -n 'openclaw/plugin-sdk/command-auth' "$PLUGIN_DIR" >/dev/null; then |
| 177 | + fail "plugin does not import openclaw/plugin-sdk/command-auth; check the plugin version" |
| 178 | +fi |
| 179 | + |
| 180 | +cat > "$CONFIG_PATH" <<CONFIG |
| 181 | +{ |
| 182 | + "plugins": { |
| 183 | + "load": { |
| 184 | + "paths": ["$PLUGIN_DIR"] |
| 185 | + } |
| 186 | + } |
| 187 | +} |
| 188 | +CONFIG |
| 189 | + |
| 190 | +COMMON_BAD_PATTERN='Maximum call stack size exceeded|Failed to read config.*RangeError|failed to load .*Maximum call stack size exceeded' |
| 191 | + |
| 192 | +run_probe() { |
| 193 | + local name="$1" |
| 194 | + local timeout_secs="$2" |
| 195 | + local expected_status="$3" |
| 196 | + local required_pattern="$4" |
| 197 | + local fallback_pattern="$5" |
| 198 | + shift 5 |
| 199 | + local logfile="$RUN_ROOT/${name}.log" |
| 200 | + local rendered_cmd |
| 201 | + rendered_cmd="$(printf '%q ' "$@")" |
| 202 | + |
| 203 | + log INFO "probe=$name timeout=${timeout_secs}s" |
| 204 | + set +e |
| 205 | + ( |
| 206 | + cd "$REPO_ROOT" |
| 207 | + env \ |
| 208 | + OPENCLAW_STATE_DIR="$STATE_DIR" \ |
| 209 | + OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1 \ |
| 210 | + OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1 \ |
| 211 | + script -q -e -c "$rendered_cmd" /dev/null |
| 212 | + ) >"$logfile" 2>&1 |
| 213 | + local status=$? |
| 214 | + set -e |
| 215 | + |
| 216 | + log INFO "probe=$name exit=$status log=$logfile" |
| 217 | + |
| 218 | + if rg -n "$COMMON_BAD_PATTERN" "$logfile" >/dev/null; then |
| 219 | + sed -n '1,220p' "$logfile" |
| 220 | + fail "probe '$name' hit the recursion/stack-overflow signature" |
| 221 | + fi |
| 222 | + |
| 223 | + if ! rg -n "$required_pattern" "$logfile" >/dev/null; then |
| 224 | + if [[ -n "$fallback_pattern" ]] && rg -n "$fallback_pattern" "$logfile" >/dev/null; then |
| 225 | + : |
| 226 | + else |
| 227 | + sed -n '1,220p' "$logfile" |
| 228 | + fail "probe '$name' did not emit the expected success/fallback markers" |
| 229 | + fi |
| 230 | + fi |
| 231 | + |
| 232 | + case "$expected_status" in |
| 233 | + zero-or-timeout) |
| 234 | + if [[ "$status" -ne 0 && "$status" -ne 124 && "$status" -ne 143 ]]; then |
| 235 | + sed -n '1,220p' "$logfile" |
| 236 | + fail "probe '$name' expected exit 0, 124, or 143, got $status" |
| 237 | + fi |
| 238 | + ;; |
| 239 | + one) |
| 240 | + if [[ "$status" -ne 1 ]]; then |
| 241 | + sed -n '1,220p' "$logfile" |
| 242 | + fail "probe '$name' expected exit 1, got $status" |
| 243 | + fi |
| 244 | + ;; |
| 245 | + one-or-timeout) |
| 246 | + if [[ "$status" -ne 1 && "$status" -ne 124 && "$status" -ne 143 ]]; then |
| 247 | + sed -n '1,220p' "$logfile" |
| 248 | + fail "probe '$name' expected exit 1, 124, or 143, got $status" |
| 249 | + fi |
| 250 | + ;; |
| 251 | + *) |
| 252 | + fail "internal error: unknown expected status policy '$expected_status'" |
| 253 | + ;; |
| 254 | + esac |
| 255 | +} |
| 256 | + |
| 257 | +run_probe \ |
| 258 | + onboard \ |
| 259 | + "$TIMEOUT_ONBOARD" \ |
| 260 | + zero-or-timeout \ |
| 261 | + 'OpenClaw setup|Security warning|I understand this is personal-by-default' \ |
| 262 | + 'node scripts/run-node\.mjs onboard|OpenClaw' \ |
| 263 | + timeout "${TIMEOUT_ONBOARD}s" pnpm openclaw onboard |
| 264 | + |
| 265 | +run_probe \ |
| 266 | + pairing_approve \ |
| 267 | + "$TIMEOUT_PAIRING" \ |
| 268 | + one-or-timeout \ |
| 269 | + "No pending pairing request found for code: ${PAIRING_CODE}" \ |
| 270 | + 'node scripts/run-node\.mjs pairing approve|OpenClaw' \ |
| 271 | + timeout "${TIMEOUT_PAIRING}s" pnpm openclaw pairing approve "$PAIRING_CHANNEL" "$PAIRING_CODE" |
| 272 | + |
| 273 | +run_probe \ |
| 274 | + channels_login \ |
| 275 | + "$TIMEOUT_LOGIN" \ |
| 276 | + zero-or-timeout \ |
| 277 | + '正在启动微信扫码登录|使用微信扫描以下二维码|如果二维码未能成功展示|等待连接结果|Gateway online' \ |
| 278 | + 'node scripts/run-node\.mjs channels login --channel|OpenClaw' \ |
| 279 | + timeout "${TIMEOUT_LOGIN}s" pnpm openclaw channels login --channel "$CHANNEL_ID" |
| 280 | + |
| 281 | +log INFO "all probes passed" |
| 282 | +log INFO "artifacts kept under: $RUN_ROOT" |
0 commit comments