@@ -30,6 +30,7 @@ async function createReleaseHelperFixture(
3030 options : {
3131 ghReleaseViewScript : string ;
3232 localTagMode ?: "absent" | "annotated-at-head" | "lightweight-at-head" | "wrong-target" ;
33+ remoteTagMode ?: "absent" | "annotated-at-head" | "lightweight-at-head" | "wrong-target" ;
3334 } ,
3435) : Promise < { binDir : string ; logPath : string ; scriptPath : string ; tempDir : string } > {
3536 const tempDir = await mkdtemp ( path . join ( tmpdir ( ) , "ray-gh-release-helper-" ) ) ;
@@ -44,6 +45,7 @@ async function createReleaseHelperFixture(
4445 const binDir = path . join ( tempDir , "bin" ) ;
4546 const logPath = path . join ( tempDir , "commands.log" ) ;
4647 const localTagMode = options . localTagMode ?? "absent" ;
48+ const remoteTagMode = options . remoteTagMode ?? "absent" ;
4749
4850 await writeExecutable (
4951 path . join ( binDir , "timeout" ) ,
@@ -85,6 +87,7 @@ async function createReleaseHelperFixture(
8587 "#!/usr/bin/env bash" ,
8688 "set -euo pipefail" ,
8789 `LOCAL_TAG_MODE=${ JSON . stringify ( localTagMode ) } ` ,
90+ `REMOTE_TAG_MODE=${ JSON . stringify ( remoteTagMode ) } ` ,
8891 'printf "git %s\\n" "$*" >> "${RAY_TEST_LOG:?}"' ,
8992 'if [ "${1:-}" = "status" ]; then' ,
9093 " exit 0" ,
@@ -127,7 +130,31 @@ async function createReleaseHelperFixture(
127130 " exit 0" ,
128131 "fi" ,
129132 'if [ "${1:-}" = "ls-remote" ]; then' ,
130- " exit 2" ,
133+ ' if [ "$REMOTE_TAG_MODE" = "absent" ]; then' ,
134+ " exit 2" ,
135+ " fi" ,
136+ ' tag_ref=""' ,
137+ ' for arg in "$@"; do' ,
138+ ' if [[ "$arg" == refs/tags/* && "$arg" != *"^{}" ]]; then' ,
139+ ' tag_ref="$arg"' ,
140+ " fi" ,
141+ " done" ,
142+ ' tag_name="${tag_ref#refs/tags/}"' ,
143+ ' if [ -z "$tag_name" ]; then' ,
144+ ' echo "missing tag ref" >&2' ,
145+ " exit 98" ,
146+ " fi" ,
147+ ' if [ "$REMOTE_TAG_MODE" = "lightweight-at-head" ]; then' ,
148+ ' printf "abcdef1234567890\\trefs/tags/%s\\n" "$tag_name"' ,
149+ " exit 0" ,
150+ " fi" ,
151+ ' printf "tagobject123456789\\trefs/tags/%s\\n" "$tag_name"' ,
152+ ' if [ "$REMOTE_TAG_MODE" = "wrong-target" ]; then' ,
153+ ' printf "deadbeef00000000\\trefs/tags/%s^{}\\n" "$tag_name"' ,
154+ " else" ,
155+ ' printf "abcdef1234567890\\trefs/tags/%s^{}\\n" "$tag_name"' ,
156+ " fi" ,
157+ " exit 0" ,
131158 "fi" ,
132159 'if [ "${1:-}" = "push" ] && [ "${2:-}" != "--atomic" ]; then' ,
133160 ' echo "release tag push must be atomic" >&2' ,
@@ -227,7 +254,7 @@ test("gh release helper gates destructive releases on clean synced main", async
227254 contents ,
228255 / r u n _ r e q u i r e d _ b o u n d e d " c h e c k i n g G i t H u b C L I a u t h e n t i c a t i o n " 6 0 g h a u t h s t a t u s / ,
229256 ) ;
230- assert . match ( contents , / r e m o t e _ t a g _ e x i s t s " \$ t a g " / ) ;
257+ assert . match ( contents , / r e m o t e _ r e l e a s e _ t a g _ r e a d y " \$ t a g " " \$ L O C A L _ H E A D " / ) ;
231258 assert . match ( contents , / g i t h u b _ r e l e a s e _ e x i s t s " \$ t a g " / ) ;
232259 assert . match ( contents , / b u n \. \/ s c r i p t s \/ r e l e a s e \/ c h e c k - s o u r c e \. m j s " \$ V E R " / ) ;
233260} ) ;
@@ -240,7 +267,12 @@ test("gh release helper bounds network release operations", async () => {
240267 assert . match ( contents , / t i m e o u t " \$ \{ s e c o n d s \} s " " \$ @ " / ) ;
241268 assert . match ( contents , / r e t u r n 1 2 4 / ) ;
242269 assert . match ( contents , / g i t f e t c h - - t a g s o r i g i n r e f s \/ h e a d s \/ m a i n : r e f s \/ r e m o t e s \/ o r i g i n \/ m a i n / ) ;
243- assert . match ( contents , / r u n _ b o u n d e d 6 0 g i t l s - r e m o t e - - e x i t - c o d e - - t a g s o r i g i n / ) ;
270+ assert . match (
271+ contents ,
272+ / r u n _ b o u n d e d 6 0 g i t l s - r e m o t e - - e x i t - c o d e - - t a g s o r i g i n " r e f s \/ t a g s \/ \$ t a g " " r e f s \/ t a g s \/ \$ t a g \^ \{ \} " / ,
273+ ) ;
274+ assert . match ( contents , / r e m o t e _ r e l e a s e _ t a g _ r e a d y \( \) \{ / ) ;
275+ assert . match ( contents , / f a i l " t i m e d o u t c h e c k i n g r e m o t e t a g \$ t a g " / ) ;
244276 assert . match ( contents , / f a i l " c o u l d n o t c h e c k r e m o t e t a g \$ t a g \( e x i t \$ s t a t u s \) " / ) ;
245277 assert . match ( contents , / l o c a l _ r e l e a s e _ t a g _ r e a d y \( \) \{ / ) ;
246278 assert . match ( contents , / g i t c a t - f i l e - t " r e f s \/ t a g s \/ \$ t a g " / ) ;
@@ -331,6 +363,68 @@ test("gh release helper reuses matching local annotated tags", async (t) => {
331363 assert . match ( commandLog , / ^ g i t p u s h - - a t o m i c o r i g i n c o r e - v 1 \. 2 \. 3 s d k - v 1 \. 2 \. 3 $ / m) ;
332364} ) ;
333365
366+ test ( "gh release helper resumes after remote tags or releases already exist safely" , async ( t ) => {
367+ const fixture = await createReleaseHelperFixture ( t , {
368+ ghReleaseViewScript : [
369+ ' if [ "${3:-}" = "core-v1.2.3" ]; then' ,
370+ " exit 0" ,
371+ " fi" ,
372+ ' echo "release not found" >&2' ,
373+ " exit 1" ,
374+ ] . join ( "\n" ) ,
375+ localTagMode : "annotated-at-head" ,
376+ remoteTagMode : "annotated-at-head" ,
377+ } ) ;
378+
379+ const result = await runFixtureReleaseHelper ( fixture ) ;
380+
381+ assert . equal ( result . code , 0 ) ;
382+ assert . match ( result . stdout , / R e u s i n g r e m o t e a n n o t a t e d t a g c o r e - v 1 \. 2 \. 3 / ) ;
383+ assert . match ( result . stdout , / R e u s i n g r e m o t e a n n o t a t e d t a g s d k - v 1 \. 2 \. 3 / ) ;
384+ assert . match ( result . stdout , / R e u s i n g G i t H u b r e l e a s e c o r e - v 1 \. 2 \. 3 / ) ;
385+ assert . match ( result . stdout , / R e l e a s e t a g s a l r e a d y p r e s e n t o n o r i g i n / ) ;
386+
387+ const commandLog = await readFile ( fixture . logPath , "utf8" ) ;
388+ assert . doesNotMatch ( commandLog , / ^ g i t t a g - a / m) ;
389+ assert . doesNotMatch ( commandLog , / ^ g i t p u s h / m) ;
390+ assert . doesNotMatch (
391+ commandLog ,
392+ / ^ g h r e l e a s e c r e a t e c o r e - v 1 \. 2 \. 3 - - g e n e r a t e - n o t e s - - t i t l e c o r e - v 1 \. 2 \. 3 $ / m,
393+ ) ;
394+ assert . match (
395+ commandLog ,
396+ / ^ g h r e l e a s e c r e a t e s d k - v 1 \. 2 \. 3 - - g e n e r a t e - n o t e s - - t i t l e s d k - v 1 \. 2 \. 3 $ / m,
397+ ) ;
398+ } ) ;
399+
400+ test ( "gh release helper rejects unsafe existing remote tags" , async ( t ) => {
401+ const lightweightFixture = await createReleaseHelperFixture ( t , {
402+ ghReleaseViewScript : [ ' echo "release not found" >&2' , " exit 1" ] . join ( "\n" ) ,
403+ remoteTagMode : "lightweight-at-head" ,
404+ } ) ;
405+
406+ const lightweightResult = await runFixtureReleaseHelper ( lightweightFixture ) ;
407+
408+ assert . notEqual ( lightweightResult . code , 0 ) ;
409+ assert . match (
410+ lightweightResult . stderr ,
411+ / r e m o t e t a g c o r e - v 1 \. 2 \. 3 a l r e a d y e x i s t s b u t i s n o t a n a n n o t a t e d t a g o r c o u l d n o t b e p e e l e d / ,
412+ ) ;
413+
414+ const wrongTargetFixture = await createReleaseHelperFixture ( t , {
415+ ghReleaseViewScript : [ ' echo "release not found" >&2' , " exit 1" ] . join ( "\n" ) ,
416+ remoteTagMode : "wrong-target" ,
417+ } ) ;
418+
419+ const wrongTargetResult = await runFixtureReleaseHelper ( wrongTargetFixture ) ;
420+
421+ assert . notEqual ( wrongTargetResult . code , 0 ) ;
422+ assert . match (
423+ wrongTargetResult . stderr ,
424+ / r e m o t e t a g c o r e - v 1 \. 2 \. 3 p o i n t s a t d e a d b e e f 0 0 0 0 0 0 0 0 , e x p e c t e d a b c d e f 1 2 3 4 5 6 7 8 9 0 / ,
425+ ) ;
426+ } ) ;
427+
334428test ( "gh release helper rejects unsafe existing local tags" , async ( t ) => {
335429 const lightweightFixture = await createReleaseHelperFixture ( t , {
336430 ghReleaseViewScript : [ ' echo "release not found" >&2' , " exit 1" ] . join ( "\n" ) ,
0 commit comments