88
99import { Listr , ListrRenderer , ListrTaskWrapper , color , figures } from 'listr2' ;
1010import assert from 'node:assert' ;
11+ import { promises as fs } from 'node:fs' ;
1112import { createRequire } from 'node:module' ;
1213import { dirname , join } from 'node:path' ;
1314import npa from 'npm-package-arg' ;
1415import { Range , compare , intersects , prerelease , satisfies , valid } from 'semver' ;
1516import { Argv } from 'yargs' ;
16- import { PackageManager } from '../../../lib/config/workspace-schema' ;
1717import {
1818 CommandModuleImplementation ,
1919 Options ,
@@ -23,14 +23,14 @@ import {
2323 SchematicsCommandArgs ,
2424 SchematicsCommandModule ,
2525} from '../../command-builder/schematics-command-module' ;
26- import { assertIsError } from '../../utilities/error' ;
2726import {
2827 NgAddSaveDependency ,
28+ PackageManager ,
2929 PackageManifest ,
3030 PackageMetadata ,
31- fetchPackageManifest ,
32- fetchPackageMetadata ,
33- } from '../../utilities/package-metadata ' ;
31+ createPackageManager ,
32+ } from '../../package-managers' ;
33+ import { assertIsError } from '../../utilities/error ' ;
3434import { isTTY } from '../../utilities/tty' ;
3535import { VERSION } from '../../utilities/version' ;
3636
@@ -44,8 +44,8 @@ interface AddCommandArgs extends SchematicsCommandArgs {
4444}
4545
4646interface AddCommandTaskContext {
47+ packageManager : PackageManager ;
4748 packageIdentifier : npa . Result ;
48- usingYarn ?: boolean ;
4949 savePackage ?: NgAddSaveDependency ;
5050 collectionName ?: string ;
5151 executeSchematic : AddCommandModule [ 'executeSchematic' ] ;
@@ -173,12 +173,12 @@ export default class AddCommandModule
173173 }
174174 }
175175
176- const taskContext : AddCommandTaskContext = {
176+ const taskContext = {
177177 packageIdentifier,
178178 executeSchematic : this . executeSchematic . bind ( this ) ,
179179 getPeerDependencyConflicts : this . getPeerDependencyConflicts . bind ( this ) ,
180180 dryRun : options . dryRun ,
181- } ;
181+ } as AddCommandTaskContext ;
182182
183183 const tasks = new Listr < AddCommandTaskContext > (
184184 [
@@ -294,91 +294,102 @@ export default class AddCommandModule
294294 }
295295 }
296296
297- private determinePackageManagerTask (
297+ private async determinePackageManagerTask (
298298 context : AddCommandTaskContext ,
299299 task : AddCommandTaskWrapper ,
300- ) : void {
301- const { packageManager } = this . context ;
302- context . usingYarn = packageManager . name === PackageManager . Yarn ;
303- task . output = `Using package manager: ${ color . dim ( packageManager . name ) } ` ;
300+ ) : Promise < void > {
301+ context . packageManager = await createPackageManager ( {
302+ cwd : this . context . root ,
303+ logger : this . context . logger ,
304+ dryRun : context . dryRun ,
305+ } ) ;
306+ task . output = `Using package manager: ${ color . dim ( context . packageManager . name ) } ` ;
304307 }
305308
306309 private async findCompatiblePackageVersionTask (
307310 context : AddCommandTaskContext ,
308311 task : AddCommandTaskWrapper ,
309312 options : Options < AddCommandArgs > ,
310313 ) : Promise < void > {
311- const { logger } = this . context ;
312- const { verbose, registry } = options ;
314+ const { registry, verbose } = options ;
315+ const { packageManager, packageIdentifier } = context ;
316+ const packageName = packageIdentifier . name ;
313317
314- assert (
315- context . packageIdentifier . name ,
316- 'Registry package identifiers should always have a name.' ,
317- ) ;
318+ assert ( packageName , 'Registry package identifiers should always have a name.' ) ;
318319
319- // only package name provided; search for viable version
320- // plus special cases for packages that did not have peer deps setup
321- let packageMetadata ;
320+ const rejectionReasons : string [ ] = [ ] ;
321+
322+ // Attempt to use the 'latest' tag from the registry.
322323 try {
323- packageMetadata = await fetchPackageMetadata ( context . packageIdentifier . name , logger , {
324+ const latestManifest = await packageManager . getPackageManifest ( packageName , 'latest' , {
324325 registry,
325- usingYarn : context . usingYarn ,
326- verbose,
327326 } ) ;
327+
328+ if ( latestManifest ) {
329+ const conflicts = await this . getPeerDependencyConflicts ( latestManifest ) ;
330+ if ( ! conflicts ) {
331+ context . packageIdentifier = npa . resolve ( latestManifest . name , latestManifest . version ) ;
332+ task . output = `Found compatible package version: ${ color . blue ( latestManifest . version ) } .` ;
333+
334+ return ;
335+ }
336+ rejectionReasons . push ( ...conflicts ) ;
337+ }
328338 } catch ( e ) {
329339 assertIsError ( e ) ;
330340 throw new CommandError ( `Unable to load package information from registry: ${ e . message } ` ) ;
331341 }
332342
333- const rejectionReasons : string [ ] = [ ] ;
343+ // 'latest' is invalid or not found, search for most recent matching package.
344+ task . output =
345+ 'Could not find a compatible version with `latest`. Searching for a compatible version.' ;
334346
335- // Start with the version tagged as `latest` if it exists
336- const latestManifest = packageMetadata . tags [ 'latest' ] ;
337- if ( latestManifest ) {
338- const latestConflicts = await this . getPeerDependencyConflicts ( latestManifest ) ;
339- if ( latestConflicts ) {
340- // 'latest' is invalid so search for most recent matching package
341- rejectionReasons . push ( ...latestConflicts ) ;
342- } else {
343- context . packageIdentifier = npa . resolve ( latestManifest . name , latestManifest . version ) ;
344- task . output = `Found compatible package version: ${ color . blue ( latestManifest . version ) } .` ;
347+ let packageMetadata ;
348+ try {
349+ packageMetadata = await packageManager . getRegistryMetadata ( packageName , {
350+ registry,
351+ } ) ;
352+ } catch ( e ) {
353+ assertIsError ( e ) ;
354+ throw new CommandError ( `Unable to load package information from registry: ${ e . message } ` ) ;
355+ }
345356
346- return ;
347- }
357+ if ( ! packageMetadata ) {
358+ throw new CommandError ( 'Unable to load package information from registry.' ) ;
348359 }
349360
350- // Allow prelease versions if the CLI itself is a prerelease
361+ // Allow prelease versions if the CLI itself is a prerelease.
351362 const allowPrereleases = ! ! prerelease ( VERSION . full ) ;
352- const versionManifests = this . #getPotentialVersionManifests ( packageMetadata , allowPrereleases ) ;
363+ const potentialVersions = this . #getPotentialVersions ( packageMetadata , allowPrereleases ) ;
353364
354- let found = false ;
355- for ( const versionManifest of versionManifests ) {
356- // Already checked the 'latest' version
357- if ( latestManifest ?. version === versionManifest . version ) {
365+ let found ;
366+ for ( const version of potentialVersions ) {
367+ const manifest = await packageManager . getPackageManifest ( packageName , version , { registry } ) ;
368+ if ( ! manifest ) {
358369 continue ;
359370 }
360371
361- const conflicts = await this . getPeerDependencyConflicts ( versionManifest ) ;
372+ const conflicts = await this . getPeerDependencyConflicts ( manifest ) ;
362373 if ( conflicts ) {
363- if ( options . verbose || rejectionReasons . length < DEFAULT_CONFLICT_DISPLAY_LIMIT ) {
374+ if ( verbose || rejectionReasons . length < DEFAULT_CONFLICT_DISPLAY_LIMIT ) {
364375 rejectionReasons . push ( ...conflicts ) ;
365376 }
366377 continue ;
367378 }
368379
369- context . packageIdentifier = npa . resolve ( versionManifest . name , versionManifest . version ) ;
370- found = true ;
380+ context . packageIdentifier = npa . resolve ( manifest . name , manifest . version ) ;
381+ found = manifest ;
371382 break ;
372383 }
373384
374385 if ( ! found ) {
375- let message = `Unable to find compatible package. Using 'latest' tag. ` ;
386+ let message = `Unable to find compatible package.` ;
376387 if ( rejectionReasons . length > 0 ) {
377388 message +=
378389 '\nThis is often because of incompatible peer dependencies.\n' +
379390 'These versions were rejected due to the following conflicts:\n' +
380391 rejectionReasons
381- . slice ( 0 , options . verbose ? undefined : DEFAULT_CONFLICT_DISPLAY_LIMIT )
392+ . slice ( 0 , verbose ? undefined : DEFAULT_CONFLICT_DISPLAY_LIMIT )
382393 . map ( ( r ) => ` - ${ r } ` )
383394 . join ( '\n' ) ;
384395 }
@@ -390,51 +401,42 @@ export default class AddCommandModule
390401 }
391402 }
392403
393- #getPotentialVersionManifests(
394- packageMetadata : PackageMetadata ,
395- allowPrereleases : boolean ,
396- ) : PackageManifest [ ] {
404+ #getPotentialVersions( packageMetadata : PackageMetadata , allowPrereleases : boolean ) : string [ ] {
397405 const versionExclusions = packageVersionExclusions [ packageMetadata . name ] ;
398- const versionManifests = Object . values ( packageMetadata . versions ) . filter (
399- ( value : PackageManifest ) => {
400- // Prerelease versions are not stable and should not be considered by default
401- if ( ! allowPrereleases && prerelease ( value . version ) ) {
402- return false ;
403- }
404- // Deprecated versions should not be used or considered
405- if ( value . deprecated ) {
406- return false ;
407- }
408- // Excluded package versions should not be considered
409- if (
410- versionExclusions &&
411- satisfies ( value . version , versionExclusions , { includePrerelease : true } )
412- ) {
413- return false ;
414- }
415406
416- return true ;
417- } ,
418- ) ;
407+ const versions = Object . values ( packageMetadata . versions ) . filter ( ( version ) => {
408+ // Prerelease versions are not stable and should not be considered by default
409+ if ( ! allowPrereleases && prerelease ( version ) ) {
410+ return false ;
411+ }
412+
413+ // Excluded package versions should not be considered
414+ if ( versionExclusions && satisfies ( version , versionExclusions , { includePrerelease : true } ) ) {
415+ return false ;
416+ }
417+
418+ return true ;
419+ } ) ;
419420
420421 // Sort in reverse SemVer order so that the newest compatible version is chosen
421- return versionManifests . sort ( ( a , b ) => compare ( b . version , a . version , true ) ) ;
422+ return versions . sort ( ( a , b ) => compare ( b , a , true ) ) ;
422423 }
423424
424425 private async loadPackageInfoTask (
425426 context : AddCommandTaskContext ,
426427 task : AddCommandTaskWrapper ,
427428 options : Options < AddCommandArgs > ,
428429 ) : Promise < void > {
429- const { logger } = this . context ;
430- const { verbose, registry } = options ;
430+ const { registry } = options ;
431431
432432 let manifest ;
433433 try {
434- manifest = await fetchPackageManifest ( context . packageIdentifier . toString ( ) , logger , {
434+ const { name, rawSpec } = context . packageIdentifier ;
435+ if ( ! name ) {
436+ throw new CommandError ( 'Package name is required.' ) ;
437+ }
438+ manifest = await context . packageManager . getPackageManifest ( name , rawSpec , {
435439 registry,
436- verbose,
437- usingYarn : context . usingYarn ,
438440 } ) ;
439441 } catch ( e ) {
440442 assertIsError ( e ) ;
@@ -443,6 +445,12 @@ export default class AddCommandModule
443445 ) ;
444446 }
445447
448+ if ( ! manifest ) {
449+ throw new CommandError (
450+ `Unable to fetch package information for '${ context . packageIdentifier } '.` ,
451+ ) ;
452+ }
453+
446454 context . hasSchematics = ! ! manifest . schematics ;
447455 context . savePackage = manifest [ 'ng-add' ] ?. save ;
448456 context . collectionName = manifest . name ;
@@ -487,43 +495,42 @@ export default class AddCommandModule
487495 task : AddCommandTaskWrapper ,
488496 options : Options < AddCommandArgs > ,
489497 ) : Promise < void > {
490- const { packageManager } = this . context ;
491498 const { registry } = options ;
499+ const { packageManager, packageIdentifier, savePackage } = context ;
492500
493501 // Only show if installation will actually occur
494502 task . title = 'Installing package' ;
495503
496- if ( context . savePackage === false ) {
504+ if ( savePackage === false ) {
497505 task . title += ' in temporary location' ;
498506
499507 // Temporary packages are located in a different directory
500508 // Hence we need to resolve them using the temp path
501- const { success, tempNodeModules } = await packageManager . installTemp (
502- context . packageIdentifier . toString ( ) ,
503- registry ? [ `--registry="${ registry } "` ] : undefined ,
509+ const { workingDirectory } = await packageManager . acquireTempPackage (
510+ packageIdentifier . toString ( ) ,
511+ {
512+ registry,
513+ } ,
504514 ) ;
505- const tempRequire = createRequire ( tempNodeModules + '/' ) ;
515+
516+ const tempRequire = createRequire ( workingDirectory + '/' ) ;
506517 assert ( context . collectionName , 'Collection name should always be available' ) ;
507518 const resolvedCollectionPath = tempRequire . resolve (
508519 join ( context . collectionName , 'package.json' ) ,
509520 ) ;
510521
511- if ( ! success ) {
512- throw new CommandError ( 'Unable to install package' ) ;
513- }
514-
515522 context . collectionName = dirname ( resolvedCollectionPath ) ;
516523 } else {
517- const success = await packageManager . install (
518- context . packageIdentifier . toString ( ) ,
519- context . savePackage ,
520- registry ? [ `--registry="${ registry } "` ] : undefined ,
521- undefined ,
524+ await packageManager . add (
525+ packageIdentifier . toString ( ) ,
526+ savePackage === 'dependencies' ? 'none' : 'exact' ,
527+ savePackage !== 'dependencies' ,
528+ false ,
529+ true ,
530+ {
531+ registry,
532+ } ,
522533 ) ;
523-
524- if ( ! success ) {
525- throw new CommandError ( 'Unable to install package' ) ;
526- }
527534 }
528535 }
529536
@@ -631,24 +638,28 @@ export default class AddCommandModule
631638 return cachedVersion ;
632639 }
633640
634- const { logger , root } = this . context ;
635- let installedPackage ;
641+ const { root } = this . context ;
642+ let installedPackagePath ;
636643 try {
637- installedPackage = this . rootRequire . resolve ( join ( name , 'package.json' ) ) ;
644+ installedPackagePath = this . rootRequire . resolve ( join ( name , 'package.json' ) ) ;
638645 } catch { }
639646
640- if ( installedPackage ) {
647+ if ( installedPackagePath ) {
641648 try {
642- const installed = await fetchPackageManifest ( dirname ( installedPackage ) , logger ) ;
643- this . #projectVersionCache. set ( name , installed . version ) ;
649+ const installedPackage = JSON . parse (
650+ await fs . readFile ( installedPackagePath , 'utf-8' ) ,
651+ ) as PackageManifest ;
652+ this . #projectVersionCache. set ( name , installedPackage . version ) ;
644653
645- return installed . version ;
654+ return installedPackage . version ;
646655 } catch { }
647656 }
648657
649658 let projectManifest ;
650659 try {
651- projectManifest = await fetchPackageManifest ( root , logger ) ;
660+ projectManifest = JSON . parse (
661+ await fs . readFile ( join ( root , 'package.json' ) , 'utf-8' ) ,
662+ ) as PackageManifest ;
652663 } catch { }
653664
654665 if ( projectManifest ) {
0 commit comments