#!/bin/sh# shellcheck disable=SC3043 # local is not POSIX, but every shell has it# shellcheck disable=SC3013,SC3045 # ditto for test {-nt,-t}GITHUB_REPO='node-cache'SUBDIR='node_modules'V="${V-0}" # default to friendly messagesset -eucd "${0%/*}/.."# shellcheck source-path=SCRIPTDIR/... test/common/git-utils.shcmd_remove() {# if we did this for ourselves the rm is enough, but it might be the case# that someone actually used git-submodule to fetch this, so clean up after# that as well. NB: deinit nicely recreates the empty directory for us.message REMOVE node_modulesrm -rf node_modulesgit submodule deinit node_modulesrm -rf -- "$(git rev-parse --absolute-git-dir)/modules/node_modules"}cmd_checkout() {# we default to check out the node_modules corresponding to the gitlink in the indexlocal force=""if [ "${1-}" = "--force" ]; thenforce="1"shiftfilocal sha="${1-$(get_index_gitlink node_modules)}"# fetch by sha to prevent us from downloading something we don't wantfetch_sha_to_cache "${sha}"# verify that our package.json is equal to the one the cached node_modules# was created with, unless --force is givenif [ -z "$force" ]; thenif ! cmp_from_cache "${sha}" '.package.json' 'package.json'; thencat >&2 <<EOF*** node_modules ${sha} doesn't match our package.json*** refusing to automatically check out node_modulesOptions:- tools/node-modules checkout --force # disable this check- tools/node-modules install # npm install with our package.json$0: *** abortingEOFexit 1fifi# we're actually going to do this; let's remove the old onecmd_remove# and check out the new one# we need to use the tag name here, unfortunatelyclone_from_cache "${sha}"}cmd_install() {test -e bots || test/common/make-bots# We first read the result directly into the cache, then we unpack it.tree="$(bots/npm download < package.json | tar_to_cache)"commit="$(sha256sum package.json | git_cache commit-tree "${tree}")"git_cache tag "sha-${commit}" "${commit}" "--no-sign"cmd_checkout "${commit}"cat <<EOFNext steps:- git add node_modules && git commit- tools/node-modules pushEOF}cmd_push() {# push via the cache: the shared history with the remote helps to thin out the pack we sendtag="sha-$(git -C node_modules rev-parse HEAD)"message PUSH "${GITHUB_REPO} ${tag}"git_cache push "${SSH_REMOTE}" "${tag}"}cmd_verify() {test -e bots || test/common/make-bots# Verifies that the package.json and node_modules of the given commit match.commit="$(git rev-parse "$1:node_modules")"fetch_sha_to_cache "${commit}"committed_tree="$(git_cache rev-parse "${commit}^{tree}")"expected_tree="$(git cat-file blob "$1:package.json" | bots/npm download | tar_to_cache)"if [ "${committed_tree}" != "${expected_tree}" ]; thenexec >&2printf "\nCommit %s package.json and node_modules aren't in sync!\n\n" "$1"git --no-pager show --stat "$1"printf "\nThe above commit refers to the following node_modules commit:\n\n"git_cache --no-pager show --no-patch "${commit}"printf "\nOur attempt to recreate that commit differs as follows:\n\n"git_cache --no-pager diff --stat "${commit}" "${expected_tree}" --git_cache --no-pager diff "${commit}" "${expected_tree}" -- .package-lock.jsonexit 1fi}cmd_runtime_tar() {if [ -z "${1:-}" ]; thenecho "Usage: $0 runtime-tar <tarball-path>" >&2exit 1fiif [ ! -e node_modules/.package-lock.json ]; thenecho "Error: This requires a checked out node_modules, run 'tools/node-modules checkout' first" >&2exit 1filocal tar_path="$1"# Verify lockfile versionlocal lockfile_versionlockfile_version="$(jq -r '.lockfileVersion' node_modules/.package-lock.json)"if [ "$lockfile_version" != "3" ]; thenecho "Error: this code is only certified for lockfileVersion 3, got $lockfile_version" >&2exit 1fi# Collect runtime packages using jq, excluding:# - devDependencies# - esbuild (arch dependent, must use distribution packages)# - unnecessary binary packages (use JavaScript sass, don't need watcher)local runtime_pkgsruntime_pkgs="$(jq -r '.packages | to_entries[] |select(.value.dev != true) |.key |select(test(".*/@?esbuild(/|$)") | not) |select(test("watcher-linux|sass-embedded") | not)' node_modules/.package-lock.json | while read -r pkg; do# Only include if exists and is not empty[ -n "$(ls -A "$pkg" 2>/dev/null)" ] && echo "$pkg"done)"# Validate that we only have architecture independent files# special-case node_modules/iconv-lite/encodings/sbcs-data-generated.js, it's JS with binary strings in it# and resolve/test/resolver/cup.coffee which is a single-byte test file# Ubuntu 24.04's file (or at least not that in the GitHub actions runner) does not detect *.woff2 file type properly# markdown-to-jsx's js/cjs is so minified it gets recognized as "data", not JavaScript.for pkg in $runtime_pkgs; doif find "$pkg" -type f -not -path '*/markdown-to-jsx/dist/*.js' -not -path '*/markdown-to-jsx/dist/*.cjs' -print0 |xargs -0 file --mime |grep -Ev \-e 'text/' \-e 'inode/x-empty' \-e 'application/(javascript|json)' \-e 'font/' \-e 'image/' \-e 'sbcs-data-generated.js' \-e 'cup.coffee' \-e '\.woff'; thenecho "Error: package '$pkg' contains architecture dependent files, cannot include in runtime tarball" >&2exit 1fidone# Create tarball{echo "node_modules/.package-lock.json"echo "$runtime_pkgs"} | tar --format=ustar --sort=name --owner=root:0 --group=root:0 --xz -cf "$tar_path" -C . -T -}# called from Makefile.amcmd_make_package_lock_json() {# Run from make to ensure package-lock.json is up to date# package-lock.json is used as the stamp file for all things that use# node_modules, so this is the main bit of glue that drives the entire process# We try our best not to touch package-lock.json unless it actually changes# This isn't going to work for a tarball, but as long as# package-lock.json is already there, and newer than package.json,# we're OKif [ ! -e .git ]; thenif [ package-lock.json -nt package.json ]; thenexit 0fiecho "*** Can't update node modules unless running from git" >&2exit 1fi# Otherwise, our main goal is to ensure that the node_modules from# the index is the one that we actually have.local shasha="$(get_index_gitlink node_modules)"if [ ! -e node_modules/.git ]; then# nothing there yet...cmd_checkoutelif [ "$(git -C node_modules rev-parse HEAD)" != "${sha}" ]; then# wrong thing there...cmd_checkoutfi# This check is more about catching local changes to package.json than# about validating something we just checked out:if ! cmp -s node_modules/.package.json package.json; thencat 2>&1 <<EOF*** package.json is out of sync with node_modules*** If you modified package.json, please run:****** tools/node-modules install****** and add the result to the index.EOFexit 1fi# Only copy the package-lock.json if it differs from the one we haveif ! cmp -s node_modules/.package-lock.json package-lock.json; thenmessage COPY package-lock.jsoncp node_modules/.package-lock.json package-lock.jsonfi# We're now in a situation where:# - the checked out node_modules is equal to the gitlink in the index# - the package.json in the tree is equal to the one in node_modules# - ditto package-lock.jsonexit 0}main() {if [ $# = 0 ]; then# don't list the "private" onesecho 'This command requires a subcommand: remove checkout install push verify runtime-tar'exit 1filocal fnamefname="$(printf 'cmd_%s' "$1" | tr '-' '_')"if ! type -t "${fname}" | grep -q function; thenecho "Unknown subcommand '$1'"exit 1fishift[ -n "${quiet}" ] || set -x"${fname}" "$@"}main "$@"