From daf97853a1e3573b4e656cd6278fd07f9a807865 Mon Sep 17 00:00:00 2001 From: bea-claude Date: Thu, 8 Oct 2026 19:21:07 +0200 Subject: [PATCH] ci(release): catch-up reads versions without needing a `latest` tag Called with a bare name, `npm view` answers only for the `latest` tag, and prints nothing, exit 0, for a package without one. dev publishes with `--tag dev`, so until a prod promotion tags it, @beplus/be has no `latest` in GitHub Packages and its versions read as none (the libraries' promotions hit exactly this: beplus/setup-beplus's fix is the same). The tag read for a leftover catch-up tag had the same blind spot. Each lookup now names a version: GitHub Packages the one the commit carries; CodeArtifact `latest`, then that version, then each earlier one dev released, until one matches. One `npm view` answers for both the versions and the catch-up tag. Co-Authored-By: Igor Lamos --- scripts/release/catch-up-codeartifact.sh | 71 +++++++++++++++++------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/scripts/release/catch-up-codeartifact.sh b/scripts/release/catch-up-codeartifact.sh index 9f11215..6859d43 100755 --- a/scripts/release/catch-up-codeartifact.sh +++ b/scripts/release/catch-up-codeartifact.sh @@ -40,30 +40,56 @@ npmrc="${temp}/.npmrc-github" echo '//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}' > "${npmrc}" # -# versions — every version of @beplus/be a registry has, one a line; nothing -# when it has none. Any other answer stops the run. +# lookup " …" — what a registry has of @beplus/be, as +# {"versions": […], "dist-tags": {…}}; {} when it has none. `npm view` answers only for a version its +# spec matches, `latest` when it names none, and prints nothing for a package without `latest` (dev +# publishes with `--tag dev`), so each spec in turn ("-" for `latest`) names a version the registry +# may have, and the first that matches answers for the whole package. Any other answer stops the run. # -function versions() { - local where="$1" out err - shift +function lookup() { + local where="$1" spec target out err + local -a specs + read -ra specs <<< "$2" + shift 2 err="$(mktemp)" - if out="$(npm view "${name}" versions --json "$@" 2> "${err}")"; then - rm -f "${err}" - node -e 'console.log([].concat(JSON.parse(process.argv[1] || "[]")).join("\n"))' "${out}" - elif grep -q E404 <<< "${out}$(cat "${err}")"; then - rm -f "${err}" - else - echo "::error::Cannot list the versions of ${name} in ${where}: ${out}$(cat "${err}")" >&2 - rm -f "${err}" - return 1 - fi + for spec in "${specs[@]}"; do + target="${name}" + [[ "${spec}" == "-" ]] || target="${name}@${spec}" + if out="$(npm view "${target}" versions dist-tags --json "$@" 2> "${err}")"; then + if [[ -n "${out}" ]]; then + rm -f "${err}" + echo "${out}" + return 0 + fi + elif grep -q 'No match found for version' <<< "${out}$(cat "${err}")"; then + continue + elif grep -q E404 <<< "${out}$(cat "${err}")"; then + break + else + echo "::error::Cannot list the versions of ${name} in ${where}: ${out}$(cat "${err}")" >&2 + rm -f "${err}" + return 1 + fi + done + rm -f "${err}" + echo '{}' } -released="$(versions "GitHub Packages" --userconfig "${npmrc}" --@beplus:registry="${github}")" -here="$(versions "${BE_ENVIRONMENT}'s CodeArtifact" --registry="${registry}" --@beplus:registry="${registry}")" -# A catch-up tag an earlier run could not remove (no PutPackageMetadata, or cancelled), if any. -stale="$(npm view "${name}" dist-tags.catch-up --registry="${registry}" --@beplus:registry="${registry}" 2> /dev/null || true)" +# +# field — a value out of lookup's answer, one a line when it is a list +# + +function field() { + node -e ' + const info = JSON.parse(process.argv[1]); + console.log([].concat(new Function("info", "return " + process.argv[2])(info) ?? []).join("\n")); + ' "$1" "$2" +} + +# The version this commit carries: verify-version.sh made sure GitHub Packages has it. +github_info="$(lookup "GitHub Packages" "${version}" --userconfig "${npmrc}" --@beplus:registry="${github}")" +released="$(field "${github_info}" 'info.versions')" # npm's own copy of semver, which every npm carries: nothing is installed for this. # shellcheck disable=SC2016 # ${…} is a JavaScript template literal, not a shell expansion @@ -73,6 +99,13 @@ earlier="$(node -e ' console.log(semver.sort(released.filter((v) => semver.lt(v, process.argv[2]))).join("\n")); ' "$(npm root -g)" "${version}" <<< "${released}")" +# `latest`, else any version dev released up to here, newest first. +newest_first="$(awk '{ lines[NR] = $0 } END { for (i = NR; i > 0; i--) print lines[i] }' <<< "${earlier}" | tr '\n' ' ')" +here_info="$(lookup "${BE_ENVIRONMENT}'s CodeArtifact" "- ${version} ${newest_first}" --registry="${registry}" --@beplus:registry="${registry}")" +here="$(field "${here_info}" 'info.versions')" +# A catch-up tag an earlier run could not remove (no PutPackageMetadata, or cancelled), if any. +stale="$(field "${here_info}" 'info["dist-tags"]?.["catch-up"]')" + dir="$(mktemp -d)" caught_up=() landed=()