Path: blob/main/Tools/scripts/npmjs-fetch-with-dependencies.sh
20806 views
#!/bin/sh1#2# MAINTAINER: [email protected]34# This script is intended to be used in fetch targets of Node.js ports.5# It fetches a given Node.js package from npmjs.org along with all its dependencies,6# and creates a tarball with the package and all its dependencies.7# It doesn't build or install the package, just fetches it and its dependencies,8# such that the subsequent build step wouldn't require network access.9# This script generates a package-lock.json file for reproducible builds10# if it doesn't already exist.111213set -eu -o pipefail14set -o pipefail1516export LC_ALL=C1718##19## npmjs-get-latest-version.sh: retrieves the latest version of a given Node.js package as registered on https://registry.npmjs.org20##2122# args and env2324PACKAGE_NAME="$1"25PACKAGE_VERSION="$2"26PACKAGE_LOCK_JSON="$3"27PACKAGE_TARBALL_OUTPUT="$4"2829if [ -z "$PACKAGE_NAME" ] || [ -z "$PACKAGE_VERSION" ] || [ -z "$PACKAGE_LOCK_JSON" ] || [ -z "$PACKAGE_TARBALL_OUTPUT" ]; then30echo "Usage: $0 <package name> <package version> <package-lock.json> <package tarball output>"31echo "Example: $0 sharp 0.34.4 outdir/sharp-package-lock.json outdir/sharp-0.34.4.tar.gz"32exit 133fi3435PACKAGE_NAME_PURE="$(echo $PACKAGE_NAME | sed 's|.*/||')"3637if [ -z "$TMPDIR" ]; then38TMPDIR="/tmp"39fi404142# check that packaged dependencies are installed4344for dep in npm jq; do45if ! which $dep >/dev/null 2>&1; then46echo "error: the '$dep' dependency is missing"47if [ $dep = "npm" ]; then48echo "... please install the 'npm' package"49elif [ $dep = "jq" ]; then50echo "... please install the 'jq' package"51fi52exit 153fi54done555657# MAIN5859# to full paths60if ! echo "${PACKAGE_LOCK_JSON}" | grep -q "^/"; then61PACKAGE_LOCK_JSON="`pwd`/${PACKAGE_LOCK_JSON}"62fi63if ! echo "${PACKAGE_TARBALL_OUTPUT}" | grep -q "^/"; then64PACKAGE_TARBALL_OUTPUT="`pwd`/${PACKAGE_TARBALL_OUTPUT}"65fi66if ! echo "${TMPDIR}" | grep -q "^/"; then67TMPDIR="`pwd`/${TMPDIR}"68fi6970# create dirs for output files71mkdir -p "$(dirname "${PACKAGE_LOCK_JSON}")"72mkdir -p "$(dirname "${PACKAGE_TARBALL_OUTPUT}")"73mkdir -p "${TMPDIR}"7475# create build dir and change to there76BUILD_DIR="${TMPDIR}/${PACKAGE_NAME_PURE}-${PACKAGE_VERSION}"77rm -rf ${BUILD_DIR}78mkdir ${BUILD_DIR}79cd ${BUILD_DIR}8081# either just fetch, or regenarate package-lock.json and fetch82if [ -f $PACKAGE_LOCK_JSON ]; then83# fail if package-lock.json does not contain the requested package and version84JSON_NAME=$(jq -r ".packages | .\"node_modules/${PACKAGE_NAME}\" .version" $PACKAGE_LOCK_JSON)85if [ "$JSON_NAME" != "$PACKAGE_VERSION" ]; then86echo "error: the existing package-lock.json ($PACKAGE_LOCK_JSON) does not contain the requested package ${PACKAGE_NAME}@${PACKAGE_VERSION}"87echo " please delete the existing package-lock.json ($PACKAGE_LOCK_JSON) and re-run this script to regenerate it"88exit 189fi9091# fetch dependencies92echo "{\"name\":\"${PACKAGE_NAME}-installer\",\"version\":\"1.0.0\",\"dependencies\":{\"${PACKAGE_NAME}\":\"${PACKAGE_VERSION}\"}}" > package.json93cp $PACKAGE_LOCK_JSON package-lock.json94HOME=${TMPDIR} npm ci --ignore-scripts --global-style --legacy-peer-deps --omit=dev95else96# info97echo "INFO: the file $PACKAGE_LOCK_JSON does not exist, we will attempt to generate it"9899# generate package-lock.json100echo "{\"name\":\"${PACKAGE_NAME}-installer\",\"version\":\"1.0.0\"}" > package.json101npm install --package-lock-only --global-style --legacy-peer-deps ${PACKAGE_NAME}@${PACKAGE_VERSION}102103# copy generated package-lock.json to the expected location104cp package-lock.json ${PACKAGE_LOCK_JSON}105106# info107echo "INFO: ${PACKAGE_LOCK_JSON} did not exist and was generated"108109# fetch dependencies110HOME=${TMPDIR} npm ci --ignore-scripts --global-style --legacy-peer-deps --omit=dev111fi112113# generate tarball with all dependencies114115cd ${TMPDIR}116117# Remove files that npm generates inconsistently across environments, breaking reproducibility:118#119# 1. .package-lock.json: npm creates this cache file in node_modules/ recording what was installed.120# Different npm versions write different content - e.g., npm 11.6.1 includes entries for ALL121# optional dependencies (with "ideallyInert": true) even for other platforms, while npm 11.6.2122# only includes actually-installed packages. This causes checksum mismatches between systems.123#124# 2. Empty @-scoped directories: When npm skips platform-specific optional dependencies (e.g.,125# @img/sharp-wasm32 on x64), some npm versions create empty placeholder directories like126# @emnapi/ while others don't. These empty directories also cause tarball differences.127#128# Neither file is needed for the build - npm regenerates .package-lock.json during install,129# and empty directories serve no purpose.130find ${PACKAGE_NAME_PURE}-${PACKAGE_VERSION} -name '.package-lock.json' -delete131find ${PACKAGE_NAME_PURE}-${PACKAGE_VERSION} -type d -name '@*' -empty -delete132133find ${PACKAGE_NAME_PURE}-${PACKAGE_VERSION} -and -exec touch -h -d 1970-01-01T00:00:00Z {} \;134find ${PACKAGE_NAME_PURE}-${PACKAGE_VERSION} -print0 | sort -z | \135tar czf ${PACKAGE_TARBALL_OUTPUT} --format=bsdtar --no-read-sparse --gid 0 --uid 0 --options gzip:!timestamp --no-recursion --null -T -136rm -rf ${PACKAGE_NAME_PURE}-${PACKAGE_VERSION}137echo "INFO: created package tarball with dependencies at: ${PACKAGE_TARBALL_OUTPUT}"138139140