#!/bin/bash

if [[ $EUID -eq 0 ]]; then
   echo "This program is not intended to be run as root." 1>&2
   exit 1
fi

error_msg="Cloudlinux NodeJS Selector demands to store node modules for application in separate folder \
(virtual environment) pointed by symlink called \"node_modules\". That's why application should not contain \
folder/file with such name in application root"

CWD=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
source "${CWD}/activate"
eval "$(${CWD}/set_env_vars.py nodejs)"

venv_node_modules="${CL_VIRTUAL_ENV}/lib/node_modules"
nodejs_npm="$CL_NODEHOME/usr/bin/npm"

# set_env_vars did not resolve this invocation to an application (its
# diagnostics go to stderr, so eval saw nothing): there is no venv to deliver
# into and the paths below would be nonsense like "$HOME//node_modules". Run
# plain npm and get out, before any bun-delivery logic.
if [[ -z "${CL_APP_ROOT:-}" ]]; then
    exec "${nodejs_npm}" "$@"
fi

app_node_modules="${HOME}/${CL_APP_ROOT}/node_modules"

invalidate_shared_node_modules_marker() {
    # A failed plain npm can leave a conservative false-negative; never claim
    # the delivered tree is still intact once npm was allowed to mutate it.
    rm -f "${venv_node_modules}/.cl-shared-node-modules.json"
}

# bun install-time delivery (docs/design/nodejs-bun-delivery.md).
#
# This wrapper is where an account's own `npm install` lands -- a person
# over SSH, or an agent in a shell, which is the workload a shared cache
# exists for: an agent redeploys dozens of times an afternoon and every one
# of those installs re-downloads what the server already has.
#
# The account cannot link out of the cache itself (the kernel refuses a hard
# link to a file it does not own), so the middle phase is asked of a root
# daemon over a unix socket. The account keeps the phases that must stay
# unprivileged: resolution runs with its own registry configuration, and the
# native builds run as itself.
#
#     resolve (account) -> deliver (root, over the socket) -> rebuild (account)
#
# If any step fails -- daemon stopped, a busy application -- the install
# fails and says why, rather than quietly becoming a plain npm install.
# Explicit administrator disable/exclusion policy selects classic npm, and so
# does an input this engine cannot serve (a resolve sentinel, or a source or
# package shape root refused), each named on stderr when it continues on a
# private copy (classic_private_reason, maintenance_private_announce);
# every other refusal remains loud. Silently
# choosing a different installer is what let two delivery defects
# sit behind healthy-looking numbers while this feature was built; an install that looks like it
# succeeded is worse than one that stops.
BUN_USER_PHASE="/usr/share/l.v.e-manager/utils/bun_user_phase"
BUN_DELIVERY_CONF="/opt/cl-bun/etc/node-modules-storage.json"
NODE_MODULES_STORAGE_PYTHON="/opt/cloudlinux/venv/bin/python3"
NODE_MODULES_STORAGE_READER="${NODE_MODULES_STORAGE_PYTHON}"
# Reads the application's lockfiles as JSON, whatever their indentation or
# line ends (npm writes them in the style of package.json).
LOCKFILE_PYTHON="/opt/cloudlinux/venv/bin/python3"
DELIVERY_CLIENT="/usr/share/l.v.e-manager/utils/cloudlinux_bun_client.py"

close_selector_lock_copy_for_npm() {
    # Selector itself keeps the same open-file-description locked until the
    # npm operation and its restart/health phases finish.  Drop only this
    # wrapper child's descriptor before npm starts, otherwise a lifecycle
    # script that backgrounds a process could inherit the application lock
    # indefinitely after Selector returns.
    local selector_fd="${CL_SELECTOR_APP_LOCK_FD:-}"
    local lock_file="$(dirname "${CL_VIRTUAL_ENV}")/.lock"
    [[ "${selector_fd}" =~ ^[0-9]+$ ]] || return 0
    [ -e "/proc/self/fd/${selector_fd}" ] || return 0
    [ "$(stat -Lc '%d:%i' "/proc/self/fd/${selector_fd}" 2>/dev/null)" \
        = "$(stat -Lc '%d:%i' "${lock_file}" 2>/dev/null)" ] || return 0
    eval "exec ${selector_fd}>&-"
}

discard_restored_private_marker() {
    # Account backups materialize hardlinks as ordinary private files but may
    # copy the delivery marker. With no server configuration, discard only a
    # regular account-owned marker after a bounded walk proves the whole tree
    # contains no shared regular inode or unsafe node type. A restored
    # root-owned single-link file is private and npm can replace its directory
    # entry without gaining write access to the inode.
    # Any ambiguity leaves the marker in place for privileged fail-closed
    # unshare handling below.
    local modules="${venv_node_modules}"
    local marker="${modules}/.cl-shared-node-modules.json"
    local account_uid unsafe scan_status
    [ ! -e "${BUN_DELIVERY_CONF}" ] || return 1
    [ -d "${modules}" ] && [ ! -L "${modules}" ] || return 1
    [ -f "${marker}" ] && [ ! -L "${marker}" ] || return 1
    account_uid=$(id -u) || return 1
    [ "$(stat -c '%u' -- "${marker}" 2>/dev/null)" = "${account_uid}" ] \
        || return 1
    command -v timeout >/dev/null 2>&1 || return 1
    # Only a foreign-owned linked inode can be a store inode: with
    # fs.protected_hardlinks=1 the account cannot link a file it does not
    # own, so its own linked bytes (`cp -al`, `rsync -aH` restores) are
    # private however many links they carry.
    unsafe=$(timeout 5 find "${modules}" -xdev \
        \( \( -type f -links +1 ! -uid "${account_uid}" \) \
           -o \( ! -type d ! -type f ! -type l \) \) \
        -print -quit 2>/dev/null)
    scan_status=$?
    [ "${scan_status}" -eq 0 ] && [ -z "${unsafe}" ] || return 1
    rm -f -- "${marker}"
}

# A self-contained account restore has private bytes even when its copied
# telemetry marker says the source application used the Store. Retire that
# stale marker only after the private-tree proof above; a failed proof keeps
# the Shared Store-aware recovery path.
if [ ! -e "${BUN_DELIVERY_CONF}" ] \
        && [ -e "${venv_node_modules}/.cl-shared-node-modules.json" ]; then
    discard_restored_private_marker || true
fi

unverified_sharing_scan() {
    # The bounded scan could not answer and there is no marker. Failing
    # closed here would put every npm call of a markerless application on
    # the heavy path -- including root unshare and its budget -- on every
    # disabled-config host, and under LVE IO a large tree loses that race
    # routinely. Only a configuration that may enable delivery keeps the
    # conservative verdict.
    config_may_enable_delivery && return 0
    return 1
}

scan_shared_tree() {
    local modules="${CL_VIRTUAL_ENV}/lib/node_modules"
    local account_uid shared_file scan_status
    [ -e "${modules}/.cl-shared-node-modules.json" ] && return 0
    [ -d "${modules}" ] || return 1
    command -v timeout >/dev/null 2>&1 || { unverified_sharing_scan; return $?; }
    account_uid=$(id -u) || { unverified_sharing_scan; return $?; }
    # pnpm, `cp -l` and backup tools may create account-owned hardlinks.
    # Shared Store cache inodes stay foreign (normally root-owned), so only
    # those ambiguous links need privileged fail-closed recovery.
    shared_file=$(timeout 5 find "${modules}" -xdev -type f -links +1 \
        ! -uid "${account_uid}" \
        -print -quit 2>/dev/null)
    scan_status=$?
    [ "${scan_status}" -eq 0 ] || { unverified_sharing_scan; return $?; }
    [ -n "${shared_file}" ]
}

shared_tree_verdict=''

shared_tree_may_exist() {
    # One scan per process: the compatibility gate and the pre-unshare check
    # ask the same question, and the answer is a bounded walk of the whole
    # tree. Nothing between them changes this tree -- the app lock is held
    # before the second caller runs.
    if [ -z "${shared_tree_verdict}" ]; then
        scan_shared_tree
        shared_tree_verdict=$?
    fi
    return "${shared_tree_verdict}"
}

config_may_enable_delivery() {
    [ -e "${BUN_DELIVERY_CONF}" ] || return 1
    [ -r "${BUN_DELIVERY_CONF}" ] || return 0
    grep -q '"enabled"[[:space:]]*:[[:space:]]*true' "${BUN_DELIVERY_CONF}" 2>/dev/null || return 1
    return 0
}

# Each spelling list once: npm's names and aliases of a command (its unique
# abbreviations are resolved to them first, resolve_npm_command_name).
is_install_spelling() {
    case "${1:-}" in
        install|i|add|in|ins|inst|insta|instal|isnt|isnta|isntal|isntall) return 0 ;;
    esac
    return 1
}

is_bare_npm_ci_spelling() {
    case "${1:-}" in
        ci|clean-install|ic|install-clean|isntall-clean) return 0 ;;
    esac
    return 1
}

is_npm_ci_test_spelling() {
    case "${1:-}" in
        install-ci-test|cit|clean-install-test|sit) return 0 ;;
    esac
    return 1
}

is_npm_ci() {
    is_bare_npm_ci_spelling "$1" || is_npm_ci_test_spelling "$1"
}

expose_app_lockfiles_for_ci() {
    # `npm ci` installs exactly the pins of the application's lockfile, and
    # with the venv as its prefix npm reads that lockfile -- and its project
    # `.npmrc` -- from venv lib. When the application root has a regular npm
    # lockfile, venv lib names exactly it: a differing copy an earlier prefix
    # install wrote there describes the tree `npm ci` replaces. A linked
    # application lockfile is a historical layout whose target may be the
    # venv copy itself, so it is never followed into a deletion. Without a
    # regular one, npm reads what venv lib has -- the pins of the previous
    # install there -- or fails with its own error. The application's
    # `.npmrc` is exposed as for every classic mutation (expose_app_npmrc).
    local name app_file venv_file app_has_lock=false
    for name in npm-shrinkwrap.json package-lock.json; do
        app_file="${HOME}/${CL_APP_ROOT}/${name}"
        [[ -f "${app_file}" && ! -L "${app_file}" ]] && app_has_lock=true
    done
    for name in npm-shrinkwrap.json package-lock.json; do
        [ "${app_has_lock}" = true ] || break
        app_file="${HOME}/${CL_APP_ROOT}/${name}"
        venv_file="${CL_VIRTUAL_ENV}/lib/${name}"
        [[ "${app_file}" -ef "${venv_file}" ]] && continue
        rm -f -- "${venv_file}" || return 1
        if [[ -f "${app_file}" && ! -L "${app_file}" ]]; then
            ln -s -- "${app_file}" "${venv_file}" || return 1
        fi
    done
    expose_app_npmrc
}

expose_app_npmrc() {
    # With the venv as its prefix npm reads its project `.npmrc` from venv
    # lib, not the application root: an application's registry, scoped
    # registry, token or legacy-peer-deps would otherwise not apply to any
    # classic mutation (or to install-modules --modules). An application
    # `.npmrc` that is a regular file -- or a link to one, such as a token
    # kept out of a Git checkout -- is linked there by its application path
    # (never followed or deleted) unless venv lib already has one, which the
    # account wrote and keeps. A dangling link -- what moving the
    # application leaves of an earlier one -- holds no configuration and is
    # replaced. The link persists, as intended: it is the application's.
    local app_file="${HOME}/${CL_APP_ROOT}/.npmrc"
    local venv_file="${CL_VIRTUAL_ENV}/lib/.npmrc"
    [[ -f "${app_file}" ]] || return 0
    if [[ -L "${venv_file}" && ! -e "${venv_file}" ]]; then
        rm -f -- "${venv_file}" || return 1
    fi
    if [[ -L "${venv_file}" && "$(readlink "${venv_file}")" == "${app_file}" ]]; then
        :
    elif [[ -e "${venv_file}" || -L "${venv_file}" ]]; then
        return 0
    fi
    if npm_cannot_load_npmrc "${app_file}"; then
        # This wrapper's own earlier link included: npm would stop on it.
        if [[ -L "${venv_file}" ]]; then
            rm -f -- "${venv_file}" || return 1
        fi
        # Said once per command, however many steps expose the file.
        [ "${npmrc_unloadable_said:-false}" = true ] && return 0
        npmrc_unloadable_said=true
        echo "npm ${npmrc_npm_version} cannot load ${app_file} while" \
             "${npmrc_unset_reference} is not set, so npm runs without it;" \
             "set it as an application environment variable to apply the" \
             "file." >&2
        return 0
    fi
    [[ -L "${venv_file}" ]] || ln -s -- "${app_file}" "${venv_file}"
}

npm_cannot_load_npmrc() {
    # npm 3-6 (Node.js 6-14) stop at config load, exit 7, on a `${NAME}` the
    # environment does not hold ("Failed to replace env in config"), and
    # npm's own CI recipe commits `_authToken=${NPM_TOKEN}`. Until CLOS-7898
    # only `npm ci` read the application's file through venv lib; linking
    # it for every mutation must not stop every install of such an
    # application. npm 7+ load it, and a set variable (an application
    # environment variable, which set_env_vars exports) is replaced.
    local references reference name major="${CL_VIRTUAL_ENV##*/}"
    npmrc_unset_reference=
    # The venv is named after its Node.js major, and Node.js 16 ships npm
    # 7 or later (no alt-nodejs 15 exists): npm is not asked its version,
    # about 250 ms, on every mutation of such an application.
    [[ "${major}" =~ ^[0-9]+$ ]] && [ "${major}" -ge 16 ] && return 1
    # One line per reference; no process substitution, the cage has no
    # /dev/fd.
    references="$(grep -o '[$][{][^}][^}]*[}]' -- "$1" 2>/dev/null)" || return 1
    while [ -n "${references}" ]; do
        reference=${references%%$'\n'*}
        if [ "${reference}" = "${references}" ]; then
            references=
        else
            references=${references#*$'\n'}
        fi
        name=${reference#??}
        name=${name%?}
        printenv -- "${name}" >/dev/null 2>&1 && continue
        npmrc_unset_reference=${reference}
        break
    done
    [ -n "${npmrc_unset_reference}" ] || return 1
    # npm 3-6 print their version before reading any configuration.
    npmrc_npm_version="$("${nodejs_npm}" --version 2>/dev/null)" || return 1
    [[ "${npmrc_npm_version%%.*}" =~ ^[0-9]+$ ]] \
        && [ "${npmrc_npm_version%%.*}" -lt 7 ]
}

ci_aside=''

set_aside_app_node_modules_for_ci() {
    # The self-healing half of the CLOS-7870 gap: `npm ci` run in the
    # application root before CLOS-7874 left a real node_modules there,
    # which every Selector install refuses. `npm ci` replaces node_modules
    # wholesale anyway, so that tree moves aside for the venv link; it is
    # removed once npm succeeded and put back if anything fails before.
    [[ -d "${app_node_modules}" && ! -L "${app_node_modules}" ]] || return 0
    # A SIGKILLed run (no trap) leaves its copy behind, and a later one may
    # get the same pid: never move a tree into it. The real directory then
    # stays, and the link check refuses as it always did.
    [[ ! -e "${app_node_modules}.cl-ci-$$" && ! -L "${app_node_modules}.cl-ci-$$" ]] \
        || return 0
    ci_aside="${app_node_modules}.cl-ci-$$"
    mv -T -- "${app_node_modules}" "${ci_aside}" || { ci_aside=''; return 1; }
    trap restore_app_node_modules_for_ci EXIT
}

restore_app_node_modules_for_ci() {
    [ -n "${ci_aside}" ] || return 0
    [ ! -L "${app_node_modules}" ] || rm -f -- "${app_node_modules}"
    mv -- "${ci_aside}" "${app_node_modules}"
}

run_legacy_npm() {
    # This is the exact pre-Shared-Store wrapper contract for everything that
    # is not a classic mutation of this application (see the dispatch below):
    # only the historical setup/list spellings target the Selector venv;
    # everything else reaches npm unchanged. Keep it in one helper so an
    # absent and a disabled config cannot drift apart again.
    if [[ "$*" =~ ^(install|i|add|list|la|ll)$ \
            || "$*" =~ ^(install|i|add|list|la|ll)[[:space:]].*$ ]]; then
        if [[ -L "${app_node_modules}" ]]; then
            rm -f "${app_node_modules}" \
                || { echo "Can't remove symlink ${app_node_modules}" >&2; exit 1; }
        elif [[ -d "${app_node_modules}" || -f "${app_node_modules}" ]]; then
            echo "${error_msg}" >&2
            exit 1
        fi
        mkdir -p "${venv_node_modules}" || exit 1
        ln -fs "${venv_node_modules}" "${app_node_modules}" || exit 1
        ln -sf "${HOME}/${CL_APP_ROOT}/package.json" \
            "${CL_VIRTUAL_ENV}/lib/package.json" || exit 1
        close_selector_lock_copy_for_npm
        exec "${nodejs_npm}" "$@" --prefix="${CL_VIRTUAL_ENV}/lib"
    fi
    close_selector_lock_copy_for_npm
    exec "${nodejs_npm}" "$@"
}

# Default-off compatibility is decided here, before any Store work. On a
# server where delivery is not enabled and the application has no marker or
# shared inode, the Store does not exist for this application: no sharing
# scan, no daemon, no delivery and no unshare (shared_store_path=false). A
# config written only to change limits must not wrap every app in them.
# Ambiguous or actually shared trees remain on the fail-closed recovery path.
#
# What the Store's absence does not change is where npm may write. A classic
# mutation of this application -- install, uninstall, update, prune, dedupe,
# `audit fix` and their aliases -- takes the locked classic path below in
# every mode. Passed through, npm ran in the application root and replaced
# the Selector's node_modules link with a real directory: a removed package
# kept loading from the venv through NODE_PATH, a named install left its new
# pins in venv lib while the application root kept the old ones, and every
# later Selector install refused the application (CLOS-7874 for `npm ci`,
# CLOS-7898 for the rest). Every other command keeps the wrapper shipped
# before this feature (run_legacy_npm, dispatched once the command is
# classified).
shared_store_path=true
if [ ! -e "${BUN_DELIVERY_CONF}" ] \
        && [ ! -e "${venv_node_modules}/.cl-shared-node-modules.json" ]; then
    shared_store_path=false
fi

# An administrator may publish a disabled config merely to tune limits. That
# must not opt every untouched application into the Store's scan and unshare.
# Only a delivery marker or a foreign-owned linked inode needs the
# compatibility/recovery path below. Canonical Selector links and lockfiles
# predate Shared Store and must not opt an ordinary application into it.
if [ -e "${BUN_DELIVERY_CONF}" ] \
        && ! config_may_enable_delivery \
        && ! shared_tree_may_exist; then
    shared_store_path=false
fi

bun_delivery_state() {
    # 0 means ask the authenticated daemon for this account's policy, 1 means
    # the plain npm path because the global feature is off, and 2 means the
    # public configuration reader could not run. Account names are never
    # present in this CageFS-readable file.
    #
    # Refuse-on-error exists so a delivery that should happen is never
    # silently skipped -- which is a reason only where the config plausibly
    # says enabled. The file is written by cl-node-modules-storage alone,
    # so absent, or with no plain `"enabled": true` in it, is the fleet
    # that never turned the feature on: that is decided right here in
    # bash, and a broken interpreter or venv cannot turn every account's
    # `npm install` into a refusal there. Anything that *looks* enabled
    # goes to the authoritative reader (a nested "enabled" in an unknown
    # key must not enable the feature by grep), and only there does a
    # reader that cannot run refuse.
    [ -e "$1" ] || return 1
    grep -q '"enabled"[[:space:]]*:[[:space:]]*true' "$1" 2>/dev/null || return 1
    # Isolated mode keeps the account's environment and working directory
    # out of Python imports.
    "${NODE_MODULES_STORAGE_READER}" -Ibb -c '
import sys
try:
    from clselector import node_modules_storage
    config = node_modules_storage.read_public_config(sys.argv[1])
    applies = config["enabled"] and node_modules_storage.nodejs_selector_supported()
except Exception as error:
    sys.stderr.write("Node modules storage configuration error: %s\\n" % error)
    sys.exit(2)
sys.exit(0 if applies else 1)
' "$1"
}

bun_delivery_run() {
    # In this shell, under the lock it already holds (lock_classic), which
    # its children prove to root. `store_refusal` names a refusal that
    # selects classic npm (exit 3): the policy's choice, a resolve sentinel
    # or the reason root gave.
    store_refusal=''
    store_unavailable=''
    delivery_outcome_unknown=false
    lock_classic || return $?
    # A freshly created application has only <venv>/<major>/bin, and the
    # daemon finds the venv by its library directory: without this the very
    # first `npm install` over SSH is refused before anything is installed.
    # The classic and resolve paths create the same account-owned directory.
    mkdir -p "${CL_VIRTUAL_ENV}/lib" || return 1
    # The maintenance route asked the policy before npm ran.
    if [ "${store_policy_checked:-false}" != true ]; then
        store_policy_check || return $?
    fi
    # A Store `npm ci` resolves the application's own lockfile.
    if [ "${CL_BUN_COMMAND:-}" = ci ]; then
        expose_app_lockfiles_for_ci || return 1
    fi
    # The phase keeps stdout clean for exactly these sentinels and says
    # nothing about them on stderr; bun's own diagnosis of a failed install
    # resolve goes to stderr and reaches the account unchanged.
    resolve_sentinel="$("${BUN_USER_PHASE}" resolve "${CL_APP_ROOT}")"
    resolve_status=$?
    # Exit 3 is the classic-npm route, but the phase also propagates bun's
    # own status: without the sentinel a future bun exit 3 would send every
    # account to classic npm silently. The code alone is not the contract.
    if [ "${resolve_status}" -eq 3 ] \
       && { [ "${resolve_sentinel}" = 'cl-bun:legacy-package-lock-v1' ] \
            || [ "${resolve_sentinel}" = 'cl-bun:unsupported-npm-shrinkwrap' ] \
            || [ "${resolve_sentinel}" = 'cl-bun:no-dependencies' ] \
            || [ "${resolve_sentinel}" = 'cl-bun:package-lock-too-large' ] \
            || case "${resolve_sentinel}" in
                 'cl-bun:unsupported-manifest '*) true ;;
                 *) false ;;
               esac; }; then
        store_refusal="${resolve_sentinel#cl-bun:}"
        return 3
    fi
    if [ "${resolve_status}" -ne 0 ] && [ "${CL_BUN_COMMAND:-}" = ci ] \
            && [ "${lockfile_delivery:-false}" != true ]; then
        # A drifted, missing or unreadable lockfile is npm's to refuse, with
        # its own error -- or to install, where only bun could not. Nothing
        # to say about it: no refusal, and npm's verdict follows.
        store_refusal=resolve_failed
        return 3
    fi
    if [ "${resolve_status}" -ne 0 ]; then
        # The phase has already printed bun's own diagnosis; name the phase
        # so the account knows npm never ran and the tree is unchanged. The
        # lockfile route (store_maintenance) says what it restored instead
        # (a failure that is not a Store refusal never goes private).
        [ "${lockfile_delivery:-false}" = true ] \
            || echo "Shared node_modules Store: resolve phase failed;" \
                    "npm install was not run" 1>&2
        return 1
    fi
    if [ -n "${maintenance_signal:-}" ]; then
        # The lockfile route was told to stop while bun resolved on the
        # account side: nothing has reached root, so nothing is sent.
        rm -f -- "${CL_VIRTUAL_ENV}/lib/.cl-bun-rebuild-env"
        return 1
    fi
    # Delivery now keeps the old tree in its privileged transaction while it
    # invokes the account-side rebuild; only a successful rebuild commits.
    # The client's exit 3 is its own (never bun's): root refused a source or
    # package shape it does not serve with the tree untouched -- or answered
    # disabled/excluded after a policy change since the preflight -- and the
    # install takes the classic route. Like the no-dependencies sentinel,
    # that route must not leave resolve's native-build settings for a
    # rebuild that never runs. The client names such a reason on stdout; an
    # `npm ci` refused for one is remembered (ci_classic_remembered).
    deliver_reason="$("${BUN_USER_PHASE}" deliver "${CL_APP_ROOT}")"
    deliver_status=$?
    case ${deliver_status} in
        0)
            # Delivered: an earlier refusal's memo is stale.
            rm -f -- "${CI_CLASSIC_MEMO}" "${MAINTENANCE_PRIVATE_MEMO}"
            return 0
            ;;
        4|129|1[3-9][0-9]|2[0-9][0-9])
            # The client sent the request and got no verdict (4), or died of
            # a signal (128+n, a hang-up's 129 too) after it may have: root
            # runs a delivery it received to the end, so nobody knows whether
            # it committed.
            delivery_outcome_unknown=true
            return "${deliver_status}"
            ;;
        3)
            rm -f -- "${CL_VIRTUAL_ENV}/lib/.cl-bun-rebuild-env"
            store_refusal="${deliver_reason:-policy}"
            if [ "${CL_BUN_COMMAND:-}" = ci ] && [ -n "${deliver_reason}" ] \
                    && [ "${lockfile_delivery:-false}" != true ]; then
                ci_input_hashes > "${CI_CLASSIC_MEMO}" \
                    || rm -f -- "${CI_CLASSIC_MEMO}"
            fi
            return 3
            ;;
        2)
            # Not asked: the daemon unreachable before the request was sent.
            store_unavailable=unavailable
            ;;
        1)
            # The account's delivery limits, which the client names here.
            case "${deliver_reason%%$'\n'*}" in
                rate_limited|daily_limit) store_unavailable="${deliver_reason%%$'\n'*}" ;;
            esac
            ;;
    esac
    return 1
}

CI_CLASSIC_MEMO="${CL_VIRTUAL_ENV}/lib/.cl-bun-ci-classic"

store_policy_check() {
    # The account's Store policy, under the application lock. 3 is the
    # administrator's choice of classic npm, named in `store_refusal`
    # (disabled or excluded); anything else but 0 is a failure to ask.
    local status
    store_refusal="$("${NODE_MODULES_STORAGE_PYTHON}" "${DELIVERY_CLIENT}" \
        policy "${CL_APP_ROOT}")"
    status=$?
    store_refusal="${store_refusal%%$'\n'*}"
    if [ "${status}" -eq 3 ]; then
        store_refusal="${store_refusal:-policy}"
        return 3
    fi
    store_refusal=''
    [ "${status}" -eq 0 ] && store_policy_checked=true
    return "${status}"
}

ci_input_hashes() {
    # The Store configuration too: an allowlist change may serve what a
    # foreign registry made the Store refuse.
    printf 'omit-dev=%s\n' "${CL_BUN_OMIT_DEV:-0}"
    sha256sum -- "${HOME}/${CL_APP_ROOT}/package-lock.json" \
        "${HOME}/${CL_APP_ROOT}/package.json" "${BUN_DELIVERY_CONF}" 2>/dev/null
}

ci_classic_remembered() {
    # An application whose lockfile the Store refused for good -- inexact,
    # a git or bundled dependency, a foreign registry -- would pay a
    # delivery from the account's budget on every `npm ci` and end at
    # rate_limited. The account-owned memo only narrows the account's own
    # Store use: while the lockfile and manifest hash as they did when root
    # refused them, `npm ci` goes to classic npm without asking the daemon.
    local hashes
    [ -f "${CI_CLASSIC_MEMO}" ] && [ ! -L "${CI_CLASSIC_MEMO}" ] || return 1
    hashes="$(ci_input_hashes)" || return 1
    [ "${hashes}" = "$(cat -- "${CI_CLASSIC_MEMO}" 2>/dev/null)" ]
}

# --- Maintenance through the Store (CLOS-7898) -----------------------------
#
# On a host where the Store delivers, a named install, uninstall, update,
# prune, dedupe or `audit fix` -- `install-modules --modules`, which reaches
# npm as `npm install <specs>`, and a bare install with options that change
# what npm resolves -- keeps the application's tree shared: npm only writes
# package.json and the lockfile (`--package-lock-only --ignore-scripts` with
# the venv prefix, so it resolves against the tree as installed and runs
# nothing), and the Store delivers that lockfile exactly, as for a bare
# `npm ci`, under the same application lock. A bare install stays an
# install there: its delivery runs the application's own install scripts,
# is counted as an install and is never skipped as unchanged. A private
# copy is made only where the Store cannot represent the result -- a
# refusal it names, the administrator's choice, or a command whose result
# is not a lockfile install (`--no-save`, `--omit`, `npm rebuild`, ...) --
# and one stderr line names the reason whenever that makes a shared tree
# private. A delivery that fails for any other reason fails the command
# over a shared tree, with package.json and the lockfile restored; over a
# tree that is already private npm installs the change on that copy, with
# one line.

MAINTENANCE_PRIVATE_MEMO="${CL_VIRTUAL_ENV}/lib/.cl-bun-maintenance-private"
# The reason of the refusal that sent this command to classic npm
# (maintenance_refusal_remembered), whose memo then follows the files.
maintenance_remembered_reason=''

maintenance_memo_key() {
    # The refusal, the Store configuration (an allowlist change may serve
    # it), the application's .npmrc (a registry or legacy-peer-deps), and
    # the package.json and lockfiles it refused (a yarn.lock or
    # pnpm-lock.yaml beside a setting bun would write): once the account
    # removed what the Store refused, the next command asks again.
    printf '%s\n' "$1"
    printf 'omit-dev=%s\n' "${CL_BUN_OMIT_DEV:-0}"
    sha256sum -- "${BUN_DELIVERY_CONF}" "${HOME}/${CL_APP_ROOT}/.npmrc" \
        "${HOME}/${CL_APP_ROOT}/package.json" \
        "${HOME}/${CL_APP_ROOT}/package-lock.json" \
        "${HOME}/${CL_APP_ROOT}/yarn.lock" \
        "${HOME}/${CL_APP_ROOT}/pnpm-lock.yaml" 2>/dev/null
    return 0
}

maintenance_refusal_remembered() {
    # A refusal the Store will repeat until something changes is not
    # paid for -- policy, resolve and a delivery unit -- on every command.
    # A Store marker proves a delivery since then, also one the account's
    # wrapper never saw (the panel hook, migration): the memo is stale.
    local reason
    [ -f "${MAINTENANCE_PRIVATE_MEMO}" ] && [ ! -L "${MAINTENANCE_PRIVATE_MEMO}" ] \
        || return 1
    if [ -e "${venv_node_modules}/.cl-shared-node-modules.json" ]; then
        rm -f -- "${MAINTENANCE_PRIVATE_MEMO}"
        return 1
    fi
    reason="$(head -n 1 -- "${MAINTENANCE_PRIVATE_MEMO}" 2>/dev/null)" || return 1
    [ -n "${reason}" ] || return 1
    [ "$(maintenance_memo_key "${reason}")" \
      = "$(cat -- "${MAINTENANCE_PRIVATE_MEMO}" 2>/dev/null)" ] || return 1
    maintenance_remembered_reason=${reason}
    maintenance_private "$(refusal_token "${reason}")" \
        "the Store refused this application earlier (${reason})" refused
}

maintenance_dependency_maps() {
    # The dependency maps of the application's package.json, as one JSON
    # line; nothing when it cannot be read.
    "${LOCKFILE_PYTHON}" -I - "${HOME}/${CL_APP_ROOT}/package.json" <<'PY' 2>/dev/null
import json
import sys

with open(sys.argv[1], encoding='utf-8') as handle:
    manifest = json.load(handle)
maps = {}
for name in ('dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'):
    entries = manifest.get(name)
    maps[name] = entries if isinstance(entries, dict) else {}
print(json.dumps(maps, sort_keys=True))
PY
}

maintenance_dependencies_only_added() {
    # maintenance_dependencies_only_added <maps before>: whether the
    # dependency maps of package.json still hold every entry of <maps
    # before> (maintenance_dependency_maps) with its spec: the command only
    # added dependencies. A removed or changed entry, or a package.json
    # that cannot be read, is not.
    "${LOCKFILE_PYTHON}" -I - "$1" "$(maintenance_dependency_maps)" <<'PY' 2>/dev/null
import json
import sys

before, after = json.loads(sys.argv[1]), json.loads(sys.argv[2])
sys.exit(0 if all(name in after[map_name] and after[map_name][name] == spec
                  for map_name, entries in before.items()
                  for name, spec in entries.items()) else 1)
PY
}

refusal_token() {
    # A refusal as the daemon counts it: `unsupported-manifest workspaces`
    # is `unsupported_manifest`, `registry_not_allowed` itself. A lockfile
    # without integrity is its own: what the account does about it differs.
    if [ "$1" = 'unsupported-manifest missing-integrity' ]; then
        printf '%s' missing_integrity
        return 0
    fi
    local token="${1%% *}"
    printf '%s' "${token//-/_}"
}

maintenance_private() {
    # $1 the reason as the daemon counts it (CL_BUN_FALLBACK_REASON, sent
    # with the unshare), $2 what the notice says, $3 the kind: `refused`
    # (the Store said no to a maintenance command, now or earlier: always
    # said), `install` (a bare install's or `npm ci`'s refusal), `policy`
    # (the administrator's choice) or anything else (`unrepresentable`).
    # All but the first are said only when they make a shared tree
    # private, and every one is counted as maintenance unless the command
    # is a bare install or `npm ci`.
    maintenance_private_token=$1
    maintenance_private_label=$2
    maintenance_private_kind=${3:-unrepresentable}
}

maintenance_hint() {
    # What the account can do about it: a reason that goes away with the
    # next command says how the tree is shared again; one that stays says
    # what to change.
    case "$1" in
        old_npm) echo "select Node.js 16 or later to keep it shared" ;;
        no_package_lock) echo "with a lockfile (package-lock enabled) the Store keeps it shared" ;;
        divergent_lockfiles) echo "reconcile the application's and its environment's lockfiles -- remove the environment's copy, or copy it over the application's -- to keep it shared" ;;
        unsupported_source|registry_not_allowed) echo "the Store serves registry packages from the allowed registries only; remove such a dependency and the next command shares the tree again" ;;
        unsupported_manifest|legacy_package_lock_v1|unsupported_npm_shrinkwrap) echo "the Store does not read this manifest or lockfile; once that changes, the next command shares the tree again" ;;
        missing_integrity) echo "package-lock.json has entries without integrity, which the Store cannot verify; npm update records it, within the ranges package.json allows, and the next command shares the tree again" ;;
        lockfile_too_large|too_many_packages|package_lock_too_large) echo "the application is above the Store's limits" ;;
        disabled|excluded|policy) echo "the Store serves this account again once the administrator enables it for the account" ;;
        no_dependencies) echo "the next dependency it gets is shared again" ;;
        *) echo "\"Run NPM Install\", a bare npm install or npm ci offers it to the Store again" ;;
    esac
}

maintenance_private_announce() {
    # Before the unshare of the classic path: one line, and the reason
    # for root's counters -- not for a bare install (with any options:
    # `--production` is the deploy line) or `npm ci`, which is an install
    # whatever sent it to classic npm.
    [ -n "${maintenance_private_token:-}" ] || return 0
    if [ "${maintenance_private_kind}" != install ] && ! is_bare_install \
            && ! is_npm_ci "${npm_classification_args[@]}"; then
        export CL_BUN_ORIGIN=maintenance
        export CL_BUN_FALLBACK_REASON="${maintenance_private_token}"
    fi
    case "${maintenance_private_kind}" in
        refused) ;;
        *) bun_delivery_needs_unshare || return 0 ;;
    esac
    if is_npm_ci "${npm_classification_args[@]}"; then
        # Classic npm ci replaces the tree itself, nothing is unshared
        # first, and a drifted package.json fails it before it touches
        # node_modules: the tree becomes private only if it succeeds.
        echo "Shared node_modules Store: ${maintenance_private_label};" \
             "classic npm ${npm_classification_args[0]} installs this" \
             "application's node_modules, which becomes a private copy only" \
             "if it succeeds ($(maintenance_hint "${maintenance_private_token}"))." >&2
        return 0
    fi
    echo "Shared node_modules Store: ${maintenance_private_label};" \
         "npm continues with a private copy of this application's" \
         "node_modules ($(maintenance_hint "${maintenance_private_token}"))." >&2
}

classic_private_reason() {
    # What still runs classic npm over a shared tree says why: a
    # refused bare install or `npm ci`, a Store the administrator disabled,
    # an option the Store cannot represent, `npm install-test`.
    local command="${npm_classification_args[0]:-}"
    if [ -n "${store_refusal:-}" ]; then
        case "${store_refusal}" in
            resolve_failed)
                # Not a refusal, but over a shared tree it is said.
                maintenance_private resolve_failed \
                    "the Store could not resolve this lockfile" install
                ;;
            remembered_ci)
                maintenance_private remembered "the Store refused this lockfile earlier" install
                ;;
            disabled|excluded|policy)
                bun_delivery_needs_unshare || return 0
                maintenance_private "${store_refusal}" \
                    "the administrator chose classic npm for this account (${store_refusal})" policy
                ;;
            no-dependencies)
                maintenance_private no_dependencies \
                    "the application has no dependencies left (no-dependencies)" install
                ;;
            *)
                maintenance_private "$(refusal_token "${store_refusal}")" \
                    "the Store refused this install (${store_refusal})" install
                ;;
        esac
        return 0
    fi
    bun_delivery_needs_unshare || return 0
    if [ "${store_state_disabled:-false}" = true ] || ! config_may_enable_delivery; then
        maintenance_private disabled "the Store is disabled on this server" policy
        return 0
    fi
    maintenance_unrepresentable "$@" && return 0
    if [ "${command}" = install-test ] || [ "${command}" = it ] \
            || is_npm_ci_test_spelling "${command}"; then
        maintenance_private install_test "npm ${command} installs and tests on a private copy"
        return 0
    fi
    maintenance_private classic_command "npm ${command} with these options runs on a private copy"
}

npm_config_file() {
    # Resolve userconfig/globalconfig from argv, environment and npmrc in
    # precedence order. Expand npm's environment references only for values
    # read from configuration, never as shell code.
    local key=$1 fallback=$2 name path= rest file
    shift 2
    npm_config_path_unmodelled=false
    while [ "$#" -gt 0 ] && [ "$1" != -- ]; do
        case "$1" in
            "--${key}="*) path=${1#*=} ;;
            "--${key}") path=${2:-}; [ "$#" -gt 1 ] && shift ;;
        esac
        shift
    done
    if [ -z "${path}" ]; then
        for name in $(compgen -e); do
            [ "${name,,}" = "npm_config_${key}" ] && [ -n "${!name}" ] && path=${!name}
        done
        for file in "${npm_project_npmrc}" "${npm_user_npmrc_path:-${HOME}/.npmrc}"; do
            [ -z "${path}" ] || break
            if npmrc_value "${file}" "${key}"; then
                path=${npmrc_value_read}
            fi
            [ "${key}" != userconfig ] || break
        done
        [[ "${path}" != *'\${'* ]] || npm_config_path_unmodelled=true
        rest=${path}
        path=
        while [[ "${rest}" == *'${'* ]]; do
            path+=${rest%%'${'*}
            rest=${rest#*'${'}
            name=${rest%%'}'*}
            [[ "${name}" != *'?' ]] || npm_config_path_unmodelled=true
            if [[ "${rest}" == *'}'* && "${name}" != *[\$\{\?]* ]] \
                    && printenv -- "${name}" >/dev/null 2>&1; then
                path+=$(printenv -- "${name}")
                rest=${rest#*'}'}
            else
                path+='${'
            fi
        done
        path+=${rest}
    fi
    [ -n "${path}" ] || path=${fallback}
    [[ "${path}" != '~/'* ]] || path="${HOME}/${path:2}"
    npm_config_path=${path}
}

npm_config_files() {
    npm_project_npmrc="${CL_VIRTUAL_ENV}/lib/.npmrc"
    [ -e "${npm_project_npmrc}" ] || npm_project_npmrc="${HOME}/${CL_APP_ROOT}/.npmrc"
    npm_config_file userconfig "${HOME}/.npmrc" "$@"
    npm_user_npmrc_path=${npm_config_path}
    npm_user_npmrc_unmodelled=${npm_config_path_unmodelled}
    local prefix="${CL_VIRTUAL_ENV}/lib"
    [ "${npm_explicit_application_prefix:-false}" != true ] || prefix=${npm_prefix_value}
    npm_config_file globalconfig "${prefix}/etc/npmrc" "$@"
    npm_global_npmrc_path=${npm_config_path}
    npm_global_npmrc_unmodelled=${npm_config_path_unmodelled}
}

store_dependency_mode() {
    local mode
    mkdir -p -- "${CL_VIRTUAL_ENV}/lib" || return 1
    expose_app_npmrc || return 1
    # Keep the legacy npmrc decision above: the phase must not recreate an
    # alias we omitted because this npm cannot expand an unset variable.
    mode="$(CL_BUN_NPMRC_READY=1 "${BUN_USER_PHASE}" dependency-mode "${CL_APP_ROOT}" \
               "${CL_VIRTUAL_ENV##*/}" "$@")" || mode=unsupported
    case "${mode}" in
        all) export CL_BUN_OMIT_DEV=0 ;;
        dev) export CL_BUN_OMIT_DEV=1 ;;
        *)
            maintenance_private omit "the effective npm dependency filter cannot be represented"
            return 1
            ;;
    esac
}

npm_configuration_disables() {
    # npm_configuration_disables <save|package-lock|shrinkwrap>: the first source
    # that sets the key wins -- environment, project, user, global.
    local key=$1 name file value= found=false
    for name in "npm_config_${key//-/_}" \
                "$(printf '%s' "NPM_CONFIG_${key//-/_}" | tr 'a-z' 'A-Z')"; do
        [ -n "${!name:-}" ] || continue
        trim_value "${!name}"
        value=${trimmed_value}
        found=true
        break
    done
    if [ "${found}" = false ]; then
        for file in "${npm_project_npmrc}" "${npm_user_npmrc_path:-${HOME}/.npmrc}" \
                    "${npm_global_npmrc_path}"; do
            npmrc_value "${file}" "${key}" || continue
            value=${npmrc_value_read}
            found=true
            break
        done
    fi
    [ "${found}" = true ] && [ -n "${value}" ] || return 1
    # npm versions disagree on non-boolean off values (0, null, undefined).
    # Let classic npm handle any value that can disable saving or locking.
    ! npm_reads_as_on "${value}"
}

npm_reads_as_on() {
    # npm_reads_as_on <value>: whether npm reads a boolean setting's value,
    # trimmed (and from an .npmrc unquoted), as on -- measured with
    # ignore-scripts on npm 8.19.4, 10.9.8 and 11.16.0: off for `false`,
    # `null`, `undefined` and a number that is zero (`0`, `00`, `0.0`), on
    # for anything else, `no`, `off` and `FALSE` included.
    case "$1" in
        false|null|undefined) return 1 ;;
    esac
    if [[ "$1" =~ ^[+-]?([0-9]*\.?[0-9]*)([eE][+-]?[0-9]+)?$ ]] \
            && [[ "${BASH_REMATCH[1]}" == *[0-9]* ]]; then
        [[ "${BASH_REMATCH[1]}" == *[1-9]* ]]
        return
    fi
    return 0
}

trim_value() {
    # Sets trimmed_value to $1 without its leading and trailing whitespace.
    trimmed_value=${1#"${1%%[![:space:]]*}"}
    trimmed_value=${trimmed_value%"${trimmed_value##*[![:space:]]}"}
}

npmrc_value() {
    # npmrc_value <file> <key>: sets npmrc_value_read to what the file's
    # last line for <key> gives it, as npm's ini reader does -- trimmed, a
    # `;` or `#` comment cut off, one pair of quotes removed, `true` for a
    # line without `=` -- or fails when no line sets it. The key matches
    # exactly (npm ignores `ignore_scripts` or upper case there), and a
    # key below a `[section]` is not npm's.
    local line value found=false
    npmrc_value_read=
    [ -f "$1" ] && [ -r "$1" ] || return 1
    while IFS= read -r line || [ -n "${line}" ]; do
        trim_value "${line}"
        line=${trimmed_value}
        case "${line}" in
            '['*) break ;;
            ''|';'*|'#'*) continue ;;
        esac
        trim_value "${line%%=*}"
        [ "${trimmed_value}" = "$2" ] || continue
        found=true
        if [[ "${line}" != *=* ]]; then
            npmrc_value_read=true
            continue
        fi
        trim_value "${line#*=}"
        value=${trimmed_value}
        case "${value}" in
            \"*\"|\'*\') value=${value:1:${#value}-2} ;;
            *)
                trim_value "${value%%[;#]*}"
                value=${trimmed_value}
                ;;
        esac
        npmrc_value_read=${value}
    done < "$1"
    [ "${found}" = true ]
}

npm_configuration_bool() {
    # npm_configuration_bool <key>: environment, project, user, global.
    # A higher-priority false value wins over a lower-priority true value.
    local key=$1 name seen=false file
    for name in $(compgen -e); do
        [ "${name,,}" = "npm_config_${key//-/_}" ] && [ -n "${!name}" ] || continue
        seen=true
        trim_value "${!name}"
        [ -z "${trimmed_value}" ] && return 0
        npm_reads_as_on "${trimmed_value}" && return 0
    done
    [ "${seen}" = true ] && return 1
    if [ "${key}" = ignore-scripts ] && { [ "${npm_user_npmrc_unmodelled:-false}" = true ] \
            || [ "${npm_global_npmrc_unmodelled:-false}" = true ]; }; then
        return 0
    fi
    for file in "${npm_project_npmrc}" "${npm_user_npmrc_path:-${HOME}/.npmrc}" \
                "${npm_global_npmrc_path}"; do
        npmrc_value "${file}" "${key}" || continue
        [ -z "${npmrc_value_read}" ] && return 0
        npm_reads_as_on "${npmrc_value_read}"
        return
    done
    return 1
}

maintenance_unrepresentable() {
    # S5: what the Store's exact install of a lockfile cannot represent.
    # Sets the reason (maintenance_private) and succeeds, or fails.
    local arg next major venv_lib
    # npm's JSON of a bare install or `npm ci` reports the install; the
    # lockfile phase's would report nothing added. First: a bare install
    # is delivered on old npm too.
    if is_bare_install || is_npm_ci "${npm_classification_args[@]}"; then
        for arg in "$@"; do
            case "${arg}" in
                --) break ;;
                --json|--json=true)
                    maintenance_private classic_command \
                        "npm ${npm_classification_args[0]} --json reports npm's own install"
                    return 0
                    ;;
            esac
        done
    fi
    major="${CL_VIRTUAL_ENV##*/}"
    if [[ "${major}" =~ ^[0-9]+$ ]] && [ "${major}" -lt 16 ]; then
        # npm 6 and older write a version-1 lockfile, which the Store
        # refuses; no alt-nodejs 15 exists.
        maintenance_private old_npm "npm 6 or older (Node.js ${major}) writes a lockfile the Store does not read"
        return 0
    fi
    if [ "${npm_explicit_application_prefix}" = true ]; then
        # Exactly venv lib is the default route; anything else in the venv,
        # or a prefix that cannot be resolved, is not.
        venv_lib="$(realpath -m -- "${CL_VIRTUAL_ENV}/lib" 2>/dev/null)"
        if [ -z "${venv_lib}" ] \
                || [ "$(realpath -m -- "${npm_prefix_value}" 2>/dev/null)" != "${venv_lib}" ]; then
            maintenance_private prefix "a --prefix inside the application's environment cannot be represented"
            return 0
        fi
    fi
    while [ "$#" -gt 0 ]; do
        arg=$1
        next=${2:-}
        case "${arg}" in
            --)
                break
                ;;
            --no-save|--no-save=true|--save=false)
                maintenance_private no_save "--no-save leaves node_modules different from package-lock.json"
                return 0
                ;;
            --save)
                if [ "${next}" = false ]; then
                    maintenance_private no_save "--no-save leaves node_modules different from package-lock.json"
                    return 0
                fi
                ;;
            --no-package-lock|--package-lock=false|--no-shrinkwrap|--shrinkwrap=false)
                # shrinkwrap: npm 7+'s deprecated alias of package-lock.
                maintenance_private no_package_lock "${arg} leaves no lockfile to install"
                return 0
                ;;
            --package-lock|--shrinkwrap)
                if [ "${next}" = false ]; then
                    maintenance_private no_package_lock "${arg} false leaves no lockfile to install"
                    return 0
                fi
                ;;
            --include=dev|--also=dev|--also=development|--include=optional|--include=peer)
                # The Store's own semantics: devDependencies are installed,
                # and optional and peer dependencies, as npm does by default.
                ;;
            --include|--also)
                case "${next}" in
                    dev|development|optional|peer) shift ;;
                    *)
                        maintenance_private omit "${arg} cannot be represented by its exact install"
                        return 0
                        ;;
                esac
                ;;
            --omit=dev) ;;
            --omit)
                if [ "${next}" = dev ]; then
                    shift
                else
                    maintenance_private omit "--omit cannot be represented by its exact install"
                    return 0
                fi
                ;;
            --omit=*|--include=*|--only|--only=*|--also=*)
                maintenance_private omit "${arg%%=*} cannot be represented by its exact install"
                return 0
                ;;
            --no-optional|--optional=false)
                # npm 7+ reads it as --omit=optional.
                maintenance_private omit "${arg} cannot be represented by its exact install"
                return 0
                ;;
            --optional)
                if [ "${next}" = false ]; then
                    maintenance_private omit "--optional false cannot be represented by its exact install"
                    return 0
                fi
                ;;
            --production|--production=true)
                if [ "${arg}" = --production ] \
                        && { [ "${next}" = false ] || [ "${next}" = true ]; }; then
                    shift
                fi
                ;;
            --ignore-scripts|--ignore-scripts=*)
                # npm reads any value after `=` but `false` as on (`=0`,
                # `=no` and an empty one too), and the next token only when
                # it is `true` or `false` (measured, npm 8.19.4 to 11.16.0).
                if [ "${arg}" = --ignore-scripts=false ]; then
                    :
                elif [ "${arg}" = --ignore-scripts ] && [ "${next}" = false ]; then
                    shift
                else
                    maintenance_private ignore_scripts "--ignore-scripts cannot be represented: its install runs dependency scripts"
                    return 0
                fi
                ;;
            --install-strategy|--install-strategy=*|--global-style|--global-style=true|--legacy-bundling|--legacy-bundling=true)
                maintenance_private install_strategy "${arg%%=*} cannot be represented: it lays out its own tree"
                return 0
                ;;
            -w|-ws|-iwr|--workspace|--workspace=*|--workspaces|--workspaces=true|--include-workspace-root|--include-workspace-root=true)
                maintenance_private workspaces "npm workspaces (${arg%%=*}) cannot be represented"
                return 0
                ;;
            --cpu|--cpu=*|--os|--os=*|--libc|--libc=*)
                # npm installs the optional packages of the platform these
                # name; root's bun installs the host's.
                maintenance_private classic_command "${arg%%=*} chooses the platform of the optional packages npm installs"
                return 0
                ;;
        esac
        shift
    done
    case "${npm_classification_args[0]:-}" in
        rebuild|rb)
            maintenance_private rebuild "npm rebuild builds a private copy"
            return 0
            ;;
    esac
    return 1
}

npm_configuration_unrepresentable() {
    # The same settings in the configuration npm reads (checked once the
    # application's .npmrc is linked into venv lib).
    npm_config_files "$@"
    if npm_configuration_disables package-lock || npm_configuration_disables shrinkwrap; then
        maintenance_private no_package_lock "package-lock setting requires classic npm"
        return 0
    fi
    if npm_configuration_disables save; then
        maintenance_private no_save "save setting requires classic npm"
        return 0
    fi
    if npm_configuration_bool ignore-scripts; then
        maintenance_private ignore_scripts "ignore-scripts is on in npm's configuration, which cannot be represented: the Store's install runs dependency scripts"
        return 0
    fi
    return 1
}

install_names_packages() {
    # Whether the install-family command names packages: a token after it
    # that is no option and no option's value as npm reads them -- the
    # separate value of an option that takes one, `true`/`false` after any
    # other -- or anything after `--`.
    local arg
    set -- ${npm_classification_args[@]+"${npm_classification_args[@]:1}"}
    while [ "$#" -gt 0 ]; do
        arg=$1
        case "${arg}" in
            --)
                [ "$#" -gt 1 ]
                return
                ;;
            -*=*)
                ;;
            -*)
                if [ "$#" -gt 1 ] && { npm_option_takes_value "${arg}" "$2" \
                        || [ "$2" = true ] || [ "$2" = false ]; }; then
                    shift
                fi
                ;;
            *)
                return 0
                ;;
        esac
        shift
    done
    return 1
}

is_bare_install() {
    # An install that names no package -- the whole tree, whatever its
    # options. npm runs the application's own install scripts for it (and
    # for no named command), so it stays an install on the Store's route.
    is_install_spelling "${npm_classification_args[0]:-}" || return 1
    ! install_names_packages
}

is_store_maintenance_command() {
    # The commands whose result is the lockfile npm writes for them. A bare
    # install with output-only options is the Store's own route
    # (is_shared_node_modules_install); with any other option it takes this
    # route as an install (is_bare_install).
    if is_install_spelling "${npm_classification_args[0]:-}"; then
        ! is_shared_node_modules_install "$@"
        return
    fi
    case "${npm_classification_args[0]:-}" in
        uninstall|unlink|remove|rm|r|un|update|upgrade|up|u|udpate|dedupe|ddp|prune)
            return 0
            ;;
        audit)
            is_classic_npm_mutation "${npm_classification_args[@]}"
            return
            ;;
    esac
    return 1
}

npm_option_without_value() {
    # npm_option_without_value <argv>: prints the option that ends the
    # account's options -- the last token before its `--`, or its last --
    # when npm reads the token after it as its value (npm_option_takes_value)
    # and the account gave none. npm takes that token even when it starts
    # with `--`, and build_npm_argv puts the wrapper's own options exactly
    # there: the first of them would become that value. Not the options
    # npm 5.6 to 11 (Node.js 8 and later) type as a plain string, measured
    # with each installed npm (5.6.0, 6.7.0, 6.13.4, 6.14.x, 8.19.4, 9.6.3,
    # 10.8.2, 10.9.8, 11.16.0, 11.17.0; 11.19.0's own parser): followed by
    # an option, npm reads them as empty and reads that option again. npm
    # 3.10.10 (Node.js 6, nopt 3) takes it as the value of those it
    # defines, which stay refused there; the others are booleans in npm 3.
    # The venv is named after the Node.js major; any other name is read as
    # Node.js 6, the refusal. A token npm reads as its shorthands, which
    # npm_canonical_options keeps as typed, is read as the last option it
    # expands to: `-reg` is `--registry`, a prefix of `-enjoy-by`
    # `--before`, single letters run together end in the last one's (`-gC`
    # is `--global --prefix`, `--passw` ends in `--workspace`). `--no-` before
    # an option npm 7 to 11 type as a boolean or a string (`cert` is
    # `[null, String]`) takes a value too: nopt reads the next token as
    # that string, also one starting with `--` (npm 10.9.8: `npm install x
    # --no-cert` reified the application root).
    local arg option name pending= major="${CL_VIRTUAL_ENV##*/}" npm3=true
    [[ "${major}" =~ ^[0-9]+$ ]] && [ "${major}" -ge 8 ] && npm3=false
    while [ "$#" -gt 0 ]; do
        arg=$1
        shift
        [ "${arg}" = -- ] && break
        if [ -n "${pending}" ]; then
            # Taken as the pending option's value, whatever it looks like.
            pending=
            continue
        fi
        option=${arg}
        name=${arg#-}
        name=${name#-}
        if [[ "${arg}" == -* ]] && [[ " ${npm_option_names} " != *" ${name} "* ]]; then
            case "${name}" in
                reg) option=--registry ;;
                enj|enjo|enjoy|enjoy-|enjoy-b|enjoy-by) option=--before ;;
                *) [[ "${name}" =~ ^[?BCDEHLOPSacdfghlmnpqsvwy]+$ ]] && option=-${name: -1} ;;
            esac
        fi
        case "${option}" in
            -*=*) ;;
            --no-*)
                name=${option#--}
                while [[ "${name}" == no-* ]]; do
                    name=${name:3}
                done
                { npm_no_option_takes_value "${name}" \
                  || [[ "${npm_ambiguous_value_options}" == *" ${arg} "* ]]; } \
                    && pending=${arg}
                ;;
            -c|--call|--diff-dst-prefix|--diff-src-prefix|--init-type|\
            --init.author.email|--init.author.name|--init.license|\
            --pack-destination|--preid) ;;
            -m|--editor|--git|--heading|--init-author-email|--init-author-name|\
            --init-license|--message|--save-prefix|--scope|--searchopts|\
            --shell|--tag|--tag-version-prefix|--user-agent|--viewer)
                [ "${npm3}" = true ] && npm_option_takes_value "${option}" "${1:-}" \
                    && pending=${arg}
                ;;
            -*)
                { npm_option_takes_value "${option}" "${1:-}" \
                  || [[ "${npm_ambiguous_value_options}" == *" ${arg} "* ]]; } \
                    && pending=${arg}
                ;;
        esac
    done
    [ -n "${pending}" ] || return 1
    printf '%s\n' "${pending}"
}

build_npm_argv() {
    # The account's argv with npm_extra_args (the venv prefix, the lockfile
    # phase) before its `--`: after it npm reads everything as a package
    # spec, and an appended `--prefix` there made npm reify in the
    # application root and replace the Selector's link. An account option
    # that would take them as its value is refused before anything runs
    # (npm_option_without_value).
    local arg inserted=false
    npm_argv=()
    for arg in "$@"; do
        if [ "${inserted}" = false ] && [ "${arg}" = -- ]; then
            npm_argv+=(${npm_extra_args[@]+"${npm_extra_args[@]}"})
            inserted=true
        fi
        npm_argv+=("${arg}")
    done
    if [ "${inserted}" = false ]; then
        npm_argv+=(${npm_extra_args[@]+"${npm_extra_args[@]}"})
    fi
}

export_native_build_options() {
    # Native-build options on the command line cross into the delivery's
    # rebuild like their environment forms (save_rebuild_environment).
    local arg name
    while [ "$#" -gt 0 ]; do
        arg=$1
        case "${arg}" in
            --)
                break
                ;;
            --build-from-source|--build-from-source=*|--nodedir=*|--arch=*|--platform=*|--target=*|--runtime=*|--devdir=*|--jobs=*|--python=*|--node-gyp=*)
                name=${arg#--}
                name=${name%%=*}
                if [[ "${arg}" == *=* ]]; then
                    export "npm_config_${name//-/_}=${arg#*=}"
                else
                    export "npm_config_${name//-/_}=true"
                fi
                ;;
            --nodedir|--arch|--platform|--target|--runtime|--devdir|--jobs|--python|--node-gyp)
                [ "$#" -gt 1 ] || break
                name=${arg#--}
                shift
                export "npm_config_${name//-/_}=$1"
                ;;
        esac
        shift
    done
}

maintenance_snapshot=''
MAINTENANCE_FILES="package.json package-lock.json npm-shrinkwrap.json"

maintenance_take_snapshot() {
    # What a failed delivery puts back -- package.json and the npm
    # lockfiles at the application root and the venv bun.lock, or their
    # absence.
    local name source
    maintenance_snapshot="$(mktemp -d "${CL_VIRTUAL_ENV}/lib/.cl-npm-maintenance.XXXXXX")" \
        || return 1
    for name in ${MAINTENANCE_FILES} bun.lock; do
        source="${HOME}/${CL_APP_ROOT}/${name}"
        [ "${name}" = bun.lock ] && source="${CL_VIRTUAL_ENV}/lib/bun.lock"
        if [ -f "${source}" ]; then
            cp -p -- "${source}" "${maintenance_snapshot}/${name}" || return 1
        else
            : > "${maintenance_snapshot}/${name}.absent" || return 1
        fi
    done
}

maintenance_files_changed() {
    local name app_file
    for name in ${MAINTENANCE_FILES}; do
        app_file="${HOME}/${CL_APP_ROOT}/${name}"
        if [ -e "${maintenance_snapshot}/${name}" ]; then
            cmp -s -- "${maintenance_snapshot}/${name}" "${app_file}" || return 0
        elif [ -e "${app_file}" ]; then
            return 0
        fi
    done
    return 1
}

maintenance_restore_snapshot() {
    # Puts back what S1 changed; fails (and the caller says so) when a file
    # could not be put back. Writes through a linked package.json, as npm
    # did; prepare-classic canonicalizes the lockfile layout again.
    local name target failed=0
    [ -n "${maintenance_snapshot}" ] || return 1
    for name in ${MAINTENANCE_FILES} bun.lock; do
        target="${HOME}/${CL_APP_ROOT}/${name}"
        [ "${name}" = bun.lock ] && target="${CL_VIRTUAL_ENV}/lib/bun.lock"
        if [ -e "${maintenance_snapshot}/${name}" ]; then
            cp -p -- "${maintenance_snapshot}/${name}" "${target}" || failed=1
        elif [ -e "${maintenance_snapshot}/${name}.absent" ]; then
            rm -f -- "${target}" || failed=1
            case "${name}" in
                package-lock.json|npm-shrinkwrap.json)
                    if [ -L "${CL_VIRTUAL_ENV}/lib/${name}" ]; then
                        rm -f -- "${CL_VIRTUAL_ENV}/lib/${name}" || failed=1
                    fi
                    ;;
            esac
        else
            failed=1
        fi
    done
    # The venv bun.lock is the snapshot's again; a copy a stopped resolve
    # set aside is not, and the next resolve would put it back.
    rm -f -- "${venv_node_modules}/.package-lock.json" \
        "${CL_VIRTUAL_ENV}/lib/.cl-bun-previous.lock"
    "${BUN_USER_PHASE}" prepare-classic "${CL_APP_ROOT}" >/dev/null 2>&1 || failed=1
    return "${failed}"
}

maintenance_restore_and_say() {
    # $1 what happened, $2 the state of node_modules. Never claims a
    # restore that did not happen.
    if maintenance_restore_snapshot; then
        echo "Shared node_modules Store: $1; package.json and package-lock.json" \
             "were restored and ${2:-node_modules is unchanged}." >&2
    else
        # Kept under a name the next command's cleanup leaves alone.
        local kept="${maintenance_snapshot%/*}/.cl-npm-previous-files.${maintenance_snapshot##*.}"
        mv -- "${maintenance_snapshot}" "${kept}" && maintenance_snapshot="${kept}"
        echo "Shared node_modules Store: $1; package.json or the lockfile could" \
             "not be put back -- compare them with your copy before the next" \
             "install (the previous ones are in ${maintenance_snapshot})." >&2
        maintenance_snapshot=''
    fi
}

maintenance_discard_snapshot() {
    [ -n "${maintenance_snapshot}" ] || return 0
    rm -rf -- "${maintenance_snapshot:?}"
    maintenance_snapshot=''
}

marker_names_current_files() {
    # The Store's marker of an exact delivery names the sha256 of the
    # package.json and package-lock.json it installed (cloudlinux_bun_delivery).
    # Only then is the tree known to be the application's current files: a
    # git pull or a hand edit since leaves a marker of other files.
    local marker="${venv_node_modules}/.cl-shared-node-modules.json" text pair key want have
    [ -f "${marker}" ] && [ ! -L "${marker}" ] || return 1
    [ ! -e "${HOME}/${CL_APP_ROOT}/npm-shrinkwrap.json" ] || return 1
    text="$(head -c 4096 -- "${marker}" 2>/dev/null)" || return 1
    for pair in package_json_sha256:package.json package_lock_sha256:package-lock.json; do
        key=${pair%%:*}
        want="$(printf '%s' "${text}" | grep -o "\"${key}\": *\"[0-9a-f]*\"" \
                | grep -o '[0-9a-f]\{64\}')"
        [ -n "${want}" ] || return 1
        have="$(sha256sum -- "${HOME}/${CL_APP_ROOT}/${pair#*:}" 2>/dev/null)" || return 1
        [ "${have%% *}" = "${want}" ] || return 1
    done
    # It names the files, not the tree as it is now: a package directory
    # deleted by hand is not the Store's install of them, and plain npm
    # reinstalls it.
    tree_holds_lockfile_packages "${text}"
}

tree_holds_lockfile_packages() {
    # Whether every package the application's package-lock.json installs
    # is in venv lib -- optional ones (skipped on this platform), links and
    # bundled packages aside. A lockfile that cannot be read says nothing.
    "${LOCKFILE_PYTHON}" -I - "${HOME}/${CL_APP_ROOT}/package-lock.json" \
        "${CL_VIRTUAL_ENV}/lib" "${CL_BUN_OMIT_DEV:-0}" "$1" <<'PY' 2>/dev/null
import json
import os
import sys

try:
    inputs = json.loads(sys.argv[4])['inputs']
    if (not isinstance(inputs, dict)
            or ('omit' in inputs and inputs['omit'] != 'dev')
            or inputs.get('omit') != ('dev' if sys.argv[3] == '1' else None)):
        sys.exit(1)
except (ValueError, KeyError, TypeError):
    sys.exit(1)
try:
    with open(sys.argv[1], encoding='utf-8') as handle:
        packages = json.load(handle).get('packages')
except (OSError, ValueError, AttributeError):
    sys.exit(0)
if not isinstance(packages, dict):
    sys.exit(0)
for path, entry in packages.items():
    if (not isinstance(path, str) or not path.startswith('node_modules/')
            or not isinstance(entry, dict) or entry.get('optional')
            or entry.get('devOptional') or entry.get('link') or entry.get('inBundle')):
        continue
    if sys.argv[3] == '1' and entry.get('dev') is True:
        continue
    if '..' in path.split('/') or not os.path.isdir(os.path.join(sys.argv[2], path)):
        sys.exit(1)
sys.exit(0)
PY
}

maintenance_signal=''

record_maintenance_signal() {
    # Between and around the phases a signal is recorded, not forwarded:
    # S1's npm is gone (its traps would signal a reaped pid), and a delivery
    # root already received runs to its end.
    maintenance_signal=$1
}

retire_stale_bun_lock() {
    # bun prefers a venv bun.lock over package-lock.json: after npm
    # wrote the application's lockfile, an older bun.lock would bring the
    # old pins back with the next "Run NPM Install". Without an npm
    # lockfile, bun.lock is the only record of the pins and stays. The
    # integrity it proves is kept in the lockfile first -- except after
    # `npm ci`, which never writes package-lock.json.
    if [ -f "${HOME}/${CL_APP_ROOT}/package-lock.json" ] \
            || [ -f "${HOME}/${CL_APP_ROOT}/npm-shrinkwrap.json" ]; then
        is_npm_ci "${npm_classification_args[@]}" || persist_bun_lock_integrity
        # With the copy a stopped resolve set aside, which the next resolve
        # would put back.
        rm -f -- "${CL_VIRTUAL_ENV}/lib/bun.lock" \
            "${CL_VIRTUAL_ENV}/lib/.cl-bun-previous.lock"
    fi
    return 0
}

persist_bun_lock_integrity() {
    # An application the Store installed without a package-lock.json has a
    # tree without npm metadata, so the lockfile npm writes for it has no
    # integrity for what is installed, and root refuses such a lockfile's
    # delivery as content it cannot verify. The venv bun.lock is bun's
    # resolution of exactly those packages from an allowed registry: an
    # entry without integrity or URL takes bun.lock's sha512 integrity of
    # the same name@version (and its URL where bun.lock names one), where
    # npm puts them. Links, bundled packages and anything bun.lock does not
    # prove stay as they are, and so does a file npm did not format (its
    # JSON written back in its own indentation and line ends is not the
    # file) or one that cannot be read. Root validates the result like any
    # lockfile.
    local bun_lock="${CL_VIRTUAL_ENV}/lib/bun.lock" lockfile
    [ -f "${bun_lock}" ] && [ ! -L "${bun_lock}" ] || return 0
    for lockfile in npm-shrinkwrap.json package-lock.json; do
        lockfile="${HOME}/${CL_APP_ROOT}/${lockfile}"
        [ -f "${lockfile}" ] && [ ! -L "${lockfile}" ] || continue
        "${LOCKFILE_PYTHON}" -I - "${bun_lock}" "${lockfile}" <<'PY' >/dev/null 2>&1
import json
import os
import re
import stat
import sys

maximum = 4 * 1024 * 1024
STRING = r'"(?:[^"\\]|\\.)*"'
ENTRY = re.compile(r'^\s*%s: \[(%s), (%s), .*, (%s)\],?$' % (STRING, STRING, STRING, STRING))
INTEGRITY = re.compile(r'sha512-[A-Za-z0-9+/]+={0,2}\Z')
URL = re.compile(r'https?://')


def read(path):
    fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK | os.O_NOFOLLOW)
    try:
        info = os.fstat(fd)
        if not stat.S_ISREG(info.st_mode) or info.st_size > maximum:
            sys.exit(0)
        chunks = []
        size = 0
        while size <= maximum:
            chunk = os.read(fd, 1024 * 1024)
            if not chunk:
                break
            chunks.append(chunk)
            size += len(chunk)
        if size > maximum:
            sys.exit(0)
        return b''.join(chunks).decode('utf-8'), info
    finally:
        os.close(fd)


text, info = read(sys.argv[2])
eol = '\r\n' if '\r\n' in text else '\n'
lines = text.split(eol)
indent = re.match(r'[ \t]*', lines[1]).group(0) if len(lines) > 1 else ''
if not indent:
    sys.exit(0)
lock = json.loads(text)
if not isinstance(lock, dict) or not isinstance(lock.get('packages'), dict):
    sys.exit(0)
packages = lock['packages']
# Nearly every lockfile has each entry's integrity: it is left as it is,
# without reading bun.lock or serialising it again.
unproven = [path for path, entry in packages.items()
            if isinstance(entry, dict) and 'node_modules/' in path
            and 'integrity' not in entry and 'resolved' not in entry
            and not entry.get('link') and not entry.get('inBundle')
            and isinstance(entry.get('version'), str)]
if not unproven:
    sys.exit(0)


def npm_text(value):
    return json.dumps(value, indent=indent, ensure_ascii=False).replace('\n', eol) + eol


if npm_text(lock) != text:
    sys.exit(0)
proven = {}
for line in read(sys.argv[1])[0].splitlines():
    match = ENTRY.match(line)
    if match:
        spec, url, integrity = (json.loads(group) for group in match.groups())
        if INTEGRITY.match(integrity) and (not url or URL.match(url)):
            proven.setdefault(spec, (url, integrity))
changed = False
for path in unproven:
    entry = packages[path]
    name = entry.get('name', path.rsplit('node_modules/', 1)[1])
    found = proven.get('%s@%s' % (name, entry['version']))
    if found is None:
        continue
    url, integrity = found
    items = []
    for key, value in entry.items():
        items.append((key, value))
        if key == 'version':
            items.extend(([('resolved', url)] if url else []) + [('integrity', integrity)])
    packages[path] = dict(items)
    changed = True
if changed:
    temporary = '%s.cl-integrity.%d' % (sys.argv[2], os.getpid())
    fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
    try:
        with os.fdopen(fd, 'w', encoding='utf-8', newline='') as handle:
            os.fchmod(handle.fileno(), stat.S_IMODE(info.st_mode))
            handle.write(npm_text(lock))
        os.replace(temporary, sys.argv[2])
    except BaseException:
        os.unlink(temporary)
        raise
PY
    done
    return 0
}

store_delivery_state() {
    # 0 when the Store applies here, 1 when it does not; a configuration
    # that cannot be checked refuses to run npm (bun_delivery_state).
    local state
    bun_delivery_state "${BUN_DELIVERY_CONF}"
    state=$?
    [ "${state}" -le 1 ] && return "${state}"
    echo "Node modules storage could not be checked; refusing to run npm." 1>&2
    exit "${state}"
}

store_maintenance_route() {
    # Returns to the classic path below (with the reason set when the tree
    # goes private for it), or exits.
    local lockfile policy_status
    if ! config_may_enable_delivery; then
        maintenance_private disabled "the Store is disabled on this server" policy
        return 0
    fi
    # The way back for a node_modules the Selector did not make comes
    # first, before anything is asked or changed.
    validate_setup_node_modules_link || exit $?
    maintenance_unrepresentable ${npm_option_args[@]+"${npm_option_args[@]}"} && return 0
    is_store_maintenance_command ${npm_option_args[@]+"${npm_option_args[@]}"} || return 0
    lock_classic || exit $?
    store_dependency_mode ${npm_option_args[@]+"${npm_option_args[@]}"} || return 0
    # A bare install always asks the Store, as without options. A refusal
    # remembered under this very configuration (the memo's key holds it)
    # goes on without the configuration reader, about 0.3 s: it would
    # deliver nothing either way.
    if ! is_bare_install && maintenance_refusal_remembered; then
        return 0
    fi
    if ! store_delivery_state; then
        maintenance_private disabled "the Store is disabled on this server" policy
        return 0
    fi
    lock_classic || exit $?
    # The administrator's choice of classic npm comes before npm runs,
    # which then runs once, the command as given (as before the Store).
    store_policy_check
    policy_status=$?
    case "${policy_status}" in
        0) ;;
        3)
            maintenance_private "${store_refusal}" \
                "the administrator chose classic npm for this account (${store_refusal})" policy
            return 0
            ;;
        1|2|4)
            if ! bun_delivery_needs_unshare; then
                # Over a tree that is already private nothing shared is lost:
                # npm runs the command there, as without the Store.
                echo "Shared node_modules Store: the Store could not be asked" \
                     "(see above); npm runs this command on the application's" \
                     "private copy of node_modules, as without the Store (the" \
                     "next command asks the Store again)." >&2
                return 0
            fi
            echo "Shared node_modules Store could not be asked (see above);" \
                 "nothing was changed." >&2
            exit "${policy_status}"
            ;;
        *)
            echo "Shared node_modules Store could not be asked (see above);" \
                 "nothing was changed." >&2
            exit "${policy_status}"
            ;;
    esac
    prepare_classic_lockfiles || exit $?
    for lockfile in package-lock.json npm-shrinkwrap.json; do
        if [[ " ${classic_canonical_locks} " != *" ${lockfile} "* ]] \
                && [[ -e "${HOME}/${CL_APP_ROOT}/${lockfile}" \
                      || -L "${HOME}/${CL_APP_ROOT}/${lockfile}" \
                      || -e "${CL_VIRTUAL_ENV}/lib/${lockfile}" ]]; then
            # The divergent-lockfile rule (CLOS-7874): npm keeps reading the
            # venv copy, which an exact delivery of the application root's
            # lockfile would ignore.
            maintenance_private divergent_lockfiles "the application root and its environment hold different lockfiles"
            return 0
        fi
    done
    for lockfile in yarn.lock pnpm-lock.yaml; do
        if [ -f "${HOME}/${CL_APP_ROOT}/${lockfile}" ] && [ ! -L "${CL_VIRTUAL_ENV}/lib/${lockfile}" ] \
                && [ -f "${CL_VIRTUAL_ENV}/lib/${lockfile}" ] \
                && ! cmp -s -- "${HOME}/${CL_APP_ROOT}/${lockfile}" "${CL_VIRTUAL_ENV}/lib/${lockfile}"; then
            # Delivery's preparation refuses a divergent pair of these
            # Bun-only lockfiles, which is no Store refusal: every command
            # would fail.
            maintenance_private divergent_lockfiles "the application root and its environment hold different ${lockfile} files"
            return 0
        fi
    done
    ensure_setup_node_modules_link || exit $?
    expose_app_npmrc || exit 1
    npm_configuration_unrepresentable ${npm_option_args[@]+"${npm_option_args[@]}"} && return 0
    store_maintenance "$@"
}

store_maintenance() {
    # S1: npm writes package.json and the lockfile only, and runs nothing:
    # npm runs the application's own install scripts for a bare
    # `--package-lock-only` install (measured, npm 10.9.8).
    local s1_status finish_status s2_status s3_status s2_input bare=false
    is_bare_install && bare=true
    # A SIGKILL leaves its snapshot behind; under the application lock no
    # other command is using one.
    find "${CL_VIRTUAL_ENV}/lib" -maxdepth 1 -type d -name '.cl-npm-maintenance.*' \
        -print0 2>/dev/null | xargs -0 -r rm -rf --
    # The previous files a failed restore kept for the account to compare
    # (maintenance_restore_and_say) go once they are a month old.
    find "${CL_VIRTUAL_ENV}/lib" -maxdepth 1 -type d -name '.cl-npm-previous-files.*' \
        -mtime +30 -print0 2>/dev/null | xargs -0 -r rm -rf --
    if ! maintenance_take_snapshot; then
        echo "Shared node_modules Store: package.json and the lockfile could" \
             "not be saved before the change; nothing was changed." >&2
        maintenance_discard_snapshot
        exit 1
    fi
    if [ "${bare}" = true ]; then
        # An install: its delivery's rebuild runs the application's
        # own install scripts, once, as npm does for a bare install, and
        # root counts it as an install (the client sends no origin).
        export CL_BUN_ORIGIN=install
    else
        # npm runs them for no named command.
        export CL_BUN_ORIGIN=maintenance
    fi
    export_native_build_options "$@"
    if has_selector_app_lock_fd; then
        npm_child_closes_fd="${CL_SELECTOR_APP_LOCK_FD}"
    fi
    npm_extra_args=(--package-lock-only --ignore-scripts
                    ${npm_prefix_args[@]+"${npm_prefix_args[@]}"})
    build_npm_argv "$@"
    run_classic_npm "${npm_argv[@]}"
    s1_status=$?
    npm_child_closes_fd=
    maintenance_signal="${classic_npm_signal:-}"
    trap 'record_maintenance_signal TERM' TERM
    trap 'record_maintenance_signal INT' INT
    trap 'record_maintenance_signal HUP' HUP
    finish_classic_lockfiles
    finish_status=$?
    # npm's hidden lockfile now describes a tree that is not on disk:
    # a later classic npm would read it and keep the old versions.
    rm -f -- "${venv_node_modules}/.package-lock.json"
    if [ -n "${maintenance_signal}" ] || [ "${finish_status}" -ne 0 ]; then
        maintenance_restore_snapshot
        maintenance_discard_snapshot
        classic_npm_signal="${maintenance_signal}"
        exit_classic_npm "${s1_status}" "${finish_status}"
    fi
    if ! maintenance_files_changed; then
        # npm's own error (`audit fix` exits 1 while advisories remain, also
        # after it reified), or nothing changed and the tree is the Store's
        # install of exactly these files. Anything else -- a tree a git pull
        # or a hand edit left behind, a private tree -- is delivered,
        # and so is every bare install, which runs the application's own
        # install scripts.
        if { [ "${s1_status}" -ne 0 ] \
             && { [ "${bare}" = true ] || [ "${npm_classification_args[0]:-}" != audit ] \
                  || [ ! -e "${HOME}/${CL_APP_ROOT}/package-lock.json" ]; }; } \
           || { [ "${bare}" = false ] && marker_names_current_files; }; then
            if [ "${s1_status}" -eq 0 ] && [ "${npm_classification_args[0]:-}" = prune ]; then
                echo "npm prune: this application's node_modules is the Shared" \
                     "node_modules Store's exact install of its package-lock.json;" \
                     "there is nothing to prune." >&2
            fi
            maintenance_discard_snapshot
            exit_classic_npm "${s1_status}" 0
        fi
    fi
    # S2: the exact delivery of that lockfile (`npm ci` semantics) -- or,
    # when npm wrote none (`npm dedupe` on an application the Store
    # installed without a package-lock.json), the Store's own install of
    # package.json against its bun.lock.
    s2_input=package.json
    if [ -e "${HOME}/${CL_APP_ROOT}/package-lock.json" ] \
            || [ -e "${HOME}/${CL_APP_ROOT}/npm-shrinkwrap.json" ]; then
        s2_input=package-lock.json
        export CL_BUN_COMMAND=ci
        # The delivered lockfile is the one the application keeps.
        persist_bun_lock_integrity
    fi
    lockfile_delivery=true
    bun_delivery_run
    s2_status=$?
    lockfile_delivery=false
    unset CL_BUN_COMMAND
    if [ "${delivery_outcome_unknown:-false}" = true ]; then
        # Root runs a delivery it received to the end; restoring the
        # files now would leave them behind a tree that may have the change.
        maintenance_discard_snapshot
        echo "Shared node_modules Store: the delivery was interrupted and its" \
             "outcome is unknown; package.json and package-lock.json hold the" \
             "change. Run the command again, or \"Run NPM Install\" or npm ci," \
             "to bring node_modules in line with them." >&2
        classic_npm_signal="${maintenance_signal}"
        exit_classic_npm "${s2_status}" 0
    fi
    if [ -n "${maintenance_signal}" ]; then
        # Told to stop with root's verdict known, or before the request was
        # sent: a delivery keeps the change; anything else left the
        # tree as it was, and the files go back to match it. Then stop,
        # before any classic npm.
        if [ "${s2_status}" -eq 0 ]; then
            maintenance_discard_snapshot
        else
            maintenance_restore_and_say "the command was interrupted"
            maintenance_discard_snapshot
        fi
        classic_npm_signal="${maintenance_signal}"
        exit_classic_npm "${s2_status}" 0
    fi
    if [ "${s2_status}" -eq 0 ]; then
        maintenance_discard_snapshot
        echo "Shared node_modules Store: installed the updated ${s2_input}" \
             "into node_modules; the tree stays shared." >&2
        # `npm audit fix` keeps its own status (1 while vulnerabilities
        # remain), also after its change was delivered.
        exit_classic_npm "${s1_status}" 0
    fi
    if [ "${s2_status}" -ne 3 ] && bun_delivery_needs_unshare; then
        # S4: no Store refusal -- the daemon unreachable, a rate or daily
        # limit, a failed resolve, delivery or rebuild -- over a shared tree,
        # and never a private conversion. Nothing changed: the files are put
        # back.
        maintenance_restore_and_say "could not install this change (the reason is above)"
        maintenance_discard_snapshot
        exit_classic_npm "${s2_status}" 0
    fi
    # S3: the Store refused. Classic npm runs the original command on a
    # private copy, and the refusal is said and counted -- a bare
    # install's as an install's. So is a change the Store could not take
    # for any other reason (unreachable, the account's delivery limits, a
    # failed resolve, delivery or rebuild -- a `patch-package` postinstall
    # cannot write the Store's inodes) over a tree that is already private:
    # nothing shared is lost, and nothing is remembered.
    local reason="${store_refusal:-policy}" remember=false
    reason="${reason%%$'\n'*}"
    if [ "${s2_status}" -ne 3 ]; then
        maintenance_private_token=''
        echo "Shared node_modules Store: the Store could not take this change" \
             "(${store_unavailable:-the reason is above}); npm installs it on the" \
             "application's private copy of node_modules, as without the Store" \
             "(the next command asks the Store again)." >&2
    elif [ "${reason}" = no-dependencies ]; then
        maintenance_private no_dependencies \
            "the application has no dependencies left (no-dependencies)" \
            "$([ "${bare}" = true ] && echo install || echo unrepresentable)"
    elif [ "${bare}" = true ]; then
        maintenance_private "$(refusal_token "${reason}")" \
            "the Store refused this install (${reason})" install
    else
        maintenance_private "$(refusal_token "${reason}")" \
            "the Store refused this change (${reason})" refused
        case "${maintenance_private_token}" in
            disabled|excluded|policy|missing_integrity) ;;
            *) remember=true ;;
        esac
        case "${reason}" in
            'unsupported-manifest missing-lockfile-entry'|legacy-package-lock-v1)
                # The install below repairs these (measured, npm 10.9.8): it
                # writes the missing entry, and npm 7+ rewrite a version-1
                # lockfile. The Store never judged what it writes, and a
                # memo keyed to it would keep the tree private for nothing.
                remember=false
                ;;
        esac
    fi
    maintenance_private_announce
    unshare_after_classic_lock
    if [ "$?" -ne 0 ] || [ -n "${maintenance_signal}" ]; then
        # The tree, private or not, is still the old one.
        maintenance_restore_and_say "the change was not installed"
        maintenance_discard_snapshot
        classic_npm_signal="${maintenance_signal}"
        exit_classic_npm 1 0
    fi
    invalidate_shared_node_modules_marker
    # bun.lock stays until the classic install wrote its lockfile: retiring
    # it then (retire_stale_bun_lock) first writes the integrity it proves
    # into that lockfile -- an application's first one, when the Store
    # installed it without -- which npm does not record for what is already
    # installed.
    rm -f -- "${venv_node_modules}/.package-lock.json" \
        "${CL_VIRTUAL_ENV}/lib/.cl-bun-previous.lock"
    close_selector_lock_copy_for_npm
    # Let npm perform the original operation, including optional-dependency
    # cleanup and all of the account's configuration. Restore the pre-S1
    # inputs first: replaying an update/audit against S1's result could apply
    # a second change. The application lock remains held throughout.
    if ! maintenance_restore_snapshot; then
        maintenance_restore_and_say "could not prepare the classic npm command" \
            "node_modules is npm's private copy"
        maintenance_discard_snapshot
        exit_classic_npm 1 0
    fi
    if [ -n "${maintenance_signal}" ]; then
        # Told to stop while the files were put back: run_classic_npm
        # starts with no signal and its own traps, so stop here, before
        # any classic npm.
        maintenance_restore_and_say "the command was interrupted" \
            "node_modules is npm's private copy"
        maintenance_discard_snapshot
        classic_npm_signal="${maintenance_signal}"
        exit_classic_npm 1 0
    fi
    npm_extra_args=(${npm_prefix_args[@]+"${npm_prefix_args[@]}"})
    build_npm_argv "$@"
    run_classic_npm "${npm_argv[@]}"
    s3_status=$?
    local rebuild_failed=false npm_completed=false npm_operation_status=${s3_status}
    finish_classic_lockfiles
    finish_status=$?
    if [ "${s3_status}" -eq 0 ] || { [ "${s3_status}" -eq 1 ] \
            && [ "${npm_classification_args[0]:-}" = audit ] && maintenance_files_changed; }; then
        # audit fix returns 1 while advisories remain, even after reifying.
        npm_completed=true
    fi
    local failed_scripts="the application's own install scripts"
    if [ "${bare}" = false ] && [ "${npm_completed}" = true ] && [ "${finish_status}" -eq 0 ] \
            && [ -z "${classic_npm_signal:-}" ] && app_declares_install_scripts; then
        # Then the application's own install scripts, once, as on every
        # delivery: what they wrote into node_modules (a Prisma client,
        # what a `prepare` generates) is rebuilt. (A bare install's classic npm
        # ran them itself.)
        run_classic_child "${BUN_USER_PHASE}" app-scripts "${CL_APP_ROOT}"
        s3_status=$?
        if [ "${s3_status}" -ne 0 ]; then
            rebuild_failed=true
        else
            s3_status=${npm_operation_status}
        fi
    fi
    if [ "${rebuild_failed}" = true ] && [ -z "${classic_npm_signal:-}" ]; then
        # The change is installed, unbuilt: the files keep it, as the tree
        # does, and the command fails.
        retire_stale_bun_lock
        echo "Shared node_modules Store: the change was installed in npm's" \
             "private copy of node_modules, but ${failed_scripts} failed" \
             "(see above); package.json and package-lock.json hold the change." >&2
    elif [ "${npm_completed}" = false ] && [ -z "${classic_npm_signal:-}" ]; then
        # A failed npm leaves package.json and the lockfile as they were.
        maintenance_restore_and_say "npm could not install this change" \
            "node_modules is npm's private copy"
    else
        retire_stale_bun_lock
    fi
    maintenance_discard_snapshot
    if [ "${remember}" = true ] && [ "${s3_status}" -eq 0 ]; then
        # Remembered, keyed to the files npm installed -- never to files a
        # failed npm put back, which never held what the Store refused.
        maintenance_memo_key "${reason}" > "${MAINTENANCE_PRIVATE_MEMO}" \
            || rm -f -- "${MAINTENANCE_PRIVATE_MEMO}"
    fi
    [ "${s3_status}" -eq 0 ] && s3_status="${s1_status}"
    exit_classic_npm "${s3_status}" "${finish_status}"
}

app_declares_install_scripts() {
    # Whether the application's package.json declares an install lifecycle
    # script (bun_user_phase app-scripts runs them); one that cannot be read
    # is the phase's to judge.
    "${LOCKFILE_PYTHON}" -I - "${HOME}/${CL_APP_ROOT}/package.json" <<'PY' 2>/dev/null
import json
import sys

try:
    with open(sys.argv[1], encoding='utf-8') as handle:
        scripts = json.load(handle).get('scripts')
except (OSError, ValueError, AttributeError):
    sys.exit(0)
events = ('preinstall', 'install', 'postinstall', 'prepublish', 'preprepare', 'prepare',
          'postprepare')
sys.exit(0 if isinstance(scripts, dict)
         and any(isinstance(scripts.get(event), str) for event in events) else 1)
PY
}

bun_delivery_needs_unshare() {
    # A marker or linked inode proves Shared Store may have created this tree.
    # A bounded-scan failure stays shared/fail-closed only while the
    # configuration may enable delivery; see unverified_sharing_scan.
    shared_tree_may_exist
}

app_lock_identity() {
    stat -Lc '%d:%i' "$(dirname "${CL_VIRTUAL_ENV}")/.lock" 2>/dev/null
}

has_selector_app_lock_fd() {
    # The Selector handed its own application lock descriptor over.
    [[ "${CL_SELECTOR_APP_LOCK_FD:-}" =~ ^[0-9]+$ ]] \
        && [ -e "/proc/self/fd/${CL_SELECTOR_APP_LOCK_FD}" ] \
        && [ "$(stat -Lc '%d:%i' "/proc/self/fd/${CL_SELECTOR_APP_LOCK_FD}" 2>/dev/null)" \
             = "$(app_lock_identity)" ]
}

pid_is_an_ancestor() {
    # Walk this process's parents in /proc.
    local target=$1 pid=$$ key value parent
    [[ "${target}" =~ ^[0-9]+$ ]] || return 1
    while [ "${pid}" -gt 1 ]; do
        parent=
        while read -r key value; do
            if [ "${key}" = "PPid:" ]; then
                parent=${value}
                break
            fi
        done 2>/dev/null < "/proc/${pid}/status"
        [ -n "${parent}" ] || return 1
        [ "${parent}" = "${target}" ] && return 0
        pid=${parent}
    done
    return 1
}

inherits_app_lock_marker() {
    # An ancestor -- this wrapper running npm, or a Selector command such as
    # install-modules or run-script ("Run JS script") -- holds this
    # application's lock and said so in the environment npm passes to its
    # scripts: `<dev>:<ino>` of the lock file and the holder's pid. Another
    # application's marker never matches, and neither does one whose holder
    # is not a live ancestor: a worker that outlived its parent (pm2, nohup)
    # keeps the variable for life, and a stale marker must not bypass the
    # lock of a later, genuinely concurrent operation (only an ancestor
    # started later with the dead holder's pid number would pass).
    local identity holder
    [ -n "${CL_SELECTOR_APP_LOCK_ID:-}" ] || return 1
    identity=${CL_SELECTOR_APP_LOCK_ID%:*}
    holder=${CL_SELECTOR_APP_LOCK_ID##*:}
    [ "${identity}" = "$(app_lock_identity)" ] && pid_is_an_ancestor "${holder}"
}

refuse_locked_application() {
    echo "application is locked by another operation: another Selector" \
         "operation or npm process holds the application lock; retry when it" \
         "finishes" >&2
}

lock_classic() {
    # All setup and classic mutation paths take this descriptor before they
    # inspect or alter the app-root node_modules link. Direct SSH npm inherits
    # this wrapper-owned descriptor; a Selector child drops only its inherited
    # duplicate before npm because the Selector parent keeps the same OFD.
    #
    # CL_BUN_VIA=selector tells the daemon this install was Selector-driven
    # (the account's panel UI runs Selector as the account and lands here),
    # so it is counted as a panel install, not an SSH one. It is set only once
    # the descriptor proved to be this application's lock; an account that
    # forges it moves only the attribution of an install it still pays for.
    #
    # Holding the lock exports CL_SELECTOR_APP_LOCK_ID (lock file and this
    # wrapper's pid) for npm run by the npm started here (a lifecycle
    # script). Such a nested run finds the lock held by its ancestor, which
    # already excludes every other operation until it finishes, and runs
    # without it (app_lock_nested): before CLOS-7898 a host without the
    # Store took no lock at all for it. The marker only lets the account's
    # own npm skip its own lock; it proves nothing to the root daemon, so a
    # nested run never takes Store delivery and never asks root to unshare.
    #
    # Taken once per wrapper: the Store route and the classic path after it
    # run in this shell under the same descriptor. A second descriptor of
    # our own would only meet our own lock and refuse.
    local lock_file
    [ "${app_lock_taken:-false}" = true ] && return 0
    lock_file="$(dirname "${CL_VIRTUAL_ENV}")/.lock"
    unset CL_BUN_VIA
    app_lock_nested=false
    if has_selector_app_lock_fd; then
        bun_lock_fd="${CL_SELECTOR_APP_LOCK_FD}"
        flock -n "${bun_lock_fd}" || { refuse_locked_application; return 1; }
        export CL_BUN_LOCK_FD="${bun_lock_fd}"
        export CL_BUN_VIA=selector
        export CL_SELECTOR_APP_LOCK_ID="$(app_lock_identity):$$"
        app_lock_taken=true
        return 0
    fi
    exec {bun_lock_fd}>"${lock_file}" || return 1
    if ! flock -n "${bun_lock_fd}"; then
        if inherits_app_lock_marker; then
            exec {bun_lock_fd}>&-
            unset CL_BUN_LOCK_FD
            app_lock_nested=true
            app_lock_taken=true
            return 0
        fi
        refuse_locked_application
        return 1
    fi
    export CL_BUN_LOCK_FD="${bun_lock_fd}"
    export CL_SELECTOR_APP_LOCK_ID="$(app_lock_identity):$$"
    app_lock_taken=true
}

lock_and_prepare_classic() {
    lock_classic || return $?
    prepare_classic_lockfiles
}

prepare_classic_lockfiles() {
    # The common user phase canonicalizes unambiguous legacy prefix lockfiles
    # while this app lock is held, in every Store mode: the application
    # root's package-lock.json is the one a later install-modules or `npm ci`
    # reads, so a classic mutation must leave its pins there rather than in
    # venv lib. Divergent regular copies are preserved for classic npm. Do
    # this after the node_modules link was validated -- a refused command
    # changes nothing -- and before scanning or asking root to unshare.
    classic_canonical_locks=""
    "${BUN_USER_PHASE}" prepare-classic "${CL_APP_ROOT}" || return 1
    local lockfile app_lock venv_lock
    # No yarn.lock: classic preparation removes its link (npm 7+ rewrites a
    # yarn.lock at its prefix in the Yarn v1 format).
    for lockfile in package-lock.json npm-shrinkwrap.json; do
        app_lock="${HOME}/${CL_APP_ROOT}/${lockfile}"
        venv_lock="${CL_VIRTUAL_ENV}/lib/${lockfile}"
        if [[ -L "${venv_lock}" && "$(readlink "${venv_lock}")" == "${app_lock}" ]]; then
            classic_canonical_locks="${classic_canonical_locks} ${lockfile}"
        fi
    done
}

finish_classic_lockfiles() {
    # npm writes prefix lockfiles with rename(2), replacing a canonical
    # symlink with a regular file. Only aliases observed after the locked
    # preflight are authoritative here; a historical divergent pair was not
    # an alias and remains untouched for classic compatibility.
    local lockfile app_lock venv_lock
    # Validate every observed alias before moving any npm output. A malformed
    # later lock must not leave an earlier app-root lock partly updated.
    for lockfile in ${classic_canonical_locks:-}; do
        app_lock="${HOME}/${CL_APP_ROOT}/${lockfile}"
        venv_lock="${CL_VIRTUAL_ENV}/lib/${lockfile}"
        if [[ -L "${venv_lock}" ]]; then
            [[ "$(readlink "${venv_lock}")" == "${app_lock}" ]] || return 1
        elif [[ -f "${venv_lock}" ]]; then
            [[ ! -L "${app_lock}" && ( ! -e "${app_lock}" || -f "${app_lock}" ) ]] || return 1
        elif [[ -e "${venv_lock}" ]]; then
            return 1
        elif [[ -e "${app_lock}" || -L "${app_lock}" ]]; then
            [[ -f "${app_lock}" && ! -L "${app_lock}" ]] || return 1
        fi
    done
    for lockfile in ${classic_canonical_locks:-}; do
        app_lock="${HOME}/${CL_APP_ROOT}/${lockfile}"
        venv_lock="${CL_VIRTUAL_ENV}/lib/${lockfile}"
        if [[ -L "${venv_lock}" ]]; then
            [[ "$(readlink "${venv_lock}")" == "${app_lock}" ]] || return 1
            continue
        fi
        if [[ -f "${venv_lock}" ]]; then
            [[ ! -L "${app_lock}" && ( ! -e "${app_lock}" || -f "${app_lock}" ) ]] || return 1
            mv -f -- "${venv_lock}" "${app_lock}" || return 1
            ln -s -- "${app_lock}" "${venv_lock}" || return 1
        elif [[ -e "${venv_lock}" ]]; then
            return 1
        elif [[ -f "${app_lock}" && ! -L "${app_lock}" ]]; then
            ln -s -- "${app_lock}" "${venv_lock}" || return 1
        elif [[ -e "${app_lock}" || -L "${app_lock}" ]]; then
            return 1
        fi
    done
    "${BUN_USER_PHASE}" prepare-classic "${CL_APP_ROOT}"
}

forward_signal_to_classic_npm() {
    classic_npm_signal=$1
    classic_npm_interrupted=1
    [ -z "${classic_npm_pid:-}" ] || kill -"$1" "${classic_npm_pid}" 2>/dev/null
}

run_classic_child() {
    # run_classic_child <command> <args>: another child run the way
    # run_classic_npm runs npm -- the application's own install scripts
    # after a classic install (bun_user_phase app-scripts).
    local nodejs_npm="$1"
    shift
    run_classic_npm "$@"
}

run_classic_npm() {
    # npm is this wrapper's child, not exec'd: the application lock and the
    # lockfile publication outlive it. A TERM, INT or HUP aimed at the
    # wrapper (a supervisor, Node's child.kill(), Ctrl-C) is passed on to
    # npm instead of orphaning it with the lock, and exit_classic_npm
    # re-raises it once the lock and lockfiles are settled. npm keeps the
    # terminal's input.
    classic_npm_signal=
    classic_npm_pid=
    trap 'forward_signal_to_classic_npm TERM' TERM
    trap 'forward_signal_to_classic_npm INT' INT
    trap 'forward_signal_to_classic_npm HUP' HUP
    if [ -n "${npm_child_closes_fd:-}" ]; then
        # The Store's maintenance route keeps the Selector's descriptor for
        # the delivery after npm; npm itself must not inherit it (see
        # close_selector_lock_copy_for_npm).
        "${nodejs_npm}" "$@" 0<&0 {npm_child_closes_fd}>&- &
    else
        "${nodejs_npm}" "$@" 0<&0 &
    fi
    classic_npm_pid=$!
    [ -z "${classic_npm_signal}" ] \
        || kill -"${classic_npm_signal}" "${classic_npm_pid}" 2>/dev/null
    # A trapped signal interrupts `wait` with 128+n, also after npm already
    # finished and was reaped. Wait again after such an interrupted wait:
    # bash keeps a reaped child's status, so that is npm's own -- a
    # successful `npm ci` must not get its old tree put back. A status of
    # 128 or less is npm's even when the signal was handled just after it
    # (bash 5.1 and 5.2 then report the signal on a further wait). Any bash
    # can lose the status of a child reaped in that very instant and report
    # -1: read as a failure, 255 (bash 4.2 cannot `return -1`), the safe
    # reading for a set-aside tree -- `npm ci` puts the old tree back and
    # nothing is lost; the wrapper then dies of the signal anyway.
    while :; do
        classic_npm_interrupted=
        wait "${classic_npm_pid}"
        classic_npm_status=$?
        [ -n "${classic_npm_interrupted}" ] \
            && [ "${classic_npm_status}" -gt 128 ] || break
    done
    return $(( classic_npm_status & 255 ))
}

exit_classic_npm() {
    # $1 is npm's status, $2 the lockfile publication's. A script calling
    # npm stopped at Ctrl-C while npm was exec'd, because bash ends a script
    # whose child died of SIGINT: die of the same signal, not of 128+n.
    if [ -n "${classic_npm_signal:-}" ]; then
        trap - "${classic_npm_signal}"
        kill -"${classic_npm_signal}" $$
    fi
    [ "$1" -eq 0 ] || exit "$1"
    exit "$2"
}

unshare_after_classic_lock() {
    bun_delivery_needs_unshare || return 0
    "${NODE_MODULES_STORAGE_PYTHON}" "${DELIVERY_CLIENT}" unshare "${CL_APP_ROOT}"
}

is_selector_node_modules_link() {
    # The Selector's own link: one into a venv under ~/nodevenv, or the
    # node_modules of any nodevenv venv, dangling or not. Moving the
    # application (`set --new-app-root`) carries it over verbatim, still
    # naming the old venv path, and so do a WHM username rename, a move to
    # /home2 or a restore onto another home base, which leave it naming the
    # old home. The pre-Store wrapper replaced any link, so such a link is
    # re-pointed under the application lock rather than refused.
    [[ -L "${app_node_modules}" ]] || return 1
    case "$(readlink "${app_node_modules}")" in
        "${HOME}/nodevenv/"*|/*/nodevenv/*/lib/node_modules|/*/nodevenv/*/lib/node_modules/) return 0 ;;
    esac
    return 1
}

install_modules_hint() {
    # The way to (re)install the application through the Selector. The panel
    # hook's NODE_MODULES_REFUSED says the same (a test holds them alike).
    printf '%s %s' "\"cloudlinux-selector install-modules --json --interpreter nodejs" \
        "--app-root ${CL_APP_ROOT}\" (\"Run NPM Install\" in the control panel)"
}

validate_setup_node_modules_link() {
    # npm keeps the Selector's link, or creates it when missing. A real
    # directory or file (what npm run in the application root used to leave)
    # or a link to anywhere else is the account's and is refused unchanged,
    # before npm, unshare or lockfile work, with the way back.
    if is_selector_node_modules_link \
            || [[ ! -e "${app_node_modules}" && ! -L "${app_node_modules}" ]]; then
        return 0
    fi
    echo "${error_msg}" >&2
    echo "Move or remove ${app_node_modules}, then run $(install_modules_hint)" \
         "to recreate the link and reinstall the dependencies listed in" \
         "package.json." >&2
    if [[ -d "${app_node_modules}" && ! -L "${app_node_modules}" ]]; then
        echo "Alternatively, \"npm ci\" sets the directory aside and installs" \
             "package-lock.json into the application's environment." >&2
    fi
    return 1
}

ensure_setup_node_modules_link() {
    validate_setup_node_modules_link || return $?
    if [[ -L "${app_node_modules}" \
          && "$(readlink "${app_node_modules}")" != "${venv_node_modules}" ]]; then
        mkdir -p "${venv_node_modules}" || return 1
        ln -sfn -- "${venv_node_modules}" "${app_node_modules}" || return 1
    elif [[ ! -e "${app_node_modules}" && ! -L "${app_node_modules}" ]]; then
        mkdir -p "${venv_node_modules}" || return 1
        ln -s "${venv_node_modules}" "${app_node_modules}" || return 1
    fi
    ln -sf "${HOME}/${CL_APP_ROOT}/package.json" "${CL_VIRTUAL_ENV}/lib/package.json"
}

# Every command name and alias of npm 7 to 11 (lib/utils/cmd-list.js);
# test_every_installed_npm_name_and_abbreviation_is_classified_alike
# compares it with each npm installed under /opt/alt, and a snapshot of
# npm 11.19's (whose `install-scripts` writes only package.json#allowScripts,
# like `approve-scripts`/`deny-scripts`: no mutation) with any host's.
npm_command_names="access add add-user adduser approve-scripts audit author bin bugs c cache ci cit clean-install clean-install-test completion config create ddp dedupe deny-scripts deprecate diff dist-tag dist-tags docs doctor edit exec explain explore find find-dupes fund get help help-search hlep home hook i ic in info init innit ins inst insta instal install install-ci-test install-clean install-scripts install-test isnt isnta isntal isntall isntall-clean issues it la link list ll ln login logout ls ogr org outdated owner pack ping pkg prefix profile prune publish query r rb rebuild remove repo restart rm root rum run run-script s sbom se search set set-script shrinkwrap show sit stage star stars start stop t team test token trust tst u udpate un undeprecate uninstall unlink unpublish unstar up update upgrade urn v verison version view whoami why x"

is_npm_command_name() {
    # npm accepts configuration flags before its command. Keep this list
    # broad enough to distinguish an option value from the command that
    # follows it; only the mutation classifiers below change behavior.
    local name
    for name in ${npm_command_names}; do
        [ "${name}" = "${1:-}" ] && return 0
    done
    return 1
}

resolve_npm_command_name() {
    # npm runs a unique prefix of a command name or alias as that command
    # (`npm uninst`, `rem`, `upd`, `pru`, `dedup`, `rebu`), so classify an
    # abbreviation the way npm reads it: print the name the token is, or the
    # one name it is a unique prefix of. npm 6 to 11 turn camelCase into
    # kebab-case first: `cleanInstall` is `clean-install`, `installTest`
    # `install-test`.
    local token="${1:-}" name match=''
    [ -n "${token}" ] || return 1
    while [[ "${token}" =~ ^([^ABCDEFGHIJKLMNOPQRSTUVWXYZ]*)([ABCDEFGHIJKLMNOPQRSTUVWXYZ])(.*)$ ]]; do
        token="${BASH_REMATCH[1]}-${BASH_REMATCH[2],,}${BASH_REMATCH[3]}"
    done
    if is_npm_command_name "${token}"; then
        printf '%s\n' "${token}"
        return 0
    fi
    for name in ${npm_command_names}; do
        case "${name}" in
            "${token}"*)
                [ -z "${match}" ] || return 1
                match="${name}"
                ;;
        esac
    done
    [ -n "${match}" ] || return 1
    printf '%s\n' "${match}"
}

npm_option_takes_value() {
    # npm_option_takes_value <option> [<next token>]: whether npm's argument
    # parser reads the next token as this option's value -- for the leading
    # options before the command, and to tell an install's option values
    # from its package specs (install_names_packages). The options npm 8 to
    # 11 define with a value (a test compares the table with every installed
    # npm): npm reads one it does not define (`--jobs`, `--nodedir`) as a
    # boolean, and the next token as a spec. `--node-gyp` takes a value only
    # since npm 11; read as one everywhere, a bare install stays bare, the
    # safer reading. Other unknown leading options are handled below by
    # consuming a following non-command token as their probable value.
    case "${1:-}" in
        -C|-L|-c|-m|-w|--_auth|--access|--allow-directory|--allow-file|\
        --allow-git|--allow-remote|--allow-scripts|--also|--audit-level|\
        --auth-type|--before|--browser|--ca|--cache|--cache-max|--cache-min|\
        --cafile|--call|--cert|--ci-name|--cidr|--cpu|--depth|--diff|\
        --diff-dst-prefix|--diff-src-prefix|--diff-unified|--editor|\
        --enjoy-by|--expect-result-count|--expires|--fetch-retries|\
        --fetch-retry-factor|--fetch-retry-maxtimeout|\
        --fetch-retry-mintimeout|--fetch-timeout|--git|--globalconfig|\
        --heading|--https-proxy|--include|--init-author-email|\
        --init-author-name|--init-author-url|--init-license|--init-module|\
        --init-type|--init-version|--init.author.email|--init.author.name|\
        --init.author.url|--init.license|--init.module|--init.version|\
        --install-strategy|--key|--libc|--local-address|--location|\
        --lockfile-version|--loglevel|--logs-dir|--logs-max|--maxsockets|\
        --message|--min-release-age|--min-release-age-exclude|--name|\
        --node-gyp|--node-options|--node-version|--noproxy|--npm-version|\
        --omit|--only|--orgs|--orgs-permission|--os|--otp|--pack-destination|\
        --package|--packages|--packages-and-scopes-permission|--password|\
        --prefix|--preid|--provenance-file|--proxy|--reg|--registry|\
        --replace-registry-host|--save-prefix|--sbom-format|--sbom-type|\
        --scope|--scopes|--script-shell|--searchexclude|--searchlimit|\
        --searchopts|--searchstaleness|--shell|--sso-poll-frequency|\
        --sso-type|--tag|--tag-version-prefix|--tmp|--token-description|\
        --umask|--user-agent|--userconfig|--viewer|--which|--workspace)
            return 0
            ;;
        --color)
            [ "${2:-}" = always ]
            return
            ;;
    esac
    return 1
}

npm_no_option_takes_value() {
    # npm_no_option_takes_value <name>: whether nopt reads the token after
    # `--no-<name>` as a value -- the options npm 7 to 11 type as a boolean
    # or null with a string (`cert` is `[null, String]`), measured with each
    # installed npm (8.19.4, 9.6.3, 10.8.2, 10.9.8, 11.16.0, 11.17.0); a
    # test compares the table with them. `--no-` before any other option is
    # a boolean.
    case "$1" in
        _auth|allow-scripts|browser|ca|cert|ci-name|cidr|cpu|diff|key|libc|\
        min-release-age-exclude|name|node-options|noproxy|orgs|os|otp|\
        package|packages|password|replace-registry-host|scopes|script-shell|\
        token-description|workspace)
            return 0
            ;;
    esac
    return 1
}

# Every option name npm 7 to 11 define (@npmcli/config's definitions);
# test_every_installed_npm_option_abbreviation_is_read_alike compares it, and
# npm_canonical_options, with each npm installed under /opt/alt.
npm_option_names="_auth access all allow-directory allow-file allow-git allow-remote allow-same-version allow-scripts allow-scripts-pending allow-scripts-pin also audit audit-level auth-type before bin-links browser bypass-2fa ca cache cache-max cache-min cafile call cert ci-name cidr color commit-hooks cpu dangerously-allow-all-scripts depth description dev diff diff-dst-prefix diff-ignore-all-space diff-name-only diff-no-prefix diff-src-prefix diff-text diff-unified dry-run editor engine-strict expect-result-count expect-results expires fetch-retries fetch-retry-factor fetch-retry-maxtimeout fetch-retry-mintimeout fetch-timeout force foreground-scripts format-package-lock fund git git-tag-version global global-style globalconfig heading https-proxy if-present ignore-scripts include include-attestations include-staged include-workspace-root init-author-email init-author-name init-author-url init-license init-module init-private init-type init-version init.author.email init.author.name init.author.url init.license init.module init.version install-links install-strategy json key legacy-bundling legacy-peer-deps libc link local-address location lockfile-version loglevel logs-dir logs-max long maxsockets message min-release-age min-release-age-exclude name node-gyp node-options node-version noproxy npm-version offline omit omit-lockfile-registry-resolved only optional orgs orgs-permission os otp pack-destination package package-lock package-lock-only packages packages-all packages-and-scopes-permission parseable password prefer-dedupe prefer-offline prefer-online prefix preid production progress provenance provenance-file proxy read-only rebuild-bundle registry replace-registry-host save save-bundle save-dev save-exact save-optional save-peer save-prefix save-prod sbom-format sbom-type scope scopes script-shell searchexclude searchlimit searchopts searchstaleness shell shrinkwrap sign-git-commit sign-git-tag sso-poll-frequency sso-type strict-allow-scripts strict-peer-deps strict-ssl tag tag-version-prefix timing tmp token-description umask unicode update-notifier usage user-agent userconfig version versions viewer which workspace workspaces workspaces-update yes"

npm_canonical_options() {
    # npm_canonical_options <argv>: sets npm_option_args to the argv with
    # each option before `--` named as npm's parser (nopt) reads it, for
    # the classifiers below; npm itself gets the argv as given. nopt reads
    # a unique prefix of an option's name as that option, with one dash or
    # two and after `no-`: `--logleve`, `-regis`, `--ignore-script`,
    # `--prod`, `--no-optiona`, `--dry`. Not a token it reads as its
    # shorthands: a shorthand's own name (`--local`, `-ws`) or single-letter
    # ones run together (`--cp` is `-c -p`). A prefix that names several
    # options stays as typed, as nopt keeps it; npm_ambiguous_value_options
    # lists such a prefix when one of them takes a value.
    local arg name value no option match count
    npm_option_args=()
    npm_ambiguous_value_options=' '
    while [ "$#" -gt 0 ]; do
        arg=$1
        shift
        if [ "${arg}" = -- ]; then
            npm_option_args+=(-- "$@")
            break
        fi
        case "${arg}" in
            -?*) ;;
            *) npm_option_args+=("${arg}"); continue ;;
        esac
        name=${arg#-}
        name=${name#-}
        value=
        if [[ "${name}" == *=* ]]; then
            value="=${name#*=}"
            name=${name%%=*}
        fi
        no=
        if [[ " ${npm_option_names} " != *" ${name} "* ]]; then
            case "${name}" in
                dd|ddd|desc|enjoy-by|help|iwr|local|no|porcelain|quiet|readonly|reg|silent|verbose|ws)
                    npm_option_args+=("${arg}")
                    continue
                    ;;
            esac
            if [[ "${name}" =~ ^[?BCDEHLOPSacdfghlmnpqsvwy]+$ ]]; then
                npm_option_args+=("${arg}")
                continue
            fi
            while [[ "${name,,}" == no-* ]]; do
                no+=no-
                name=${name:3}
            done
            match=
            count=0
            for option in ${npm_option_names}; do
                case "${option}" in
                    "${name}") match=${option}; count=1; break ;;
                    "${name}"*) match=${option}; count=$((count + 1)) ;;
                esac
            done
            if [ "${count}" -ne 1 ]; then
                # npm 7 to 11 do not all define the same of them: `--ci` is
                # `--cidr` on npm 10 and 11, which takes a value, `--nod`
                # `--node-options` on npm 9 and 10. npm_option_without_value
                # reads it as taking one when any of them does, after `no-`
                # too (`--no-ci`, npm_no_option_takes_value).
                if [ -z "${value}" ]; then
                    for option in ${npm_option_names}; do
                        case "${option}" in
                            "${name}"*)
                                if { [ -z "${no}" ] \
                                     && npm_option_takes_value "--${option}" "${1:-}"; } \
                                   || { [ -n "${no}" ] \
                                        && npm_no_option_takes_value "${option}"; }; then
                                    npm_ambiguous_value_options+="${arg} "
                                    break
                                fi
                                ;;
                        esac
                    done
                fi
                npm_option_args+=("${arg}")
                continue
            fi
            name=${match}
        fi
        npm_option_args+=("--${no}${name}${value}")
    done
}

classify_npm_command_args() {
    # Preserve the original argv for npm. Strip only leading global options
    # from the copy used to decide whether the invocation mutates the tree.
    local option npm_command
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --)
                shift
                break
                ;;
            --*=*|-?=*)
                shift
                ;;
            -*)
                option=$1
                shift
                if npm_option_takes_value "${option}" "${1:-}"; then
                    [ "$#" -gt 0 ] && shift
                elif [ "$#" -gt 0 ] && [[ "$1" != -* ]] \
                        && ! is_npm_command_name "$1" \
                        && ! { npm_command="$(resolve_npm_command_name "$1")" \
                               && is_classic_npm_mutation "${npm_command}" "${@:2}"; }; then
                    # npm configuration keys may be supplied as
                    # `--key value`. An unknown token that is not a command
                    # is the value; continue looking for the actual command.
                    # npm reads many options as booleans (`--no-save`, `-s`,
                    # `--force`), so a token that abbreviates a mutation
                    # (`npm --no-save uninst x`) is read as that command:
                    # either reading that gives a mutation takes the locked
                    # path, while `npm --editor vi uninstall x` still finds
                    # `uninstall` (`vi` would be `view`).
                    shift
                fi
                ;;
            *)
                break
                ;;
        esac
    done
    npm_classification_args=("$@")
    # Only the command itself may be an abbreviation: `npm --editor vi
    # uninstall x` runs uninstall, and `vi` is the editor, not `view`.
    if [ "$#" -gt 0 ] && npm_command="$(resolve_npm_command_name "$1")"; then
        npm_classification_args[0]="${npm_command}"
    fi
}

inspect_npm_mode_options() {
    local -a mode_args=("$@")
    local help=false
    # These options change whether npm can touch this application's
    # node_modules at all. Inspect the original argv up to `--`; unlike the
    # command classifier, this does not need to remove or reorder anything.
    npm_global_mode=false
    npm_preview_mode=
    npm_preview_from_config=false
    npm_lockfile_only=
    npm_explicit_prefix=false
    # npm takes the last prefix it is given; so do we, or an account could
    # name this venv first and something else last and be handled as this
    # application while npm installed elsewhere (or the reverse).
    npm_prefix_value=
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --)
                break
                ;;
            -g|--global|--global=true|--location=global)
                npm_global_mode=true
                ;;
            --location)
                shift
                if [ "${1:-}" = global ]; then
                    npm_global_mode=true
                fi
                ;;
            --prefix|-C)
                npm_explicit_prefix=true
                shift
                npm_prefix_value="${1:-}"
                ;;
            --prefix=*|-C=*)
                npm_explicit_prefix=true
                npm_prefix_value="${1#*=}"
                ;;
            --dry-run|--dry-run=*)
                npm_preview_mode=true
                if [ "$1" = --dry-run=false ] \
                        || { [ "$1" = --dry-run ] && [ "${2:-}" = false ]; }; then
                    npm_preview_mode=false
                fi
                ;;
            --no-dry-run|--no-dry-run=*)
                npm_preview_mode=false
                if [ "$1" = --no-dry-run=false ] \
                        || { [ "$1" = --no-dry-run ] && [ "${2:-}" = false ]; }; then
                    npm_preview_mode=true
                fi
                ;;
            --help|--help=true|-h|-\?)
                help=true
                ;;
            --package-lock-only|--package-lock-only=*)
                npm_lockfile_only=true
                if [ "$1" = --package-lock-only=false ] \
                        || { [ "$1" = --package-lock-only ] && [ "${2:-}" = false ]; }; then
                    npm_lockfile_only=false
                fi
                ;;
            --no-package-lock-only|--no-package-lock-only=*)
                npm_lockfile_only=false
                if [ "$1" = --no-package-lock-only=false ] \
                        || { [ "$1" = --no-package-lock-only ] && [ "${2:-}" = false ]; }; then
                    npm_lockfile_only=true
                fi
                ;;
        esac
        [ "$#" -gt 0 ] && shift
    done
    # npm reads an empty prefix (`--prefix=`, an empty $DIR in a deploy
    # script) as the working directory.
    if [ "${npm_explicit_prefix}" = true ] && [ -z "${npm_prefix_value}" ]; then
        npm_prefix_value=${PWD}
    fi
    # And a leading `~/` (`--prefix="~/app"`, quoted) as the home directory.
    [[ "${npm_prefix_value}" != '~/'* ]] || npm_prefix_value="${HOME}/${npm_prefix_value:2}"
    [ "${help}" != true ] || npm_preview_mode=true
    npm_help_mode=${help}
    npm_config_files "${mode_args[@]}"
    if [ -z "${npm_preview_mode}" ]; then
        npm_preview_mode=false
        if npm_configuration_bool dry-run; then
            npm_preview_mode=true
            npm_preview_from_config=true
        fi
    fi
    if [ -z "${npm_lockfile_only}" ]; then
        npm_lockfile_only=false
        npm_configuration_bool package-lock-only && npm_lockfile_only=true
    fi
}

npm_prefix_targets_application() {
    # An explicit prefix that resolves to this application's venv IS this
    # application, whatever npm calls it: `npm install --prefix
    # ~/nodevenv/app/22/lib pkg` mutates exactly the tree a bare install
    # does. Treating every explicit prefix as "somewhere else" let that
    # through with no application lock, no unshare of a still-shared tree,
    # and a marker left claiming the tree was intact.
    local prefix="${1:-}" resolved venv_resolved
    [ -n "${prefix}" ] || return 1
    [ -n "${CL_VIRTUAL_ENV:-}" ] || return 1
    # `realpath` is coreutils and is in every cage. If it is not, we cannot
    # tell the two apart, and the safe answer is the locked application path
    # rather than an unprotected mutation of a possibly shared tree.
    resolved="$(realpath -m -- "${prefix}" 2>/dev/null)" || return 0
    venv_resolved="$(realpath -m -- "${CL_VIRTUAL_ENV}" 2>/dev/null)" || return 0
    [ -n "${resolved}" ] && [ -n "${venv_resolved}" ] || return 0
    case "${resolved}" in
        "${venv_resolved}"|"${venv_resolved}"/*)
            return 0
            ;;
    esac
    return 1
}

npm_prefix_is_application_root() {
    # The application root's node_modules is the Selector's link: an npm
    # mutation with that directory as its prefix would replace it.
    local resolved app_resolved
    resolved="$(realpath -m -- "${1:-}" 2>/dev/null)" || return 1
    app_resolved="$(realpath -m -- "${HOME}/${CL_APP_ROOT}" 2>/dev/null)" || return 1
    [ -n "${resolved}" ] && [ "${resolved}" = "${app_resolved}" ]
}

is_read_only_setup_command() {
    case "${1:-}" in
        list|la|ll) return 0 ;;
    esac
    return 1
}

is_shared_node_modules_install() {
    # A bare install or `npm ci`: the whole tree, which the Store serves.
    # Options that change no installed file keep that route -- and, for
    # `npm ci`, which installs the lockfile as it is, options that change
    # only how npm would resolve, or install what the Store installs anyway
    # (devDependencies, `--install-links`). Anything else (a package spec,
    # an option npm resolves with, `--json`, whose JSON reports npm's own
    # install) is not a bare install.
    local command="${npm_classification_args[0]:-}" ci=false seen_command=false
    if is_bare_npm_ci_spelling "${command}"; then
        ci=true
    elif ! is_install_spelling "${command}"; then
        return 1
    fi
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --omit=dev|--production=true|--production=false|--no-production)
                ;;
            --omit)
                shift
                [ "${1:-}" = dev ] || return 1
                ;;
            --production)
                if [ "${2:-}" = true ] || [ "${2:-}" = false ]; then shift; fi
                ;;
            --no-audit|--no-fund|--silent|--quiet|--no-progress|--progress=false|\
            --audit=false|--fund=false|--loglevel=*|-s|-q|-d|-dd|-ddd)
                ;;
            --loglevel)
                shift
                [ "$#" -gt 0 ] || return 1
                ;;
            --legacy-peer-deps|--legacy-peer-deps=true|--strict-peer-deps|\
            --strict-peer-deps=true|--force|--force=true|--registry=*|\
            --prefer-online|--prefer-offline|--include=dev|--also=dev|\
            --also=development|--include=optional|--include=peer|\
            --install-links|--install-links=true)
                [ "${ci}" = true ] || return 1
                ;;
            --include|--also)
                [ "${ci}" = true ] || return 1
                shift
                case "${1:-}" in
                    dev|development|optional|peer) ;;
                    *) return 1 ;;
                esac
                ;;
            --registry)
                [ "${ci}" = true ] || return 1
                shift
                [ "$#" -gt 0 ] || return 1
                ;;
            -*)
                return 1
                ;;
            *)
                [ "${seen_command}" = false ] || return 1
                seen_command=true
                ;;
        esac
        shift
    done
}

is_classic_npm_mutation() {
    if is_install_spelling "${1:-}" || is_npm_ci "${1:-}"; then
        return 0
    fi
    case "${1:-}" in
        install-test|it|update|upgrade|up|u|udpate|uninstall|unlink|remove|rm|r|un|prune|dedupe|ddp|link|ln|rebuild|rb)
            return 0
            ;;
        find-dupes)
            # npm 6 and older (Node.js 14 and older; no alt-nodejs 15) run
            # it as dedupe, npm 7+ as `dedupe --dry-run`. Any other venv
            # name is read as npm 6, the locked path.
            local major="${CL_VIRTUAL_ENV##*/}"
            ! { [[ "${major}" =~ ^[0-9]+$ ]] && [ "${major}" -ge 16 ]; }
            return
            ;;
        audit)
            local audit_arg
            shift
            for audit_arg in "$@"; do
                case "$audit_arg" in
                    fix|--fix) return 0 ;;
                esac
            done
            return 1
            ;;
    esac
    return 1
}

# Only the Store route below exports CL_BUN_COMMAND=ci, and only its
# maintenance route says CL_BUN_ORIGIN=maintenance and why a tree goes
# private; inherited values must not narrow or relabel anything else.
unset CL_BUN_COMMAND CL_BUN_ORIGIN CL_BUN_FALLBACK_REASON CL_BUN_OMIT_DEV
npm_canonical_options "$@"
classify_npm_command_args ${npm_option_args[@]+"${npm_option_args[@]}"}
inspect_npm_mode_options ${npm_option_args[@]+"${npm_option_args[@]}"}

# Global npm and a prefix outside this venv have a different target; help/dry-run
# does not change this application tree. Preserve npm's original argv and avoid
# any application lock, unshare, marker, link, or prefix side effect.
npm_explicit_application_prefix=false
if [ "${npm_explicit_prefix}" = true ] \
        && npm_prefix_targets_application "${npm_prefix_value}"; then
    npm_explicit_application_prefix=true
fi
npm_targets_application=true
if [ "${npm_global_mode}" = true ] \
        || [ "${npm_preview_mode}" = true ] \
        || { [ "${npm_explicit_prefix}" = true ] \
             && [ "${npm_explicit_application_prefix}" = false ]; }; then
    npm_targets_application=false
fi

# A mutation whose explicit prefix is the application root (`npm install
# --prefix $DEPLOYPATH pkg` in a cPanel deploy task) is the application:
# npm keeps the account's argv and gets the venv prefix last, which npm
# honours -- what the pre-Store wrapper's appended prefix did. Run there,
# npm would replace the Selector's node_modules link.
if [ "${npm_targets_application}" = false ] \
        && [ "${npm_global_mode}" = false ] && [ "${npm_preview_mode}" = false ] \
        && is_classic_npm_mutation "${npm_classification_args[@]}" \
        && npm_prefix_is_application_root "${npm_prefix_value}"; then
    npm_targets_application=true
fi

# npm 6.7, 8 and 9 (Node.js 11, 16 and 19) `ci` empties node_modules without
# reading dry-run -- through the Selector's link the venv tree, the Store's
# marker included; npm 10 (Node.js 18, 20 and later) previews (measured with
# each installed npm). Below Node.js 18, on 19 and on a venv named otherwise
# there is no preview to run: refused before npm runs, whatever its prefix.
if [ "${npm_preview_mode}" = true ] && [ "${npm_help_mode}" = false ] \
        && [ "${npm_global_mode}" = false ] && is_npm_ci "${npm_classification_args[@]}" \
        && ! { [[ "${CL_VIRTUAL_ENV##*/}" =~ ^[0-9]+$ ]] && [ "${CL_VIRTUAL_ENV##*/}" -ge 18 ] \
               && [ "${CL_VIRTUAL_ENV##*/}" -ne 19 ]; }; then
    echo "npm ${npm_classification_args[0]} with dry-run is no preview on this" \
         "application's npm (Node.js ${CL_VIRTUAL_ENV##*/}): npm before 10 removes" \
         "node_modules for it; nothing was changed." >&2
    exit 1
fi

# An explicit prefix outside the venv and the application root names another
# project (`npm install --prefix ./client`): npm gets the account's argv
# unchanged, in every mode. The pre-Store wrapper appended the venv prefix
# after it, which npm honours, so it installed the application instead.
if [ "${npm_explicit_prefix}" = true ] && [ "${npm_targets_application}" = false ]; then
    close_selector_lock_copy_for_npm
    exec "${nodejs_npm}" "$@"
fi

# A configured dry-run uses the same project configuration as a real
# application mutation. The prefix keeps the Selector link intact; npm
# performs the preview without any Store call, lockfile preparation or unshare.
if [ "${npm_preview_from_config}" = true ] && [ "${npm_global_mode}" = false ] \
        && is_classic_npm_mutation "${npm_classification_args[@]}"; then
    close_selector_lock_copy_for_npm
    # Use the application's .npmrc without creating a venv link for a preview.
    npm_extra_args=(--prefix="${npm_project_npmrc%/.npmrc}" --dry-run)
    build_npm_argv "$@"
    exec "${nodejs_npm}" "${npm_argv[@]}"
fi

# Without the Store, only a classic mutation of this application changes
# route (see shared_store_path above); queries, scripts, global mode and
# previews keep the pre-feature wrapper, except a prefix elsewhere, which
# reached npm unchanged above.
if [ "${shared_store_path}" = false ] \
        && { [ "${npm_targets_application}" = false ] \
             || ! is_classic_npm_mutation "${npm_classification_args[@]}"; }; then
    run_legacy_npm "$@"
fi

if [ "${npm_targets_application}" = false ]; then
    close_selector_lock_copy_for_npm
    exec "${nodejs_npm}" "$@"
fi

# With the venv as its prefix npm's global directory moves into the venv
# too, so `npm link <name>` would install <name> from the registry instead
# of linking the account's package, and a bare `npm link` would register the
# application there. Refuse before anything changes.
case "${npm_classification_args[0]:-}" in
    link|ln)
        echo "npm link is not supported for a Node.js Selector application:" \
             "its dependencies live in the application's environment" \
             "(${CL_VIRTUAL_ENV}/lib). Declare the dependency in package.json" \
             "and run $(install_modules_hint)." >&2
        exit 1
        ;;
esac

# npm reads the token after an option that takes a value as that value, also
# one that starts with `--`. Every route below adds the wrapper's options
# where the account's end (build_npm_argv): a trailing `--loglevel`, or
# `--registry $REG` with an empty $REG, took the venv prefix -- npm reified
# the application root and replaced the Selector's link -- or the lockfile
# phase's `--package-lock-only` (a full install into a shared tree before any
# unshare). Refused before
# anything changes; npm itself would have read the next option as the value.
if is_classic_npm_mutation "${npm_classification_args[@]}" \
        && valueless_option="$(npm_option_without_value ${npm_option_args[@]+"${npm_option_args[@]}"})"; then
    echo "npm option ${valueless_option} has no value: npm would read the" \
         "option after it -- one this wrapper adds, such as the application's" \
         "--prefix -- as its value. Give it one (${valueless_option}=<value>)" \
         "or leave it out; nothing was changed." >&2
    exit 1
fi

# Everything below runs npm against this application. It normally names the
# venv library as the prefix itself; when the account already named a prefix
# inside this venv, npm keeps the argv it was given -- appending a second
# --prefix would silently retarget theirs.
npm_prefix_args=(--prefix="${CL_VIRTUAL_ENV}/lib")
if [ "${npm_explicit_application_prefix}" = true ]; then
    npm_prefix_args=()
fi

# package-lock-only changes dependency metadata but not node_modules. Keep it
# serialized with delivery and canonicalize unambiguous legacy lock inputs,
# but do not detach a shared tree or invalidate its marker.
if [ "${npm_lockfile_only}" = true ] \
        && is_classic_npm_mutation "${npm_classification_args[@]}"; then
    lock_and_prepare_classic || exit $?
    # npm resolves the selected prefix from venv lib, so expose the same
    # application manifest as the ordinary setup path without creating or
    # detaching node_modules.
    ln -sf "${HOME}/${CL_APP_ROOT}/package.json" \
        "${CL_VIRTUAL_ENV}/lib/package.json" || exit 1
    expose_app_npmrc || exit 1
    close_selector_lock_copy_for_npm
    npm_extra_args=(${npm_prefix_args[@]+"${npm_prefix_args[@]}"})
    build_npm_argv "$@"
    run_classic_npm "${npm_argv[@]}"
    npm_status=$?
    finish_classic_lockfiles
    finish_status=$?
    retire_stale_bun_lock
    exit_classic_npm "${npm_status}" "${finish_status}"
fi

# Every classic mutation has one path: the application lock, lockfile
# preparation, the Selector's node_modules link (kept, created when missing,
# re-pointed when it names another ~/nodevenv path; a real directory or a
# foreign link refused), the optional Store unshare, then npm with the venv
# library as its prefix and the new lockfile published at the application
# root. Where the Store delivers, a bare install (with output-only options,
# any spelling) and a bare `npm ci` (with its aliases and options that do
# not change what it installs) use Bun delivery first, and the maintenance
# commands and a bare install with other options the Store's lockfile route
# (store_maintenance_route); the rest, and what they hand back, run here.
if is_read_only_setup_command "${npm_classification_args[@]}"; then
    # Metadata queries can run while Selector owns the application flock.
    # They must not canonicalize inputs, alter links, or detach a tree.
    close_selector_lock_copy_for_npm
    npm_extra_args=(${npm_prefix_args[@]+"${npm_prefix_args[@]}"})
    build_npm_argv "$@"
    exec "${nodejs_npm}" "${npm_argv[@]}"
elif is_classic_npm_mutation "${npm_classification_args[@]}"; then
    # A bare `install`/`i` is the whole tree, which is what the cache can
    # serve, and so is a bare `npm ci` when root proves it exact. A named
    # install, uninstall, update, prune, dedupe or `audit fix`, and a bare
    # install with options npm resolves with, is delivered from the
    # lockfile npm wrote for it (store_maintenance_route).
    maintenance_private_token=''
    store_refusal=''
    store_state_disabled=false
    if [ "${shared_store_path}" = true ] \
            && is_store_maintenance_command ${npm_option_args[@]+"${npm_option_args[@]}"} \
            && { has_selector_app_lock_fd || ! inherits_app_lock_marker; }; then
        store_maintenance_route "$@"
    fi
    if [ "${shared_store_path}" = true ] && is_shared_node_modules_install ${npm_option_args[@]+"${npm_option_args[@]}"} \
            && { has_selector_app_lock_fd || ! inherits_app_lock_marker; }; then
        # The resolve phase refuses a node_modules the Selector did not make
        # too, but without the way back; a bare install gets the same answer
        # as without the Store. `npm ci` sets a real directory aside itself.
        is_npm_ci "${npm_classification_args[@]}" \
            || validate_setup_node_modules_link || exit $?
        if store_delivery_state; then
            # Resolve and the client deliver `npm ci` exactly or not at
            # all. The application lock is taken here, once: a refusal
            # continues on the classic path below under the same one.
            is_npm_ci "${npm_classification_args[@]}" && export CL_BUN_COMMAND=ci
            lock_classic || exit $?
            if ! store_dependency_mode "$@"; then
                : # The classic path below runs npm with its original filter.
            elif [ "${CL_BUN_COMMAND:-}" = ci ] && ci_classic_remembered; then
                store_refusal=remembered_ci
            else
                lock_classic || exit $?
                bun_delivery_run
                delivery_status=$?
                [ "${delivery_status}" -eq 3 ] || exit "${delivery_status}"
            fi
        else
            # Off for this host (or the Node.js Selector unsupported).
            store_state_disabled=true
        fi
    fi

    lock_classic || exit $?
    if is_npm_ci "${npm_classification_args[@]}"; then
        set_aside_app_node_modules_for_ci || exit 1
    fi
    validate_setup_node_modules_link || exit $?
    # Root unshares only for the holder of the application lock descriptor.
    # A nested run has none, so a tree that may still be shared refuses here,
    # before any lockfile work, instead of at the daemon.
    if [ "${app_lock_nested}" = true ] && [ "${shared_store_path}" = true ] \
            && ! is_npm_ci "${npm_classification_args[@]}" \
            && bun_delivery_needs_unshare; then
        echo "npm started by another npm or by a Selector command (such as" \
             "\"Run JS script\") cannot make this application's Shared" \
             "node_modules Store tree private: the parent holds the" \
             "application lock that root requires. Run the npm command" \
             "directly in the application's environment, for example over" \
             "SSH, first; that makes the tree private." >&2
        exit 1
    fi
    prepare_classic_lockfiles || exit $?
    # npm 10's `ci` removes every entry of node_modules before it
    # installs and never writes into an existing inode: copying a
    # delivered tree private first would only feed npm's removal. A
    # refusal before that removal leaves the tree shared and its marker
    # valid; a failure part-way leaves the marker, which sends the next
    # mutation through the unshare below (shared_tree_may_exist). Without
    # the Store there is nothing to unshare and no scan is run.
    if [ "${shared_store_path}" = true ] && [ "${app_lock_nested}" = false ]; then
        # Whatever runs classic npm over a shared tree says why.
        [ -n "${maintenance_private_token}" ] || classic_private_reason ${npm_option_args[@]+"${npm_option_args[@]}"}
        maintenance_private_announce
    fi
    if ! is_npm_ci "${npm_classification_args[@]}"; then
        if [ "${shared_store_path}" = true ] && [ "${app_lock_nested}" = false ]; then
            unshare_after_classic_lock || exit $?
        fi
        invalidate_shared_node_modules_marker
    fi
    ensure_setup_node_modules_link || exit $?
    if is_npm_ci "${npm_classification_args[@]}"; then
        expose_app_lockfiles_for_ci || exit 1
    fi
    expose_app_npmrc || exit 1
    # With no existing lock input npm must create a regular prefix lock:
    # npm 10 rejects a dangling package-lock symlink. Keep this shell (and
    # therefore the application lock) around long enough to move the new
    # lock to the canonical app root and replace it with the venv alias.
    close_selector_lock_copy_for_npm
    npm_extra_args=(${npm_prefix_args[@]+"${npm_prefix_args[@]}"})
    build_npm_argv "$@"
    remembered_maps=''
    [ -z "${maintenance_remembered_reason}" ] || remembered_maps="$(maintenance_dependency_maps)"
    run_classic_npm "${npm_argv[@]}"
    npm_status=$?
    if [ "${npm_status}" -eq 0 ] && [ -n "${ci_aside}" ]; then
        rm -rf -- "${ci_aside}"
        ci_aside=''
    fi
    if [ "${npm_status}" -eq 0 ] && is_npm_ci "${npm_classification_args[@]}"; then
        invalidate_shared_node_modules_marker
    fi
    finish_classic_lockfiles
    finish_status=$?
    retire_stale_bun_lock
    if [ -n "${maintenance_remembered_reason}" ] && [ "${npm_status}" -eq 0 ] \
            && [ "${finish_status}" -eq 0 ] && [ -z "${classic_npm_signal:-}" ] \
            && maintenance_dependencies_only_added "${remembered_maps}"; then
        # The remembered refusal follows the files this command installed:
        # keyed to the old ones, the next command would pay a resolve and a
        # refused delivery again. Only when it added dependencies: one it
        # removed or whose spec it changed (an uninstall, `npm install
        # pkg@^1` over a git `pkg`) may have been what the Store refused,
        # and the next command asks (maintenance_hint).
        maintenance_memo_key "${maintenance_remembered_reason}" > "${MAINTENANCE_PRIVATE_MEMO}" \
            || rm -f -- "${MAINTENANCE_PRIVATE_MEMO}"
    fi
    exit_classic_npm "${npm_status}" "${finish_status}"
else
    # Scripts, queries and everything npm does not reify run where the
    # account ran them.
    close_selector_lock_copy_for_npm
    exec "${nodejs_npm}" "$@"
fi
