Files
Ekklesia/scripts/test-package-publication.sh
ekklesiaandClaude Opus 5.5 2d861c6bf0
CI / verify (pull_request) Successful in 1m17s
CI / infrastructure-smoke (pull_request) Successful in 31s
CI / package-release (pull_request) Successful in 49s
CI / image-build (pull_request) Successful in 59s
CI / migration-smoke (pull_request) Successful in 2m16s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / package-publication (pull_request) Successful in 56s
CI / deploy-smoke (split) (pull_request) Successful in 2m39s
chore: publish and document packages at the registry's current Zayin location
The package registry moved with the repositories to
git.zayin.net/api/packages/Zayin/npm/; the old River_of_Glory_Church address
now answers 502, so the next manual Publish packages run would have failed.
Point the workflow, the release and module-development docs, and the
disposable-Gitea publication proof's owner at Zayin. The CI Docker label keys
keep their names: they are identifiers the sweep matches on, not hosts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 22:23:52 -05:00

137 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# The package train publication proof (ADR 0053), against a disposable Gitea of the same version as
# the real registry, never the real one: the publication tool publishes one exact release train,
# and the suite proves the round trip, partial-train recovery, the hard stop on a burned version,
# dist-tag convergence, and that the registry's public owner serves packages anonymously while only
# the publisher team may publish.
#
# bash scripts/test-package-publication.sh [--release <release directory>]
#
# With --release, it publishes that release (CI hands it the package-release job's artifact).
# Without, it builds one from a worktree of HEAD, so local changes to the tool are tested against a
# release of the committed packages.
set -Eeuo pipefail
repository_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repository_root"
# shellcheck source=scripts/lib/compose-job-network.sh
source "$repository_root/scripts/lib/compose-job-network.sh"
# shellcheck source=scripts/lib/compose-diagnostics.sh
source "$repository_root/scripts/lib/compose-diagnostics.sh"
# shellcheck source=scripts/lib/ci-project.sh
source "$repository_root/scripts/lib/ci-project.sh"
fail() {
echo "package publication proof failed: $*" >&2
exit 1
}
release=""
while (($#)); do
case "$1" in
--release)
release="${2:-}"
shift 2 || fail "--release needs a directory"
;;
*) fail "usage: test-package-publication.sh [--release <release directory>]" ;;
esac
done
command -v docker >/dev/null || fail "Docker is required"
docker compose version >/dev/null 2>&1 || fail "Docker Compose is required"
compose_job_network_initialize || fail "the current Docker job container could not be identified"
project="$(ci_project_name package-registry)"
work="$(mktemp -d "${TMPDIR:-/tmp}/ekklesia-package-publication-XXXXXX")"
worktree="$work/release-checkout"
if [[ "$COMPOSE_JOB_NETWORK_RUNNING_IN_CONTAINER" == "true" ]]; then
export GITEA_PORT="0" GITEA_ROOT_URL="http://gitea:3000/"
else
GITEA_PORT="$(node -e 'const s = require("net").createServer().listen(0, "127.0.0.1", () => { console.log(s.address().port); s.close(); })')"
export GITEA_PORT GITEA_ROOT_URL="http://127.0.0.1:$GITEA_PORT/"
fi
compose=(docker compose --project-name "$project" --file scripts/lib/package-registry.compose.yaml)
cleanup() {
local exit_status=$?
trap - EXIT INT TERM
compose_job_network_disconnect || exit_status=1
"${compose[@]}" down --volumes --remove-orphans --timeout 10 >/dev/null 2>&1 || exit_status=1
if [[ -d "$worktree" ]]; then
git -C "$repository_root" worktree remove --force "$worktree" >/dev/null 2>&1 || true
fi
rm -rf "$work"
exit "$exit_status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
if [[ -z "$release" ]]; then
echo "==> Building a release of HEAD to publish"
store="$(dirname "$(pnpm store path)")"
git worktree add --detach --quiet "$worktree" HEAD
(cd "$worktree" && pnpm install --offline --frozen-lockfile --store-dir "$store" --reporter=silent)
node "$worktree/scripts/package-release.ts" --out "$work/release" >/dev/null
release="$(find "$work/release" -mindepth 1 -maxdepth 1 -type d)"
fi
release="$(cd "$release" && pwd)"
# Masked in any Compose diagnostics below.
COMPOSE_DIAGNOSTICS_SECRETS=()
echo "==> Starting a disposable Gitea 1.27.2"
"${compose[@]}" config --quiet || fail "the disposable registry's configuration is invalid"
if ! up_output="$("${compose[@]}" up --detach --wait --wait-timeout 180 gitea 2>&1)"; then
compose_report_not_ready "$up_output" "${compose[@]}"
fail "the disposable Gitea did not become ready"
fi
if [[ "$COMPOSE_JOB_NETWORK_RUNNING_IN_CONTAINER" == "true" ]]; then
compose_job_network_connect gitea "${compose[@]}" || fail "the job could not join the registry's network"
fi
origin="${GITEA_ROOT_URL%/}"
admin_password="admin-$RANDOM$RANDOM$RANDOM-$RANDOM"
publisher_password="publisher-$RANDOM$RANDOM$RANDOM-$RANDOM"
outsider_password="outsider-$RANDOM$RANDOM$RANDOM-$RANDOM"
"${compose[@]}" exec -T -u git gitea gitea admin user create --admin --username ekklesia-admin \
--password "$admin_password" --email [email protected] --must-change-password=false >/dev/null
api() {
local method="$1" path="$2" body="${3:-}" auth="${4:-ekklesia-admin:$admin_password}"
curl -fsS -X "$method" -u "$auth" -H "Content-Type: application/json" \
${body:+--data "$body"} "$origin/api/v1$path"
}
echo "==> Creating public package owners, the publisher team, and an outsider"
for user in publisher outsider; do
password_variable="${user}_password"
api POST /admin/users "{\"username\":\"$user\",\"password\":\"${!password_variable}\",\"email\":\"$user@example.invalid\",\"must_change_password\":false}" >/dev/null
done
owners=(Zayin ekklesia-burned-version ekklesia-tag-retry ekklesia-artifact-layout)
for owner in "${owners[@]}"; do
api POST /orgs "{\"username\":\"$owner\",\"visibility\":\"public\"}" >/dev/null
team="$(api POST "/orgs/$owner/teams" '{"name":"package-publishers","includes_all_repositories":false,"can_create_org_repo":false,"units":["repo.packages"],"units_map":{"repo.packages":"write"}}' |
node -e 'process.stdout.write(String(JSON.parse(require("fs").readFileSync(0, "utf8")).id))')"
api PUT "/teams/$team/members/publisher" >/dev/null
done
token_for() {
local user="$1" password="$2"
api POST "/users/$user/tokens" "{\"name\":\"publish-$RANDOM\",\"scopes\":[\"write:package\"]}" "$user:$password" |
node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(0, "utf8")).sha1)'
}
publish_token="$(token_for publisher "$publisher_password")"
outsider_token="$(token_for outsider "$outsider_password")"
echo "==> Publishing the release train to it"
EKKLESIA_TEST_REGISTRY_ORIGIN="$origin" \
EKKLESIA_TEST_RELEASE="$release" \
EKKLESIA_TEST_PUBLISH_TOKEN="$publish_token" \
EKKLESIA_TEST_OUTSIDER_TOKEN="$outsider_token" \
EKKLESIA_TEST_OWNERS="${owners[*]}" \
pnpm exec vitest run --config vitest.packages.integration.config.ts
echo "Package publication proof passed."