From af64a8ea4c537f6b5e9f2bee0fbddc59648f7d32 Mon Sep 17 00:00:00 2001 From: Oleks Date: Tue, 2 Jun 2026 08:56:38 +0300 Subject: [PATCH] =?UTF-8?q?feat(npm):=20mkS390xNpmPublishMulti=20=E2=80=94?= =?UTF-8?q?=20multi-version=20npm=20publish=20per=20tag=20(#192)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors mkPyPiWheelPublishMulti for npm: publishes a fixed {version,file, distTag?} list, each staged into its own dir and npm-published with its dist-tag (idempotent). file may be a .node or a plain binary; packageJson declares main-vs-bin. Unblocks nextjs-swc (next15 dist-tag) + sentry-cli. Shared parity_npm_publish_dir helper added. --- CHANGELOG.md | 9 ++++ ci/parity-lib.sh | 24 ++++++++++ flake.nix | 1 + lib/builders.nix | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e9563..55de6f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ semantic versioning; the version is a conceptual tag (no git tag is created). ## Unreleased +- **Feature: `mkS390xNpmPublishMulti` (cluster #192).** A multi-version npm + builder mirroring the PyPI multi one: publishes a fixed list of + `{ version; file; distTag? }` per tag, each staged into its own dir and + `npm publish`ed with its dist-tag (idempotent — "already exists" == success). + `file` may be a `.node` addon OR a plain binary, and `packageJson` (with a + `$VERSION` the stage heredoc expands) declares the shape (`main` vs `bin`), so + it covers both nextjs-swc (16.1.6 `@latest` + 15.2.0 `@next15`) and sentry-cli + (a binary published as an npm package at two versions). Shared + `parity_npm_publish_dir` helper added to `ci/parity-lib.sh`. - **Feature: `mkPyPiWheelPublishMulti` (cluster #197).** A multi-version PyPI builder that publishes a fixed list of `{ version; wheel; }` per tag instead of just the default — the pre-parity behaviour several `*-s390x` repos rely on. diff --git a/ci/parity-lib.sh b/ci/parity-lib.sh index 39223fc..6743448 100644 --- a/ci/parity-lib.sh +++ b/ci/parity-lib.sh @@ -173,6 +173,30 @@ 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 diff --git a/flake.nix b/flake.nix index 474414e..591c446 100644 --- a/flake.nix +++ b/flake.nix @@ -34,6 +34,7 @@ mkPyPiWheelPublish = wrap "mkPyPiWheelPublish"; mkPyPiWheelPublishMulti = wrap "mkPyPiWheelPublishMulti"; mkS390xNpmPublish = wrap "mkS390xNpmPublish"; + mkS390xNpmPublishMulti = wrap "mkS390xNpmPublishMulti"; mkGenericBinaryPublish = wrap "mkGenericBinaryPublish"; mkNix2ContainerPublish = wrap "mkNix2ContainerPublish"; mkGoBinaryPublish = wrap "mkGoBinaryPublish"; diff --git a/lib/builders.nix b/lib/builders.nix index 13d7aa9..6269e1f 100644 --- a/lib/builders.nix +++ b/lib/builders.nix @@ -355,6 +355,121 @@ let }; }; + # ========================================================================= + # s390x npm native addon / binary, MULTI-version (cluster #192). Publishes a + # fixed list of { version; file; distTag? } per tag — the behaviour repos like + # nextjs-swc (16.1.6 @latest + 15.2.0 @next15) and sentry-cli (3.2.2 + 2.38.2) + # rely on. `file` may be a .node addon OR a plain binary; `fileName` is its name + # inside the package and `packageJson` (with a $VERSION the stage heredoc + # expands) declares the shape (a `main` for an addon, a `bin` for an exe). Each + # version is staged into its own dir and published with its dist-tag; idempotent + # (npm "already exists" == success). No dev-tag guard: explicit fixed version + # list, gated by the dry-run default. + # Args: { pname, versions = [ { version; file; distTag ? "latest"; } ], + # fileName, packageJson, arch ? "s390x", registry* } + # ========================================================================= + mkS390xNpmPublishMulti = + { + pname, + versions, + fileName, + packageJson, + arch ? "s390x", + registryHost ? "git.oleks.space", + registryOwner ? "oleks", + passEntry ? "infra/gitea/personal_access_token_packages_rw", + }: + let + head = preamble { inherit registryHost registryOwner passEntry; }; + nVersions = builtins.length versions; + # One stage block per version: its own dir, the file, a version-stamped + # package.json (the <"$vd/.disttag" + VERSION=${lib.escapeShellArg e.version} + cat >"$vd/package.json" <"$d/.parity-meta" + echo " staged ${toString nVersions} version dir(s) under $d" + ''; + stage = pkgs.writeShellApplication { + name = "stage-${arch}"; + runtimeInputs = baseInputs ++ [ pkgs.nodejs ]; + text = stageText; + }; + dryList = '' + echo "[dry-run] would 'npm publish' these ${pname} versions -> https://$PARITY_REGISTRY_HOST/api/packages/$PARITY_REGISTRY_OWNER/npm/:" + for vd in "$d"/*/; do + [ -f "$vd/package.json" ] || continue + echo " - $(basename "$vd") (dist-tag $(cat "$vd/.disttag"))" + done + echo "[dry-run] re-run with --publish / PUBLISH=1 to push." + ''; + uploadLoop = '' + tok="$(parity_resolve_token)" + parity_registry_preflight + rc=0 + for vd in "$d"/*/; do + [ -f "$vd/package.json" ] || continue + parity_npm_publish_dir "$vd" "$tok" "$(cat "$vd/.disttag")" || rc=1 + done + exit "$rc" + ''; + publish = pkgs.writeShellApplication { + name = "publish-${arch}"; + runtimeInputs = baseInputs ++ [ pkgs.nodejs ]; + text = '' + ${head} + parity_parse_args "Build + publish all ${toString nVersions} ${pname} ${arch} npm versions to the Gitea npm registry" "$@" + ${lib.getExe stage} + d="$(parity_stage_path ${lib.escapeShellArg arch})" + if [ "$PARITY_PUBLISH" != "1" ]; then + ${dryList} + exit 0 + fi + ${uploadLoop} + ''; + }; + pushStaged = pkgs.writeShellApplication { + name = "push-staged"; + runtimeInputs = baseInputs ++ [ pkgs.nodejs ]; + text = '' + ${head} + parity_parse_args "Replay the staged ${pname} ${arch} npm versions from .parity-stage" "$@" + d="$(parity_stage_path ${lib.escapeShellArg arch})" + if ! find "$d" -mindepth 2 -name package.json -print -quit | grep -q .; then + echo "BLOCKER(no-stage): no staged versions under $d — run 'nix run .#stage-${arch}' first." >&2 + exit 1 + fi + if [ "$PARITY_PUBLISH" != "1" ]; then + ${dryList} + exit 0 + fi + ${uploadLoop} + ''; + }; + in + { + "stage-${arch}" = + mkApp stage "Stage all ${nVersions} ${pname} ${arch} npm versions into .parity-stage (no registry contact)."; + "publish-${arch}" = + mkApp publish "Stage + publish all ${pname} ${arch} npm versions (dry-run by default; --publish to push)."; + "publish" = mkApp publish "Publish all ${pname} ${arch} npm versions (${arch} only for this repo)."; + "push-staged" = + mkApp pushStaged "Replay the staged ${pname} ${arch} npm versions from .parity-stage (dry-run by default; --publish to push)."; + }; + # ========================================================================= # Generic binary (single-arch). Stage a built binary, PUT it to the Gitea # generic registry (idempotent: 201 created / 409 exists). @@ -857,6 +972,7 @@ in mkPyPiWheelPublish mkPyPiWheelPublishMulti mkS390xNpmPublish + mkS390xNpmPublishMulti mkGenericBinaryPublish mkNix2ContainerPublish mkGoBinaryPublish