@@ -32,11 +32,120 @@ describe('PackageManager', () => {
3232
3333 expect ( await pm . getRegistryManifest ( 'foo' , '99.99.99' ) ) . toBeNull ( ) ;
3434 expect ( await pm . getRegistryManifest ( 'foo' , '^99.0.0' ) ) . toBeNull ( ) ;
35+ expect ( await pm . getRegistryManifest ( 'foo' , '2.0.0-rc.1' ) ) . toBeNull ( ) ;
3536 expect ( await pm . getRegistryManifest ( 'foo' , '^2.0.0' ) ) . toEqual (
3637 jasmine . objectContaining ( { version : '2.0.0' } ) ,
3738 ) ;
3839 } ) ;
3940
41+ it ( 'should cache null for mismatched versions without caching the fallback manifest' , async ( ) => {
42+ const pm = new PackageManager ( host , '/tmp' , SUPPORTED_PACKAGE_MANAGERS [ 'yarn' ] ) ;
43+ runCommandSpy . and . resolveTo ( {
44+ stdout : JSON . stringify ( { name : 'foo' , version : '2.0.0' } ) ,
45+ stderr : '' ,
46+ } ) ;
47+
48+ expect ( await pm . getRegistryManifest ( 'foo' , '99.99.99' ) ) . toBeNull ( ) ;
49+ expect ( await pm . getRegistryManifest ( 'foo' , '99.99.99' ) ) . toBeNull ( ) ;
50+ expect ( runCommandSpy ) . toHaveBeenCalledTimes ( 1 ) ;
51+
52+ // Ensure the rejected fallback manifest was not cached under `foo@2.0.0`.
53+ runCommandSpy . and . resolveTo ( {
54+ stdout : JSON . stringify ( { name : 'foo' , version : '2.0.0' , homepage : 'https://angular.dev' } ) ,
55+ stderr : '' ,
56+ } ) ;
57+ expect ( await pm . getRegistryManifest ( 'foo' , '2.0.0' ) ) . toEqual ( {
58+ name : 'foo' ,
59+ version : '2.0.0' ,
60+ homepage : 'https://angular.dev' ,
61+ } ) ;
62+ expect ( runCommandSpy ) . toHaveBeenCalledTimes ( 2 ) ;
63+ } ) ;
64+
65+ it ( 'should validate dist-tags against fetched dist-tags metadata and strip dist-tags from the manifest' , async ( ) => {
66+ // Modern yarn falls back to `latest` when a requested dist-tag does not exist.
67+ const pm = new PackageManager ( host , '/tmp' , SUPPORTED_PACKAGE_MANAGERS [ 'yarn' ] ) ;
68+ runCommandSpy . and . resolveTo ( {
69+ stdout : JSON . stringify ( {
70+ name : 'foo' ,
71+ version : '2.0.0' ,
72+ 'dist-tags' : { latest : '2.0.0' , next : '2.1.0-next.0' } ,
73+ } ) ,
74+ stderr : '' ,
75+ } ) ;
76+
77+ expect ( await pm . getRegistryManifest ( 'foo' , 'nonexistent-tag' ) ) . toBeNull ( ) ;
78+ expect ( await pm . getManifest ( 'foo@nonexistent-tag' ) ) . toBeNull ( ) ;
79+ // If a package manager returned `2.0.0` when `next` (`2.1.0-next.0`) was requested, reject it.
80+ expect ( await pm . getRegistryManifest ( 'foo' , 'next' ) ) . toBeNull ( ) ;
81+
82+ expect ( await pm . getRegistryManifest ( 'foo' , 'latest' ) ) . toEqual ( {
83+ name : 'foo' ,
84+ version : '2.0.0' ,
85+ } ) ;
86+
87+ expect ( runCommandSpy ) . toHaveBeenCalledWith (
88+ 'yarn' ,
89+ [
90+ 'npm' ,
91+ 'info' ,
92+ '--json' ,
93+ 'foo@latest' ,
94+ '--fields' ,
95+ [ ...MANIFEST_FIELDS , 'dist-tags' ] . join ( ',' ) ,
96+ ] ,
97+ jasmine . anything ( ) ,
98+ ) ;
99+ } ) ;
100+
101+ it ( 'should handle prerelease manifests accurately for wildcard, prerelease, and stable ranges' , async ( ) => {
102+ const pm = new PackageManager ( host , '/tmp' , SUPPORTED_PACKAGE_MANAGERS [ 'yarn' ] ) ;
103+ runCommandSpy . and . resolveTo ( {
104+ stdout : JSON . stringify ( { name : 'foo' , version : '2.0.0-beta.1' } ) ,
105+ stderr : '' ,
106+ } ) ;
107+
108+ // Unmet stable major/x-ranges should not accept a prerelease `latest` fallback.
109+ expect ( await pm . getRegistryManifest ( 'foo' , '2' ) ) . toBeNull ( ) ;
110+ expect ( await pm . getRegistryManifest ( 'foo' , '2.x' ) ) . toBeNull ( ) ;
111+ expect ( await pm . getRegistryManifest ( 'foo' , '^2.0.0' ) ) . toBeNull ( ) ;
112+
113+ // Wildcard (`*`, produced by bare specifiers) and matching prerelease specs should succeed.
114+ expect ( await pm . getRegistryManifest ( 'foo' , '*' ) ) . toEqual ( {
115+ name : 'foo' ,
116+ version : '2.0.0-beta.1' ,
117+ } ) ;
118+ expect ( await pm . getRegistryManifest ( 'foo' , '2.0.0-beta.1' ) ) . toEqual ( {
119+ name : 'foo' ,
120+ version : '2.0.0-beta.1' ,
121+ } ) ;
122+ expect ( await pm . getRegistryManifest ( 'foo' , '^2.0.0-beta.0' ) ) . toEqual ( {
123+ name : 'foo' ,
124+ version : '2.0.0-beta.1' ,
125+ } ) ;
126+ } ) ;
127+
128+ it ( 'should apply the same prerelease range handling to non-yarn package managers' , async ( ) => {
129+ const pm = new PackageManager ( host , '/tmp' , descriptor ) ;
130+ runCommandSpy . and . resolveTo ( {
131+ stdout : JSON . stringify ( { name : 'foo' , version : '2.0.0-beta.1' } ) ,
132+ stderr : '' ,
133+ } ) ;
134+
135+ // A prerelease does not satisfy a stable range without `includePrerelease`.
136+ expect ( await pm . getRegistryManifest ( 'foo' , '^2.0.0' ) ) . toBeNull ( ) ;
137+
138+ // Wildcard and matching prerelease ranges accept the prerelease.
139+ expect ( await pm . getRegistryManifest ( 'foo' , '*' ) ) . toEqual ( {
140+ name : 'foo' ,
141+ version : '2.0.0-beta.1' ,
142+ } ) ;
143+ expect ( await pm . getRegistryManifest ( 'foo' , '^2.0.0-beta.0' ) ) . toEqual ( {
144+ name : 'foo' ,
145+ version : '2.0.0-beta.1' ,
146+ } ) ;
147+ } ) ;
148+
40149 it ( 'should quote complex range specifiers when required by the host' , async ( ) => {
41150 // Simulate a quoting host
42151 Object . assign ( host , { requiresQuoting : true } ) ;
@@ -525,6 +634,27 @@ describe('PackageManager', () => {
525634
526635 expect ( await pm . getRegistryMetadata ( 'non-existent' ) ) . toBeNull ( ) ;
527636 } ) ;
637+
638+ it ( 'should not duplicate the dist-tags field when fetching yarn metadata' , async ( ) => {
639+ const pm = new PackageManager ( host , '/tmp' , SUPPORTED_PACKAGE_MANAGERS [ 'yarn' ] ) ;
640+ runCommandSpy . and . resolveTo ( {
641+ stdout : JSON . stringify ( {
642+ name : 'foo' ,
643+ 'dist-tags' : { latest : '1.0.0' } ,
644+ versions : [ '1.0.0' ] ,
645+ time : { } ,
646+ } ) ,
647+ stderr : '' ,
648+ } ) ;
649+
650+ await pm . getRegistryMetadata ( 'foo' ) ;
651+
652+ expect ( runCommandSpy ) . toHaveBeenCalledWith (
653+ 'yarn' ,
654+ [ 'npm' , 'info' , '--json' , 'foo' , '--fields' , 'name,dist-tags,versions,time' ] ,
655+ jasmine . anything ( ) ,
656+ ) ;
657+ } ) ;
528658 } ) ;
529659
530660 describe ( 'getCurrentPackageName' , ( ) => {
0 commit comments