# shellcheck shell=bash # shellcheck disable=SC2034 # vars/functions here are consumed by sourcing apps # # parity-lib shared shell helpers (cluster #192, emmett#44). # # This file is the single source of truth for the cross-cutting concerns every # parity publish app shares: token resolution, version derivation, the dev-tag # guard, argument parsing (the dry-run gate), registry preflight and the # on-disk stage directory convention. Each archetype builder in lib/builders.nix # generates a small writeShellApplication that sources THIS file and then does # only its archetype-specific build + push. # # TOKEN HYGIENE: nothing here ever echoes the token. Scripts that source this # MUST run under `set -euo pipefail` only and MUST NOT enable `set -x`. # --------------------------------------------------------------------------- # Conventions (overridable by the sourcing app before calling the helpers). # --------------------------------------------------------------------------- PARITY_PASS_ENTRY="${PARITY_PASS_ENTRY:-infra/gitea/personal_access_token_packages_rw}" PARITY_REGISTRY_HOST="${PARITY_REGISTRY_HOST:-git.oleks.space}" PARITY_REGISTRY_OWNER="${PARITY_REGISTRY_OWNER:-oleks}" # Local on-disk stage. BUILD-parity artifacts land here; push-staged replays them. PARITY_STAGE_DIR="${PARITY_STAGE_DIR:-${PWD}/.parity-stage}" # Snapshot whether the caller set $VERSION EXPLICITLY, captured at source time — # BEFORE any app body overwrites VERSION with the derived default version # (VERSION="$(parity_derive_version )"). parity_devtag_guard reads this # snapshot, not the live $VERSION, so an accidental local `--publish` with no # explicit version and no v* tag is still blocked instead of silently shipping # the flake's default version. Set-once: re-sourcing won't clobber the capture. : "${PARITY_VERSION_EXPLICIT=${VERSION:-}}" export PARITY_VERSION_EXPLICIT # --------------------------------------------------------------------------- # Token resolution: $REGISTRY_TOKEN -> `pass` fallback -> named hard fail. # Prints the token on stdout so a caller can `tok="$(parity_resolve_token)"`. # The CALLER must capture it and never echo it. This function itself is silent. # --------------------------------------------------------------------------- parity_resolve_token() { if [ -n "${REGISTRY_TOKEN:-}" ]; then printf '%s' "$REGISTRY_TOKEN" return 0 fi if command -v pass >/dev/null 2>&1; then local t if t="$(pass show "$PARITY_PASS_ENTRY" 2>/dev/null || true)" && [ -n "$t" ]; then printf '%s' "$t" return 0 fi fi echo "BLOCKER(empty-token): set \$REGISTRY_TOKEN (CI from_secret) or store '$PARITY_PASS_ENTRY' in pass; refusing to publish without credentials." >&2 return 1 } # --------------------------------------------------------------------------- # Version derivation, identical for CI and local. # Precedence: $VERSION -> strip-v + strip-trailing-increment of $CI_COMMIT_TAG # -> the default passed as $1 (the flake's pinned version). # Tag shape accepted: v optionally with a - build increment suffix. # Prints the derived version on stdout. # --------------------------------------------------------------------------- parity_derive_version() { local default_version="${1:-}" if [ -n "${VERSION:-}" ]; then printf '%s' "$VERSION" return 0 fi if [ -n "${CI_COMMIT_TAG:-}" ]; then printf '%s' "$CI_COMMIT_TAG" | sed 's/^v//; s/-[0-9]*$//' return 0 fi if [ -n "$default_version" ]; then printf '%s' "$default_version" return 0 fi echo "BLOCKER(no-version): no \$VERSION, no \$CI_COMMIT_TAG and no default version." >&2 return 1 } # --------------------------------------------------------------------------- # Dev-tag guard. Refuse a real (registry-mutating, :latest/release) publish # unless an explicit $VERSION is set OR $CI_COMMIT_TAG looks like a v* tag. # This stops an accidental local `--publish` from clobbering the registry with # the flake's default development version. # --------------------------------------------------------------------------- parity_devtag_guard() { # Read the source-time snapshot, NOT the live $VERSION (which the app body # has already set to the derived default by the time this runs). if [ -n "${PARITY_VERSION_EXPLICIT:-}" ]; then return 0 fi if printf '%s' "${CI_COMMIT_TAG:-}" | grep -Eq '^v[0-9]'; then return 0 fi echo "BLOCKER(dev-tag): refusing to publish without an explicit version." >&2 echo " Set VERSION= or push a v* tag (CI_COMMIT_TAG) before publishing." >&2 return 1 } # --------------------------------------------------------------------------- # Argument gate. Sets the global PARITY_PUBLISH (0 = dry-run default, 1 = push). # Honors $PUBLISH=1, --publish and --dry-run. --help prints usage and exits 0. # Usage: parity_parse_args "" "$@" # --------------------------------------------------------------------------- parity_parse_args() { local desc="$1" shift PARITY_PUBLISH="${PUBLISH:-0}" local a for a in "$@"; do case "$a" in --publish) PARITY_PUBLISH=1 ;; --dry-run) PARITY_PUBLISH=0 ;; -h | --help) echo "$desc" echo "" echo "Dry-run by default: builds/stages and prints what WOULD be pushed." echo " --publish | PUBLISH=1 actually mutate the registry" echo " --dry-run force dry-run" echo "Env: VERSION= override version; REGISTRY_TOKEN registry token" echo " (fallback: pass $PARITY_PASS_ENTRY)." exit 0 ;; *) echo "error: unknown argument '$a' (try --help)" >&2 exit 2 ;; esac done } # --------------------------------------------------------------------------- # Registry preflight: name the blocker if the registry is unreachable BEFORE # attempting any push (CI shares fate with the cluster the registry lives on). # --------------------------------------------------------------------------- parity_registry_preflight() { if ! curl -fsS -o /dev/null --max-time 10 "https://${PARITY_REGISTRY_HOST}/api/v1/version" 2>/dev/null; then echo "BLOCKER(registry-down): https://${PARITY_REGISTRY_HOST} unreachable — re-run when the registry is back; staged artifacts in ${PARITY_STAGE_DIR} are unchanged." >&2 return 1 fi } # --------------------------------------------------------------------------- # POST one wheel to the Gitea PyPI registry. Idempotent: 409 == already present. # Args: . Never echoes the token. Used by # both the single- and multi-version PyPI publish apps so the upload contract # lives in one place. Returns non-zero (without aborting a caller loop) on a # real upload failure so a multi-version run can attempt every version. # --------------------------------------------------------------------------- parity_pypi_post() { local pname="$1" ver="$2" whl="$3" tok="$4" sha http sha="$(sha256sum "$whl" | cut -d' ' -f1)" http="$(curl -s -o /dev/null -w '%{http_code}' -X POST \ -H "Authorization: token $tok" \ -F ':action=file_upload' -F 'protocol_version=1' \ -F "sha256_digest=$sha" -F "name=$pname" -F "version=$ver" \ -F 'filetype=bdist_wheel' -F "content=@$whl" \ "https://$PARITY_REGISTRY_HOST/api/packages/$PARITY_REGISTRY_OWNER/pypi")" case "$http" in 200 | 201 | 409) echo "Published $pname $ver (HTTP $http)" ;; *) echo "BLOCKER(upload-failed): $pname $ver POST returned HTTP $http" >&2 return 1 ;; esac } # --------------------------------------------------------------------------- # Derive a wheel's version from its filename. PEP 427: the second dash-separated # field of "----.whl" is the version. # --------------------------------------------------------------------------- parity_wheel_version() { basename "$1" | cut -d- -f2 } # --------------------------------------------------------------------------- # `npm publish` a ready-to-publish package directory to the Gitea npm registry. # Idempotent: an "already exists"/409 is treated as success. Never echoes the # token (the output is sed-redacted). Args: []. Used by # both the single- and multi-version npm publish apps. # --------------------------------------------------------------------------- parity_npm_publish_dir() { local dir="$1" tok="$2" dt="${3:-latest}" reg out rc reg="https://$PARITY_REGISTRY_HOST/api/packages/$PARITY_REGISTRY_OWNER/npm/" out="$(cd "$dir" && npm publish --registry="$reg" \ "--//$PARITY_REGISTRY_HOST/api/packages/$PARITY_REGISTRY_OWNER/npm/:_authToken=$tok" \ --tag "$dt" 2>&1)" rc=$? printf '%s\n' "$out" | sed "s/$tok/***REDACTED***/g" if [ "$rc" -ne 0 ]; then if printf '%s' "$out" | grep -qiE 'already exists|409|conflict'; then echo "npm: $(basename "$dir") already published — idempotent success." return 0 fi return "$rc" fi echo "Published $(basename "$dir") (dist-tag $dt)" } # --------------------------------------------------------------------------- # Stage directory helpers. The convention: each staged artifact is written to # ${PARITY_STAGE_DIR}// alongside a one-line meta file recording the # version + intended registry coordinates, so push-staged can replay later. # --------------------------------------------------------------------------- parity_stage_path() { # $1 = arch printf '%s/%s' "$PARITY_STAGE_DIR" "$1" } parity_stage_reset() { # $1 = arch ; wipes + recreates that arch's stage dir, prints the path. local d d="$(parity_stage_path "$1")" rm -rf "$d" mkdir -p "$d" printf '%s' "$d" }