@@ -14,15 +14,39 @@ import { readPackageJson } from '@socketsecurity/lib/packages/operations'
1414import { pEach } from '@socketsecurity/lib/promises'
1515import { withSpinner } from '@socketsecurity/lib/spinner'
1616
17+ import { whichSync } from '@socketsecurity/lib/bin'
18+
1719import { cleanTestScript } from '../../test/utils/script-cleaning.mjs'
1820import { testRunners } from '../../test/utils/test-runners.mjs'
1921import { DEFAULT_CONCURRENCY } from '../constants/core.mjs'
2022import { ROOT_PATH } from '../constants/paths.mjs'
2123import { spawn } from './spawn.mjs'
2224import process from 'node:process'
2325
26+ // Resolve real pnpm binary, bypassing SFW shim.
27+ // SFW shims intercept pnpm and proxy registry requests, stripping metadata
28+ // fields (e.g. "time") which causes pnpm v11 ERR_PNPM_MISSING_TIME failures.
29+ // Strip the SFW shim dir from PATH to find the real binary.
30+ function getRealPnpmBin ( ) {
31+ const sfwShimDir = process . env [ 'SFW_SHIM_DIR' ]
32+ if ( sfwShimDir ) {
33+ const cleanPath = ( process . env [ 'PATH' ] ?? '' )
34+ . split ( path . delimiter )
35+ . filter ( p => p !== sfwShimDir )
36+ . join ( path . delimiter )
37+ const real = whichSync ( 'pnpm' , { nothrow : true , path : cleanPath } )
38+ if ( real && typeof real === 'string' ) {
39+ return real
40+ }
41+ }
42+ return 'pnpm'
43+ }
44+
45+ /** Real pnpm binary path (bypasses SFW shim if present). */
46+ export const PNPM_REAL_BIN = getRealPnpmBin ( )
47+
2448// Shared pnpm flags to make it behave like npm with hoisting.
25- const PNPM_NPM_LIKE_FLAGS = [
49+ export const PNPM_NPM_LIKE_FLAGS = [
2650 '--config.shamefully-hoist=true' ,
2751 '--config.node-linker=hoisted' ,
2852 '--config.auto-install-peers=false' ,
@@ -31,7 +55,7 @@ const PNPM_NPM_LIKE_FLAGS = [
3155
3256// Basic pnpm install flags for CI-friendly behavior.
3357// These are for isolated test installs of third-party packages we don't control.
34- const PNPM_INSTALL_BASE_FLAGS = [
58+ export const PNPM_INSTALL_BASE_FLAGS = [
3559 // Allow git-resolved subdeps in third-party packages (e.g. evalmd → markdown-it).
3660 '--config.block-exotic-subdeps=false' ,
3761 // Prevent interactive prompts in CI environments.
@@ -47,25 +71,25 @@ const PNPM_INSTALL_BASE_FLAGS = [
4771]
4872
4973// Pnpm install flags with hoisting for npm-like behavior.
50- const PNPM_HOISTED_INSTALL_FLAGS = [
74+ export const PNPM_HOISTED_INSTALL_FLAGS = [
5175 ...PNPM_NPM_LIKE_FLAGS ,
5276 ...PNPM_INSTALL_BASE_FLAGS ,
5377]
5478
5579// Environment override to force pnpm to install devDependencies.
5680// By default, pnpm skips devDependencies when CI or NODE_ENV=production is detected.
57- const PNPM_INSTALL_ENV = { CI : undefined , NODE_ENV : undefined }
81+ export const PNPM_INSTALL_ENV = { CI : undefined , NODE_ENV : undefined }
5882
5983/**
6084 * Reads and caches editable package.json files to avoid redundant disk I/O.
6185 * @type {Map<string, any> }
6286 */
63- const editablePackageJsonCache = new Map ( )
87+ export const editablePackageJsonCache = new Map ( )
6488
6589/**
6690 * Reads an editable package.json with caching support.
6791 */
68- async function readCachedEditablePackageJson ( pkgPath , options = { } ) {
92+ export async function readCachedEditablePackageJson ( pkgPath , options = { } ) {
6993 const cacheKey = pkgPath
7094
7195 if ( ! editablePackageJsonCache . has ( cacheKey ) ) {
@@ -83,14 +107,14 @@ async function readCachedEditablePackageJson(pkgPath, options = {}) {
83107/**
84108 * Clears the editable package.json cache.
85109 */
86- function clearPackageJsonCache ( ) {
110+ export function clearPackageJsonCache ( ) {
87111 editablePackageJsonCache . clear ( )
88112}
89113
90114/**
91115 * Updates multiple package.json files in parallel.
92116 */
93- async function updatePackagesJson ( packages , options = { } ) {
117+ export async function updatePackagesJson ( packages , options = { } ) {
94118 const { concurrency = DEFAULT_CONCURRENCY , spinner } = options
95119
96120 await pEach (
@@ -111,7 +135,7 @@ async function updatePackagesJson(packages, options = {}) {
111135/**
112136 * Collects package.json data from multiple packages.
113137 */
114- async function collectPackageData ( paths , options = { } ) {
138+ export async function collectPackageData ( paths , options = { } ) {
115139 const {
116140 concurrency = DEFAULT_CONCURRENCY ,
117141 fields = [ 'name' , 'version' , 'description' ] ,
@@ -142,7 +166,7 @@ async function collectPackageData(paths, options = {}) {
142166/**
143167 * Common patterns for processing packages with spinner feedback.
144168 */
145- async function processWithSpinner ( items , processor , options = { } ) {
169+ export async function processWithSpinner ( items , processor , options = { } ) {
146170 const {
147171 concurrency = DEFAULT_CONCURRENCY ,
148172 errorMessage,
@@ -195,7 +219,7 @@ async function processWithSpinner(items, processor, options = {}) {
195219/**
196220 * Resolves the real path of a file or directory, handling symlinks.
197221 */
198- async function resolveRealPath ( pathStr ) {
222+ export async function resolveRealPath ( pathStr ) {
199223 try {
200224 return await fs . realpath ( pathStr )
201225 } catch {
@@ -206,7 +230,7 @@ async function resolveRealPath(pathStr) {
206230/**
207231 * Computes a hash of override package dependencies for cache validation.
208232 */
209- async function computeOverrideHash ( overridePath ) {
233+ export async function computeOverrideHash ( overridePath ) {
210234 try {
211235 const pkgJsonPath = path . join ( overridePath , 'package.json' )
212236 const pkgJson = await readPackageJson ( pkgJsonPath )
@@ -224,7 +248,7 @@ async function computeOverrideHash(overridePath) {
224248/**
225249 * Copies Socket override files to a package directory.
226250 */
227- async function copySocketOverride ( fromPath , toPath , options ) {
251+ export async function copySocketOverride ( fromPath , toPath , options ) {
228252 const opts = { __proto__ : null , ...options }
229253 const { excludePackageJson = true } = opts
230254
@@ -263,7 +287,7 @@ async function copySocketOverride(fromPath, toPath, options) {
263287/**
264288 * Builds test environment with proper PATH for test runners.
265289 */
266- function buildTestEnv ( packageTempDir , installedPath ) {
290+ export function buildTestEnv ( packageTempDir , installedPath ) {
267291 const packageBinPath = path . join ( packageTempDir , 'node_modules' , '.bin' )
268292 const nestedBinPath = path . join ( installedPath , 'node_modules' , '.bin' )
269293 const rootBinPath = path . join ( ROOT_PATH , 'node_modules' , '.bin' )
@@ -276,7 +300,7 @@ function buildTestEnv(packageTempDir, installedPath) {
276300/**
277301 * Run a command with spawn.
278302 */
279- async function runCommand ( command , args , options = { } ) {
303+ export async function runCommand ( command , args , options = { } ) {
280304 try {
281305 const result = await spawn ( command , args , {
282306 stdio : 'pipe' ,
@@ -304,7 +328,7 @@ async function runCommand(command, args, options = {}) {
304328 * @param {string } [options.versionSpec] - Version or URL to install (optional, for npm packages)
305329 * @returns {Promise<{installed: boolean, packagePath?: string, reason?: string}> }
306330 */
307- async function installPackageForTesting ( sourcePath , packageName , options = { } ) {
331+ export async function installPackageForTesting ( sourcePath , packageName , options = { } ) {
308332 const { versionSpec } = options
309333
310334 if ( ! existsSync ( sourcePath ) ) {
@@ -484,21 +508,3 @@ async function installPackageForTesting(sourcePath, packageName, options = {}) {
484508 }
485509}
486510
487- export {
488- buildTestEnv ,
489- clearPackageJsonCache ,
490- collectPackageData ,
491- computeOverrideHash ,
492- copySocketOverride ,
493- editablePackageJsonCache ,
494- installPackageForTesting ,
495- PNPM_HOISTED_INSTALL_FLAGS ,
496- PNPM_INSTALL_BASE_FLAGS ,
497- PNPM_INSTALL_ENV ,
498- PNPM_NPM_LIKE_FLAGS ,
499- processWithSpinner ,
500- readCachedEditablePackageJson ,
501- resolveRealPath ,
502- runCommand ,
503- updatePackagesJson ,
504- }
0 commit comments