@@ -5,6 +5,7 @@ import { join } from 'node:path'
55
66const {
77 resolveUpgradeContext,
8+ pickLatestStableTag,
89 buildTemplateString,
910 readVersion,
1011 readChannel,
@@ -36,17 +37,22 @@ afterEach(() => {
3637
3738// ─── resolveUpgradeContext ───────────────────────────────────────────────────
3839
40+ // The resolved latest stable tag is passed into resolveUpgradeContext (the
41+ // network lookup lives in the async caller). Tests inject a fixture tag so the
42+ // pure resolution stays deterministic.
43+ const STABLE = 'v9.9.9'
44+
3945describe ( 'resolveUpgradeContext' , ( ) => {
4046 describe ( 'default behavior' , ( ) => {
41- it ( 'should default to the stable channel + stable branch when no options and channel is stable' , ( ) => {
42- const ctx = resolveUpgradeContext ( { } , 'stable' )
47+ it ( 'should default to the stable channel + latest stable tag when no options and channel is stable' , ( ) => {
48+ const ctx = resolveUpgradeContext ( { } , 'stable' , STABLE )
4349 expect ( ctx . channel ) . toBe ( 'stable' )
44- expect ( ctx . ref ) . toBe ( 'stable' )
50+ expect ( ctx . ref ) . toBe ( STABLE )
4551 expect ( ctx . targetVersion ) . toBeUndefined ( )
4652 } )
4753
4854 it ( 'should stay on canary (main branch) if current channel is canary and no flags' , ( ) => {
49- const ctx = resolveUpgradeContext ( { } , 'canary' )
55+ const ctx = resolveUpgradeContext ( { } , 'canary' , STABLE )
5056 expect ( ctx . channel ) . toBe ( 'canary' )
5157 expect ( ctx . ref ) . toBe ( 'main' )
5258 expect ( ctx . targetVersion ) . toBeUndefined ( )
@@ -55,94 +61,126 @@ describe('resolveUpgradeContext', () => {
5561
5662 describe ( '--canary flag' , ( ) => {
5763 it ( 'should switch to canary (main branch) when --canary is set' , ( ) => {
58- const ctx = resolveUpgradeContext ( { canary : true } , 'stable' )
64+ const ctx = resolveUpgradeContext ( { canary : true } , 'stable' , STABLE )
5965 expect ( ctx . channel ) . toBe ( 'canary' )
6066 expect ( ctx . ref ) . toBe ( 'main' )
6167 } )
6268
6369 it ( 'should stay on canary (main branch) when already on canary and --canary is set' , ( ) => {
64- const ctx = resolveUpgradeContext ( { canary : true } , 'canary' )
70+ const ctx = resolveUpgradeContext ( { canary : true } , 'canary' , STABLE )
6571 expect ( ctx . channel ) . toBe ( 'canary' )
6672 expect ( ctx . ref ) . toBe ( 'main' )
6773 } )
6874 } )
6975
7076 describe ( '--stable flag' , ( ) => {
71- it ( 'should switch to the stable branch when --stable is set from canary' , ( ) => {
72- const ctx = resolveUpgradeContext ( { stable : true } , 'canary' )
77+ it ( 'should switch to the latest stable tag when --stable is set from canary' , ( ) => {
78+ const ctx = resolveUpgradeContext ( { stable : true } , 'canary' , STABLE )
7379 expect ( ctx . channel ) . toBe ( 'stable' )
74- expect ( ctx . ref ) . toBe ( 'stable' )
80+ expect ( ctx . ref ) . toBe ( STABLE )
7581 } )
7682
77- it ( 'should stay on the stable branch when already stable and --stable is set' , ( ) => {
78- const ctx = resolveUpgradeContext ( { stable : true } , 'stable' )
83+ it ( 'should stay on the latest stable tag when already stable and --stable is set' , ( ) => {
84+ const ctx = resolveUpgradeContext ( { stable : true } , 'stable' , STABLE )
7985 expect ( ctx . channel ) . toBe ( 'stable' )
80- expect ( ctx . ref ) . toBe ( 'stable' )
86+ expect ( ctx . ref ) . toBe ( STABLE )
8187 } )
8288 } )
8389
8490 describe ( '--version flag' , ( ) => {
8591 it ( 'should use version tag without v prefix' , ( ) => {
86- const ctx = resolveUpgradeContext ( { version : '0.70.23' } , 'stable' )
92+ const ctx = resolveUpgradeContext ( { version : '0.70.23' } , 'stable' , STABLE )
8793 expect ( ctx . channel ) . toBe ( 'stable' )
8894 expect ( ctx . ref ) . toBe ( 'v0.70.23' )
8995 expect ( ctx . targetVersion ) . toBe ( '0.70.23' )
9096 } )
9197
9298 it ( 'should use version tag with v prefix as-is' , ( ) => {
93- const ctx = resolveUpgradeContext ( { version : 'v0.70.23' } , 'stable' )
99+ const ctx = resolveUpgradeContext ( { version : 'v0.70.23' } , 'stable' , STABLE )
94100 expect ( ctx . channel ) . toBe ( 'stable' )
95101 expect ( ctx . ref ) . toBe ( 'v0.70.23' )
96102 expect ( ctx . targetVersion ) . toBe ( 'v0.70.23' )
97103 } )
98104
99105 it ( 'should take priority over --canary' , ( ) => {
100- const ctx = resolveUpgradeContext ( { version : '0.70.23' , canary : true } , 'stable' )
106+ const ctx = resolveUpgradeContext ( { version : '0.70.23' , canary : true } , 'stable' , STABLE )
101107 expect ( ctx . channel ) . toBe ( 'stable' )
102108 expect ( ctx . ref ) . toBe ( 'v0.70.23' )
103109 expect ( ctx . targetVersion ) . toBe ( '0.70.23' )
104110 } )
105111
106112 it ( 'should take priority over --stable' , ( ) => {
107- const ctx = resolveUpgradeContext ( { version : '0.70.23' , stable : true } , 'canary' )
113+ const ctx = resolveUpgradeContext ( { version : '0.70.23' , stable : true } , 'canary' , STABLE )
108114 expect ( ctx . channel ) . toBe ( 'stable' )
109115 expect ( ctx . ref ) . toBe ( 'v0.70.23' )
110116 } )
111117
112118 it ( 'should take priority over persisted canary channel' , ( ) => {
113- const ctx = resolveUpgradeContext ( { version : '0.70.23' } , 'canary' )
119+ const ctx = resolveUpgradeContext ( { version : '0.70.23' } , 'canary' , STABLE )
114120 expect ( ctx . channel ) . toBe ( 'stable' )
115121 expect ( ctx . ref ) . toBe ( 'v0.70.23' )
116122 } )
117123
118124 it ( 'should handle semver-only strings' , ( ) => {
119- const ctx = resolveUpgradeContext ( { version : '1.0.0' } , 'stable' )
125+ const ctx = resolveUpgradeContext ( { version : '1.0.0' } , 'stable' , STABLE )
120126 expect ( ctx . ref ) . toBe ( 'v1.0.0' )
121127 } )
122128
123129 it ( 'should handle pre-release version strings' , ( ) => {
124- const ctx = resolveUpgradeContext ( { version : '1.0.0-beta.1' } , 'stable' )
130+ const ctx = resolveUpgradeContext ( { version : '1.0.0-beta.1' } , 'stable' , STABLE )
125131 expect ( ctx . ref ) . toBe ( 'v1.0.0-beta.1' )
126132 } )
127133 } )
128134
129135 describe ( 'flag priority' , ( ) => {
130136 it ( '--version beats --canary beats --stable beats persisted channel' , ( ) => {
131137 // version > canary
132- const v = resolveUpgradeContext ( { version : '1.0.0' , canary : true , stable : true } , 'canary' )
138+ const v = resolveUpgradeContext ( { version : '1.0.0' , canary : true , stable : true } , 'canary' , STABLE )
133139 expect ( v . ref ) . toBe ( 'v1.0.0' )
134140
135141 // canary > stable (when no version)
136- const c = resolveUpgradeContext ( { canary : true , stable : true } , 'stable' )
142+ const c = resolveUpgradeContext ( { canary : true , stable : true } , 'stable' , STABLE )
137143 expect ( c . channel ) . toBe ( 'canary' )
138144
139145 // stable > persisted canary (when no version/canary)
140- const s = resolveUpgradeContext ( { stable : true } , 'canary' )
146+ const s = resolveUpgradeContext ( { stable : true } , 'canary' , STABLE )
141147 expect ( s . channel ) . toBe ( 'stable' )
148+ expect ( s . ref ) . toBe ( STABLE )
142149 } )
143150 } )
144151} )
145152
153+ // ─── pickLatestStableTag ─────────────────────────────────────────────────────
154+
155+ describe ( 'pickLatestStableTag' , ( ) => {
156+ it ( 'picks the highest vX.Y.Z tag by semver, not lexically' , ( ) => {
157+ expect ( pickLatestStableTag ( [ 'v0.70.9' , 'v0.70.162' , 'v0.70.52' , 'v0.9.99' ] ) ) . toBe ( 'v0.70.162' )
158+ } )
159+
160+ it ( 'compares major/minor/patch numerically' , ( ) => {
161+ expect ( pickLatestStableTag ( [ 'v1.0.0' , 'v0.99.99' , 'v1.2.0' , 'v1.10.0' ] ) ) . toBe ( 'v1.10.0' )
162+ } )
163+
164+ it ( 'ignores pre-releases, deref entries, and non-version tags' , ( ) => {
165+ expect ( pickLatestStableTag ( [
166+ 'v0.70.163-beta.1' ,
167+ 'v0.70.163^{}' ,
168+ 'latest' ,
169+ 'browser-extension-v2.0.0' ,
170+ 'v0.70.162' ,
171+ ] ) ) . toBe ( 'v0.70.162' )
172+ } )
173+
174+ it ( 'trims whitespace around tags' , ( ) => {
175+ expect ( pickLatestStableTag ( [ ' v0.70.10 ' , 'v0.70.2' ] ) ) . toBe ( 'v0.70.10' )
176+ } )
177+
178+ it ( 'returns null when no clean version tag qualifies' , ( ) => {
179+ expect ( pickLatestStableTag ( [ 'latest' , 'v1.0.0-rc.1' , '' ] ) ) . toBeNull ( )
180+ expect ( pickLatestStableTag ( [ ] ) ) . toBeNull ( )
181+ } )
182+ } )
183+
146184describe ( 'local framework source selection' , ( ) => {
147185 it ( 'does not auto-detect a checkout for an exact version pin' , ( ) => {
148186 expect ( shouldAutoDetectLocalStacks ( { version : '0.70.104' } ) ) . toBe ( false )
0 commit comments