diff --git a/src/unixTerminal.test.ts b/src/unixTerminal.test.ts index a666e91a..c6378008 100644 --- a/src/unixTerminal.test.ts +++ b/src/unixTerminal.test.ts @@ -18,7 +18,37 @@ const FIXTURES_PATH = path.normalize(path.join(__dirname, '..', 'fixtures', 'utf if (process.platform !== 'win32') { // Dynamic require to avoid loading pty.node on Windows // eslint-disable-next-line @typescript-eslint/naming-convention - const { UnixTerminal } = require('./unixTerminal') as { UnixTerminal: typeof UnixTerminalType }; + const { UnixTerminal, resolveSpawnHelperPath } = require('./unixTerminal') as { UnixTerminal: typeof UnixTerminalType; resolveSpawnHelperPath: (nativeDir: string, moduleDir: string) => string }; + + describe('resolveSpawnHelperPath', () => { + it('rewrites a packed asar path to its unpacked form', () => { + const helperPath = resolveSpawnHelperPath( + '/Example.app/Contents/Resources/app.asar/node_modules/node-pty/build/Release', + '/Example.app/Contents/Resources/app.asar/node_modules/node-pty/lib'); + assert.strictEqual(helperPath, '/Example.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/build/Release/spawn-helper'); + }); + + it('does not double-rewrite an already-unpacked asar path', () => { + const helperPath = resolveSpawnHelperPath( + '/Example.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/build/Release', + '/Example.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/lib'); + assert.strictEqual(helperPath, '/Example.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/build/Release/spawn-helper'); + }); + + it('rewrites a packed node_modules.asar path to its unpacked form', () => { + const helperPath = resolveSpawnHelperPath( + '/app/node_modules.asar/node-pty/build/Release', + '/app/node_modules.asar/node-pty/lib'); + assert.strictEqual(helperPath, '/app/node_modules.asar.unpacked/node-pty/build/Release/spawn-helper'); + }); + + it('does not double-rewrite an already-unpacked node_modules.asar path', () => { + const helperPath = resolveSpawnHelperPath( + '/app/node_modules.asar.unpacked/node-pty/build/Release', + '/app/node_modules.asar.unpacked/node-pty/lib'); + assert.strictEqual(helperPath, '/app/node_modules.asar.unpacked/node-pty/build/Release/spawn-helper'); + }); + }); describe('UnixTerminal', () => { describe('Constructor', () => { diff --git a/src/unixTerminal.ts b/src/unixTerminal.ts index 2776d501..44d202b8 100644 --- a/src/unixTerminal.ts +++ b/src/unixTerminal.ts @@ -14,10 +14,32 @@ import { assign, loadNativeModule } from './utils'; const native = loadNativeModule('pty'); const pty: IUnixNative = native.module; -let helperPath = native.dir + '/spawn-helper'; -helperPath = path.resolve(__dirname, helperPath); -helperPath = helperPath.replace('app.asar', 'app.asar.unpacked'); -helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked'); + +/** + * Resolve the absolute path of the platform spawn helper binary. + * + * @param nativeDir - the directory that holds the compiled native module. + * @param moduleDir - the directory of this module (resolves relative dirs). + * @returns the absolute spawn-helper path. + */ +export function resolveSpawnHelperPath(nativeDir: string, moduleDir: string): string { + let helperPath = path.resolve(moduleDir, nativeDir + '/spawn-helper'); + // String.prototype.replace matches the first occurrence, so when helperPath + // already contains 'app.asar.unpacked' (e.g. node-pty is installed under an + // unpacked asar directory) the substring 'app.asar' matches the prefix of + // 'app.asar.unpacked' and produces 'app.asar.unpacked.unpacked' — a path + // that does not exist on disk. Skip each rewrite when the unpacked variant + // is already present. + if (helperPath.indexOf('app.asar.unpacked') === -1) { + helperPath = helperPath.replace('app.asar', 'app.asar.unpacked'); + } + if (helperPath.indexOf('node_modules.asar.unpacked') === -1) { + helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked'); + } + return helperPath; +} + +const helperPath = resolveSpawnHelperPath(native.dir, __dirname); const DEFAULT_FILE = 'sh'; const DEFAULT_NAME = 'xterm';