Files
openchamber/packages/electron/scripts/rebuild-native.mjs
T
Bohdan Triapitsyn 56f2b972f0 chore(deps): drop better-sqlite3 and its desktop packaging support
The SQLite write into OpenCode's database was the only consumer of
better-sqlite3 in the repository. Everything that existed to ship its native
binary went with it:

- the dependency in @openchamber/web and @openchamber/electron
- the afterPack hook staging better_sqlite3.node into app.asar.unpacked
- a dedicated @electron/rebuild pass (onlyModules) and its binary assertion,
  so desktop packaging now runs one native rebuild instead of two
- the bundler external entry and the AppImage required-native-module check

Desktop packaging, the AppImage verification tests, and the extension bundle
were re-validated after a clean reinstall, so no stale module could satisfy a
missed import.
2026-08-03 23:38:01 +03:00

159 lines
5.4 KiB
JavaScript

#!/usr/bin/env node
import path from 'node:path';
import { existsSync } from 'node:fs';
import fsp from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import { rebuild } from '@electron/rebuild';
import { resolveTargetArchitecture } from './target-architecture.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const electronDir = path.resolve(__dirname, '..');
const repoRoot = path.resolve(electronDir, '..', '..');
const require = createRequire(import.meta.url);
const electronPkg = require('electron/package.json');
const electronVersion = electronPkg.version;
const targetArchitecture = resolveTargetArchitecture();
const copyDirectory = async (src, dst) => {
await fsp.mkdir(dst, { recursive: true });
const entries = await fsp.readdir(src, { withFileTypes: true });
for (const entry of entries) {
const from = path.join(src, entry.name);
const to = path.join(dst, entry.name);
if (entry.isDirectory()) {
await copyDirectory(from, to);
} else {
await fsp.copyFile(from, to);
}
}
};
const getWindowsShortPath = (target) => {
if (process.platform !== 'win32') return target;
try {
const escaped = target.replace(/'/g, "''");
const output = execFileSync(
'powershell.exe',
['-NoProfile', '-Command', `$fso = New-Object -ComObject Scripting.FileSystemObject; $fso.GetFolder('${escaped}').ShortPath`],
{ encoding: 'utf8' },
).trim();
return output || target;
} catch {
return target;
}
};
const createWindowsRebuildPath = (target) => {
if (process.platform !== 'win32') {
return { buildPath: target, cleanup: () => {} };
}
for (const letter of 'ZYXWVUTSRQPONMLKJIHGFED') {
const drive = `${letter}:`;
if (existsSync(`${drive}\\`)) continue;
try {
execFileSync('subst.exe', [drive, target], { stdio: 'ignore' });
return {
buildPath: `${drive}\\`,
cleanup: () => {
try {
execFileSync('subst.exe', [drive, '/d'], { stdio: 'ignore' });
} catch {
// Best-effort cleanup. The build result should not depend on this.
}
},
};
} catch {
// Try the next drive letter.
}
}
const shortPath = getWindowsShortPath(target);
if (shortPath === target && /\s/.test(target)) {
throw new Error(
`Unable to create a space-free Windows rebuild path for ${target}. `
+ 'All subst drive letters are unavailable and the volume did not return an 8.3 short path.',
);
}
return { buildPath: shortPath, cleanup: () => {} };
};
const writeWindowsNodeAddonApiIndex = async (nodeAddonApiDir, exportedNodeAddonApiDir) => {
if (process.platform !== 'win32') return;
const shortDir = getWindowsShortPath(exportedNodeAddonApiDir);
await fsp.writeFile(
path.join(nodeAddonApiDir, 'index.js'),
`const path = require('path');
const includeDir = ${JSON.stringify(shortDir)};
module.exports = {
include: \`"${shortDir}"\`,
include_dir: includeDir,
gyp: path.join(includeDir, 'node_api.gyp:nothing'),
targets: path.join(includeDir, 'node_addon_api.gyp'),
isNodeApiBuiltin: true,
needsFlag: false
};
`,
);
};
const ensureWindowsNodeAddonApiForNodePty = async (rebuildRootPath) => {
if (process.platform !== 'win32') return async () => {};
const nodePtyPackagePath = require.resolve('node-pty/package.json');
const nodePtyDir = path.dirname(nodePtyPackagePath);
const rootNodeAddonApiDir = path.dirname(require.resolve('node-addon-api/package.json'));
const tempNodeAddonApiDir = path.join(repoRoot, 'node_modules', '.openchamber-node-addon-api-7.1.1');
const exportedTempNodeAddonApiDir = path.join(rebuildRootPath, 'node_modules', '.openchamber-node-addon-api-7.1.1');
const localNodeAddonApiDir = path.join(nodePtyDir, 'node_modules', 'node-addon-api');
await fsp.rm(tempNodeAddonApiDir, { recursive: true, force: true });
await copyDirectory(rootNodeAddonApiDir, tempNodeAddonApiDir);
await fsp.access(path.join(tempNodeAddonApiDir, 'package.json'));
await fsp.rm(localNodeAddonApiDir, { recursive: true, force: true });
await copyDirectory(rootNodeAddonApiDir, localNodeAddonApiDir);
await writeWindowsNodeAddonApiIndex(localNodeAddonApiDir, exportedTempNodeAddonApiDir);
await fsp.access(path.join(localNodeAddonApiDir, 'package.json'));
return async () => {
await fsp.rm(localNodeAddonApiDir, { recursive: true, force: true });
await fsp.rm(tempNodeAddonApiDir, { recursive: true, force: true });
};
};
console.log(`[electron] rebuilding native modules against Electron ${electronVersion}...`);
// Rebuild against the hoisted root node_modules (bun workspace layout).
// force=true re-links regardless of cached state; prebuild-install lookup is
// bypassed by @electron/rebuild in favor of direct node-gyp builds.
const rebuildPath = createWindowsRebuildPath(repoRoot);
let cleanupNodeAddonApi = async () => {};
try {
cleanupNodeAddonApi = await ensureWindowsNodeAddonApiForNodePty(rebuildPath.buildPath);
await rebuild({
buildPath: rebuildPath.buildPath,
electronVersion,
force: true,
arch: targetArchitecture.electronBuilder,
onlyModules: ['node-pty', 'bun-pty'],
});
} finally {
try {
await cleanupNodeAddonApi();
} finally {
rebuildPath.cleanup();
}
}
console.log('[electron] native modules rebuilt successfully');