mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-04-17 03:21:00 +02:00
* fix: setup-trusted-publishers.sh works with real npm trust CLI Two issues found when running the script for the first time after #7490: 1. `npm trust github --file` wants ONLY the workflow filename basename (e.g. `test-and-release.yml`), not the full `.github/workflows/test-and-release.yml` path. npm errors out with "GitHub Actions workflow must be just a file not a path" otherwise. Constants updated. 2. `npm trust github` requires 2FA on accounts that have it enabled, and there is no way to disable that requirement. Add a `--otp <code>` pass-through flag and forward it to every call so a maintainer can batch-process multiple packages within a single TOTP window. Documented the limitation in the script header. Also reword the call site so the npm command line is built without shell-string round-tripping (passing $CMD through `$( $CMD )` was unrelated to this bug but was bad practice). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: setup-trusted-publishers.sh recognizes 409 as already-configured When --skip-existing is set, treat HTTP 409 Conflict from POST /-/package/<name>/trust as 'already configured' so re-runs of the bulk script don't fail on packages that were configured in a previous run. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: cover setup-trusted-publishers.sh, harden against set -e, document --otp Addresses qodo review on #7491: - Add backend regression test that shims `npm` on PATH and asserts `--file` is given the workflow basename (never a path), `--otp` is forwarded to every `npm trust github` call when supplied, and the loop survives a non-zero exit so `--skip-existing` can absorb 409 Conflict responses from the registry. - Wrap the `npm trust github` invocation in `set +e` / `set -e`. The `if configure_one` already shields the function from errexit in practice, but a future refactor moving the call site out of an `if` would silently reintroduce the bug — the explicit shim makes intent obvious and survives such refactors. - Document `--otp` and the 2FA / TOTP-expiry workflow in doc/npm-trusted-publishing.md so maintainers don't follow the docs and hit EOTP. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
170 lines
5.5 KiB
Bash
Executable File
170 lines
5.5 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# Configure npm Trusted Publishers (OIDC) for ep_etherpad and every
|
|
# ether/ep_* plugin in bulk.
|
|
#
|
|
# Prerequisites:
|
|
# - npm CLI >= 11.5.1 (the version that ships `npm trust github`)
|
|
# - Logged into npmjs.com as a maintainer of the packages: `npm login`
|
|
# - `gh` CLI logged in (only needed for plugin discovery; pass --packages
|
|
# to skip discovery and use a static list)
|
|
#
|
|
# Usage:
|
|
# bin/setup-trusted-publishers.sh # all ether/ep_* plugins + ep_etherpad
|
|
# bin/setup-trusted-publishers.sh --dry-run # print what would happen
|
|
# bin/setup-trusted-publishers.sh --packages ep_align,ep_webrtc
|
|
# bin/setup-trusted-publishers.sh --skip-existing # don't fail if already configured
|
|
# bin/setup-trusted-publishers.sh --otp 123456 # supply 2FA OTP up front
|
|
#
|
|
# Note: `npm trust github` requires 2FA. If your account has 2FA enabled
|
|
# (it should), pass --otp once and the same code will be reused for every
|
|
# package call inside the same minute. The TOTP code typically expires
|
|
# every 30s, so you may need to run the script in chunks via --packages.
|
|
#
|
|
# Each package gets a GitHub Actions trusted publisher pointing at the
|
|
# canonical workflow file used by that package family:
|
|
# - plugins: .github/workflows/test-and-release.yml
|
|
# - ep_etherpad: .github/workflows/releaseEtherpad.yml
|
|
#
|
|
# Existing configurations cannot be overwritten — only one trust relationship
|
|
# per package is allowed today. Use `--skip-existing` to ignore those failures.
|
|
|
|
set -eu
|
|
|
|
# `npm trust github --file` wants ONLY the workflow filename (basename),
|
|
# not the full .github/workflows/<name> path.
|
|
PLUGIN_WORKFLOW="test-and-release.yml"
|
|
CORE_WORKFLOW="releaseEtherpad.yml"
|
|
CORE_PACKAGE="ep_etherpad"
|
|
CORE_REPO="etherpad-lite"
|
|
ORG="ether"
|
|
|
|
DRY_RUN=0
|
|
SKIP_EXISTING=0
|
|
PACKAGES=""
|
|
OTP=""
|
|
|
|
usage() {
|
|
sed -n '2,/^$/p' "$0" | sed 's/^# \?//'
|
|
exit "${1:-0}"
|
|
}
|
|
|
|
# ---------- arg parsing ----------
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--dry-run) DRY_RUN=1; shift ;;
|
|
--skip-existing) SKIP_EXISTING=1; shift ;;
|
|
--packages) PACKAGES="$2"; shift 2 ;;
|
|
--otp) OTP="$2"; shift 2 ;;
|
|
-h|--help) usage 0 ;;
|
|
*) printf 'Unknown flag: %s\n' "$1" >&2; usage 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ---------- prereq checks ----------
|
|
is_cmd() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
is_cmd npm || { echo "npm CLI not found." >&2; exit 1; }
|
|
|
|
NPM_MAJOR=$(npm --version | cut -d. -f1)
|
|
NPM_MINOR=$(npm --version | cut -d. -f2)
|
|
NPM_PATCH=$(npm --version | cut -d. -f3)
|
|
if [ "$NPM_MAJOR" -lt 11 ] || \
|
|
{ [ "$NPM_MAJOR" -eq 11 ] && [ "$NPM_MINOR" -lt 5 ]; } || \
|
|
{ [ "$NPM_MAJOR" -eq 11 ] && [ "$NPM_MINOR" -eq 5 ] && [ "$NPM_PATCH" -lt 1 ]; }; then
|
|
echo "npm >= 11.5.1 required (you have $(npm --version)). Run: npm install -g npm@latest" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Verify auth (whoami fails if not logged in). Skipped in --dry-run.
|
|
if [ "$DRY_RUN" != "1" ]; then
|
|
if ! npm whoami >/dev/null 2>&1; then
|
|
echo "Not logged into npm. Run 'npm login' first." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# ---------- discover packages ----------
|
|
if [ -z "$PACKAGES" ]; then
|
|
is_cmd gh || {
|
|
echo "gh CLI not found. Either install it or pass --packages ep_a,ep_b,..." >&2
|
|
exit 1
|
|
}
|
|
echo "Discovering ether/ep_* repos..."
|
|
PACKAGES=$(gh repo list "$ORG" --limit 300 --json name,isArchived \
|
|
--jq '.[] | select(.name | startswith("ep_")) | select(.isArchived | not) | .name' \
|
|
| tr '\n' ',' | sed 's/,$//')
|
|
PACKAGES="${CORE_PACKAGE},${PACKAGES}"
|
|
fi
|
|
|
|
# ---------- per-package setup ----------
|
|
configure_one() {
|
|
PKG="$1"
|
|
if [ "$PKG" = "$CORE_PACKAGE" ]; then
|
|
REPO="$CORE_REPO"
|
|
WORKFLOW="$CORE_WORKFLOW"
|
|
else
|
|
REPO="$PKG"
|
|
WORKFLOW="$PLUGIN_WORKFLOW"
|
|
fi
|
|
|
|
printf '%-40s -> %s/%s @ %s\n' "$PKG" "$ORG" "$REPO" "$WORKFLOW"
|
|
|
|
if [ "$DRY_RUN" = "1" ]; then
|
|
printf ' (dry-run) would run: npm trust github %s --repository %s/%s --file %s --yes\n' \
|
|
"$PKG" "$ORG" "$REPO" "$WORKFLOW"
|
|
return 0
|
|
fi
|
|
|
|
# Disable -e around the npm call so a non-zero exit can never short-circuit
|
|
# the STATUS / --skip-existing handling below. In practice the wrapping
|
|
# `if configure_one` already suppresses errexit inside this function (POSIX
|
|
# errexit-in-conditional behaviour), but relying on that is fragile — anyone
|
|
# later refactoring the call site out of an `if` would silently reintroduce
|
|
# the bug. The explicit shim makes the intent obvious and survives such
|
|
# refactors.
|
|
set +e
|
|
if [ -n "$OTP" ]; then
|
|
OUTPUT=$(npm trust github "$PKG" --repository "$ORG/$REPO" --file "$WORKFLOW" --otp "$OTP" --yes 2>&1)
|
|
else
|
|
OUTPUT=$(npm trust github "$PKG" --repository "$ORG/$REPO" --file "$WORKFLOW" --yes 2>&1)
|
|
fi
|
|
STATUS=$?
|
|
set -e
|
|
if [ "$STATUS" -eq 0 ]; then
|
|
printf ' ok\n'
|
|
else
|
|
# The npm registry returns 409 Conflict when a trust relationship
|
|
# already exists (you can only have one per package today). Treat
|
|
# that as success when --skip-existing is set, alongside the older
|
|
# "already exists/configured" string match.
|
|
if [ "$SKIP_EXISTING" = "1" ] && \
|
|
echo "$OUTPUT" | grep -qiE "409 Conflict|already (exists|configured)"; then
|
|
printf ' already configured (skipped)\n'
|
|
return 0
|
|
fi
|
|
printf ' FAILED:\n%s\n' "$OUTPUT" | sed 's/^/ /'
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
FAILED=""
|
|
TOTAL=0
|
|
OK=0
|
|
IFS=','
|
|
for PKG in $PACKAGES; do
|
|
TOTAL=$((TOTAL + 1))
|
|
if configure_one "$PKG"; then
|
|
OK=$((OK + 1))
|
|
else
|
|
FAILED="$FAILED $PKG"
|
|
fi
|
|
done
|
|
unset IFS
|
|
|
|
printf '\n%d/%d packages configured\n' "$OK" "$TOTAL"
|
|
if [ -n "$FAILED" ]; then
|
|
printf 'Failed:%s\n' "$FAILED"
|
|
exit 1
|
|
fi
|