// Idempotent post-install patches for running @deepseek-ai/dsh on Termux/Android (bionic). // Re-run safely after every `npm install`. If dsh's upstream source changes shape, // each patch just logs a skip instead of corrupting the file. 'use strict'; const fs = require('fs'); const path = require('path'); const { spawnSync } = require('child_process'); const projectRoot = path.join(__dirname, '..'); const nodeModules = path.join(projectRoot, 'node_modules'); function patchFile(file, oldStr, newStr, label) { if (!fs.existsSync(file)) { console.log(`[dsh-patch] ${label}: file not found, skip (${file})`); return; } const content = fs.readFileSync(file, 'utf8'); if (content.includes(newStr)) { console.log(`[dsh-patch] ${label}: already patched, skip`); return; } if (!content.includes(oldStr)) { console.error(`[dsh-patch] ${label}: expected original text not found - dsh internals may have changed, skipping patch`); return; } fs.writeFileSync(file, content.replace(oldStr, newStr)); console.log(`[dsh-patch] ${label}: patched`); } // --- Patch 1: dsh-app-boot's internalModules() unconditionally requires the // native "node-addon-require-builtin" addon, which has no Android prebuild and // deliberately refuses to compile one locally. cordis-plugin-loader already // has a --expose-internals fast path elsewhere; mirror it here. --- patchFile( path.join(nodeModules, '@deepseek-ai/dsh-app-boot/lib/index.js'), `function internalModules() { const addon = createRequire(import.meta.url)("node-addon-require-builtin"); const esmModule = addon.requireBuiltin("internal/modules/esm/loader"); const cjsModule = addon.requireBuiltin("internal/modules/cjs/loader"); const cjsHelpers = addon.requireBuiltin("internal/modules/helpers"); const esmUtils = addon.requireBuiltin("internal/modules/esm/utils"); const esmResolve = addon.requireBuiltin("internal/modules/esm/resolve");`, `function internalModules() { const req = createRequire(import.meta.url); const requireBuiltin = process.execArgv.includes("--expose-internals") ? (id) => req(id) : (() => { const addon = req("node-addon-require-builtin"); return (id) => addon.requireBuiltin(id); })(); const esmModule = requireBuiltin("internal/modules/esm/loader"); const cjsModule = requireBuiltin("internal/modules/cjs/loader"); const cjsHelpers = requireBuiltin("internal/modules/helpers"); const esmUtils = requireBuiltin("internal/modules/esm/utils"); const esmResolve = requireBuiltin("internal/modules/esm/resolve");`, 'dsh-app-boot internalModules() --expose-internals fallback', ); // --- Patch 2: node-addon-system's flock.js only allowlists linux/darwin. // Add an android branch that loads a locally-compiled binding instead of a // (nonexistent) @deepseek-ai/node-addon-system-android-arm64 package. --- const flockFile = path.join(nodeModules, '@deepseek-ai/node-addon-system/lib/flock.js'); patchFile( flockFile, `function loadBinding() { if (binding) return binding; const { platform, arch } = process; if (platform !== 'linux' && platform !== 'darwin') {`, `function loadBinding() { if (binding) return binding; const { platform, arch } = process; if (platform === 'android') { const require = createRequire(import.meta.url); binding = require(join(import.meta.dirname, '..', 'bin', 'android-arm64', 'system.node')); return binding; } if (platform !== 'linux' && platform !== 'darwin') {`, 'node-addon-system flock.js android branch', ); // --- Patch 3: compile flock.c (published in the package, BSD-3-Clause) into // bin/android-arm64/system.node with Termux's own toolchain. Node-gyp's // bundled common.gypi references ${android_ndk_path} unconditionally on // Android hosts; Termux's own CMake/gyp Android detection additionally needs // $PREFIX set so it can find $PREFIX/include/android/api-level.h. --- const systemPkgDir = path.join(nodeModules, '@deepseek-ai/node-addon-system'); const srcFile = path.join(systemPkgDir, 'src/flock.c'); const outBinary = path.join(systemPkgDir, 'bin', 'android-arm64', 'system.node'); if (process.platform !== 'android') { console.log('[dsh-patch] not running on Android - skipping flock native build'); } else if (!fs.existsSync(srcFile)) { console.log(`[dsh-patch] flock.c not found (${srcFile}), skip native build`); } else if (fs.existsSync(outBinary)) { console.log('[dsh-patch] flock system.node already built, skip'); } else { const prefix = process.env.PREFIX || '/data/data/com.termux/files/usr'; const buildDir = path.join(__dirname, '.flock-build'); fs.mkdirSync(buildDir, { recursive: true }); fs.copyFileSync(srcFile, path.join(buildDir, 'flock.c')); fs.writeFileSync( path.join(buildDir, 'binding.gyp'), JSON.stringify({ targets: [{ target_name: 'system', sources: ['flock.c'], defines: ['NAPI_VERSION=8'] }] }, null, 2), ); fs.writeFileSync( path.join(buildDir, 'package.json'), JSON.stringify({ name: 'flock-build', version: '1.0.0', private: true, gypfile: true }, null, 2), ); const nodeGyp = path.join(prefix, 'lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js'); const env = { ...process.env, PREFIX: prefix, npm_config_android_ndk_path: prefix }; console.log('[dsh-patch] compiling flock native addon for android-arm64...'); const result = spawnSync(process.execPath, [nodeGyp, 'rebuild'], { cwd: buildDir, env, stdio: 'inherit' }); if (result.status === 0) { fs.mkdirSync(path.dirname(outBinary), { recursive: true }); fs.copyFileSync(path.join(buildDir, 'build/Release/system.node'), outBinary); console.log('[dsh-patch] flock system.node: built and installed'); } else { console.error('[dsh-patch] WARNING: flock native build failed; session file-locking (flock) will not work'); } }