@@ -14,11 +14,13 @@ import {
1414 fetchPeerDependencies ,
1515 checkDeps ,
1616 checkDevDeps ,
17- checkPackageJson
17+ checkPackageJson ,
18+ parsePackageName
1819} from "../../lib/utils/npm-utils.js" ;
1920import { defineInMemoryFs } from "../_utils/in-memory-fs.js" ;
2021import { assert , describe , afterEach , it } from "vitest" ;
2122import fs from "node:fs" ;
23+ import process from "node:process" ;
2224
2325//------------------------------------------------------------------------------
2426// Helpers
@@ -177,6 +179,15 @@ describe("npmUtils", () => {
177179 stub . restore ( ) ;
178180 } ) ;
179181
182+ it ( "should invoke bun to install a single desired package" , ( ) => {
183+ const stub = sinon . stub ( spawn , "sync" ) . returns ( { stdout : "" } ) ;
184+
185+ installSyncSaveDev ( "desired-package" , "bun" ) ;
186+ assert ( stub . calledOnce ) ;
187+ assert . strictEqual ( stub . firstCall . args [ 0 ] , "bun" ) ;
188+ assert . deepStrictEqual ( stub . firstCall . args [ 1 ] , [ "install" , "-D" , "desired-package" ] ) ;
189+ stub . restore ( ) ;
190+ } ) ;
180191
181192 it ( "should accept an array of packages to install" , ( ) => {
182193 const stub = sinon . stub ( spawn , "sync" ) . returns ( { stdout : "" } ) ;
@@ -203,21 +214,77 @@ describe("npmUtils", () => {
203214 } ) ;
204215 } ) ;
205216
217+ describe ( "parsePackageName()" , ( ) => {
218+ it ( "should parse package name with version" , ( ) => {
219+ const result = parsePackageName ( "eslint@9.0.0" ) ;
220+
221+ assert . deepStrictEqual ( result , { name : "eslint" , version : "9.0.0" } ) ;
222+ } ) ;
223+
224+ it ( "should parse package name without version" , ( ) => {
225+ const result = parsePackageName ( "eslint" ) ;
226+
227+ assert . deepStrictEqual ( result , { name : "eslint" , version : "latest" } ) ;
228+ } ) ;
229+
230+ it ( "should handle scoped packages with version" , ( ) => {
231+ const result = parsePackageName ( "@typescript-eslint/eslint-plugin@5.0.0" ) ;
232+
233+ assert . deepStrictEqual ( result , { name : "@typescript-eslint/eslint-plugin" , version : "5.0.0" } ) ;
234+ } ) ;
235+
236+ it ( "should handle scoped packages without version" , ( ) => {
237+ const result = parsePackageName ( "@typescript-eslint/eslint-plugin" ) ;
238+
239+ assert . deepStrictEqual ( result , { name : "@typescript-eslint/eslint-plugin" , version : "latest" } ) ;
240+ } ) ;
241+ } ) ;
242+
206243 describe ( "fetchPeerDependencies()" , ( ) => {
207- it ( "should execute 'npm show --json <packageName> peerDependencies' command" , ( ) => {
244+ it ( "should execute 'npm show --json <packageName> peerDependencies' command" , async ( ) => {
208245 const stub = sinon . stub ( spawn , "sync" ) . returns ( { stdout : "" } ) ;
209246
210- fetchPeerDependencies ( "desired-package" ) ;
247+ await fetchPeerDependencies ( "desired-package" ) ;
211248 assert ( stub . calledOnce ) ;
212249 assert . strictEqual ( stub . firstCall . args [ 0 ] , "npm" ) ;
213250 assert . deepStrictEqual ( stub . firstCall . args [ 1 ] , [ "show" , "--json" , "desired-package" , "peerDependencies" ] ) ;
214251 stub . restore ( ) ;
215252 } ) ;
216253
217- it ( "should return null if npm throws ENOENT error" , ( ) => {
254+ // Skip on Node.js v21 due to a bug where fetch cannot be stubbed
255+ // See: https://github.com/sinonjs/sinon/issues/2590
256+ it . skipIf ( process . version . startsWith ( "v21" ) ) ( "should fetch peer dependencies from npm registry when npm is not available" , async ( ) => {
257+ const npmStub = sinon . stub ( spawn , "sync" ) . returns ( { error : { code : "ENOENT" } } ) ;
258+ const fetchStub = sinon . stub ( globalThis , "fetch" ) ;
259+
260+ const mockResponse = {
261+ json : sinon . stub ( ) . resolves ( {
262+ "dist-tags" : { latest : "9.0.0" } ,
263+ versions : {
264+ "9.0.0" : {
265+ peerDependencies : { eslint : "9.0.0" }
266+ }
267+ }
268+ } ) ,
269+ ok : true ,
270+ status : 200
271+ } ;
272+
273+ fetchStub . resolves ( mockResponse ) ;
274+
275+ const result = await fetchPeerDependencies ( "desired-package" ) ;
276+
277+ assert ( fetchStub . calledOnceWith ( "https://registry.npmjs.org/desired-package" ) ) ;
278+ assert . deepStrictEqual ( result , [ "eslint@9.0.0" ] ) ;
279+
280+ npmStub . restore ( ) ;
281+ fetchStub . restore ( ) ;
282+ } ) ;
283+
284+ it ( "should return null if an error is thrown" , async ( ) => {
218285 const stub = sinon . stub ( spawn , "sync" ) . returns ( { error : { code : "ENOENT" } } ) ;
219286
220- const peerDependencies = fetchPeerDependencies ( "desired-package" ) ;
287+ const peerDependencies = await fetchPeerDependencies ( "desired-package" ) ;
221288
222289 assert . isNull ( peerDependencies ) ;
223290
0 commit comments