2525 * - dlx-package.ts: Installs npm packages from registries
2626 *
2727 * Implementation:
28- * - Uses pacote for package installation (no npm CLI required)
28+ * - Uses Arborist for package installation (like npx, no npm CLI required)
2929 * - Split into downloadPackage() and executePackage() for flexibility
3030 * - dlxPackage() combines both for convenience
3131 */
3232
3333import { WIN32 } from '../constants/platform'
34- import { getPacoteCachePath } from '../constants/packages'
3534import { generateCacheKey } from './cache'
3635import Arborist from '../external/@npmcli/arborist'
3736import libnpmexec from '../external/libnpmexec'
3837import npmPackageArg from '../external/npm-package-arg'
39- import pacote from '../external/pacote'
4038import { readJsonSync , safeMkdir } from '../fs'
4139import { normalizePath } from '../paths/normalize'
42- import { getSocketDlxDir } from '../paths/socket'
40+ import { getSocketCacacheDir , getSocketDlxDir } from '../paths/socket'
4341import { processLock } from '../process-lock'
4442import type { SpawnExtra , SpawnOptions } from '../spawn'
4543import { spawn } from '../spawn'
@@ -261,24 +259,16 @@ async function ensurePackageInstalled(
261259 }
262260 }
263261
264- // Use pacote to extract the package .
265- // Pacote leverages npm cache when available but doesn't require npm CLI .
266- const pacoteCachePath = getPacoteCachePath ( )
262+ // Install package and dependencies using Arborist (like npx does) .
263+ // Arborist handles everything: fetching, extracting, dependency resolution, and bin links .
264+ // This creates the proper flat node_modules structure with .bin symlinks.
267265 try {
268- /* c8 ignore next 4 - External pacote call */
269- await pacote . extract ( packageSpec , installedDir , {
270- // Use consistent pacote cache path (respects npm cache locations when available).
271- cache : pacoteCachePath || path . join ( packageDir , '.cache' ) ,
272- } )
273-
274- // Install dependencies using Arborist.
275- // pacote.extract() only extracts the package tarball, it does NOT install dependencies.
276- // We must use Arborist to install dependencies after extraction.
277266 // Arborist is imported at the top
278267 /* c8 ignore next 3 - External Arborist constructor */
279268 const arb = new Arborist ( {
280- path : installedDir ,
281- cache : pacoteCachePath || path . join ( packageDir , '.cache' ) ,
269+ path : packageDir ,
270+ // Use Socket's shared cacache directory (~/.socket/_cacache).
271+ cache : getSocketCacacheDir ( ) ,
282272 // Skip devDependencies (production-only like npx).
283273 omit : [ 'dev' ] ,
284274 // Security: Skip install/preinstall/postinstall scripts to prevent arbitrary code execution.
@@ -293,9 +283,11 @@ async function ensurePackageInstalled(
293283 silent : true ,
294284 } )
295285
296- /* c8 ignore next 2 - External Arborist calls */
297- await arb . buildIdealTree ( )
298- await arb . reify ( { save : false } )
286+ // Use reify with 'add' to install the package and its dependencies in one step.
287+ // This matches npx's approach: arb.reify({ add: [packageSpec] })
288+ // save: true creates package.json and package-lock.json at the root (like npx).
289+ /* c8 ignore next - External Arborist call */
290+ await arb . reify ( { save : true , add : [ packageSpec ] } )
299291 } catch ( e ) {
300292 const code = ( e as any ) . code
301293 if ( code === 'E404' || code === 'ETARGET' ) {
0 commit comments