From e35a909180b898c4aa2284a14a86ad1a426912a7 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sat, 6 Jul 2024 12:43:20 -0400 Subject: [PATCH 1/9] style(scripts): Improve npm-dist-tag.sh usage message and comments --- scripts/npm-dist-tag.sh | 48 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index 5c9e6f5f576..cc697d3e2c4 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -1,13 +1,17 @@ #! /bin/bash usage() { cat << END_USAGE -Usage: -$0 [--dry-run] [lerna] add [-] - Add to package dist-tags for current version or specified . -$0 [--dry-run] [lerna] - Remove from package dist-tags. -$0 [--dry-run] [lerna] [] - List package dist-tags, or just the one named . +Usage: $0 [--dry-run] [lerna] []... + +Commands: +add [-] + Read package name and version from package.json and add to its dist-tags + on npm for either that version or version x.y.z-. + + Read package name from package.json and remove from its dist-tags on npm. + [] + Read package name from package.json and list its dist-tag mappings from npm + (optionally limited to the dist-tag named ). With "--dry-run", npm commands are printed to standard error rather than executed. @@ -35,11 +39,11 @@ if test "${1:-}" = "--dry-run"; then fi echo-to-stderr() { echo "$@"; } 1>&2 -# Check the first argument. +# Check for `lerna`. case "${1-}" in lerna) - # npm-dist-tag.sh lerna [args]... - # Run `npm-dist-tag.sh [args]...` in every package directory. + # npm-dist-tag.sh lerna [arg]... + # Run `npm-dist-tag.sh [arg]...` in every package directory. # Find the absolute path to this script. thisdir=$(cd "$(dirname -- "${BASH_SOURCE[0]}")" > /dev/null && pwd -P) @@ -61,15 +65,18 @@ case "$priv" in ;; esac -# Get the second argument, if any. -TAG=${2-} - # Read package.json for the package name and current version. pkg=$(jq -r .name package.json) +version=$(jq -r .version package.json) + +# Process remaining arguments: [ [-]]. +CMD="${1-}" +TAG="${2-}" case ${3-} in -*) - # Instead of current package version, reference an already-published version - # with the specified pre-release suffix. + # "add -" scans published versions for an exact match of + # the specified pre-release suffix and applies the new dist-tag to that + # version rather than to the version read from package.json. version=$(npm view "$pkg" versions --json \ | # cf. https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions @@ -77,25 +84,24 @@ case ${3-} in ;; *) test "$#" -le 2 || fail "Invalid pre-release suffix!" - version=$(jq -r .version package.json) ;; esac -case "${1-}" in +case "$CMD" in add) - # Add $TAG to the current-directory package's dist-tags. + # Add $TAG to dist-tags. test -n "$TAG" || fail "Missing tag!" test "$#" -le 3 || fail "Too many arguments!" $npm dist-tag add "$pkg@$version" "$TAG" ;; remove | rm) - # Remove $TAG from the current-directory package's dist-tags. + # Remove $TAG from dist-tags. test -n "$TAG" || fail "Missing tag!" test "$#" -le 2 || fail "Too many arguments!" $npm dist-tag rm "$pkg" "$TAG" ;; list | ls) - # List the current-directory package's dist-tags, or just the specific $TAG. + # List either all dist-tags or just the specific $TAG. test "$#" -le 2 || fail "Too many arguments!" if test -n "$TAG"; then if test -n "$dryrun"; then @@ -109,7 +115,7 @@ case "${1-}" in fi ;; *) - test "${1-"--help"}" = "--help" || fail "Bad command!" + test "$CMD" = "--help" || fail "Bad command!" usage ;; esac From 25989124242cbf3211b3b4f760fac5a2865916d1 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sat, 6 Jul 2024 12:47:59 -0400 Subject: [PATCH 2/9] refactor(scripts): Improve npm-dist-tag.sh readability --- scripts/npm-dist-tag.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index cc697d3e2c4..c545ca0c827 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -77,10 +77,13 @@ case ${3-} in # "add -" scans published versions for an exact match of # the specified pre-release suffix and applies the new dist-tag to that # version rather than to the version read from package.json. + + # cf. https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions + semver_prefix="^((^|[.])(0|[1-9][0-9]*)){3}" + version=$(npm view "$pkg" versions --json \ - | - # cf. https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions - jq --arg s "$3" -r '.[] | select(sub("^((^|[.])(0|[1-9][0-9]*)){3}"; "") == $s)' || true) + | jq --arg p "$semver_prefix" --arg suffix "$3" -r '.[] | select(sub($p; "") == $suffix)' \ + | tail -n 1) ;; *) test "$#" -le 2 || fail "Invalid pre-release suffix!" From 5aecfc0a164b374e644bb00828680d93acef7080 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sat, 6 Jul 2024 12:54:16 -0400 Subject: [PATCH 3/9] refactor(scripts): Avoid repeated package.json reads in npm-dist-tag.sh --- scripts/npm-dist-tag.sh | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index c545ca0c827..235d16436f5 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -55,19 +55,21 @@ case "${1-}" in ;; esac -# If the package.json says it's private, we don't have a published version whose -# tags we can manipulate. -priv=$(jq -r .private package.json) -case "$priv" in - true) - echo 1>&2 "Skipping $(basename "$0") for private package $(jq .name package.json)" - exit 0 - ;; -esac +# Read current-directory package.json data into shell variables: pkg, version, priv. +eval "$(jq < package.json -r --arg Q "'" ' + pick(.name, .version, .private) + | to_entries + | .[] + | ({ name: "pkg", private: "priv" }[.key] // .key) as $key + | ((.value // "") | tostring | gsub($Q; $Q + "\\" + $Q + $Q)) as $value + | ($key + "=" + $Q + $value + $Q) +')" -# Read package.json for the package name and current version. -pkg=$(jq -r .name package.json) -version=$(jq -r .version package.json) +# dist-tags are only applicable to published packages. +if test "$priv" = true; then + echo 1>&2 "Skipping private package $pkg" + exit 0 +fi # Process remaining arguments: [ [-]]. CMD="${1-}" From 7dcdb26ededebde7b6b312fa11c7942fcf0335ad Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sat, 6 Jul 2024 13:00:48 -0400 Subject: [PATCH 4/9] feat(scripts): Update npm-dist-tag.sh to respect --help in a private-package dir --- scripts/npm-dist-tag.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index 235d16436f5..b38caa9177b 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -54,6 +54,7 @@ case "${1-}" in exec npm run -- lerna exec --concurrency=1 --no-bail "$thisdir/$thisprog" -- $dryrun ${1+"$@"} ;; esac +CMD="${1-"--help"}" # Read current-directory package.json data into shell variables: pkg, version, priv. eval "$(jq < package.json -r --arg Q "'" ' @@ -66,13 +67,12 @@ eval "$(jq < package.json -r --arg Q "'" ' ')" # dist-tags are only applicable to published packages. -if test "$priv" = true; then +if test "$priv" = true -a "$CMD" != "--help"; then echo 1>&2 "Skipping private package $pkg" exit 0 fi -# Process remaining arguments: [ [-]]. -CMD="${1-}" +# Process command arguments: [ [-]]. TAG="${2-}" case ${3-} in -*) From 6c11dfdd5f30b06b638dd870e4f35deccfb28a67 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sat, 6 Jul 2024 13:05:28 -0400 Subject: [PATCH 5/9] feat(scripts): Support `npm-dist-tag.sh lerna` from a non-top-level directory --- scripts/npm-dist-tag.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index b38caa9177b..49d3fb5903b 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -48,10 +48,11 @@ case "${1-}" in # Find the absolute path to this script. thisdir=$(cd "$(dirname -- "${BASH_SOURCE[0]}")" > /dev/null && pwd -P) thisprog=$(basename -- "${BASH_SOURCE[0]}") + cd "$thisdir" # Strip the first argument (`lerna`), so that `$@` gives us remaining args. shift - exec npm run -- lerna exec --concurrency=1 --no-bail "$thisdir/$thisprog" -- $dryrun ${1+"$@"} + exec npm run -- lerna exec --concurrency=1 --no-bail -- "$thisdir/$thisprog" "$dryrun" "$@" ;; esac CMD="${1-"--help"}" From e9d722ba4dc7825aabfc1cfd8a3128075c7dc67c Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sat, 6 Jul 2024 13:06:49 -0400 Subject: [PATCH 6/9] chore(scripts): Make npm-dist-tag.sh filtering more robust --- scripts/npm-dist-tag.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index 49d3fb5903b..8327e50a602 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -112,9 +112,9 @@ case "$CMD" in if test -n "$TAG"; then if test -n "$dryrun"; then # Print the entire pipeline. - $npm dist-tag ls "$pkg" \| sed -ne "s/^$TAG: //p" + $npm dist-tag ls "$pkg" \| awk -vP="$TAG" -F: '$1==P' else - $npm dist-tag ls "$pkg" | sed -ne "s/^$TAG: //p" + $npm dist-tag ls "$pkg" | awk -vP="$TAG" -F: '$1==P' fi else $npm dist-tag ls "$pkg" From e89b8665a6b5af4d3154913722b12fc2d4fa8cd0 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sat, 6 Jul 2024 13:09:53 -0400 Subject: [PATCH 7/9] feat(scripts): Add `npm-dist-tag.sh --otp-stream` for a better CLI experience Ref #9079 --- scripts/npm-dist-tag.sh | 61 +++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index 8327e50a602..b1f5543805f 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -1,7 +1,7 @@ #! /bin/bash usage() { cat << END_USAGE -Usage: $0 [--dry-run] [lerna] []... +Usage: $0 [--dry-run|--otp-stream=] [lerna] []... Commands: add [-] @@ -15,6 +15,12 @@ add [-] With "--dry-run", npm commands are printed to standard error rather than executed. +With "--otp-stream=", for each npm command the value of an "--otp" option +is read from the specified file (generally expected to be a named pipe created +by \`mkfifo\`) and failing commands are retried until the read value is empty. +Alternatively, environment variable configuration \`npm_config_auth_type=legacy\` +causes npm itself to prompt for and read OTP values from standard input. + If the first operand is "lerna", the operation is extended to all packages. END_USAGE exit 1 @@ -29,15 +35,34 @@ fail() { # Exit on any errors. set -ueo pipefail -# Check for `--dry-run`. +# Check for `--dry-run` and `--otp-stream`. npm=npm -dryrun= -if test "${1:-}" = "--dry-run"; then - dryrun=$1 - npm="echo-to-stderr npm" - shift -fi +style= +otpfile= +case "${1:-}" in + --dry-run) + style="$1" + npm="echo-to-stderr npm" + shift + ;; + --otp-stream=*) + style="$1" + otpfile="${1##--otp-stream=}" + npm="npm-otp" + shift + ;; +esac echo-to-stderr() { echo "$@"; } 1>&2 +npm-otp() { + printf 1>&2 "Reading OTP from %s ... " "$otpfile" + otp=$(head -n 1 "$otpfile") + if test -z "$otp"; then + echo 1>&2 "No OTP" + return 66 + fi + echo 1>&2 OK + npm --otp="$otp" "$@" +} # Check for `lerna`. case "${1-}" in @@ -52,7 +77,7 @@ case "${1-}" in # Strip the first argument (`lerna`), so that `$@` gives us remaining args. shift - exec npm run -- lerna exec --concurrency=1 --no-bail -- "$thisdir/$thisprog" "$dryrun" "$@" + exec npm run -- lerna exec --concurrency=1 --no-bail -- "$thisdir/$thisprog" "$style" "$@" ;; esac CMD="${1-"--help"}" @@ -98,19 +123,31 @@ case "$CMD" in # Add $TAG to dist-tags. test -n "$TAG" || fail "Missing tag!" test "$#" -le 3 || fail "Too many arguments!" - $npm dist-tag add "$pkg@$version" "$TAG" + while true; do + $npm dist-tag add "$pkg@$version" "$TAG" && break || ret=$? + [[ "$style" =~ --otp-stream ]] || exit $ret + [ $ret -ne 66 ] && continue + echo Aborting + exit 1 + done ;; remove | rm) # Remove $TAG from dist-tags. test -n "$TAG" || fail "Missing tag!" test "$#" -le 2 || fail "Too many arguments!" - $npm dist-tag rm "$pkg" "$TAG" + while true; do + $npm dist-tag rm "$pkg" "$TAG" && break || ret=$? + [[ "$style" =~ --otp-stream ]] || exit $ret + [ $ret -ne 66 ] && continue + echo Aborting + exit 1 + done ;; list | ls) # List either all dist-tags or just the specific $TAG. test "$#" -le 2 || fail "Too many arguments!" if test -n "$TAG"; then - if test -n "$dryrun"; then + if test "$style" = "--dry-run"; then # Print the entire pipeline. $npm dist-tag ls "$pkg" \| awk -vP="$TAG" -F: '$1==P' else From b6a7f439383e436567e512a0370f5f0986d0d937 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sun, 7 Jul 2024 13:27:16 -0400 Subject: [PATCH 8/9] style(scripts): Update npm-dist-tag.sh variable names to match package.json fields --- scripts/npm-dist-tag.sh | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index b1f5543805f..37022ddea35 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -82,19 +82,18 @@ case "${1-}" in esac CMD="${1-"--help"}" -# Read current-directory package.json data into shell variables: pkg, version, priv. +# Read current-directory package.json fields "name"/"version"/"private" into shell variables. eval "$(jq < package.json -r --arg Q "'" ' pick(.name, .version, .private) | to_entries | .[] - | ({ name: "pkg", private: "priv" }[.key] // .key) as $key | ((.value // "") | tostring | gsub($Q; $Q + "\\" + $Q + $Q)) as $value - | ($key + "=" + $Q + $value + $Q) + | (.key + "=" + $Q + $value + $Q) ')" # dist-tags are only applicable to published packages. -if test "$priv" = true -a "$CMD" != "--help"; then - echo 1>&2 "Skipping private package $pkg" +if test "$private" = true -a "$CMD" != "--help"; then + echo 1>&2 "Skipping private package $name" exit 0 fi @@ -109,7 +108,7 @@ case ${3-} in # cf. https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions semver_prefix="^((^|[.])(0|[1-9][0-9]*)){3}" - version=$(npm view "$pkg" versions --json \ + version=$(npm view "$name" versions --json \ | jq --arg p "$semver_prefix" --arg suffix "$3" -r '.[] | select(sub($p; "") == $suffix)' \ | tail -n 1) ;; @@ -124,7 +123,7 @@ case "$CMD" in test -n "$TAG" || fail "Missing tag!" test "$#" -le 3 || fail "Too many arguments!" while true; do - $npm dist-tag add "$pkg@$version" "$TAG" && break || ret=$? + $npm dist-tag add "$name@$version" "$TAG" && break || ret=$? [[ "$style" =~ --otp-stream ]] || exit $ret [ $ret -ne 66 ] && continue echo Aborting @@ -136,7 +135,7 @@ case "$CMD" in test -n "$TAG" || fail "Missing tag!" test "$#" -le 2 || fail "Too many arguments!" while true; do - $npm dist-tag rm "$pkg" "$TAG" && break || ret=$? + $npm dist-tag rm "$name" "$TAG" && break || ret=$? [[ "$style" =~ --otp-stream ]] || exit $ret [ $ret -ne 66 ] && continue echo Aborting @@ -149,12 +148,12 @@ case "$CMD" in if test -n "$TAG"; then if test "$style" = "--dry-run"; then # Print the entire pipeline. - $npm dist-tag ls "$pkg" \| awk -vP="$TAG" -F: '$1==P' + $npm dist-tag ls "$name" \| awk -vP="$TAG" -F: '$1==P' else - $npm dist-tag ls "$pkg" | awk -vP="$TAG" -F: '$1==P' + $npm dist-tag ls "$name" | awk -vP="$TAG" -F: '$1==P' fi else - $npm dist-tag ls "$pkg" + $npm dist-tag ls "$name" fi ;; *) From 5920b6ba477a755fdcc2c2f22f402c45087faea9 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sun, 7 Jul 2024 13:42:35 -0400 Subject: [PATCH 9/9] chore(scripts): Explain the package.json-to-shell-variable logic in npm-dist-tag.sh --- scripts/npm-dist-tag.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/npm-dist-tag.sh b/scripts/npm-dist-tag.sh index 37022ddea35..884ff5e7336 100755 --- a/scripts/npm-dist-tag.sh +++ b/scripts/npm-dist-tag.sh @@ -82,13 +82,18 @@ case "${1-}" in esac CMD="${1-"--help"}" -# Read current-directory package.json fields "name"/"version"/"private" into shell variables. +# Read current-directory package.json fields "name"/"version"/"private" into shell variables +# by evaluating single-quoted assignments like `='...'`. eval "$(jq < package.json -r --arg Q "'" ' pick(.name, .version, .private) | to_entries | .[] - | ((.value // "") | tostring | gsub($Q; $Q + "\\" + $Q + $Q)) as $value - | (.key + "=" + $Q + $value + $Q) + # Replace a null/false value with empty string. + | ((.value // "") | tostring) as $str_value + # Enclosing single-quote `$Q`s preserve the literal value of each character + # except actual single-quotes, which are replaced with an escape sequence by + # the `gsub`. + | (.key + "=" + $Q + ($str_value | gsub($Q; $Q + "\\" + $Q + $Q)) + $Q) ')" # dist-tags are only applicable to published packages.