← back to Pm2 Migration

root-shim.js

76 lines

// pm2-migration/root-shim.js
// Universal path-rewrite shim. Loaded via node --require so all fs calls
// that reference Kamatera-style /root/... paths get transparently redirected
// to /Users/macstudio3/kamatera-mirror/...
//
// Used by every migrated agent in ecosystem.config.cjs as:
//   node_args: "--require /Users/macstudio3/Projects/pm2-migration/root-shim.js"
//
// No agent code changes required. Add new dirs to MIRROR as needed.

const fs   = require("fs");
const os   = require("os");
const path = require("path");

const MIRROR = process.env.KAMATERA_MIRROR || path.join(os.homedir(), "kamatera-mirror");
const PREFIX = "/root/";

function rewrite(p) {
  if (typeof p !== "string") return p;
  if (!p.startsWith(PREFIX)) return p;
  return path.join(MIRROR, p.slice(PREFIX.length)); // strip "/root/" → mirror root
}

// Wrap any fs method whose first arg is a path string.
const PATH_FIRST_METHODS = [
  "readFileSync", "writeFileSync", "appendFileSync", "existsSync", "statSync",
  "lstatSync", "readdirSync", "mkdirSync", "rmdirSync", "rmSync", "unlinkSync",
  "renameSync", "copyFileSync", "accessSync", "chmodSync", "chownSync",
  "createReadStream", "createWriteStream", "openSync", "watchFile",
  "readFile", "writeFile", "appendFile", "stat", "lstat", "readdir",
  "mkdir", "rmdir", "rm", "unlink", "rename", "copyFile", "access",
  "chmod", "chown", "open", "exists",
];

for (const m of PATH_FIRST_METHODS) {
  const orig = fs[m];
  if (typeof orig !== "function") continue;
  fs[m] = function (p, ...rest) {
    return orig.call(fs, rewrite(p), ...rest);
  };
}

// fs.promises mirror
if (fs.promises) {
  for (const m of [
    "readFile", "writeFile", "appendFile", "stat", "lstat", "readdir",
    "mkdir", "rmdir", "rm", "unlink", "rename", "copyFile", "access",
    "chmod", "chown", "open",
  ]) {
    const orig = fs.promises[m];
    if (typeof orig !== "function") continue;
    fs.promises[m] = function (p, ...rest) {
      return orig.call(fs.promises, rewrite(p), ...rest);
    };
  }
}

// path.resolve / path.join called with /root/... will produce /root/... — handle as-is.
// dotenv config({ path: '/root/...' }) calls fs.readFileSync internally, so it's covered.

// Patch Node's module resolver — require('/root/...') needs to resolve to mirror.
// This catches both `require('/root/foo')` and `require('/root/foo/bar')` patterns
// that fs-shimming alone can't reach (Module._resolveFilename uses internal fs).
const Module = require("module");
const origResolve = Module._resolveFilename;
Module._resolveFilename = function (request, parent, ...rest) {
  if (typeof request === "string" && request.startsWith(PREFIX)) {
    request = path.join(MIRROR, request.slice(PREFIX.length));
  }
  return origResolve.call(this, request, parent, ...rest);
};

if (process.env.LOCAL_MIGRATION === "1" && !process.env.SHIM_QUIET) {
  console.log(`[root-shim] /root/* → ${MIRROR}/* (fs + require)`);
}