File size: 11,905 Bytes
5448d8b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#!/usr/bin/env node

/**

 * OmniRoute — Postinstall Native Module Fix

 *

 * The npm package ships with a Next.js standalone build that includes

 * native modules compiled for the build platform (Linux x64) inside

 * dist/node_modules/. However, npm also installs these as top-level

 * dependencies (in the root node_modules/), correctly compiled for

 * the user's platform.

 *

 * This script copies the correctly-built native binaries from the root

 * into the standalone dist directory — no rebuild or build tools needed.

 *

 * Modules repaired:

 *   - better-sqlite3 (SQLite bindings)

 *   - wreq-js (TLS client for OAuth providers)

 *

 * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/129

 * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/321

 * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/426

 * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/1634

 */

import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

import { PUBLISHED_BUILD_ARCH, PUBLISHED_BUILD_PLATFORM } from "./native-binary-compat.mjs";
import { hasStandaloneAppBundle, isTermux } from "./postinstallSupport.mjs";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const ROOT = join(__dirname, "..", "..");

const appBinary = join(
  ROOT,
  "dist",
  "node_modules",
  "better-sqlite3",
  "build",
  "Release",
  "better_sqlite3.node"
);
const rootBinary = join(
  ROOT,
  "node_modules",
  "better-sqlite3",
  "build",
  "Release",
  "better_sqlite3.node"
);

async function fixBetterSqliteBinary() {
  if (!existsSync(join(ROOT, "dist", "node_modules", "better-sqlite3"))) {
    return;
  }

  const platformMatch =
    process.platform === PUBLISHED_BUILD_PLATFORM && process.arch === PUBLISHED_BUILD_ARCH;

  if (platformMatch) {
    try {
      process.dlopen({ exports: {} }, appBinary);
      return;
    } catch (err) {
      console.warn(`  ⚠️  Bundled binary incompatible despite platform match: ${err.message}`);
    }
  }

  console.log(`\n  🔧 Fixing better-sqlite3 binary for ${process.platform}-${process.arch}...`);

  if (existsSync(rootBinary)) {
    try {
      mkdirSync(dirname(appBinary), { recursive: true });
      copyFileSync(rootBinary, appBinary);
    } catch (err) {
      console.warn(`  ⚠️  Failed to copy binary: ${err.message}`);
    }

    try {
      process.dlopen({ exports: {} }, appBinary);
      console.log("  ✅ Native module fixed successfully!\n");
      return;
    } catch (err) {
      console.warn(`  ⚠️  Copied binary failed to load: ${err.message}`);
    }
  }

  console.log("  📥  Attempting to download prebuilt binary via node-pre-gyp...");
  try {
    const { execSync } = await import("node:child_process");
    const preGypBin = join(
      ROOT,
      "dist",
      "node_modules",
      ".bin",
      process.platform === "win32" ? "node-pre-gyp.cmd" : "node-pre-gyp"
    );
    const preGypFallback = join(
      ROOT,
      "dist",
      "node_modules",
      "@mapbox",
      "node-pre-gyp",
      "bin",
      "node-pre-gyp"
    );
    const preGypCmd = existsSync(preGypBin) ? preGypBin : preGypFallback;

    if (existsSync(preGypCmd)) {
      execSync(`"${process.execPath}" "${preGypCmd}" install --fallback-to-build=false`, {
        cwd: join(ROOT, "dist", "node_modules", "better-sqlite3"),
        stdio: "inherit",
        timeout: 60_000,
      });
      mkdirSync(dirname(appBinary), { recursive: true });

      try {
        process.dlopen({ exports: {} }, appBinary);
        console.log("  ✅ Prebuilt binary downloaded and loaded successfully!\n");
        return;
      } catch (loadErr) {
        console.warn(`  ⚠️  Downloaded binary failed to load: ${loadErr.message}`);
      }
    } else {
      console.warn("  ⚠️  node-pre-gyp not found, skipping prebuilt download.");
    }
  } catch (err) {
    console.warn(`  ⚠️  node-pre-gyp download failed: ${err.message.split("\n")[0]}`);
  }

  console.log("  ⚠️  Attempting npm rebuild (requires build tools)...");

  try {
    const { execSync } = await import("node:child_process");

    // On Android/Termux, rebuild from source with --build-from-source flag
    const isAndroid = process.platform === "android" || isTermux();
    const rebuildCmd = isAndroid
      ? "npm install better-sqlite3 --build-from-source --force"
      : "npm rebuild better-sqlite3";

    const env = { ...process.env };
    if (isAndroid) {
      env.GYP_DEFINES = "android_ndk_path=''";
    }

    execSync(rebuildCmd, {
      cwd: join(ROOT, "dist"),
      stdio: "inherit",
      timeout: isAndroid ? 600_000 : 300_000, // ARM compilation is slower
      env,
    });

    process.dlopen({ exports: {} }, appBinary);
    console.log("  ✅ Native module rebuilt successfully!\n");
    return;
  } catch (err) {
    const isTimeout = err.killed || err.signal === "SIGTERM";
    if (isTimeout) {
      const secs = isAndroid ? 600 : 300;
      console.warn(`  ⚠️  npm rebuild timed out after ${secs}s.`);
    } else {
      console.warn(`  ⚠️  npm rebuild failed: ${err.message}`);
    }
  }

  console.warn("\n  ⚠️  Could not fix better-sqlite3 native module automatically.");
  console.warn("     The server may not start correctly.");
  console.warn("     Manual fix options:");
  if (process.platform === "win32") {
    console.warn("     Option A (easiest — no build tools needed):");
    console.warn(`       cd "${join(ROOT, "dist", "node_modules", "better-sqlite3")}"`);
    console.warn("       npx @mapbox/node-pre-gyp install --fallback-to-build=false");
    console.warn("     Option B (requires Build Tools for Visual Studio):");
    console.warn(`       cd "${join(ROOT, "dist")}" && npm rebuild better-sqlite3`);
    console.warn("       Install from: https://visualstudio.microsoft.com/visual-cpp-build-tools/");
    console.warn("       Also ensure Python is installed: https://python.org");
  } else if (process.platform === "darwin") {
    console.warn(`     cd ${join(ROOT, "dist")} && npm rebuild better-sqlite3`);
    console.warn("     If build tools are missing: xcode-select --install");
  } else {
    console.warn(`     cd ${join(ROOT, "dist")} && npm rebuild better-sqlite3`);
  }
  console.warn("");
}

/**

 * Fix wreq-js native binary for the standalone dist directory.

 *

 * wreq-js ships platform-specific .node binaries under rust/.

 * The standalone build may only contain Linux binaries from the CI.

 * This copies the correct platform binary from the root install.

 *

 * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/1634

 */
async function fixWreqJsBinary() {
  // wreq-js native module is not loadable in Termux (libgcc path mismatch).
  // The runtime already falls back gracefully when wreq-js is unavailable.
  if (process.platform === "android" || isTermux()) {
    console.log(
      "  [postinstall] wreq-js: skipped on Termux/Android " +
        "(libgcc not available — OAuth TLS fingerprinting will use the fallback path)"
    );
    return;
  }

  const appWreqDir = join(ROOT, "dist", "node_modules", "wreq-js", "rust");
  const rootWreqDir = join(ROOT, "node_modules", "wreq-js", "rust");

  if (!existsSync(join(ROOT, "dist", "node_modules", "wreq-js"))) {
    return;
  }

  const binaryName = `wreq-js.${process.platform}-${process.arch}.node`;
  const appBinaryPath = join(appWreqDir, binaryName);
  const rootBinaryPath = join(rootWreqDir, binaryName);

  // Check if the platform binary already exists and loads
  if (existsSync(appBinaryPath)) {
    try {
      process.dlopen({ exports: {} }, appBinaryPath);
      return; // Already working
    } catch (err) {
      console.warn(`  ⚠️  wreq-js binary exists but failed to load: ${err.message}`);
    }
  }

  console.log(`\n  🔧 Fixing wreq-js binary for ${process.platform}-${process.arch}...`);

  // Strategy 1: Copy from root node_modules
  if (existsSync(rootBinaryPath)) {
    try {
      mkdirSync(appWreqDir, { recursive: true });
      copyFileSync(rootBinaryPath, appBinaryPath);
      process.dlopen({ exports: {} }, appBinaryPath);
      console.log("  ✅ wreq-js native module fixed successfully!\n");
      return;
    } catch (err) {
      console.warn(`  ⚠️  Copied wreq-js binary failed to load: ${err.message}`);
    }
  }

  // Strategy 2: Copy entire rust/ directory from root (gets all platform binaries)
  if (existsSync(rootWreqDir)) {
    try {
      mkdirSync(appWreqDir, { recursive: true });
      const files = readdirSync(rootWreqDir);
      for (const file of files) {
        if (file.endsWith(".node")) {
          copyFileSync(join(rootWreqDir, file), join(appWreqDir, file));
        }
      }
      if (existsSync(appBinaryPath)) {
        process.dlopen({ exports: {} }, appBinaryPath);
        console.log("  ✅ wreq-js native module fixed (full copy) successfully!\n");
        return;
      }
    } catch (err) {
      console.warn(`  ⚠️  wreq-js full copy failed: ${err.message}`);
    }
  }

  // Strategy 3: Rebuild wreq-js inside dist/
  console.log("  📥 Attempting npm rebuild wreq-js...");
  try {
    const { execSync } = await import("node:child_process");
    execSync("npm rebuild wreq-js", {
      cwd: join(ROOT, "dist"),
      stdio: "inherit",
      timeout: 120_000,
    });
    if (existsSync(appBinaryPath)) {
      process.dlopen({ exports: {} }, appBinaryPath);
      console.log("  ✅ wreq-js native module rebuilt successfully!\n");
      return;
    }
  } catch (err) {
    console.warn(`  ⚠️  wreq-js rebuild failed: ${err.message}`);
  }

  console.warn(
    `\n  ⚠️  Could not fix wreq-js native module for ${process.platform}-${process.arch}.`
  );
  console.warn("     OAuth-based providers (Codex, Cursor, etc.) may not work.");
  console.warn(`     Manual fix: cd ${join(ROOT, "dist")} && npm install wreq-js --no-save\n`);
}

async function ensureSwcHelpers() {
  if (!hasStandaloneAppBundle(ROOT)) {
    return;
  }

  const swcHelpersApp = join(ROOT, "dist", "node_modules", "@swc", "helpers");
  const swcHelpersRoot = join(ROOT, "node_modules", "@swc", "helpers");

  if (existsSync(swcHelpersApp)) {
    return;
  }

  if (existsSync(swcHelpersRoot)) {
    try {
      const { cpSync } = await import("node:fs");
      mkdirSync(join(ROOT, "dist", "node_modules", "@swc"), { recursive: true });
      cpSync(swcHelpersRoot, swcHelpersApp, { recursive: true });
      console.log("  ✅ @swc/helpers copied to standalone dist/node_modules.\n");
    } catch (err) {
      console.warn(`  ⚠️  Could not copy @swc/helpers: ${err.message}`);
      console.warn(
        "     Try manually: cp -r node_modules/@swc/helpers dist/node_modules/@swc/helpers\n"
      );
    }
    return;
  }

  console.warn("  ⚠️  @swc/helpers not found in root node_modules either.");
  console.warn("     Try: npm install --save-exact @swc/helpers@0.5.19\n");
}

async function syncProjectEnv() {
  try {
    const { syncEnv } = await import("./sync-env.mjs");
    syncEnv({ rootDir: ROOT });
  } catch (err) {
    console.warn(`  ⚠️  .env sync skipped: ${err.message}`);
  }
}

await fixBetterSqliteBinary();
await fixWreqJsBinary();
await ensureSwcHelpers();
await syncProjectEnv();

// Warm up native runtimes (better-sqlite3 in ~/.omniroute/runtime/).
// Non-fatal: errors are caught inside postinstall.mjs.
try {
  await import("../postinstall.mjs");
} catch {
  // Silently skip — runtime warm-up is best-effort.
}