Compare commits
No commits in common. "2c29ec45bd69d253076d5242190f22f1902bf804" and "0c58b7a4a5f110ebd1d450556b833d233f4c1c79" have entirely different histories.
2c29ec45bd
...
0c58b7a4a5
|
|
@ -8,7 +8,7 @@ const path = require('path');
|
||||||
// this process by nodejs-mobile-cordova). The copy under node_modules/ is
|
// this process by nodejs-mobile-cordova). The copy under node_modules/ is
|
||||||
// whichever architecture it happened to be built for last; replace it with
|
// whichever architecture it happened to be built for last; replace it with
|
||||||
// the prebuild matching this device's actual arch before anything requires
|
// the prebuild matching this device's actual arch before anything requires
|
||||||
// better-sqlite3. See scripts/build-better-sqlite3-node18.sh.
|
// better-sqlite3. See scripts/build-better-sqlite3-arm.sh.
|
||||||
(function installBetterSqlite3Prebuild() {
|
(function installBetterSqlite3Prebuild() {
|
||||||
const archDirs = {
|
const archDirs = {
|
||||||
arm64: 'arm64-v8a',
|
arm64: 'arm64-v8a',
|
||||||
|
|
@ -31,14 +31,60 @@ const path = require('path');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// nodejs-mobile's Node 18.20.4 build ships WITHOUT ICU (verified:
|
// Node 12.19 predates crypto.randomUUID (added in Node 14.17), used by
|
||||||
// process.versions.icu === undefined, typeof Intl === 'undefined'), so `Intl`
|
// services/authService.js for session token IDs.
|
||||||
// is entirely absent. Polyfill just the bits the server uses — en-US number
|
const nodeCrypto = require('crypto');
|
||||||
|
if (typeof nodeCrypto.randomUUID !== 'function') {
|
||||||
|
nodeCrypto.randomUUID = function () {
|
||||||
|
const b = nodeCrypto.randomBytes(16);
|
||||||
|
b[6] = (b[6] & 0x0f) | 0x40;
|
||||||
|
b[8] = (b[8] & 0x3f) | 0x80;
|
||||||
|
const hex = b.toString('hex');
|
||||||
|
return [
|
||||||
|
hex.slice(0, 8),
|
||||||
|
hex.slice(8, 12),
|
||||||
|
hex.slice(12, 16),
|
||||||
|
hex.slice(16, 20),
|
||||||
|
hex.slice(20),
|
||||||
|
].join('-');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node 12.19 predates crypto.hkdfSync (added in Node 15.0), used by
|
||||||
|
// services/encryptionService.js's deriveKey to derive the token-encryption
|
||||||
|
// key from TOKEN_ENCRYPTION_KEY. Implements HKDF-Extract/Expand (RFC 5869)
|
||||||
|
// via createHmac.
|
||||||
|
if (typeof nodeCrypto.hkdfSync !== 'function') {
|
||||||
|
nodeCrypto.hkdfSync = function (digest, ikm, salt, info, keylen) {
|
||||||
|
const hashLen = nodeCrypto.createHash(digest).digest().length;
|
||||||
|
const saltBuf = salt && salt.length ? Buffer.from(salt) : Buffer.alloc(hashLen, 0);
|
||||||
|
const ikmBuf = Buffer.isBuffer(ikm) ? ikm : Buffer.from(ikm);
|
||||||
|
const infoBuf = Buffer.isBuffer(info) ? info : Buffer.from(info || '');
|
||||||
|
|
||||||
|
const prk = nodeCrypto.createHmac(digest, saltBuf).update(ikmBuf).digest();
|
||||||
|
|
||||||
|
let t = Buffer.alloc(0);
|
||||||
|
let okm = Buffer.alloc(0);
|
||||||
|
let counter = 1;
|
||||||
|
while (okm.length < keylen) {
|
||||||
|
t = nodeCrypto
|
||||||
|
.createHmac(digest, prk)
|
||||||
|
.update(t)
|
||||||
|
.update(infoBuf)
|
||||||
|
.update(Buffer.from([counter]))
|
||||||
|
.digest();
|
||||||
|
okm = Buffer.concat([okm, t]);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
return okm.slice(0, keylen);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodejs-mobile-cordova 0.4.3 embeds Node 12.19 built without ICU, so
|
||||||
|
// `Intl` is entirely undefined. Polyfill just the bits the server uses
|
||||||
|
// (utils/money.js, routes/dataSources.js, routes/status.js) — en-US number
|
||||||
// formatting with thousands separators, and a no-op DateTimeFormat whose
|
// formatting with thousands separators, and a no-op DateTimeFormat whose
|
||||||
// resolvedOptions().timeZone the callers already treat as optional.
|
// resolvedOptions().timeZone the callers already treat as optional.
|
||||||
//
|
|
||||||
// (crypto.randomUUID and crypto.hkdfSync are NATIVE on Node 18 — the Node-12
|
|
||||||
// shims for them are gone. See PLAN/FUTURE Batch 6.)
|
|
||||||
if (typeof global.Intl === 'undefined') {
|
if (typeof global.Intl === 'undefined') {
|
||||||
global.Intl = {
|
global.Intl = {
|
||||||
NumberFormat: function (_locale, options) {
|
NumberFormat: function (_locale, options) {
|
||||||
|
|
@ -68,10 +114,6 @@ if (typeof global.Intl === 'undefined') {
|
||||||
// on-device — nodejs-mobile-cordova copies nodejs-project here at runtime).
|
// on-device — nodejs-mobile-cordova copies nodejs-project here at runtime).
|
||||||
process.env.DB_PATH = path.join(__dirname, 'data', 'bills.db');
|
process.env.DB_PATH = path.join(__dirname, 'data', 'bills.db');
|
||||||
process.env.BACKUP_PATH = path.join(__dirname, 'backups');
|
process.env.BACKUP_PATH = path.join(__dirname, 'backups');
|
||||||
// Android has no /tmp; point os.tmpdir() at app-writable storage so the daily
|
|
||||||
// worker's temp-export cleanup doesn't error (ENOENT scandir '/tmp').
|
|
||||||
process.env.TMPDIR = path.join(__dirname, 'tmp');
|
|
||||||
try { fs.mkdirSync(process.env.TMPDIR, { recursive: true }); } catch (_) { /* best effort */ }
|
|
||||||
process.env.PORT = process.env.PORT || '3000';
|
process.env.PORT = process.env.PORT || '3000';
|
||||||
process.env.BIND_HOST = '127.0.0.1';
|
process.env.BIND_HOST = '127.0.0.1';
|
||||||
// The Capacitor WebView's origin (androidScheme: 'https') is cross-origin
|
// The Capacitor WebView's origin (androidScheme: 'https') is cross-origin
|
||||||
|
|
@ -81,30 +123,13 @@ process.env.CORS_ORIGIN = 'https://localhost';
|
||||||
|
|
||||||
const cordova = require('cordova-bridge');
|
const cordova = require('cordova-bridge');
|
||||||
|
|
||||||
// Surface fatal errors to logcat and to the WebView (LoadingScreen) instead of
|
// Wait for the WebView to hand over the device-bound encryption key (stored
|
||||||
// dying silently behind an endless "Starting…" spinner.
|
// in Android Keystore / iOS Keychain — see src/crypto.ts) before starting the
|
||||||
function reportError(where, err) {
|
// server, so encryptionService.js picks up TOKEN_ENCRYPTION_KEY on first use.
|
||||||
const msg = err && err.stack ? err.stack : String(err);
|
|
||||||
console.error('[main.js] ' + where + ':', msg);
|
|
||||||
try {
|
|
||||||
cordova.channel.post('message', { type: 'serverError', message: String((err && err.message) || err) });
|
|
||||||
} catch (_) { /* channel may not be ready */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
process.on('uncaughtException', (err) => reportError('uncaughtException', err));
|
|
||||||
process.on('unhandledRejection', (err) => reportError('unhandledRejection', err));
|
|
||||||
|
|
||||||
// Wait for the WebView to hand over the device-bound encryption key (stored in
|
|
||||||
// Android Keystore / iOS Keychain — see src/crypto.ts) before starting the
|
|
||||||
// server, so encryptionService picks up TOKEN_ENCRYPTION_KEY on first use.
|
|
||||||
cordova.channel.on('message', function (msg) {
|
cordova.channel.on('message', function (msg) {
|
||||||
if (msg && msg.type === 'encryptionKey' && typeof msg.key === 'string') {
|
if (msg && msg.type === 'encryptionKey' && typeof msg.key === 'string') {
|
||||||
process.env.TOKEN_ENCRYPTION_KEY = msg.key;
|
process.env.TOKEN_ENCRYPTION_KEY = msg.key;
|
||||||
try {
|
require('./server/server.js');
|
||||||
require('./server/server.cts');
|
|
||||||
} catch (err) {
|
|
||||||
reportError('server failed to start', err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -2,21 +2,20 @@
|
||||||
"name": "nodejs-project",
|
"name": "nodejs-project",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"//": "Server-side runtime deps only (client deps ship prebuilt in dist/). Versions mirror ../../../bill-tracker. openid-client is intentionally omitted — it is ESM-only and unused in single-user local mode; scripts/prepare-local-mode-deps.js installs a CJS stub. kysely is ESM-only and gets bundled to CJS by the same script.",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@simplewebauthn/server": "^13.0.0",
|
"@simplewebauthn/server": "^13.0.0",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"better-sqlite3": "^12.9.0",
|
"better-sqlite3": "^12.9.0",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^5.2.1",
|
"express": "^4.18.2",
|
||||||
"express-rate-limit": "^8.4.1",
|
"express-rate-limit": "^8.4.1",
|
||||||
"kysely": "^0.29.3",
|
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
"nodemailer": "^9.0.3",
|
"nodemailer": "^8.0.9",
|
||||||
|
"openid-client": "^5.7.1",
|
||||||
"otplib": "^13.4.1",
|
"otplib": "^13.4.1",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodejs-mobile-gyp": "^0.4.0"
|
"nodejs-mobile-gyp": "^0.4.0"
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Cross-compiles better-sqlite3 for arm64-v8a and armeabi-v7a (real Android
|
||||||
|
# devices) and writes the resulting better_sqlite3.node binaries into
|
||||||
|
# nodejs-assets/nodejs-project/prebuilds/<abi>/. main.js installs the one
|
||||||
|
# matching the device's actual process.arch at runtime — see main.js's
|
||||||
|
# installBetterSqlite3Prebuild().
|
||||||
|
#
|
||||||
|
# The existing x86_64 build (emulator) in
|
||||||
|
# nodejs-assets/nodejs-project/node_modules/better-sqlite3/build/Release/
|
||||||
|
# already covers nodejs-assets/nodejs-project/prebuilds/x86_64/ via
|
||||||
|
# fix-better-sqlite3-android.sh; re-copy it manually if that build changes:
|
||||||
|
# cp nodejs-assets/nodejs-project/node_modules/better-sqlite3/build/Release/better_sqlite3.node \
|
||||||
|
# nodejs-assets/nodejs-project/prebuilds/x86_64/
|
||||||
|
#
|
||||||
|
# Requires: Android NDK (ANDROID_HOME or ANDROID_NDK_HOME set), and
|
||||||
|
# `npm install` already run in nodejs-assets/nodejs-project (so
|
||||||
|
# better-sqlite3's source + nodejs-mobile-gyp are present).
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/build-better-sqlite3-arm.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MOBILE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PKG_SRC="$MOBILE_DIR/nodejs-assets/nodejs-project/node_modules/better-sqlite3"
|
||||||
|
PREBUILDS_DIR="$MOBILE_DIR/nodejs-assets/nodejs-project/prebuilds"
|
||||||
|
GYP="$MOBILE_DIR/nodejs-assets/nodejs-project/node_modules/.bin/nodejs-mobile-gyp"
|
||||||
|
NODEDIR="$MOBILE_DIR/node_modules/nodejs-mobile-cordova/libs/android/libnode"
|
||||||
|
|
||||||
|
NDK="${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}"
|
||||||
|
if [[ -z "$NDK" ]]; then
|
||||||
|
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
|
||||||
|
[[ -n "$SDK" ]] && NDK="$(ls -d "$SDK"/ndk/*/ 2>/dev/null | sort -V | tail -1)"
|
||||||
|
fi
|
||||||
|
NDK="${NDK%/}"
|
||||||
|
|
||||||
|
if [[ ! -d "$PKG_SRC" ]]; then
|
||||||
|
echo "Error: $PKG_SRC not found. Run npm install in nodejs-assets/nodejs-project first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -z "$NDK" || ! -d "$NDK" ]]; then
|
||||||
|
echo "Error: Android NDK not found. Set ANDROID_NDK_HOME (or ANDROID_HOME with an ndk/ subdir)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -x "$GYP" ]]; then
|
||||||
|
echo "Error: $GYP not found. Run npm install in nodejs-assets/nodejs-project first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64"
|
||||||
|
API=24 # matches android/variables.gradle minSdkVersion
|
||||||
|
|
||||||
|
# Each ABI's libnode.so ships gzipped; better-sqlite3's binding.gyp (via
|
||||||
|
# nodejs-mobile's common.gypi) needs the real .so to add it as a NEEDED entry.
|
||||||
|
for abi in arm64-v8a armeabi-v7a; do
|
||||||
|
LIBNODE_DIR="$NODEDIR/bin/$abi"
|
||||||
|
if [[ ! -f "$LIBNODE_DIR/libnode.so" && -f "$LIBNODE_DIR/libnode.so.gz" ]]; then
|
||||||
|
gunzip -k "$LIBNODE_DIR/libnode.so.gz"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
build_one() {
|
||||||
|
local abi="$1" gyp_arch="$2" cc="$3" cxx="$4"
|
||||||
|
local build_dir="$MOBILE_DIR/build-output/better-sqlite3-$abi"
|
||||||
|
|
||||||
|
echo "── Building better-sqlite3 for $abi ($gyp_arch) ──"
|
||||||
|
rm -rf "$build_dir"
|
||||||
|
mkdir -p "$build_dir"
|
||||||
|
rsync -a --exclude=build --exclude=prebuilds "$PKG_SRC/" "$build_dir/"
|
||||||
|
|
||||||
|
# v8::Object::CreationContext() was removed in newer V8; nodejs-mobile's
|
||||||
|
# bundled V8 (Node 12.19) only has the old non-checked accessor. Same patch
|
||||||
|
# as fix-better-sqlite3-android.sh.
|
||||||
|
sed -i 's/obj->GetCreationContext().ToLocalChecked()/obj->CreationContext()/' \
|
||||||
|
"$build_dir/src/util/binder.cpp"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$build_dir"
|
||||||
|
CC="$TOOLCHAIN/bin/$cc" \
|
||||||
|
CXX="$TOOLCHAIN/bin/$cxx" \
|
||||||
|
AR="$TOOLCHAIN/bin/llvm-ar" \
|
||||||
|
GYP_DEFINES="OS=android" \
|
||||||
|
"$GYP" configure --target=12.19.0 --arch="$gyp_arch" --nodedir="$NODEDIR"
|
||||||
|
|
||||||
|
CC="$TOOLCHAIN/bin/$cc" \
|
||||||
|
CXX="$TOOLCHAIN/bin/$cxx" \
|
||||||
|
AR="$TOOLCHAIN/bin/llvm-ar" \
|
||||||
|
"$GYP" build
|
||||||
|
)
|
||||||
|
|
||||||
|
mkdir -p "$PREBUILDS_DIR/$abi"
|
||||||
|
cp "$build_dir/build/Release/better_sqlite3.node" "$PREBUILDS_DIR/$abi/better_sqlite3.node"
|
||||||
|
echo "Wrote $PREBUILDS_DIR/$abi/better_sqlite3.node"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_one arm64-v8a arm64 aarch64-linux-android${API}-clang aarch64-linux-android${API}-clang++
|
||||||
|
build_one armeabi-v7a arm armv7a-linux-androideabi${API}-clang armv7a-linux-androideabi${API}-clang++
|
||||||
|
|
||||||
|
echo "Done. Verify with: file $PREBUILDS_DIR/*/better_sqlite3.node"
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Cross-compiles better-sqlite3 for the embedded Node 18.20.4 (nodejs-mobile) ABI
|
|
||||||
# for all three Android ABIs and writes the resulting better_sqlite3.node binaries
|
|
||||||
# into nodejs-assets/nodejs-project/prebuilds/<abi>/. main.js installs the one
|
|
||||||
# matching the device's actual process.arch at runtime — see
|
|
||||||
# installBetterSqlite3Prebuild().
|
|
||||||
#
|
|
||||||
# Unlike the old Node-12 build, NO binder.cpp patch is needed: Node 18's V8 10.2
|
|
||||||
# still exposes the (deprecated) v8::Object::CreationContext() that better-sqlite3
|
|
||||||
# 12.x uses, so it compiles as-is (deprecation warning only).
|
|
||||||
#
|
|
||||||
# Requires: Android NDK (ANDROID_HOME or ANDROID_NDK_HOME set), the Node 18 runtime
|
|
||||||
# staged into the plugin (scripts/install-node18-runtime.sh), and `npm install`
|
|
||||||
# already run in nodejs-assets/nodejs-project.
|
|
||||||
#
|
|
||||||
# Usage: ./scripts/build-better-sqlite3-node18.sh [abi ...] (default: all three)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NODE_TARGET="18.20.4"
|
|
||||||
API=24 # matches android/variables.gradle minSdkVersion
|
|
||||||
|
|
||||||
MOBILE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
PKG_SRC="$MOBILE_DIR/nodejs-assets/nodejs-project/node_modules/better-sqlite3"
|
|
||||||
PREBUILDS_DIR="$MOBILE_DIR/nodejs-assets/nodejs-project/prebuilds"
|
|
||||||
GYP="$MOBILE_DIR/nodejs-assets/nodejs-project/node_modules/.bin/nodejs-mobile-gyp"
|
|
||||||
NODEDIR="$MOBILE_DIR/node_modules/nodejs-mobile-cordova/libs/android/libnode"
|
|
||||||
|
|
||||||
NDK="${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}"
|
|
||||||
if [[ -z "$NDK" ]]; then
|
|
||||||
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
|
|
||||||
[[ -n "$SDK" ]] && NDK="$(ls -d "$SDK"/ndk/*/ 2>/dev/null | sort -V | tail -1)"
|
|
||||||
fi
|
|
||||||
NDK="${NDK%/}"
|
|
||||||
|
|
||||||
[[ -d "$PKG_SRC" ]] || { echo "Error: $PKG_SRC not found. Run npm install in nodejs-project first." >&2; exit 1; }
|
|
||||||
[[ -n "$NDK" && -d "$NDK" ]] || { echo "Error: Android NDK not found (set ANDROID_NDK_HOME or ANDROID_HOME)." >&2; exit 1; }
|
|
||||||
[[ -x "$GYP" ]] || { echo "Error: $GYP not found. Run npm install in nodejs-project first." >&2; exit 1; }
|
|
||||||
grep -q "18" "$NODEDIR/include/node/node_version.h" || { echo "Error: $NODEDIR is not the Node 18 runtime — run scripts/install-node18-runtime.sh first." >&2; exit 1; }
|
|
||||||
|
|
||||||
TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64"
|
|
||||||
|
|
||||||
abi_arch() { case "$1" in arm64-v8a) echo arm64;; armeabi-v7a) echo arm;; x86_64) echo x64;; *) echo "" ;; esac; }
|
|
||||||
abi_cc() { case "$1" in
|
|
||||||
arm64-v8a) echo "aarch64-linux-android${API}-clang" ;;
|
|
||||||
armeabi-v7a) echo "armv7a-linux-androideabi${API}-clang" ;;
|
|
||||||
x86_64) echo "x86_64-linux-android${API}-clang" ;;
|
|
||||||
esac; }
|
|
||||||
|
|
||||||
build_one() {
|
|
||||||
local abi="$1"
|
|
||||||
local gyp_arch cc cxx
|
|
||||||
gyp_arch="$(abi_arch "$abi")"; cc="$(abi_cc "$abi")"; cxx="${cc}++"
|
|
||||||
[[ -n "$gyp_arch" ]] || { echo "Unknown ABI: $abi" >&2; exit 1; }
|
|
||||||
|
|
||||||
# better-sqlite3's binding.gyp (via nodejs-mobile's common.gypi) needs the real
|
|
||||||
# libnode.so (not the .gz) to add it as a NEEDED entry at link time.
|
|
||||||
local libnode_dir="$NODEDIR/bin/$abi"
|
|
||||||
if [[ ! -f "$libnode_dir/libnode.so" && -f "$libnode_dir/libnode.so.gz" ]]; then
|
|
||||||
gunzip -k "$libnode_dir/libnode.so.gz"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local build_dir="$MOBILE_DIR/build-output/better-sqlite3-$abi"
|
|
||||||
echo "── Building better-sqlite3 for $abi ($gyp_arch) against Node $NODE_TARGET ──"
|
|
||||||
rm -rf "$build_dir"; mkdir -p "$build_dir"
|
|
||||||
rsync -a --exclude=build --exclude=prebuilds "$PKG_SRC/" "$build_dir/"
|
|
||||||
|
|
||||||
(
|
|
||||||
cd "$build_dir"
|
|
||||||
CC="$TOOLCHAIN/bin/$cc" CXX="$TOOLCHAIN/bin/$cxx" AR="$TOOLCHAIN/bin/llvm-ar" \
|
|
||||||
GYP_DEFINES="OS=android" "$GYP" configure \
|
|
||||||
--target="$NODE_TARGET" --arch="$gyp_arch" --nodedir="$NODEDIR"
|
|
||||||
|
|
||||||
# nodejs-mobile's common.gypi only adds libnode.so as a NEEDED entry when
|
|
||||||
# target_arch=="x86_64", but gyp reports "x64" — so the rule never fires and
|
|
||||||
# the addon can't resolve V8/Node symbols (e.g. v8::Isolate::GetCurrent) at
|
|
||||||
# dlopen time on Android. Inject the ABI's libnode.so into the link so its
|
|
||||||
# SONAME (libnode.so) is recorded as NEEDED. (patchelf isn't required.)
|
|
||||||
local mk="build/better_sqlite3.target.mk"
|
|
||||||
if [[ -f "$mk" ]] && ! grep -q 'libnode\.so' "$mk"; then
|
|
||||||
sed -i "s#^\([[:space:]]*\)-llog\$#\1-llog $libnode_dir/libnode.so#" "$mk"
|
|
||||||
grep -q 'libnode\.so' "$mk" || { echo "Error: failed to inject libnode.so into $mk" >&2; exit 1; }
|
|
||||||
fi
|
|
||||||
|
|
||||||
CC="$TOOLCHAIN/bin/$cc" CXX="$TOOLCHAIN/bin/$cxx" AR="$TOOLCHAIN/bin/llvm-ar" \
|
|
||||||
make -C build BUILDTYPE=Release
|
|
||||||
)
|
|
||||||
|
|
||||||
mkdir -p "$PREBUILDS_DIR/$abi"
|
|
||||||
cp "$build_dir/build/Release/better_sqlite3.node" "$PREBUILDS_DIR/$abi/better_sqlite3.node"
|
|
||||||
echo "Wrote $PREBUILDS_DIR/$abi/better_sqlite3.node"
|
|
||||||
}
|
|
||||||
|
|
||||||
ABIS=("$@")
|
|
||||||
[[ ${#ABIS[@]} -eq 0 ]] && ABIS=(x86_64 arm64-v8a armeabi-v7a)
|
|
||||||
for abi in "${ABIS[@]}"; do build_one "$abi"; done
|
|
||||||
|
|
||||||
echo "Done. Verify with: file $PREBUILDS_DIR/*/better_sqlite3.node"
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Re-applies the two patches better-sqlite3 needs to load correctly under
|
||||||
|
# nodejs-mobile (Node 12.19 / Android x86_64 emulator). Required after any
|
||||||
|
# fresh `npm install` in nodejs-assets/nodejs-project, since both patches
|
||||||
|
# target files inside node_modules.
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/fix-better-sqlite3-android.sh
|
||||||
|
# Run from the mobile project root, after `npm install` has already built
|
||||||
|
# better-sqlite3 once via nodejs-mobile-gyp (so build/better_sqlite3.target.mk
|
||||||
|
# and the Release/ dir already exist).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MOBILE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PKG_DIR="$MOBILE_DIR/nodejs-assets/nodejs-project/node_modules/better-sqlite3"
|
||||||
|
LIBNODE="$MOBILE_DIR/android/app/libs/cdvnodejsmobile/libnode/bin/x86_64/libnode.so"
|
||||||
|
|
||||||
|
if [[ ! -d "$PKG_DIR" ]]; then
|
||||||
|
echo "Error: $PKG_DIR not found. Run npm install in nodejs-assets/nodejs-project first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. v8::Object::CreationContext() was removed in newer V8; nodejs-mobile's
|
||||||
|
# bundled V8 (Node 12.19) only has the old non-checked accessor.
|
||||||
|
BINDER="$PKG_DIR/src/util/binder.cpp"
|
||||||
|
if grep -q 'GetCreationContext().ToLocalChecked()' "$BINDER"; then
|
||||||
|
sed -i 's/obj->GetCreationContext().ToLocalChecked()/obj->CreationContext()/' "$BINDER"
|
||||||
|
echo "Patched $BINDER"
|
||||||
|
else
|
||||||
|
echo "$BINDER already uses CreationContext() (no patch needed)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. nodejs-mobile-cordova's common.gypi only adds the libnode.so NEEDED entry
|
||||||
|
# when target_arch=="x86_64", but gyp reports "x64" for 64-bit x86, so the
|
||||||
|
# rule never fires. Add it to LIBS directly so the addon can resolve V8/Node
|
||||||
|
# symbols (e.g. v8::Isolate::GetCurrent) at dlopen time on Android.
|
||||||
|
TARGET_MK="$PKG_DIR/build/better_sqlite3.target.mk"
|
||||||
|
if [[ ! -f "$TARGET_MK" ]]; then
|
||||||
|
echo "Error: $TARGET_MK not found. Run npm install to build better-sqlite3 first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "libnode.so" "$TARGET_MK"; then
|
||||||
|
sed -i "/^\t-llog \\\\\$/a\\\\\t$LIBNODE" "$TARGET_MK"
|
||||||
|
echo "Patched $TARGET_MK"
|
||||||
|
else
|
||||||
|
echo "$TARGET_MK already references libnode.so (no patch needed)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rebuild and relink with the patched sources/Makefile.
|
||||||
|
cd "$PKG_DIR/build"
|
||||||
|
rm -f Release/better_sqlite3.node Release/obj.target/better_sqlite3.node
|
||||||
|
make BUILDTYPE=Release better_sqlite3.node
|
||||||
|
|
||||||
|
# Keep the x86_64 prebuild (emulator) in sync — main.js picks the right one
|
||||||
|
# for the device's process.arch at runtime. arm64-v8a/armeabi-v7a prebuilds
|
||||||
|
# are produced separately by build-better-sqlite3-arm.sh.
|
||||||
|
PREBUILD_DIR="$MOBILE_DIR/nodejs-assets/nodejs-project/prebuilds/x86_64"
|
||||||
|
mkdir -p "$PREBUILD_DIR"
|
||||||
|
cp "$PKG_DIR/build/Release/better_sqlite3.node" "$PREBUILD_DIR/better_sqlite3.node"
|
||||||
|
|
||||||
|
echo "Done. Verify with: readelf -d \"$PKG_DIR/build/Release/better_sqlite3.node\" | grep libnode"
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
#!/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."
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
// Makes the two ESM-only server dependencies loadable by the embedded Node 18
|
|
||||||
// (nodejs-mobile) runtime, which cannot require() ESM. Run AFTER
|
|
||||||
// `npm install` inside nodejs-assets/nodejs-project (re-run after any install).
|
|
||||||
//
|
|
||||||
// • kysely (used: routes/categories.cts -> db/kysely.cts) — bundled to a
|
|
||||||
// self-contained CommonJS file; its package entry is repointed at it.
|
|
||||||
// • openid-client (UNUSED in single-user local mode, but eagerly require()'d
|
|
||||||
// at boot via auth.cts/authOidc.cts -> oidcService.cts) — replaced with a
|
|
||||||
// CommonJS stub so boot doesn't crash. Any actual use throws.
|
|
||||||
//
|
|
||||||
// Both are loaded on the web app (Node 22, native ESM); this only affects the
|
|
||||||
// on-device copy.
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const esbuild = require('esbuild'); // from the mobile project's own node_modules
|
|
||||||
|
|
||||||
const PROJECT = path.join(__dirname, '..', 'nodejs-assets', 'nodejs-project');
|
|
||||||
const NM = path.join(PROJECT, 'node_modules');
|
|
||||||
|
|
||||||
function fail(msg) {
|
|
||||||
console.error('prepare-local-mode-deps: ' + msg);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── kysely → CommonJS ────────────────────────────────────────────────────────
|
|
||||||
(function bundleKysely() {
|
|
||||||
const dir = path.join(NM, 'kysely');
|
|
||||||
if (!fs.existsSync(dir)) fail('kysely not installed — run `npm install` in nodejs-project first.');
|
|
||||||
|
|
||||||
const entry = path.join(dir, 'dist', 'index.js'); // ESM entry
|
|
||||||
const outfile = path.join(dir, 'dist', 'index.cjs');
|
|
||||||
esbuild.buildSync({
|
|
||||||
entryPoints: [entry],
|
|
||||||
bundle: true,
|
|
||||||
format: 'cjs',
|
|
||||||
platform: 'node',
|
|
||||||
target: 'node18',
|
|
||||||
outfile,
|
|
||||||
logLevel: 'silent',
|
|
||||||
});
|
|
||||||
|
|
||||||
const pkgPath = path.join(dir, 'package.json');
|
|
||||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
||||||
pkg.main = './dist/index.cjs';
|
|
||||||
pkg.exports = {
|
|
||||||
'.': { require: './dist/index.cjs', import: './dist/index.js', default: './dist/index.cjs' },
|
|
||||||
'./package.json': './package.json',
|
|
||||||
};
|
|
||||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
||||||
console.log(' kysely -> bundled to CommonJS (dist/index.cjs)');
|
|
||||||
})();
|
|
||||||
|
|
||||||
// ── openid-client → CommonJS stub ────────────────────────────────────────────
|
|
||||||
(function stubOpenidClient() {
|
|
||||||
const dir = path.join(NM, 'openid-client');
|
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(dir, 'package.json'),
|
|
||||||
JSON.stringify({ name: 'openid-client', version: '0.0.0-local-stub', main: 'index.js' }, null, 2) + '\n',
|
|
||||||
);
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(dir, 'index.js'),
|
|
||||||
"'use strict';\n" +
|
|
||||||
'// Local-mode stub. OIDC/SSO is not available when running on-device.\n' +
|
|
||||||
'function notSupported() { throw new Error("OIDC is not available in local mode"); }\n' +
|
|
||||||
'module.exports = new Proxy({}, { get: function () { return notSupported; } });\n',
|
|
||||||
);
|
|
||||||
console.log(' openid-client -> CommonJS stub (OIDC disabled in local mode)');
|
|
||||||
})();
|
|
||||||
|
|
||||||
// ── ICU-less \p{} regex compatibility ────────────────────────────────────────
|
|
||||||
// nodejs-mobile's Node 18 ships WITHOUT ICU, so V8 cannot resolve Unicode
|
|
||||||
// property escapes (\p{...}) — any regex using them throws "Invalid property
|
|
||||||
// name" at compile time. path-to-regexp (express 5's router) uses
|
|
||||||
// \p{ID_Start}/\p{ID_Continue} at module load to validate route-param names,
|
|
||||||
// which crashes server boot. Rewrite to ASCII-safe classes; every route param
|
|
||||||
// in this app is ASCII (:id, :userId, …). (minimatch also contains \p{} but only
|
|
||||||
// as lazy POSIX-class strings that this app's globs never trigger — left as-is.)
|
|
||||||
(function patchPathToRegexp() {
|
|
||||||
const f = path.join(NM, 'path-to-regexp', 'dist', 'index.js');
|
|
||||||
if (!fs.existsSync(f)) return;
|
|
||||||
let s = fs.readFileSync(f, 'utf8');
|
|
||||||
const before = s;
|
|
||||||
s = s.replace(/\\p\{ID_Start\}/g, 'a-zA-Z').replace(/\\p\{ID_Continue\}/g, 'a-zA-Z0-9');
|
|
||||||
if (s !== before) {
|
|
||||||
fs.writeFileSync(f, s);
|
|
||||||
console.log(' path-to-regexp -> ASCII identifier regex (ICU-less \\p{} fix)');
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
console.log('prepare-local-mode-deps: done.');
|
|
||||||
|
|
@ -1,20 +1,17 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Copies the bill-tracker backend into mobile/nodejs-assets/nodejs-project/server/
|
# Copies the bill-tracker backend source into mobile/nodejs-assets/nodejs-project/server/
|
||||||
# so it can be bundled into the Android app and run by nodejs-mobile (Node 18) in
|
# so it can be bundled into the Android app and run by nodejs-mobile in local mode.
|
||||||
# local mode.
|
|
||||||
#
|
|
||||||
# The backend is now TypeScript (server.cts + .cts/.mts). Node 18 can't run TS, so
|
|
||||||
# this transpiles the server's own sources to CommonJS in place (keeping filenames;
|
|
||||||
# see scripts/transpile-node18.js). Dependencies run as-is on Node 18 and are NOT
|
|
||||||
# transpiled — install them separately (see the "Next" steps printed at the end).
|
|
||||||
#
|
#
|
||||||
# Usage: ./scripts/sync-nodejs-project.sh [--source /path/to/bill-tracker]
|
# Usage: ./scripts/sync-nodejs-project.sh [--source /path/to/bill-tracker]
|
||||||
# --source defaults to ../bill-tracker (sibling directory). Run from mobile root.
|
# Run from the mobile project root.
|
||||||
|
#
|
||||||
|
# --source defaults to ../bill-tracker (sibling directory).
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
MOBILE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
MOBILE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
# Parse --source flag
|
||||||
REPO_ROOT=""
|
REPO_ROOT=""
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|
@ -22,13 +19,14 @@ while [[ $# -gt 0 ]]; do
|
||||||
*) echo "Unknown arg: $1" >&2; exit 1 ;;
|
*) echo "Unknown arg: $1" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$REPO_ROOT" ]]; then
|
if [[ -z "$REPO_ROOT" ]]; then
|
||||||
REPO_ROOT="$(cd "$MOBILE_DIR/../bill-tracker" && pwd)"
|
REPO_ROOT="$(cd "$MOBILE_DIR/../bill-tracker" && pwd)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$REPO_ROOT/server.cts" ]]; then
|
if [[ ! -f "$REPO_ROOT/server.js" ]]; then
|
||||||
echo "Error: Bill Tracker source not found at $REPO_ROOT (no server.cts)." >&2
|
echo "Error: Bill Tracker source not found at $REPO_ROOT" >&2
|
||||||
echo "Use --source /path/to/bill-tracker to specify the project root." >&2
|
echo "Use --source /path/to/bill-tracker to specify the Bill Tracker project root." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -38,41 +36,43 @@ echo "Syncing backend source from $REPO_ROOT -> $DEST"
|
||||||
rm -rf "$DEST"
|
rm -rf "$DEST"
|
||||||
mkdir -p "$DEST"
|
mkdir -p "$DEST"
|
||||||
|
|
||||||
cp "$REPO_ROOT/server.cts" "$DEST/"
|
cp "$REPO_ROOT/server.js" "$DEST/"
|
||||||
|
|
||||||
# Backend source trees. `types/` is type-only (erased by the transpile) but cheap
|
for dir in db routes services middleware utils workers; do
|
||||||
# insurance; `setup/` holds firstRun.cts (required by server.cts on empty DB).
|
cp -R "$REPO_ROOT/$dir" "$DEST/$dir"
|
||||||
for dir in db routes services middleware utils workers setup types; do
|
|
||||||
[[ -d "$REPO_ROOT/$dir" ]] && cp -R "$REPO_ROOT/$dir" "$DEST/$dir"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# routes/user.cts requires ../scripts/seedDemoData.cts (relative to server/).
|
# routes/user.js requires ../scripts/seedDemoData (relative to server/).
|
||||||
mkdir -p "$DEST/scripts"
|
mkdir -p "$DEST/scripts"
|
||||||
cp "$REPO_ROOT/scripts/seedDemoData.cts" "$DEST/scripts/"
|
cp "$REPO_ROOT/scripts/seedDemoData.js" "$DEST/scripts/"
|
||||||
|
|
||||||
# Seed JSON read at first-run init:
|
# Drop dev SQLite DB files and node_modules if they were copied along with db/.
|
||||||
# db/data/*.json -> carried with db/ above (database.cts reads them)
|
rm -f "$DEST"/db/*.db "$DEST"/db/*.db-* "$DEST"/db/*.db-shm "$DEST"/db/*.db-wal
|
||||||
# docs/top_200_*.json -> db/migrations/versionedMigrations.cts reads ../../docs/
|
|
||||||
mkdir -p "$DEST/docs"
|
|
||||||
cp "$REPO_ROOT/docs/top_200_us_subscriptions_researched_2026-06-06.json" "$DEST/docs/"
|
|
||||||
|
|
||||||
# Drop dev SQLite DBs and any stray node_modules that rode along with db/.
|
|
||||||
rm -f "$DEST"/db/*.db "$DEST"/db/*.db-* 2>/dev/null || true
|
|
||||||
rm -rf "$DEST/db/node_modules"
|
rm -rf "$DEST/db/node_modules"
|
||||||
|
|
||||||
# Transpile the server's TypeScript sources to CommonJS in place (Node 18).
|
# Seed-data JSON files read by db/database.js during first-run initialization.
|
||||||
# Runs BEFORE dist/ is copied so the browser ESM bundle isn't touched.
|
mkdir -p "$DEST/docs"
|
||||||
node "$MOBILE_DIR/scripts/transpile-node18.js" "$DEST"
|
cp "$REPO_ROOT/docs/advisory_non_bill_transaction_filters_us_ms_5000.json" "$DEST/docs/"
|
||||||
|
cp "$REPO_ROOT/docs/top_200_us_subscriptions_researched_2026-06-06.json" "$DEST/docs/"
|
||||||
|
cp "$REPO_ROOT/docs/merchant_store_match_us_nems_online_5k_v0_2.json" "$DEST/docs/"
|
||||||
|
|
||||||
# Built frontend, served as static files by server.cts (path.join(__dirname,'dist')).
|
# nodejs-mobile-cordova 0.4.3 embeds Node 12.19, which doesn't support
|
||||||
|
# optional chaining (?.) / nullish coalescing (??) used in this project and
|
||||||
|
# in some npm dependencies (e.g. express-rate-limit). Transpile server-side
|
||||||
|
# .js files down to Node 12-compatible syntax in place. Runs before the
|
||||||
|
# frontend dist/ is copied below so its browser ESM bundle isn't rewritten
|
||||||
|
# to CJS (which breaks with "module is not defined" in the WebView).
|
||||||
|
node "$MOBILE_DIR/scripts/transpile-node12.js" "$DEST"
|
||||||
|
|
||||||
|
# Built frontend, served as static files by server.js (path.join(__dirname, 'dist')).
|
||||||
cp -R "$REPO_ROOT/dist" "$DEST/dist"
|
cp -R "$REPO_ROOT/dist" "$DEST/dist"
|
||||||
|
|
||||||
cat <<EOF
|
# Also transpile already-installed dependencies, if present (run
|
||||||
Done syncing server source.
|
# `npm install` in nodejs-assets/nodejs-project after this script to pick up
|
||||||
|
# new/updated deps, then re-run this script to transpile them).
|
||||||
|
NODE_MODULES="$MOBILE_DIR/nodejs-assets/nodejs-project/node_modules"
|
||||||
|
if [ -d "$NODE_MODULES" ]; then
|
||||||
|
node "$MOBILE_DIR/scripts/transpile-node12.js" "$NODE_MODULES"
|
||||||
|
fi
|
||||||
|
|
||||||
Next (only when deps changed):
|
echo "Done. Next: cd \"$MOBILE_DIR/nodejs-assets/nodejs-project\" && npm install"
|
||||||
1. cd "$MOBILE_DIR/nodejs-assets/nodejs-project" && npm install
|
|
||||||
2. node "$MOBILE_DIR/scripts/prepare-local-mode-deps.js" # kysely->CJS, stub openid-client
|
|
||||||
3. "$MOBILE_DIR/scripts/install-node18-runtime.sh" # (re)apply Node 18 runtime + bridge patch
|
|
||||||
4. "$MOBILE_DIR/scripts/build-better-sqlite3-node18.sh" # better-sqlite3 for the Node 18 ABI
|
|
||||||
EOF
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
// Transpiles all .js files under a directory (in place) to syntax supported
|
||||||
|
// by nodejs-mobile-cordova's embedded Node 12.19 — primarily optional
|
||||||
|
// chaining (?.) and nullish coalescing (??) used by some npm dependencies
|
||||||
|
// and by this project's own source.
|
||||||
|
//
|
||||||
|
// Usage: node transpile-node12.js <dir> [<dir> ...]
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Resolve esbuild from this project's own node_modules (no longer parent repo)
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
|
||||||
|
const SKIP_DIRS = new Set(['.bin']);
|
||||||
|
|
||||||
|
function walk(dir, cb) {
|
||||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
if (SKIP_DIRS.has(entry.name)) continue;
|
||||||
|
const full = path.join(dir, entry.name);
|
||||||
|
if (entry.isSymbolicLink()) continue;
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
walk(full, cb);
|
||||||
|
} else if (entry.name.endsWith('.js') || entry.name.endsWith('.cjs') || entry.name === 'package.json') {
|
||||||
|
cb(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
let pkgCount = 0;
|
||||||
|
for (const dir of process.argv.slice(2)) {
|
||||||
|
if (!fs.existsSync(dir)) continue;
|
||||||
|
walk(dir, (file) => {
|
||||||
|
if (path.basename(file) === 'package.json') {
|
||||||
|
// esbuild converts ESM (import/export) to CJS (module.exports) below,
|
||||||
|
// but Node still treats .js files as ES modules if the nearest
|
||||||
|
// package.json has "type": "module" — drop that so the converted
|
||||||
|
// CJS output is loaded correctly under Node 12.
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||||
|
if (pkg.type === 'module') {
|
||||||
|
delete pkg.type;
|
||||||
|
fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + '\n');
|
||||||
|
pkgCount++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const src = fs.readFileSync(file, 'utf8');
|
||||||
|
let out;
|
||||||
|
try {
|
||||||
|
out = esbuild.transformSync(src, { target: 'node12', format: 'cjs', loader: 'js' }).code;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Skipping ${file}: ${err.message.split('\n')[0]}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Node 12 doesn't understand the "node:" builtin-module prefix
|
||||||
|
// (added in Node 14.18/16), used by some dependencies (e.g. express-rate-limit).
|
||||||
|
out = out.replace(/require\((['"])node:([a-z/_-]+)\1\)/g, "require($1$2$1)");
|
||||||
|
|
||||||
|
if (out !== src) {
|
||||||
|
fs.writeFileSync(file, out);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log(`Transpiled ${count} file(s) and dropped "type": "module" from ${pkgCount} package.json file(s) for Node 12 compatibility.`);
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
// Transpiles the Bill Tracker server's TypeScript sources to plain CommonJS for
|
|
||||||
// the embedded Node 18.20.4 runtime (nodejs-mobile). Node 18 runs modern JS
|
|
||||||
// syntax natively, so — unlike the old Node-12 pass — this ONLY strips
|
|
||||||
// TypeScript; it does not rewrite dependency syntax and does not touch
|
|
||||||
// node_modules (their JS runs as-is on Node 18).
|
|
||||||
//
|
|
||||||
// The server is CommonJS-with-TS: files use require()/module.exports plus
|
|
||||||
// `import type`/`as typeof import()` for types, and require() each other with
|
|
||||||
// EXPLICIT `.cts`/`.mts` extensions (115 .cts + 40 .mts call sites). We therefore
|
|
||||||
// transpile each .cts/.mts file IN PLACE, keeping its original name: esbuild
|
|
||||||
// strips the types and emits CJS, and Node 18's CommonJS loader loads unknown
|
|
||||||
// extensions (.cts/.mts) through the default `.js` handler as CommonJS — so every
|
|
||||||
// explicit-extension require() path keeps resolving with zero path rewriting.
|
|
||||||
//
|
|
||||||
// Usage: node transpile-node18.js <serverDir> [<dir> ...]
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const esbuild = require('esbuild');
|
|
||||||
|
|
||||||
const SKIP_DIRS = new Set(['.bin', 'node_modules', 'dist']);
|
|
||||||
|
|
||||||
function loaderFor(file) {
|
|
||||||
if (/\.(cts|mts|ts)$/.test(file)) return 'ts';
|
|
||||||
if (/\.(cjs|js)$/.test(file)) return 'js';
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function walk(dir, cb) {
|
|
||||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
||||||
if (SKIP_DIRS.has(entry.name)) continue;
|
|
||||||
if (entry.isSymbolicLink()) continue;
|
|
||||||
const full = path.join(dir, entry.name);
|
|
||||||
if (entry.isDirectory()) walk(full, cb);
|
|
||||||
else if (loaderFor(entry.name)) cb(full);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
let failed = 0;
|
|
||||||
for (const dir of process.argv.slice(2)) {
|
|
||||||
if (!fs.existsSync(dir)) continue;
|
|
||||||
walk(dir, (file) => {
|
|
||||||
const src = fs.readFileSync(file, 'utf8');
|
|
||||||
let out;
|
|
||||||
try {
|
|
||||||
out = esbuild.transformSync(src, {
|
|
||||||
loader: loaderFor(file),
|
|
||||||
format: 'cjs',
|
|
||||||
target: 'node18',
|
|
||||||
sourcefile: file,
|
|
||||||
}).code;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`FAILED ${file}: ${err.message.split('\n')[0]}`);
|
|
||||||
failed++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (out !== src) {
|
|
||||||
fs.writeFileSync(file, out);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed) {
|
|
||||||
console.error(`Transpile finished with ${failed} failure(s).`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
console.log(`Transpiled ${count} file(s) to CommonJS for Node 18.`);
|
|
||||||
Loading…
Reference in New Issue