build(desktop): stage get-windows like node-pty
get-windows@9.3.0 (MIT, zero runtime deps on macOS/Linux) is external to the esbuild bundle and staged into dist/node_modules per target platform: the universal Swift helper on macOS, the prebuilt N-API binding on Windows (fail-closed magic-byte validation), nothing on Linux (xprop at runtime). The staged lib/windows.js is rewritten to load the binding directly so @mapbox/node-pre-gyp's tree stays out of the package.
This commit is contained in:
committed by
brooklyn!
parent
f22ae72921
commit
f463a7e8ee
@@ -104,6 +104,7 @@
|
||||
"emojibase-data": "16.0.3",
|
||||
"fflate": "0.8.3",
|
||||
"frimousse": "0.3.0",
|
||||
"get-windows": "9.3.0",
|
||||
"hast-util-from-html-isomorphic": "2.0.0",
|
||||
"hast-util-to-text": "4.0.2",
|
||||
"ignore": "7.0.6",
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
import { existsSync, rmSync, renameSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { Arch } from 'electron-builder'
|
||||
import { stageNodePty } from './stage-native-deps.mjs'
|
||||
import { stageNodePty, stageGetWindows } from './stage-native-deps.mjs'
|
||||
|
||||
export function cleanStaleAppOutDir(appOutDir) {
|
||||
if (!appOutDir || typeof appOutDir !== 'string') {
|
||||
@@ -144,11 +144,16 @@ export default async function beforePack(context) {
|
||||
await stageNodePty({ platform, arch: archName })
|
||||
console.log(`[before-pack] re-staged node-pty for target ${platform}-${archName}`)
|
||||
}
|
||||
// get-windows' native payload is per-platform, not per-arch (the macOS
|
||||
// helper is universal, Windows stages every prebuilt binding dir), so
|
||||
// it re-stages for the universal target too.
|
||||
stageGetWindows({ platform })
|
||||
console.log(`[before-pack] re-staged get-windows for target ${platform}`)
|
||||
}
|
||||
} catch (err) {
|
||||
// This one SHOULD fail the build — a missing/wrong native binary for the
|
||||
// target arch means a broken package shipped to users, which is worse
|
||||
// than a build that fails loudly here.
|
||||
throw new Error(`[before-pack] failed to stage node-pty for this target: ${err.message}`)
|
||||
throw new Error(`[before-pack] failed to stage native deps for this target: ${err.message}`)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ const mainOut = resolve(distDir, 'electron-main.mjs')
|
||||
const preloadEntry = resolve(root, 'electron/preload.ts')
|
||||
const preloadOut = resolve(distDir, 'electron-preload.js')
|
||||
|
||||
const external = ['electron', 'node-pty', 'fs']
|
||||
const external = ['electron', 'node-pty', 'get-windows', 'fs']
|
||||
// Production bundles bake packaged=true so unpackaged `electron .` still
|
||||
// behaves like a packaged build. Dev bundles (`--dev`) leave the env alone
|
||||
// so HERMES_DESKTOP_DEV_SERVER / source-tree resolution keep working.
|
||||
|
||||
@@ -349,8 +349,142 @@ export function stageNodePty({ platform = process.platform, arch = process.arch
|
||||
return stageNodePtyInto(srcRoot, destRoot, { platform, arch })
|
||||
}
|
||||
|
||||
// ─── get-windows (read_window_below tool) ────────────────────────────
|
||||
//
|
||||
// Staged like node-pty: external to the esbuild bundle, resolved at runtime
|
||||
// from dist/node_modules (which asarUnpack ships unpacked via `dist/**`).
|
||||
//
|
||||
// The published package's lib/windows.js statically imports
|
||||
// @mapbox/node-pre-gyp — and index.js statically imports lib/windows.js on
|
||||
// EVERY platform — so shipping it verbatim would drag node-pre-gyp's whole
|
||||
// dependency tree into the package. pre-gyp is only used to *locate* the
|
||||
// prebuilt .node we stage ourselves, so the staged copy replaces
|
||||
// lib/windows.js with a resolver that requires the staged binding directly
|
||||
// (and fails soft to no-op stubs, matching upstream's missing-binding
|
||||
// behavior).
|
||||
|
||||
const STAGED_WINDOWS_JS = `// Rewritten by stage-native-deps.mjs: resolves the staged prebuilt binding
|
||||
// directly instead of through @mapbox/node-pre-gyp (see stageGetWindowsInto).
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {createRequire} from 'node:module';
|
||||
|
||||
const getAddon = () => {
|
||||
\tconst require = createRequire(import.meta.url);
|
||||
\tconst bindingRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), 'binding');
|
||||
|
||||
\ttry {
|
||||
\t\tfor (const dir of fs.readdirSync(bindingRoot)) {
|
||||
\t\t\tconst bindingPath = path.join(bindingRoot, dir, 'node-get-windows.node');
|
||||
\t\t\tif (fs.existsSync(bindingPath)) {
|
||||
\t\t\t\treturn require(bindingPath);
|
||||
\t\t\t}
|
||||
\t\t}
|
||||
\t} catch {}
|
||||
|
||||
\treturn {
|
||||
\t\tgetActiveWindow() {},
|
||||
\t\tgetOpenWindows() {},
|
||||
\t};
|
||||
};
|
||||
|
||||
export async function activeWindow() {
|
||||
\treturn getAddon().getActiveWindow();
|
||||
}
|
||||
|
||||
export function activeWindowSync() {
|
||||
\treturn getAddon().getActiveWindow();
|
||||
}
|
||||
|
||||
export function openWindows() {
|
||||
\treturn getAddon().getOpenWindows();
|
||||
}
|
||||
|
||||
export function openWindowsSync() {
|
||||
\treturn getAddon().getOpenWindows();
|
||||
}
|
||||
`
|
||||
|
||||
function resolveGetWindowsRoot() {
|
||||
// get-windows' exports map doesn't expose ./package.json; resolve the entry
|
||||
// (index.js sits at the package root) and take its directory.
|
||||
const entryPath = require.resolve('get-windows', {
|
||||
paths: [projectRoot]
|
||||
})
|
||||
return dirname(entryPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage get-windows into `destRoot` for `platform`.
|
||||
*
|
||||
* Per-platform native payload: macOS ships the `main` Swift helper binary
|
||||
* (universal, present in every published tarball), Windows the node-pre-gyp
|
||||
* prebuilt under lib/binding (downloaded by the package's install script on
|
||||
* a Windows host — cross-platform packs already can't happen, see
|
||||
* stageNodePtyInto), Linux nothing (it shells out to xprop at runtime).
|
||||
*/
|
||||
export function stageGetWindowsInto(srcRoot, destRoot, { platform = process.platform } = {}) {
|
||||
rmSync(destRoot, { recursive: true, force: true })
|
||||
mkdirSync(destRoot, { recursive: true })
|
||||
|
||||
cpSync(join(srcRoot, 'package.json'), join(destRoot, 'package.json'))
|
||||
cpSync(join(srcRoot, 'index.js'), join(destRoot, 'index.js'))
|
||||
copyGlobByExt(join(srcRoot, 'lib'), join(destRoot, 'lib'), ['.js'])
|
||||
|
||||
writeFileSync(join(destRoot, 'lib', 'windows.js'), STAGED_WINDOWS_JS)
|
||||
|
||||
if (platform === 'darwin') {
|
||||
const helper = join(srcRoot, 'main')
|
||||
if (!existsSync(helper)) {
|
||||
throw new Error('[stage-native-deps] get-windows is missing its macOS helper binary (main)')
|
||||
}
|
||||
cpSync(helper, join(destRoot, 'main'))
|
||||
makeExecutable(join(destRoot, 'main'))
|
||||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
const bindingRoot = join(srcRoot, 'lib', 'binding')
|
||||
const bindingDirs = existsSync(bindingRoot)
|
||||
? readdirSync(bindingRoot).filter((dir) =>
|
||||
existsSync(join(bindingRoot, dir, 'node-get-windows.node'))
|
||||
)
|
||||
: []
|
||||
if (bindingDirs.length === 0) {
|
||||
throw new Error(
|
||||
'[stage-native-deps] get-windows has no win32 prebuilt binding under lib/binding; ' +
|
||||
'reinstall dependencies on the Windows build host.'
|
||||
)
|
||||
}
|
||||
for (const dir of bindingDirs) {
|
||||
const dest = join(destRoot, 'lib', 'binding', dir)
|
||||
mkdirSync(dest, { recursive: true })
|
||||
const destFile = join(dest, 'node-get-windows.node')
|
||||
cpSync(join(bindingRoot, dir, 'node-get-windows.node'), destFile)
|
||||
const classified = classifyNativeBinary(destFile)
|
||||
if (classified !== platform) {
|
||||
throw new Error(
|
||||
`[stage-native-deps] get-windows binding ${dir}/node-get-windows.node: ` +
|
||||
`expected ${platform}, got ${classified ?? 'unknown'}. ` +
|
||||
'Refusing to stage a binary compiled for the wrong platform.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[stage-native-deps] staged get-windows (${platform}) -> ${destRoot}`)
|
||||
return destRoot
|
||||
}
|
||||
|
||||
export function stageGetWindows({ platform = process.platform } = {}) {
|
||||
const srcRoot = resolveGetWindowsRoot()
|
||||
const destRoot = resolve(projectRoot, 'dist/node_modules/get-windows')
|
||||
return stageGetWindowsInto(srcRoot, destRoot, { platform })
|
||||
}
|
||||
|
||||
// Allow direct CLI invocation: node scripts/stage-native-deps.mjs [platform] [arch]
|
||||
if (isMain(import.meta.url)) {
|
||||
const [platform, arch] = process.argv.slice(2)
|
||||
stageNodePty({ platform, arch })
|
||||
stageGetWindows({ platform })
|
||||
}
|
||||
|
||||
Generated
+1675
-31
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user