@@ -84,10 +84,41 @@ export async function buildStub(options = {}) {
8484 return 1
8585 }
8686
87- // Step 3: Create output directory
87+ // Step 3: Check and install required tools
88+ console . log ( '🔧 Checking build requirements...' )
89+
90+ // Check for essential tools on all platforms
91+ const essentialTools = await checkEssentialTools ( platform , arch , quiet )
92+ if ( ! essentialTools ) {
93+ console . error ( '❌ Failed to install essential build tools' )
94+ return 1
95+ }
96+
97+ // Additional checks for macOS
98+ if ( platform === 'darwin' ) {
99+ // Check for ldid on ARM64 (required for proper signing)
100+ if ( arch === 'arm64' ) {
101+ const ldidCheck = await checkAndInstallLdid ( quiet )
102+ if ( ldidCheck === 'not-found' ) {
103+ console . error ( '❌ Could not install ldid - binary will be malformed' )
104+ console . error ( ' The stub binary will not work properly on ARM64' )
105+ console . error ( ' Try installing manually: brew install ldid' )
106+ // Exit early - no point building a broken binary
107+ return 1
108+ } else if ( ldidCheck === 'newly-installed' ) {
109+ console . log ( '✅ ldid installed successfully' )
110+ } else {
111+ console . log ( '✅ ldid is available' )
112+ }
113+ }
114+ }
115+
116+ console . log ( '✅ All build requirements met\n' )
117+
118+ // Step 4: Create output directory
88119 await mkdir ( STUB_DIR , { recursive : true } )
89120
90- // Step 4 : Build with pkg
121+ // Step 5 : Build with pkg
91122 const target = getPkgTarget ( platform , arch , nodeVersion )
92123 const outputName = getOutputName ( platform , arch )
93124 const outputPath = join ( STUB_DIR , outputName )
@@ -211,16 +242,15 @@ export async function buildStub(options = {}) {
211242 return 1
212243 }
213244
214- // Step 5 : Sign with ldid on ARM64 macOS (fixes yao-pkg malformed binary issue )
245+ // Step 6 : Sign with ldid on ARM64 macOS (if needed )
215246 if ( platform === 'darwin' && arch === 'arm64' && existsSync ( outputPath ) ) {
216- console . log ( '🔏 Signing macOS ARM64 binary...' )
247+ console . log ( '🔏 Signing macOS ARM64 binary with ldid ...' )
217248 const signResult = await signMacOSBinaryWithLdid ( outputPath , quiet )
218249 if ( signResult === 'ldid-not-found' ) {
219- console . error ( '⚠️ Warning: Could not install or find ldid' )
220- console . error ( ' Binary may be malformed without ldid signing' )
221- console . error ( ' To fix manually: brew install ldid && ldid -S ./binaries/stub/socket-macos-arm64' )
250+ console . error ( '⚠️ Warning: ldid disappeared after install?' )
222251 } else if ( signResult !== 0 ) {
223252 console . error ( '⚠️ Warning: Failed to sign with ldid' )
253+ console . error ( ' The binary may be malformed' )
224254 } else {
225255 console . log ( '✅ Binary signed with ldid successfully\n' )
226256 }
@@ -257,12 +287,190 @@ export async function buildStub(options = {}) {
257287 return 0
258288}
259289
290+ /**
291+ * Check and install essential build tools
292+ */
293+ async function checkEssentialTools ( platform , arch , quiet = false ) {
294+ const tools = [ ]
295+
296+ // Git is essential for many operations
297+ tools . push ( { name : 'git' , installCmd : 'git' } )
298+
299+ // Platform-specific tools
300+ if ( platform === 'darwin' ) {
301+ // Xcode Command Line Tools provide essential build tools
302+ const hasXcodeTools = await checkCommand ( 'xcodebuild' , [ '-version' ] )
303+ if ( ! hasXcodeTools ) {
304+ console . log ( ' Xcode Command Line Tools not found, installing...' )
305+ // This will prompt for installation
306+ await new Promise ( ( resolve ) => {
307+ const child = spawn ( 'xcode-select' , [ '--install' ] , {
308+ stdio : 'inherit'
309+ } )
310+ child . on ( 'exit' , resolve )
311+ child . on ( 'error' , resolve )
312+ } )
313+ }
314+ } else if ( platform === 'linux' ) {
315+ // Linux needs build-essential
316+ tools . push ( { name : 'make' , installCmd : 'build-essential' } )
317+ tools . push ( { name : 'gcc' , installCmd : 'build-essential' } )
318+ } else if ( platform === 'win32' ) {
319+ // Windows needs Visual Studio Build Tools
320+ // These are harder to auto-install, so just check
321+ const hasMSBuild = await checkCommand ( 'msbuild' , [ '/version' ] )
322+ if ( ! hasMSBuild ) {
323+ console . error ( ' Visual Studio Build Tools not found' )
324+ console . error ( ' Please install from: https://visualstudio.microsoft.com/downloads/' )
325+ return false
326+ }
327+ }
328+
329+ // Check and install tools sequentially
330+ const installTool = async ( tool ) => {
331+ const hasCommand = await checkCommand ( tool . name )
332+ if ( ! hasCommand ) {
333+ console . log ( ` ${ tool . name } not found` )
334+ if ( platform === 'darwin' ) {
335+ // Try to install via Homebrew
336+ const installed = await installViaHomebrew ( tool . installCmd , quiet )
337+ if ( ! installed ) {
338+ console . error ( ` Failed to install ${ tool . name } ` )
339+ return false
340+ }
341+ } else if ( platform === 'linux' ) {
342+ console . error ( ` Please install ${ tool . installCmd } manually` )
343+ console . error ( ` Ubuntu/Debian: sudo apt-get install ${ tool . installCmd } ` )
344+ console . error ( ` RHEL/Fedora: sudo dnf install ${ tool . installCmd } ` )
345+ return false
346+ }
347+ }
348+ return true
349+ }
350+
351+ // Process tools sequentially (needed for dependency order)
352+ for ( const tool of tools ) {
353+ // eslint-disable-next-line no-await-in-loop
354+ const success = await installTool ( tool )
355+ if ( ! success ) {
356+ return false
357+ }
358+ }
359+
360+ return true
361+ }
362+
363+ /**
364+ * Check if a command exists
365+ */
366+ async function checkCommand ( command , args = [ '--version' ] ) {
367+ return new Promise ( ( resolve ) => {
368+ const child = spawn ( command , args , {
369+ stdio : 'pipe'
370+ } )
371+ child . on ( 'exit' , ( code ) => resolve ( code === 0 ) )
372+ child . on ( 'error' , ( ) => resolve ( false ) )
373+ } )
374+ }
375+
376+ /**
377+ * Install a package via Homebrew
378+ */
379+ async function installViaHomebrew ( packageName , quiet = false ) {
380+ // First ensure Homebrew is available
381+ let brewAvailable = await checkCommand ( 'brew' )
382+ if ( ! brewAvailable ) {
383+ console . log ( ' Installing Homebrew first...' )
384+ brewAvailable = await installHomebrew ( quiet )
385+ if ( ! brewAvailable ) {
386+ return false
387+ }
388+ }
389+
390+ // Install the package
391+ console . log ( ` Installing ${ packageName } via Homebrew...` )
392+ return new Promise ( ( resolve ) => {
393+ const child = spawn ( 'brew' , [ 'install' , packageName ] , {
394+ stdio : quiet ? 'pipe' : 'inherit'
395+ } )
396+ child . on ( 'exit' , ( code ) => resolve ( code === 0 ) )
397+ child . on ( 'error' , ( ) => resolve ( false ) )
398+ } )
399+ }
400+
401+ /**
402+ * Check for ldid and install if needed
403+ * @returns {Promise<'available'|'newly-installed'|'not-found'> }
404+ */
405+ async function checkAndInstallLdid ( quiet = false ) {
406+ // First check if ldid is already available
407+ const ldidAvailable = await new Promise ( ( resolve ) => {
408+ const child = spawn ( 'which' , [ 'ldid' ] , {
409+ stdio : 'pipe'
410+ } )
411+ child . on ( 'exit' , ( code ) => resolve ( code === 0 ) )
412+ child . on ( 'error' , ( ) => resolve ( false ) )
413+ } )
414+
415+ if ( ldidAvailable ) {
416+ return 'available'
417+ }
418+
419+ // Try to install ldid
420+ console . log ( ' ldid not found, auto-installing...' )
421+ const installed = await installLdidViaBrew ( quiet )
422+
423+ return installed ? 'newly-installed' : 'not-found'
424+ }
425+
426+ /**
427+ * Install Homebrew if not available
428+ */
429+ async function installHomebrew ( quiet = false ) {
430+ console . log ( ' Homebrew not found, installing...' )
431+ console . log ( ' This may take a few minutes...' )
432+
433+ // Download and run Homebrew installer
434+ return new Promise ( ( resolve ) => {
435+ const child = spawn ( '/bin/bash' , [ '-c' ,
436+ 'curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | /bin/bash'
437+ ] , {
438+ stdio : quiet ? 'pipe' : 'inherit' ,
439+ // Non-interactive install
440+ env : { ...process . env , NONINTERACTIVE : '1' }
441+ } )
442+
443+ child . on ( 'exit' , async ( code ) => {
444+ if ( code === 0 ) {
445+ // Add Homebrew to PATH for Apple Silicon Macs
446+ if ( process . arch === 'arm64' ) {
447+ process . env . PATH = `/opt/homebrew/bin:${ process . env . PATH } `
448+ } else {
449+ process . env . PATH = `/usr/local/bin:${ process . env . PATH } `
450+ }
451+ console . log ( ' Homebrew installed successfully' )
452+ resolve ( true )
453+ } else {
454+ console . error ( ' Failed to install Homebrew automatically' )
455+ console . error ( ' Please install manually from https://brew.sh' )
456+ resolve ( false )
457+ }
458+ } )
459+ child . on ( 'error' , ( error ) => {
460+ if ( ! quiet ) {
461+ console . error ( ' Error installing Homebrew:' , error . message )
462+ }
463+ resolve ( false )
464+ } )
465+ } )
466+ }
467+
260468/**
261469 * Install ldid using Homebrew
262470 */
263471async function installLdidViaBrew ( quiet = false ) {
264472 // First check if brew is available
265- const brewAvailable = await new Promise ( ( resolve ) => {
473+ let brewAvailable = await new Promise ( ( resolve ) => {
266474 const child = spawn ( 'which' , [ 'brew' ] , {
267475 stdio : 'pipe'
268476 } )
@@ -271,13 +479,11 @@ async function installLdidViaBrew(quiet = false) {
271479 } )
272480
273481 if ( ! brewAvailable ) {
274- if ( ! quiet ) {
275- console . error ( ' Homebrew not found, cannot auto-install ldid' )
276- console . error ( ' To install Homebrew, run:' )
277- console . error ( ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' )
278- console . error ( ' Then re-run this build command' )
482+ // Try to install Homebrew automatically
483+ brewAvailable = await installHomebrew ( quiet )
484+ if ( ! brewAvailable ) {
485+ return false
279486 }
280- return false
281487 }
282488
283489 // Install ldid using brew
@@ -310,7 +516,7 @@ async function installLdidViaBrew(quiet = false) {
310516 * Sign macOS ARM64 binary with ldid (fixes yao-pkg malformed binary issue)
311517 */
312518async function signMacOSBinaryWithLdid ( binaryPath , quiet = false ) {
313- // First check if ldid is available
519+ // Verify ldid is still available (should have been installed earlier)
314520 const ldidAvailable = await new Promise ( ( resolve ) => {
315521 const child = spawn ( 'which' , [ 'ldid' ] , {
316522 stdio : 'pipe'
@@ -320,13 +526,7 @@ async function signMacOSBinaryWithLdid(binaryPath, quiet = false) {
320526 } )
321527
322528 if ( ! ldidAvailable ) {
323- // Try to install ldid automatically
324- console . log ( ' ldid not found, attempting to install via Homebrew...' )
325- const brewInstalled = await installLdidViaBrew ( quiet )
326- if ( ! brewInstalled ) {
327- return 'ldid-not-found'
328- }
329- console . log ( ' ldid installed successfully' )
529+ return 'ldid-not-found'
330530 }
331531
332532 // Remove existing signature first (if any)
0 commit comments