Files
dingtalk-workspace-cli/verify/verify-all-channels.sh
修雨 3e9e76df2c feat: add stable and beta Homebrew channels (#613)
* feat: add stable and beta Homebrew channels

* fix: align beta formula with tap conventions

* fix: use dedicated token for Homebrew PRs

* chore: minimize release token permissions

* docs: record Homebrew token setup

* docs: keep Homebrew automation token long-lived

* fix(verify): assert channel versions and prove homebrew coexistence

The six-channel verifier previously ran `dws version` without comparing
it to the version each channel advertises, so a stale or wrong binary
still reported PASS. It also uninstalled stable before installing beta,
which could not prove the keg-only beta coexists with stable.

- smoke() now takes an expected version and fails the channel on mismatch
- npm/homebrew derive the expected version from the package manager;
  curl/upgrade derive it from the latest GitHub release tag
- homebrew installs keg-only beta while stable stays installed, then
  asserts stable's version, binary SHA and PATH link are unchanged
- cleanup uninstalls both script-installed formulae, still refusing to
  touch a pre-existing user install
- add regression tests for version assertion and coexistence semantics

* style(formula): satisfy brew style for stable and beta formulae

- reword desc so it no longer starts with the formula name
- drop the unnecessary `require "fileutils"` and use the mixed-in cp_r
  instead of the FileUtils. qualifier

* fix(verify): compare channel versions exactly

* fix(homebrew): keep generated formulae style-clean

* fix(homebrew): sync formulae with latest releases
2026-07-15 10:16:26 +08:00

235 lines
8.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Verify public DWS delivery channels without replacing the caller's dws.
#
# Every channel installs into an isolated location, asserts that the binary
# reports the version the channel advertises, runs a smoke test, then cleans
# up. A wrong or stale version fails the channel instead of reporting PASS.
set -uo pipefail
REPO="${DWS_VERIFY_REPO:-DingTalk-Real-AI/dingtalk-workspace-cli}"
TAP="${DWS_VERIFY_HOMEBREW_TAP:-DingTalk-Real-AI/dingtalk-workspace-cli}"
TAP_REPO_URL="${DWS_VERIFY_HOMEBREW_REPO_URL:-https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git}"
PACKAGE="${DWS_VERIFY_NPM_PACKAGE:-dingtalk-workspace-cli}"
ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dws-six-channel-XXXXXX")"
RESULTS="$ROOT/results"
mkdir -p "$RESULTS"
cleanup() { rm -rf "$ROOT"; }
trap cleanup EXIT INT TERM
pass() { printf 'PASS\t%s\t%s\n' "$1" "$2" > "$RESULTS/$1"; }
fail() { printf 'FAIL\t%s\t%s\n' "$1" "$2" > "$RESULTS/$1"; }
skip() { printf 'SKIP\t%s\t%s\n' "$1" "$2" > "$RESULTS/$1"; }
run_step() {
name="$1"
shift
printf '\n==> %s\n' "$name"
if "$@"; then
pass "$name" "install, version assertion and smoke test succeeded"
else
status=$?
fail "$name" "command failed with exit code $status"
fi
}
# smoke <binary> <expected_version>
# Runs `dws version --format json` and requires an exact version match. Missing
# package-manager metadata is a channel failure because it cannot prove that the
# installed binary came from the advertised channel.
smoke() {
local binary="$1"
local expected="${2:-}"
local version_output
local reported
test -x "$binary" || { printf 'binary not executable: %s\n' "$binary" >&2; return 1; }
if [[ -z "$expected" ]]; then
printf 'expected version is empty; cannot verify channel provenance\n' >&2
return 1
fi
version_output="$("$binary" version --format json 2>&1)" || {
printf 'dws version failed for %s\n' "$binary" >&2
return 1
}
printf 'version output: %s\n' "$version_output"
reported="$(
printf '%s\n' "$version_output" \
| sed -nE 's/.*"version"[[:space:]]*:[[:space:]]*"v?([^\"]+)".*/\1/p' \
| head -n1
)"
if [[ -z "$reported" ]]; then
printf 'could not parse version from: %s\n' "$version_output" >&2
return 1
fi
expected="${expected#v}"
if [[ "$reported" != "$expected" ]]; then
printf 'version mismatch: expected %s, got %s\n' "$expected" "$reported" >&2
return 1
fi
"$binary" --help >/dev/null
}
# Latest stable release tag drives the curl and dws-upgrade assertions.
resolve_latest_version() {
tag="$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" 2>/dev/null \
| grep -m1 '"tag_name"' \
| sed -E 's/.*"tag_name" *: *"([^"]+)".*/\1/')"
printf '%s' "${tag#v}"
}
LATEST_VERSION="$(resolve_latest_version)"
verify_curl() (
set -e
home="$ROOT/curl/home"
bin="$ROOT/curl/bin"
mkdir -p "$home" "$bin"
HOME="$home" DWS_INSTALL_DIR="$bin" DWS_NO_SKILLS=1 \
bash <(curl -fsSL "https://raw.githubusercontent.com/$REPO/main/scripts/install.sh")
smoke "$bin/dws" "$LATEST_VERSION"
rm -f "$bin/dws"
)
verify_powershell() (
set -e
home="$ROOT/powershell/home"
bin="$ROOT/powershell/bin"
mkdir -p "$home" "$bin"
HOME="$home" DWS_INSTALL_DIR="$bin" DWS_NO_SKILLS=1 pwsh -NoLogo -NoProfile -Command \
"Invoke-RestMethod 'https://raw.githubusercontent.com/$REPO/main/scripts/install.ps1' | Invoke-Expression"
smoke "$bin/dws.exe" "$LATEST_VERSION"
rm -f "$bin/dws.exe"
)
verify_npm() (
set -e
tag="$1"
home="$ROOT/npm-$tag/home"
prefix="$ROOT/npm-$tag/prefix"
cache="$ROOT/npm-$tag/cache"
mkdir -p "$home" "$prefix" "$cache"
expected="$(HOME="$home" npm_config_cache="$cache" npm view "$PACKAGE@$tag" version 2>/dev/null | tail -n1)"
HOME="$home" npm_config_prefix="$prefix" npm_config_cache="$cache" \
npm uninstall -g "$PACKAGE" >/dev/null 2>&1 || true
HOME="$home" npm_config_prefix="$prefix" npm_config_cache="$cache" \
npm install -g "$PACKAGE@$tag"
smoke "$prefix/bin/dws" "$expected"
HOME="$home" npm_config_prefix="$prefix" npm_config_cache="$cache" \
npm uninstall -g "$PACKAGE" >/dev/null
)
verify_homebrew() (
set -e
installed_formulae=()
added_tap=0
cleanup_brew() {
for formula in "${installed_formulae[@]:-}"; do
[[ -n "$formula" ]] && brew uninstall "$formula" >/dev/null 2>&1 || true
done
[[ "$added_tap" == "1" ]] && brew untap "$TAP" >/dev/null 2>&1 || true
}
trap cleanup_brew EXIT INT TERM
# Never touch a Homebrew install the caller already owns.
for existing_formula in "$PACKAGE" "$PACKAGE-beta"; do
if brew list --formula "$existing_formula" >/dev/null 2>&1; then
printf 'Refusing to remove the existing Homebrew installation of %s.\n' "$existing_formula" >&2
return 1
fi
done
if ! brew tap | grep -Fx "$TAP" >/dev/null 2>&1; then
brew tap "$TAP" "$TAP_REPO_URL"
added_tap=1
fi
# Install stable and record its state before beta lands.
brew install "$TAP/$PACKAGE"
installed_formulae+=("$PACKAGE")
stable_version="$(brew list --versions "$PACKAGE" | awk '{print $2}')"
stable_bin="$(brew --prefix "$PACKAGE")/bin/dws"
linked_dws="$(brew --prefix)/bin/dws"
smoke "$stable_bin" "$stable_version"
stable_sha_before="$(shasum -a 256 "$stable_bin" | awk '{print $1}')"
stable_link_before="$(readlink "$linked_dws" 2>/dev/null || true)"
# Install keg-only beta alongside the still-present stable formula.
brew install "$TAP/$PACKAGE-beta"
installed_formulae+=("$PACKAGE-beta")
beta_version="$(brew list --versions "$PACKAGE-beta" | awk '{print $2}')"
smoke "$(brew --prefix "$PACKAGE-beta")/bin/dws" "$beta_version"
# Coexistence: the beta install must not disturb stable.
if ! brew list --formula "$PACKAGE" >/dev/null 2>&1; then
printf 'stable formula disappeared after installing beta\n' >&2
return 1
fi
stable_version_after="$(brew list --versions "$PACKAGE" | awk '{print $2}')"
stable_sha_after="$(shasum -a 256 "$stable_bin" | awk '{print $1}')"
stable_link_after="$(readlink "$linked_dws" 2>/dev/null || true)"
if [[ "$stable_version_after" != "$stable_version" ]]; then
printf 'stable version changed after beta install: %s -> %s\n' "$stable_version" "$stable_version_after" >&2
return 1
fi
if [[ "$stable_sha_after" != "$stable_sha_before" ]]; then
printf 'stable binary changed after beta install\n' >&2
return 1
fi
if [[ "$stable_link_after" != "$stable_link_before" ]]; then
printf 'stable link changed after beta install: %s -> %s\n' "$stable_link_before" "$stable_link_after" >&2
return 1
fi
# The linked dws on PATH must still be stable, not the keg-only beta.
smoke "$linked_dws" "$stable_version"
)
verify_upgrade() (
set -e
home="$ROOT/upgrade/home"
bin="$ROOT/upgrade/bin"
mkdir -p "$home" "$bin"
HOME="$home" DWS_INSTALL_DIR="$bin" DWS_NO_SKILLS=1 \
bash <(curl -fsSL "https://raw.githubusercontent.com/$REPO/main/scripts/install.sh")
printf 'before: %s\n' "$($bin/dws version --format json)"
HOME="$home" "$bin/dws" upgrade --force --skip-skills -y
printf 'after: %s\n' "$($bin/dws version --format json)"
smoke "$bin/dws" "$LATEST_VERSION"
rm -f "$bin/dws"
)
run_step curl verify_curl
if [[ "${OS:-}" == "Windows_NT" ]] && command -v pwsh >/dev/null 2>&1; then
run_step powershell verify_powershell
else
skip powershell "requires native Windows and pwsh"
fi
if command -v npm >/dev/null 2>&1; then
run_step npm-stable verify_npm latest
run_step npm-beta verify_npm beta
else
skip npm-stable "npm is not installed"
skip npm-beta "npm is not installed"
fi
if [[ "$(uname -s)" == "Darwin" ]] && command -v brew >/dev/null 2>&1; then
run_step homebrew verify_homebrew
else
skip homebrew "requires macOS and Homebrew"
fi
run_step dws-upgrade verify_upgrade
printf '\n%-8s %-14s %s\n' STATUS CHANNEL DETAIL
printf '%s\n' '-------- -------------- ----------------------------------------------'
failed=0
for name in curl powershell npm-stable npm-beta homebrew dws-upgrade; do
IFS=$'\t' read -r status channel detail < "$RESULTS/$name"
printf '%-8s %-14s %s\n' "$status" "$channel" "$detail"
[[ "$status" == "FAIL" ]] && failed=1
done
exit "$failed"