microsoft/TypeAgent

Public

mirrored from https://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dfb72a4bbafa850817eef384b862b6836b1af43f

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

ts/tools/scripts/copy-better-sqlite3-node.js

156lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4// Save the Node.js-compatible better-sqlite3 native binary to a safe location.
5//
6// Problem: electron-builder's install-app-deps (in packages/shell postinstall)
7// rebuilds better-sqlite3 for Electron, wiping the entire build/ directory.
8// This runs in the root postinstall BEFORE electron-builder, so we save the
9// correct Node.js binary to prebuild-node/ (outside build/) where
10// electron-builder won't touch it.
11//
12// If the binary in build/Release/ is already for Electron (wrong ABI), we
13// re-download the correct Node.js prebuilt via prebuild-install.
14//
15// Works with pnpm's store layout on Windows, macOS, and Linux.
16
17const fs = require("fs");
18const path = require("path");
19const { execFileSync, spawnSync } = require("child_process");
20
21const expectedABI = process.versions.modules; // e.g. "127" for Node 22
22
23// Probe binary compatibility in a CHILD process. Loading a native addon built
24// for a different ABI (e.g. Electron) can SIGSEGV the loader rather than throw,
25// which an in-process try/catch cannot recover from. Isolating the dlopen in a
26// child turns a crash into a non-zero exit we can detect safely.
27function isNodeCompatible(binaryPath) {
28 const res = spawnSync(
29 process.execPath,
30 ["-e", "process.dlopen({ exports: {} }, process.argv[1])", binaryPath],
31 { stdio: "ignore" },
32 );
33 return res.status === 0;
34}
35
36// Find all better-sqlite3 installations in the pnpm store
37const pnpmDir = path.resolve(__dirname, "..", "..", "node_modules", ".pnpm");
38const entries = fs
39 .readdirSync(pnpmDir)
40 .filter((e) => e.startsWith("better-sqlite3@"));
41
42if (entries.length === 0) {
43 console.error("No better-sqlite3 installations found in", pnpmDir);
44 process.exit(1);
45}
46
47let hasError = false;
48
49for (const entry of entries) {
50 const pkgDir = path.join(pnpmDir, entry, "node_modules", "better-sqlite3");
51 if (!fs.existsSync(path.join(pkgDir, "package.json"))) {
52 continue;
53 }
54
55 console.log(`\n📦 Processing ${entry}:`);
56 console.log(" ", pkgDir);
57
58 const dstDir = path.join(pkgDir, "prebuild-node");
59 const dst = path.join(dstDir, "better_sqlite3.node");
60
61 // If prebuild-node/ already has a compatible binary, skip
62 if (fs.existsSync(dst) && isNodeCompatible(dst)) {
63 console.log(
64 "✅ Already has compatible Node.js binary in prebuild-node/",
65 );
66 continue;
67 }
68
69 const releaseBinary = path.join(
70 pkgDir,
71 "build",
72 "Release",
73 "better_sqlite3.node",
74 );
75
76 // If build/Release/ has a compatible Node.js binary, just copy it
77 if (fs.existsSync(releaseBinary) && isNodeCompatible(releaseBinary)) {
78 fs.mkdirSync(dstDir, { recursive: true });
79 fs.copyFileSync(releaseBinary, dst);
80 console.log("✅ Copied compatible binary from build/Release/");
81 console.log(" → ", dst);
82 continue;
83 }
84
85 // Binary is missing or wrong ABI (e.g. Electron) — re-download for Node.js
86 console.log(
87 "⬇️ Downloading Node.js-compatible prebuilt via prebuild-install...",
88 );
89 const tempBuildDir = path.join(pkgDir, "build-node-temp");
90 try {
91 // prebuild-install writes to build/Release/, so use a temp dir
92 // to avoid disturbing any existing Electron binary
93 fs.rmSync(tempBuildDir, { recursive: true, force: true });
94 const origBuild = path.join(pkgDir, "build");
95 const hasBuild = fs.existsSync(origBuild);
96 if (hasBuild) {
97 fs.renameSync(origBuild, tempBuildDir);
98 }
99 try {
100 execFileSync(
101 process.execPath,
102 [
103 require.resolve("prebuild-install/bin", {
104 paths: [pkgDir],
105 }),
106 "--runtime",
107 "node",
108 "--target",
109 process.version,
110 ],
111 {
112 cwd: pkgDir,
113 stdio: "inherit",
114 },
115 );
116
117 const downloaded = path.join(
118 pkgDir,
119 "build",
120 "Release",
121 "better_sqlite3.node",
122 );
123 if (!fs.existsSync(downloaded)) {
124 throw new Error("prebuild-install did not produce a binary");
125 }
126
127 fs.mkdirSync(dstDir, { recursive: true });
128 fs.copyFileSync(downloaded, dst);
129 console.log("✅ Node.js binary saved to prebuild-node/");
130
131 // Remove the temp build dir created by prebuild-install
132 fs.rmSync(path.join(pkgDir, "build"), {
133 recursive: true,
134 force: true,
135 });
136 } finally {
137 // Restore original build dir (may contain Electron binary)
138 if (hasBuild) {
139 if (!fs.existsSync(origBuild)) {
140 fs.renameSync(tempBuildDir, origBuild);
141 } else {
142 fs.rmSync(tempBuildDir, { recursive: true, force: true });
143 }
144 }
145 }
146 } catch (e) {
147 console.error(`❌ Failed for ${entry}:`, e.message);
148 fs.rmSync(tempBuildDir, { recursive: true, force: true });
149 hasError = true;
150 continue;
151 }
152}
153
154if (hasError) {
155 process.exit(1);
156}
157