@@ -150,6 +150,35 @@ describe("cloneRepo", () => {
150150 expect ( mockGitInstance . checkout ) . toHaveBeenCalledWith ( "v1.0.0" ) ;
151151 } ) ;
152152
153+ it ( "sparse + tag: falls back to alternate v-prefix on fetch failure" , async ( ) => {
154+ const noVConfig : RepoConfig = {
155+ ...sparseConfig ,
156+ tag : "1.0.0" , // no v prefix
157+ } ;
158+ mockExistsSync . mockReturnValue ( false ) ;
159+ mockGitInstance . clone . mockResolvedValue ( undefined ) ;
160+ mockGitInstance . raw . mockResolvedValue ( undefined ) ;
161+ // First fetch (without v) fails, second (with v) succeeds
162+ mockGitInstance . fetch
163+ . mockRejectedValueOnce ( new Error ( "not found" ) )
164+ . mockResolvedValueOnce ( undefined ) ;
165+ mockGitInstance . checkout . mockResolvedValue ( undefined ) ;
166+
167+ const result = await cloneRepo ( noVConfig ) ;
168+ expect ( result ) . toContain ( "Cloned aztec-packages" ) ;
169+
170+ // First attempt: refs/tags/1.0.0
171+ expect ( mockGitInstance . fetch ) . toHaveBeenCalledWith ( [
172+ "--depth=1" , "origin" , "refs/tags/1.0.0:refs/tags/1.0.0" ,
173+ ] ) ;
174+ // Fallback: refs/tags/v1.0.0
175+ expect ( mockGitInstance . fetch ) . toHaveBeenCalledWith ( [
176+ "--depth=1" , "origin" , "refs/tags/v1.0.0:refs/tags/v1.0.0" ,
177+ ] ) ;
178+ // Checkout uses the resolved tag name
179+ expect ( mockGitInstance . checkout ) . toHaveBeenCalledWith ( "v1.0.0" ) ;
180+ } ) ;
181+
153182 it ( "sparse + commit: clones with sparse flags, fetches commit" , async ( ) => {
154183 const commitConfig : RepoConfig = {
155184 ...sparseConfig ,
@@ -211,6 +240,30 @@ describe("cloneRepo", () => {
211240 expect ( sparseCheckoutCalls ) . toHaveLength ( 0 ) ;
212241 } ) ;
213242
243+ it ( "non-sparse + tag: falls back to stripping v-prefix on fetch failure" , async ( ) => {
244+ const vConfig : RepoConfig = {
245+ ...nonSparseConfig ,
246+ tag : "v2.0.0" ,
247+ } ;
248+ mockExistsSync . mockReturnValue ( false ) ;
249+ mockGitInstance . clone . mockResolvedValue ( undefined ) ;
250+ // First fetch (with v) fails, second (without v) succeeds
251+ mockGitInstance . fetch
252+ . mockRejectedValueOnce ( new Error ( "not found" ) )
253+ . mockResolvedValueOnce ( undefined ) ;
254+ mockGitInstance . checkout . mockResolvedValue ( undefined ) ;
255+
256+ await cloneRepo ( vConfig ) ;
257+
258+ expect ( mockGitInstance . fetch ) . toHaveBeenCalledWith ( [
259+ "--depth=1" , "origin" , "refs/tags/v2.0.0:refs/tags/v2.0.0" ,
260+ ] ) ;
261+ expect ( mockGitInstance . fetch ) . toHaveBeenCalledWith ( [
262+ "--depth=1" , "origin" , "refs/tags/2.0.0:refs/tags/2.0.0" ,
263+ ] ) ;
264+ expect ( mockGitInstance . checkout ) . toHaveBeenCalledWith ( "2.0.0" ) ;
265+ } ) ;
266+
214267 it ( "force=true clones to temp dir then swaps" , async ( ) => {
215268 // existsSync calls:
216269 // 1) needsReclone -> isRepoCloned(.git) -> false (needs reclone)
@@ -476,6 +529,34 @@ describe("needsReclone", () => {
476529 expect ( result ) . toBe ( true ) ;
477530 } ) ;
478531
532+ it ( "returns false when tag matches via v-prefix alternate" , async ( ) => {
533+ mockExistsSync . mockReturnValue ( true ) ;
534+ // Repo is checked out at "v1.0.0" but config requests "1.0.0" (no v)
535+ mockGitInstance . raw . mockResolvedValue ( "v1.0.0\n" ) ;
536+
537+ const result = await needsReclone ( {
538+ name : "test" ,
539+ url : "test" ,
540+ tag : "1.0.0" ,
541+ description : "test" ,
542+ } ) ;
543+ expect ( result ) . toBe ( false ) ;
544+ } ) ;
545+
546+ it ( "returns false when tag matches via v-prefix stripped" , async ( ) => {
547+ mockExistsSync . mockReturnValue ( true ) ;
548+ // Repo is checked out at "1.0.0" but config requests "v1.0.0"
549+ mockGitInstance . raw . mockResolvedValue ( "1.0.0\n" ) ;
550+
551+ const result = await needsReclone ( {
552+ name : "test" ,
553+ url : "test" ,
554+ tag : "v1.0.0" ,
555+ description : "test" ,
556+ } ) ;
557+ expect ( result ) . toBe ( false ) ;
558+ } ) ;
559+
479560 it ( "returns false for branch-only config when cloned" , async ( ) => {
480561 mockExistsSync . mockReturnValue ( true ) ;
481562
0 commit comments