@@ -8,7 +8,6 @@ import which from 'which'
88
99import { parse as parseBunLockb } from '@socketregistry/hyrious__bun.lockb/index.cjs'
1010import { Logger } from '@socketsecurity/registry/lib/logger'
11- import { isObjectObject } from '@socketsecurity/registry/lib/objects'
1211import { readPackageJson } from '@socketsecurity/registry/lib/packages'
1312import { naturalCompare } from '@socketsecurity/registry/lib/sorts'
1413import { spawn } from '@socketsecurity/registry/lib/spawn'
@@ -41,18 +40,17 @@ export const AGENTS = [BUN, NPM, PNPM, YARN_BERRY, YARN_CLASSIC, VLT] as const
4140export type Agent = ( typeof AGENTS ) [ number ]
4241export type StringKeyValueObject = { [ key : string ] : string }
4342
44- const binByAgent = {
45- __proto__ : null ,
46- [ BUN ] : BUN ,
47- [ NPM ] : NPM ,
48- [ PNPM ] : PNPM ,
49- [ YARN_BERRY ] : YARN ,
50- [ YARN_CLASSIC ] : YARN ,
51- [ VLT ] : VLT
52- }
43+ const binByAgent = new Map < Agent , string > ( [
44+ [ BUN , BUN ] ,
45+ [ NPM , NPM ] ,
46+ [ PNPM , PNPM ] ,
47+ [ YARN_BERRY , YARN ] ,
48+ [ YARN_CLASSIC , YARN ] ,
49+ [ VLT , VLT ]
50+ ] )
5351
5452async function getAgentExecPath ( agent : Agent ) : Promise < string > {
55- const binName = binByAgent [ agent ]
53+ const binName = binByAgent . get ( agent ) !
5654 return ( await which ( binName , { nothrow : true } ) ) ?? binName
5755}
5856
@@ -63,6 +61,9 @@ async function getAgentVersion(
6361 let result
6462 try {
6563 result =
64+ // Coerce version output into a valid semver version by passing it through
65+ // semver.coerce which strips leading v's, carets (^), comparators (<,<=,>,>=,=),
66+ // and tildes (~).
6667 semver . coerce (
6768 // All package managers support the "--version" flag.
6869 ( await spawn ( agentExecPath , [ '--version' ] , { cwd } ) ) . stdout
@@ -97,7 +98,7 @@ type ReadLockFile =
9798 | ( ( lockPath : string ) => Promise < string | undefined > )
9899 | ( ( lockPath : string , agentExecPath : string ) => Promise < string | undefined > )
99100
100- const readLockFileByAgent : Record < Agent , ReadLockFile > = ( ( ) => {
101+ const readLockFileByAgent : Map < Agent , ReadLockFile > = ( ( ) => {
101102 function wrapReader < T extends ( ...args : any [ ] ) => Promise < any > > (
102103 reader : T
103104 ) : ( ...args : Parameters < T > ) => Promise < Awaited < ReturnType < T > > | undefined > {
@@ -115,32 +116,35 @@ const readLockFileByAgent: Record<Agent, ReadLockFile> = (() => {
115116 async ( lockPath : string ) => await readFileUtf8 ( lockPath )
116117 )
117118
118- return {
119- [ BUN ] : wrapReader ( async ( lockPath : string , agentExecPath : string ) => {
120- const ext = path . extname ( lockPath )
121- if ( ext === LOCK_EXT ) {
122- return await defaultReader ( lockPath )
123- }
124- if ( ext === BINARY_LOCK_EXT ) {
125- const lockBuffer = await binaryReader ( lockPath )
126- if ( lockBuffer ) {
127- try {
128- return parseBunLockb ( lockBuffer )
129- } catch { }
119+ return new Map ( [
120+ [
121+ BUN ,
122+ wrapReader ( async ( lockPath : string , agentExecPath : string ) => {
123+ const ext = path . extname ( lockPath )
124+ if ( ext === LOCK_EXT ) {
125+ return await defaultReader ( lockPath )
130126 }
131- // To print a Yarn lockfile to your console without writing it to disk
132- // use `bun bun.lockb`.
133- // https://bun.sh/guides/install/yarnlock
134- return ( await spawn ( agentExecPath , [ lockPath ] ) ) . stdout . trim ( )
135- }
136- return undefined
137- } ) ,
138- [ NPM ] : defaultReader ,
139- [ PNPM ] : defaultReader ,
140- [ VLT ] : defaultReader ,
141- [ YARN_BERRY ] : defaultReader ,
142- [ YARN_CLASSIC ] : defaultReader
143- }
127+ if ( ext === BINARY_LOCK_EXT ) {
128+ const lockBuffer = await binaryReader ( lockPath )
129+ if ( lockBuffer ) {
130+ try {
131+ return parseBunLockb ( lockBuffer )
132+ } catch { }
133+ }
134+ // To print a Yarn lockfile to your console without writing it to disk
135+ // use `bun bun.lockb`.
136+ // https://bun.sh/guides/install/yarnlock
137+ return ( await spawn ( agentExecPath , [ lockPath ] ) ) . stdout . trim ( )
138+ }
139+ return undefined
140+ } )
141+ ] ,
142+ [ NPM , defaultReader ] ,
143+ [ PNPM , defaultReader ] ,
144+ [ VLT , defaultReader ] ,
145+ [ YARN_BERRY , defaultReader ] ,
146+ [ YARN_CLASSIC , defaultReader ]
147+ ] )
144148} ) ( )
145149
146150export type DetectOptions = {
@@ -151,17 +155,17 @@ export type DetectOptions = {
151155type EnvBase = {
152156 agent : Agent
153157 agentExecPath : string
158+ agentSupported : boolean
154159 features : {
155160 // Fixed by https://github.com/npm/cli/pull/8089.
156161 // Landed in npm v11.2.0.
157162 npmBuggyOverrides : boolean
158163 }
159- minimumNodeVersion : string
160164 npmExecPath : string
161165 pkgSupported : boolean
162- targets : {
163- browser : boolean
164- node : boolean
166+ pkgRequirements : {
167+ agent : string
168+ node : string
165169 }
166170}
167171
@@ -221,13 +225,14 @@ export async function detectPackageEnvironment({
221225 let agent : Agent | undefined
222226 let agentVersion : SemVer | undefined
223227 if ( pkgManager ) {
228+ // A valid "packageManager" field value is "<package manager name>@<version>".
229+ // https://nodejs.org/api/packages.html#packagemanager
224230 const atSignIndex = pkgManager . lastIndexOf ( '@' )
225231 if ( atSignIndex !== - 1 ) {
226232 const name = pkgManager . slice ( 0 , atSignIndex ) as Agent
227233 const version = pkgManager . slice ( atSignIndex + 1 )
228234 if ( version && AGENTS . includes ( name ) ) {
229235 agent = name
230- agentVersion = semver . coerce ( version ) ?? undefined
231236 }
232237 }
233238 }
@@ -244,7 +249,6 @@ export async function detectPackageEnvironment({
244249 onUnknown ?.( pkgManager )
245250 }
246251 const agentExecPath = await getAgentExecPath ( agent )
247-
248252 const npmExecPath =
249253 agent === NPM ? agentExecPath : await getAgentExecPath ( NPM )
250254 if ( agentVersion === undefined ) {
@@ -253,23 +257,31 @@ export async function detectPackageEnvironment({
253257 if ( agent === YARN_CLASSIC && ( agentVersion ?. major ?? 0 ) > 1 ) {
254258 agent = YARN_BERRY
255259 }
256- const targets = {
257- browser : false ,
258- node : true
259- }
260- let lockSrc : string | undefined
261260 // Lazily access constants.maintainedNodeVersions.
262- let minimumNodeVersion = constants . maintainedNodeVersions . last
261+ const { maintainedNodeVersions } = constants
262+ // Lazily access constants.minimumVersionByAgent.
263+ const minSupportedAgentVersion = constants . minimumVersionByAgent . get ( agent ) !
264+ const minSupportedNodeVersion = maintainedNodeVersions . last
265+ let lockSrc : string | undefined
266+ let pkgMinAgentVersion = minSupportedAgentVersion
267+ let pkgMinNodeVersion = minSupportedNodeVersion
263268 if ( pkgJson ) {
264- const browserField = pkgJson . browser
265- if ( isNonEmptyString ( browserField ) || isObjectObject ( browserField ) ) {
266- targets . browser = true
267- }
269+ const agentRange = pkgJson . engines ?. [ agent ]
268270 const nodeRange = pkgJson . engines ?. [ 'node' ]
271+ if ( isNonEmptyString ( agentRange ) ) {
272+ // Roughly check agent range as semver.coerce will strip leading
273+ // v's, carets (^), comparators (<,<=,>,>=,=), and tildes (~).
274+ const coerced = semver . coerce ( agentRange )
275+ if ( coerced && semver . lt ( coerced , pkgMinAgentVersion ) ) {
276+ pkgMinAgentVersion = coerced . version
277+ }
278+ }
269279 if ( isNonEmptyString ( nodeRange ) ) {
280+ // Roughly check Node range as semver.coerce will strip leading
281+ // v's, carets (^), comparators (<,<=,>,>=,=), and tildes (~).
270282 const coerced = semver . coerce ( nodeRange )
271- if ( coerced && semver . lt ( coerced , minimumNodeVersion ) ) {
272- minimumNodeVersion = coerced . version
283+ if ( coerced && semver . lt ( coerced , pkgMinNodeVersion ) ) {
284+ pkgMinNodeVersion = coerced . version
273285 }
274286 }
275287 const browserslistQuery = pkgJson [ 'browserslist' ] as string [ ] | undefined
@@ -280,50 +292,56 @@ export async function detectPackageEnvironment({
280292 const browserslistNodeTargets = browserslistTargets
281293 . filter ( v => v . startsWith ( 'node ' ) )
282294 . map ( v => v . slice ( 5 /*'node '.length*/ ) )
283- if ( ! targets . browser && browserslistTargets . length ) {
284- targets . browser =
285- browserslistTargets . length !== browserslistNodeTargets . length
286- }
287295 if ( browserslistNodeTargets . length ) {
288296 const coerced = semver . coerce ( browserslistNodeTargets [ 0 ] )
289- if ( coerced && semver . lt ( coerced , minimumNodeVersion ) ) {
290- minimumNodeVersion = coerced . version
297+ if ( coerced && semver . lt ( coerced , pkgMinNodeVersion ) ) {
298+ pkgMinNodeVersion = coerced . version
291299 }
292300 }
293301 }
294- // Lazily access constants.maintainedNodeVersions.
295- targets . node = constants . maintainedNodeVersions . some ( v =>
296- semver . satisfies ( v , `>=${ minimumNodeVersion } ` )
297- )
298302 lockSrc =
299303 typeof lockPath === 'string'
300- ? await readLockFileByAgent [ agent ] ( lockPath , agentExecPath )
304+ ? await readLockFileByAgent . get ( agent ) ! ( lockPath , agentExecPath )
301305 : undefined
302306 } else {
303307 lockName = undefined
304308 lockPath = undefined
305309 }
306- const pkgSupported = targets . browser || targets . node
310+ // Does system agent version meet our minimum supported agent version?
311+ const agentSupported =
312+ ! ! agentVersion &&
313+ semver . satisfies ( agentVersion , `>=${ minSupportedAgentVersion } ` )
314+
315+ // Does our minimum supported agent version meet the package's requirements?
316+ // Does our supported Node versions meet the package's requirements?
317+ const pkgSupported =
318+ semver . satisfies ( minSupportedAgentVersion , `>=${ pkgMinAgentVersion } ` ) &&
319+ maintainedNodeVersions . some ( v =>
320+ semver . satisfies ( v , `>=${ pkgMinNodeVersion } ` )
321+ )
322+
307323 const npmBuggyOverrides =
308324 agent === NPM &&
309325 ! ! agentVersion &&
310326 semver . lt ( agentVersion , NPM_BUGGY_OVERRIDES_PATCHED_VERSION )
327+
311328 return {
312329 agent,
313330 agentExecPath,
331+ agentSupported,
314332 agentVersion,
333+ features : { npmBuggyOverrides } ,
315334 lockName,
316335 lockPath,
317336 lockSrc,
318- minimumNodeVersion,
319337 npmExecPath,
320338 pkgJson : editablePkgJson ,
321339 pkgPath,
322340 pkgSupported,
323- features : {
324- npmBuggyOverrides
325- } ,
326- targets
341+ pkgRequirements : {
342+ agent : pkgMinAgentVersion ,
343+ node : pkgMinNodeVersion
344+ }
327345 }
328346}
329347
0 commit comments