Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions apps/desktop/scripts/stage-native-deps.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,20 @@ function copyGlobByExt(srcDir, destDir, extensions) {
* lib/unixTerminal.js requires at a fixed relative path. Filtering this
* directory by ['.node'] silently drops it — the package then looks
* fine, ships fine, and crashes the first time a terminal is spawned.
* Directories are copied wholesale to also cover any nested native
* payload (e.g. a conpty/ subfolder some build layouts produce).
* Directories are copied wholesale except for Windows conpty payloads,
* which are walked file by file because Node's recursive cpSync can fail
* with EIO on that directory even when copying each file succeeds.
*/
function copyBuildRelease(srcDir, destDir) {
if (!existsSync(srcDir)) return
mkdirSync(destDir, { recursive: true })
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
if (entry.isDirectory()) {
cpSync(join(srcDir, entry.name), join(destDir, entry.name), { recursive: true })
if (entry.name === 'conpty') {
copyGlobByExt(join(srcDir, entry.name), join(destDir, entry.name), ['.dll', '.exe'])
} else {
cpSync(join(srcDir, entry.name), join(destDir, entry.name), { recursive: true })
}
continue
}
if (entry.name === 'spawn-helper' || /\.(node|dll|exe)$/.test(entry.name)) {
Expand Down Expand Up @@ -231,7 +236,7 @@ export function stageNodePtyInto(srcRoot, destRoot, { platform = process.platfor
mkdirSync(destPrebuild, { recursive: true })
for (const entry of readdirSync(prebuildDir, { withFileTypes: true })) {
if (entry.name === 'conpty' && entry.isDirectory()) {
cpSync(join(prebuildDir, 'conpty'), join(destPrebuild, 'conpty'), { recursive: true })
copyGlobByExt(join(prebuildDir, 'conpty'), join(destPrebuild, 'conpty'), ['.dll', '.exe'])
continue
}
if (entry.isFile() && /\.(node|dll|exe)$/.test(entry.name)) {
Expand Down
46 changes: 46 additions & 0 deletions apps/desktop/scripts/stage-native-deps.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,28 @@ test('cross-target: matching prebuild IS staged for a foreign target', () => {
}
})

test('win32 prebuild: complete conpty payload is staged', () => {
const tmp = fs.mkdtempSync(join(os.tmpdir(), 'hermes-stage-'))
try {
const srcRoot = join(tmp, 'node-pty')
const destRoot = join(tmp, 'dest')

makeFakeNodePty(srcRoot, { prebuildPlatform: 'win32', prebuildArch: 'x64' })
const conptyDir = join(srcRoot, 'prebuilds', 'win32-x64', 'conpty')
fs.mkdirSync(conptyDir, { recursive: true })
fs.writeFileSync(join(conptyDir, 'conpty.dll'), 'conpty-dll')
fs.writeFileSync(join(conptyDir, 'OpenConsole.exe'), 'open-console')

stageNodePtyInto(srcRoot, destRoot, { platform: 'win32', arch: 'x64' })

const stagedConpty = join(destRoot, 'prebuilds', 'win32-x64', 'conpty')
assert.equal(fs.readFileSync(join(stagedConpty, 'conpty.dll'), 'utf8'), 'conpty-dll')
assert.equal(fs.readFileSync(join(stagedConpty, 'OpenConsole.exe'), 'utf8'), 'open-console')
} finally {
fs.rmSync(tmp, { recursive: true, force: true })
}
})

test('cross-target: foreign target with no prebuild throws (fail closed)', () => {
const tmp = fs.mkdtempSync(join(os.tmpdir(), 'hermes-stage-'))
try {
Expand Down Expand Up @@ -262,6 +284,30 @@ test('host-target: host build/Release IS staged for a matching target', () => {
}
})

test('host-target: nested conpty build payload is staged', () => {
const tmp = fs.mkdtempSync(join(os.tmpdir(), 'hermes-stage-'))
try {
const srcRoot = join(tmp, 'node-pty')
const destRoot = join(tmp, 'dest')

makeFakeNodePty(srcRoot)
const buildReleaseDir = join(srcRoot, 'build', 'Release')
makeFakeNode(join(buildReleaseDir, 'pty.node'), process.platform)
const conptyDir = join(buildReleaseDir, 'conpty')
fs.mkdirSync(conptyDir, { recursive: true })
fs.writeFileSync(join(conptyDir, 'conpty.dll'), 'conpty-dll')
fs.writeFileSync(join(conptyDir, 'OpenConsole.exe'), 'open-console')

stageNodePtyInto(srcRoot, destRoot, { platform: process.platform, arch: process.arch })

const stagedConpty = join(destRoot, 'build', 'Release', 'conpty')
assert.equal(fs.readFileSync(join(stagedConpty, 'conpty.dll'), 'utf8'), 'conpty-dll')
assert.equal(fs.readFileSync(join(stagedConpty, 'OpenConsole.exe'), 'utf8'), 'open-console')
} finally {
fs.rmSync(tmp, { recursive: true, force: true })
}
})

test('validation rejects a staged binary with the wrong platform magic', () => {
const tmp = fs.mkdtempSync(join(os.tmpdir(), 'hermes-stage-'))
try {
Expand Down