Skip to content
Open
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
6 changes: 3 additions & 3 deletions notifiers/balloon.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ Usage
4 = Closed or faded out

*/
const path = require( 'path' );
const notifier = path.resolve( __dirname, '../vendor/notifu/notifu' );
const notifier = require.resolve( '../vendor/notifu/notifu.exe' ); // Require resolve will use relative pathing correctly
const checkGrowl = require( '../lib/checkGrowl' );
const utils = require( '../lib/utils' );
const Toaster = require( './toaster' );
Expand Down Expand Up @@ -126,7 +125,8 @@ function doNotification( options, notifierOptions, callback )
options = utils.mapToNotifu( options );
options.p = options.p || 'Example Notification:';

const fullNotifierPath = notifier + ( is64Bit ? '64' : '' ) + '.exe';
// Strip .exe extension from resolved path before adding 64-bit suffix, then re-add .exe
const fullNotifierPath = notifier.replace( /\.exe$/i, '' ) + ( is64Bit ? '64' : '' ) + '.exe';
const localNotifier = notifierOptions.customPath || fullNotifierPath;

// console.log('Loading ' + fullNotifierPath);
Expand Down
3 changes: 1 addition & 2 deletions notifiers/notificationcenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
*/
const utils = require( '../lib/utils' );
const Growl = require( './growl' );
const path = require( 'path' );
const notifier = path.join( __dirname, '../vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier' );
const notifier = require.resolve( '../vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier' ); // Require resolve will use relative pathing correctly

const EventEmitter = require( 'events' ).EventEmitter;
const util = require( 'util' );
Expand Down
6 changes: 3 additions & 3 deletions notifiers/toaster.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
Wrapper for the toaster (https://github.com/nels-o/toaster)
*/

const path = require( 'path' );
const notifier = path.resolve( __dirname, '../vendor/ntfyToast/ntfytoast' );
const notifier = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' ); // Require resolve will use relative pathing correctly
const utils = require( '../lib/utils' );
const Balloon = require( './balloon' );
const crypto = require( 'crypto' );
Expand Down Expand Up @@ -141,7 +140,8 @@ function notifyRaw( options, callback )
resultBuffer = out;
options.pipeName = server.namedPipe;

const localNotifier = options.customPath || this.options.customPath || notifier + '.exe';
// No need to append .exe since require.resolve() already includes the full extension
const localNotifier = options.customPath || this.options.customPath || notifier;
// options.customPath || this.options.customPath || notifier + '-x' + (is64Bit ? '64' : '86') + '.exe';

options = utils.mapToWin8( options );
Expand Down
186 changes: 186 additions & 0 deletions test/module-resolution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/**
* Tests for module path resolution using require.resolve()
* These tests verify that vendor binaries are correctly resolved
* regardless of how the module is installed or bundled.
*/

const path = require( 'path' );
const fs = require( 'fs' );

describe( 'Module Path Resolution', () =>
{
describe( 'Vendor binary paths', () =>
{
it( 'should resolve Windows Toast (ntfytoast.exe) path correctly', () =>
{
const toasterPath = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' );

expect( toasterPath ).toBeTruthy();
expect( toasterPath ).toContain( 'ntfytoast.exe' );
expect( path.isAbsolute( toasterPath ) ).toBeTruthy();
});

it( 'should resolve Windows Balloon (notifu.exe) path correctly', () =>
{
const notifuPath = require.resolve( '../vendor/notifu/notifu.exe' );

expect( notifuPath ).toBeTruthy();
expect( notifuPath ).toContain( 'notifu.exe' );
expect( path.isAbsolute( notifuPath ) ).toBeTruthy();
});

it( 'should resolve macOS terminal-notifier path correctly', () =>
{
const terminalNotifierPath = require.resolve( '../vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier' );

expect( terminalNotifierPath ).toBeTruthy();
expect( terminalNotifierPath ).toContain( 'terminal-notifier' );
expect( path.isAbsolute( terminalNotifierPath ) ).toBeTruthy();
});
});

describe( 'Path resolution consistency', () =>
{
it( 'should resolve paths identically when called multiple times', () =>
{
const path1 = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' );
const path2 = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' );

expect( path1 ).toBe( path2 );
});

it( 'should resolve to actual file system locations', () =>
{
const toasterPath = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' );
const notifuPath = require.resolve( '../vendor/notifu/notifu.exe' );
const terminalNotifierPath = require.resolve( '../vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier' );

// At least one should exist depending on platform
const paths = [ toasterPath, notifuPath, terminalNotifierPath ];
const existingPaths = paths.filter( ( p ) => fs.existsSync( p ) );

expect( existingPaths.length ).toBeGreaterThan( 0 );
});
});

describe( 'Bundler/Webpack compatibility', () =>
{
it( 'should resolve without relying on __dirname', () =>
{
// Simulate webpack/bundler environment where __dirname might be altered
const originalDirname = __dirname;

// require.resolve should still work correctly
const toasterPath = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' );

expect( toasterPath ).toBeTruthy();
expect( path.isAbsolute( toasterPath ) ).toBeTruthy();

// Verify __dirname is still intact (we didn't actually modify it)
expect( __dirname ).toBe( originalDirname );
});

it( 'should resolve relative to module location, not caller location', () =>
{
// require.resolve() is relative to this file's location
const resolvedPath = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' );

// The resolved path should contain the vendor directory
expect( resolvedPath ).toContain( 'vendor' );
expect( resolvedPath ).toContain( 'ntfyToast' );
});

it( 'should handle symlinked node_modules correctly', () =>
{
// require.resolve follows symlinks and resolves to real paths
const toasterPath = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' );
const realPath = fs.realpathSync( toasterPath );

// Both should be absolute paths
expect( path.isAbsolute( toasterPath ) ).toBeTruthy();
expect( path.isAbsolute( realPath ) ).toBeTruthy();
});
});

describe( 'Notifier implementations use correct paths', () =>
{
it( 'should have Toaster using require.resolve()', () =>
{
// Read the toaster.js file to verify it uses require.resolve
const toasterSource = fs.readFileSync(
path.join( __dirname, '../notifiers/toaster.js' ),
'utf8'
);

expect( toasterSource ).toContain( 'require.resolve' );
expect( toasterSource ).toContain( '../vendor/ntfyToast/ntfytoast.exe' );
});

it( 'should have Balloon using require.resolve()', () =>
{
const balloonSource = fs.readFileSync(
path.join( __dirname, '../notifiers/balloon.js' ),
'utf8'
);

expect( balloonSource ).toContain( 'require.resolve' );
expect( balloonSource ).toContain( '../vendor/notifu/notifu.exe' );
});

it( 'should have NotificationCenter using require.resolve()', () =>
{
const ncSource = fs.readFileSync(
path.join( __dirname, '../notifiers/notificationcenter.js' ),
'utf8'
);

expect( ncSource ).toContain( 'require.resolve' );
expect( ncSource ).toContain( 'terminal-notifier' );
});

it( 'should not use path.resolve() with __dirname for vendor binaries', () =>
{
const toasterSource = fs.readFileSync(
path.join( __dirname, '../notifiers/toaster.js' ),
'utf8'
);
const balloonSource = fs.readFileSync(
path.join( __dirname, '../notifiers/balloon.js' ),
'utf8'
);
const ncSource = fs.readFileSync(
path.join( __dirname, '../notifiers/notificationcenter.js' ),
'utf8'
);

// Check that old pattern (path.resolve(__dirname, '../vendor/...')) is not present
const oldPattern = /path\.(resolve|join)\s*\(\s*__dirname.*vendor/;

expect( oldPattern.test( toasterSource ) ).toBeFalsy();
expect( oldPattern.test( balloonSource ) ).toBeFalsy();
expect( oldPattern.test( ncSource ) ).toBeFalsy();
});
});

describe( 'Extension handling', () =>
{
it( 'should include .exe extension in resolved Windows paths', () =>
{
const toasterPath = require.resolve( '../vendor/ntfyToast/ntfytoast.exe' );
const notifuPath = require.resolve( '../vendor/notifu/notifu.exe' );

expect( path.extname( toasterPath ) ).toBe( '.exe' );
expect( path.extname( notifuPath ) ).toBe( '.exe' );
});

it( 'should handle paths with multiple dots correctly', () =>
{
const terminalNotifierPath = require.resolve(
'../vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier'
);

expect( terminalNotifierPath ).toContain( 'mac.noindex' );
expect( terminalNotifierPath ).toContain( '.app' );
});
});
});
Loading