@@ -60,6 +60,7 @@ final class BinaryAssetManager
6060 * source: 'github'|'npm'|'github-zip',
6161 * repo?: string,
6262 * npm_package?: string,
63+ * npm_dist_tag?: string,
6364 * assets: array<int,array{pattern:string,target:string,executable:bool,extract_path?:string}>,
6465 * target_dir: string,
6566 * version_in_path?: bool,
@@ -289,8 +290,19 @@ private function downloadNpmAsset(
289290 ): void {
290291 $ assetConfig = $ this ->toolConfig ['assets ' ][0 ];
291292 $ platform = self ::detectPlatform ();
292- $ npmPackage = sprintf ('@esbuild/%s ' , $ platform );
293- $ url = sprintf ('https://registry.npmjs.org/%s/-/%s-%s.tgz ' , $ npmPackage , $ platform , $ version );
293+
294+ // Use the actual npm package from configuration instead of hardcoded @esbuild
295+ $ npmPackage = $ this ->toolConfig ['npm_package ' ];
296+
297+ // For scoped packages (@scope/package), use the correct URL format
298+ if (str_starts_with ($ npmPackage , '@ ' )) {
299+ $ scope = substr ($ npmPackage , 0 , strpos ($ npmPackage , '/ ' ));
300+ $ packageName = substr ($ npmPackage , strpos ($ npmPackage , '/ ' ) + 1 );
301+ $ url = sprintf ('https://registry.npmjs.org/%s/-/%s-%s.tgz ' , $ npmPackage , $ packageName , $ version );
302+ } else {
303+ // For regular packages, download the full package
304+ $ url = sprintf ('https://registry.npmjs.org/%s/-/%s-%s.tgz ' , $ npmPackage , $ npmPackage , $ version );
305+ }
294306
295307 $ context = stream_context_create ([
296308 'http ' => [
@@ -321,22 +333,34 @@ private function downloadNpmAsset(
321333 $ extractDir = $ tmpDir . '/extracted ' ;
322334 $ this ->ensureDirectory ($ extractDir );
323335
324- exec (sprintf ('tar -xzf %s -C %s %s 2>&1 ' , escapeshellarg ($ tgzPath ), escapeshellarg ($ extractDir ), escapeshellarg ($ extractPath )), $ output , $ returnCode );
336+ // Extract the entire tarball
337+ exec (sprintf ('tar -xzf %s -C %s 2>&1 ' , escapeshellarg ($ tgzPath ), escapeshellarg ($ extractDir )), $ output , $ returnCode );
325338
326339 if (0 !== $ returnCode ) {
327340 throw new RuntimeException (sprintf ('Failed to extract %s tarball: %s ' , $ this ->toolConfig ['name ' ], implode ("\n" , $ output )));
328341 }
329342
330- $ extractedBinary = $ extractDir . '/ ' . $ extractPath ;
331-
332- if (!is_file ($ extractedBinary )) {
333- throw new RuntimeException (sprintf ('Extracted binary not found at %s ' , $ extractedBinary ));
334- }
335-
343+ // Handle both file and directory extraction
344+ $ sourcePath = $ extractDir . '/ ' . $ extractPath ;
336345 $ targetPath = $ targetDir . '/ ' . $ assetConfig ['target ' ];
337346
338- if (!rename ($ extractedBinary , $ targetPath )) {
339- throw new RuntimeException (sprintf ('Failed to move %s binary to %s ' , $ this ->toolConfig ['name ' ], $ targetPath ));
347+ // If target is '.', copy the entire extracted directory
348+ if ('. ' === $ assetConfig ['target ' ]) {
349+ // Copy entire extracted directory contents to target
350+ exec (sprintf ('cp -r %s/* %s/ 2>&1 ' , escapeshellarg ($ sourcePath ), escapeshellarg ($ targetDir )), $ output , $ returnCode );
351+
352+ if (0 !== $ returnCode ) {
353+ throw new RuntimeException (sprintf ('Failed to copy %s package: %s ' , $ this ->toolConfig ['name ' ], implode ("\n" , $ output )));
354+ }
355+ } else {
356+ // Original behavior: move specific file
357+ if (!is_file ($ sourcePath )) {
358+ throw new RuntimeException (sprintf ('Extracted binary not found at %s ' , $ sourcePath ));
359+ }
360+
361+ if (!rename ($ sourcePath , $ targetPath )) {
362+ throw new RuntimeException (sprintf ('Failed to move %s binary to %s ' , $ this ->toolConfig ['name ' ], $ targetPath ));
363+ }
340364 }
341365
342366 if ($ assetConfig ['executable ' ]) {
@@ -410,7 +434,8 @@ private function fetchLatestCommit(): array
410434 */
411435 private function fetchLatestNpmVersion (): array
412436 {
413- $ packageUrl = sprintf ('https://registry.npmjs.org/%s/latest ' , $ this ->toolConfig ['npm_package ' ]);
437+ $ distTag = $ this ->toolConfig ['npm_dist_tag ' ] ?? 'latest ' ;
438+ $ packageUrl = sprintf ('https://registry.npmjs.org/%s/%s ' , $ this ->toolConfig ['npm_package ' ], $ distTag );
414439
415440 $ context = stream_context_create ([
416441 'http ' => [
0 commit comments