diff --git a/scripts/install-node18-runtime.sh b/scripts/install-node18-runtime.sh new file mode 100755 index 0000000..23e5757 --- /dev/null +++ b/scripts/install-node18-runtime.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# Batch 6 — swap the embedded runtime to Node 18.20.4. +# +# The published nodejs-mobile-cordova (0.4.3) and its community fork's `unstable` +# branch both still ship Node 12.19.0 for Android. Node 18 for Android exists only +# in the nodejs-mobile CORE release (github.com/nodejs-mobile/nodejs-mobile). This +# script stages that core runtime into the installed plugin so the existing native +# glue (native-lib.cpp calls node::Start, cordova-bridge is N-API) rebuilds against +# Node 18 — verified: node::Start, AddLinkedBinding, and the N-API surface are all +# present in the v18 libnode, and the SONAME is unchanged (libnode.so). +# +# node_modules/ is regenerated by `npm install`, so RE-RUN this after any install +# (same pattern as fix-better-sqlite3-android.sh). Run from the mobile project root. +# +# Node 18 core ships arm64-v8a / armeabi-v7a / x86_64 only — there is no 32-bit x86 +# build, so the plugin's stale v12 x86 libnode is left untouched (x86 should be +# dropped from abiFilters + prepare-android-assets.js in Batch 7). + +set -euo pipefail + +NODE_MOBILE_VERSION="18.20.4" +ZIP_NAME="nodejs-mobile-v${NODE_MOBILE_VERSION}-android.zip" +ZIP_URL="https://github.com/nodejs-mobile/nodejs-mobile/releases/download/v${NODE_MOBILE_VERSION}/${ZIP_NAME}" +ABIS=(arm64-v8a armeabi-v7a x86_64) + +MOBILE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PLUGIN_LIBNODE="$MOBILE_DIR/node_modules/nodejs-mobile-cordova/libs/android/libnode" +CACHE_DIR="$MOBILE_DIR/build-output/nodejs-mobile-runtime" # gitignored +EXTRACT_DIR="$CACHE_DIR/nm18-android" + +if [[ ! -d "$PLUGIN_LIBNODE" ]]; then + echo "Error: nodejs-mobile-cordova not installed at $PLUGIN_LIBNODE" >&2 + echo "Run 'npm install' first." >&2 + exit 1 +fi + +mkdir -p "$CACHE_DIR" + +# 1. Fetch the core Android runtime (cached). +if [[ ! -f "$CACHE_DIR/$ZIP_NAME" ]]; then + echo "Downloading $ZIP_NAME (~57 MB)…" + curl -fL --retry 3 -o "$CACHE_DIR/$ZIP_NAME" "$ZIP_URL" +fi + +rm -rf "$EXTRACT_DIR" +mkdir -p "$EXTRACT_DIR" +unzip -q -o "$CACHE_DIR/$ZIP_NAME" -d "$EXTRACT_DIR" + +# 2. Swap libnode.so per ABI (plugin ships them gzipped; hook gunzips at build time). +for abi in "${ABIS[@]}"; do + src="$EXTRACT_DIR/bin/$abi/libnode.so" + destdir="$PLUGIN_LIBNODE/bin/$abi" + if [[ ! -f "$src" ]]; then + echo "Error: missing $src in the core archive" >&2 + exit 1 + fi + mkdir -p "$destdir" + gzip -c "$src" > "$destdir/libnode.so.gz" + echo " staged libnode.so.gz -> bin/$abi ($(du -h "$destdir/libnode.so.gz" | cut -f1))" +done + +# 3. Swap headers (used to compile native-lib.cpp and native modules, e.g. +# better-sqlite3 in Batch 8). +rm -rf "$PLUGIN_LIBNODE/include/node" +mkdir -p "$PLUGIN_LIBNODE/include" +cp -R "$EXTRACT_DIR/include/node" "$PLUGIN_LIBNODE/include/node" + +# 3.5 Patch the cordova-bridge linked-binding registration for Node 18. +# +# Node 18's NAPI_MODULE_X macro drops its priv/flags args and expands to the +# symbol-based NAPI_MODULE registration, which Node only consumes when it +# dlopen()s a .node file. cordova-bridge is compiled INTO the app's native lib, +# so it was never registered and process._linkedBinding('cordova_bridge') failed +# with "No such binding was linked". Register the linked binding explicitly via +# the still-supported napi_module_register path (routes NM_F_LINKED modules into +# the global modlist_linked at static-init, before node::Start). Idempotent. +BRIDGE_CPP="$MOBILE_DIR/node_modules/nodejs-mobile-cordova/src/common/cordova-bridge/cordova-bridge.cpp" +if [[ -f "$BRIDGE_CPP" ]]; then + python3 - "$BRIDGE_CPP" <<'PY' +import sys +p = sys.argv[1] +src = open(p).read() +old = "NAPI_MODULE_X(cordova_bridge, Init, NULL, NM_F_LINKED)" +new = """static napi_module _cordova_bridge_module = { + NAPI_MODULE_VERSION, + NM_F_LINKED, + __FILE__, + Init, + "cordova_bridge", + NULL, + {0, 0, 0, 0}, +}; +__attribute__((constructor)) static void _register_cordova_bridge(void) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + napi_module_register(&_cordova_bridge_module); +#pragma GCC diagnostic pop +}""" +if "_register_cordova_bridge" in src: + print(" cordova-bridge.cpp already patched for Node 18 linked binding") +elif old in src: + open(p, "w").write(src.replace(old, new)) + print(" patched cordova-bridge.cpp linked-binding registration for Node 18") +else: + sys.exit(" ERROR: cordova-bridge.cpp registration line not found — plugin changed?") +PY +fi + +# 4. Confirm. +ver_h="$PLUGIN_LIBNODE/include/node/node_version.h" +maj=$(grep -E 'define NODE_MAJOR_VERSION' "$ver_h" | awk '{print $3}') +min=$(grep -E 'define NODE_MINOR_VERSION' "$ver_h" | awk '{print $3}') +pat=$(grep -E 'define NODE_PATCH_VERSION' "$ver_h" | awk '{print $3}') +echo "Done. Plugin libnode headers now report Node ${maj}.${min}.${pat}." +echo "Next: rebuild the app (npx cap sync android + gradle) and boot main.js to confirm process.version."