@@ -335,33 +335,106 @@ test('run pagination fails closed on empty, inconsistent, or duplicate pages', (
335335 : { total_count : 101 , workflow_runs : [ item ( 100 ) ] } ) . listRuns ( 'owner/repo' ) , / d u p l i c a t e w o r k f l o w r u n s / ) ;
336336} ) ;
337337
338- test ( 'each split publishing job rechecks eligibility and gates its registry' , ( ) => {
339- const workflow = readFileSync ( join ( __dirname , '../workflows/marketplace-pre-release.yml' ) , 'utf8' ) ;
340- for ( const [ job , name ] of [ [ 'publish-vs-marketplace' , marketplace ] , [ 'publish-open-vsx' , openVsx ] ] ) {
338+ test ( 'consolidated release workflow configures trusted publishing and rechecks pre-release eligibility' , ( ) => {
339+ const workflow = readFileSync ( join ( __dirname , '../workflows/release.yml' ) , 'utf8' ) ;
340+ assert . ok ( ! workflow . includes ( 'VSCE_PAT' ) ) ;
341+ assert . ok ( ! workflow . includes ( 'OVSX_PAT' ) ) ;
342+ assert . ok ( ! workflow . includes ( 'VSCE_TOKEN' ) ) ;
343+ assert . ok ( ! workflow . includes ( 'OPEN_VSX_TOKEN' ) ) ;
344+ assert . ok ( workflow . includes ( "format('stable-publishing-{0}', github.ref)" ) ) ;
345+ assert . ok ( ! workflow . includes ( "format('release-{0}', github.ref)" ) ) ;
346+
347+ for ( const [ job , envName , command ] of [
348+ [ 'publish-vs-marketplace' , 'vs-marketplace' , 'vsce publish --oidc' ] ,
349+ [ 'publish-open-vsx' , 'open-vsx' , 'ovsx publish --trusted-publishing' ] ,
350+ ] ) {
341351 const start = workflow . indexOf ( `\n ${ job } :` ) ;
342352 assert . ok ( start >= 0 ) ;
343- const end = workflow . indexOf ( '\n publish-' , start + 1 ) ;
344- const body = workflow . slice ( start , end < 0 ? undefined : end ) ;
353+ const nextJob = workflow . slice ( start + 1 ) . search ( / \n [ a - z - ] + : / ) ;
354+ const body = nextJob < 0 ? workflow . slice ( start ) : workflow . slice ( start , start + 1 + nextJob ) ;
355+ assert . ok ( body . includes ( `environment: ${ envName } ` ) ) ;
356+ assert . ok ( body . includes ( 'id-token: write' ) ) ;
357+ assert . ok ( body . includes ( command ) ) ;
358+ }
359+
360+ for ( const [ job , name , envName , command ] of [
361+ [ 'prerelease-publish-vs-marketplace' , marketplace , 'vs-marketplace' , 'vsce publish --oidc --pre-release' ] ,
362+ [ 'prerelease-publish-open-vsx' , openVsx , 'open-vsx' , 'ovsx publish --trusted-publishing' ] ,
363+ ] ) {
364+ const start = workflow . indexOf ( `\n ${ job } :` ) ;
365+ assert . ok ( start >= 0 ) ;
366+ const nextJob = workflow . slice ( start + 1 ) . search ( / \n [ a - z - ] + : / ) ;
367+ const body = nextJob < 0 ? workflow . slice ( start ) : workflow . slice ( start , start + 1 + nextJob ) ;
368+ assert . ok ( body . includes ( `environment: ${ envName } ` ) ) ;
369+ assert . ok ( body . includes ( 'id-token: write' ) ) ;
370+ assert . ok ( body . includes ( 'actions: read' ) ) ;
345371 const eligibility = body . indexOf ( 'id: eligibility' ) ;
346372 assert . ok ( eligibility >= 0 ) ;
347373 assert . ok ( body . indexOf ( '.github/scripts/marketplace-pre-release-history.js' , eligibility ) > eligibility ) ;
348374 const publication = body . indexOf ( `- name: ${ name } ` ) ;
349375 assert . ok ( publication > eligibility ) ;
350376 assert . ok ( body . slice ( publication ) . includes ( "if: steps.eligibility.outputs.publish == 'true'" ) ) ;
377+ assert . ok ( body . slice ( publication ) . includes ( command ) ) ;
351378 }
352379} ) ;
353380
381+ test ( 'canonical repository run lookup includes retired marketplace-pre-release workflow ID' , ( ) => {
382+ const queriedPaths = [ ] ;
383+ const api = createGhApi ( ( _command , args ) => {
384+ const path = args . find ( ( arg ) => arg . startsWith ( 'repos/' ) ) ;
385+ queriedPaths . push ( path ) ;
386+ const id = path . includes ( '/376461048/' ) ? 10 : 20 ;
387+ return `${ JSON . stringify ( {
388+ total_count : 1 ,
389+ workflow_runs : [ run ( id , '2026-11-10' , 'sha-a' ) ] ,
390+ } ) } \n`;
391+ } ) ;
392+ const runs = api . listRuns ( 'REditorSupport/vscode-R' ) ;
393+ assert . deepEqual ( queriedPaths , [
394+ 'repos/REditorSupport/vscode-R/actions/workflows/release.yml/runs' ,
395+ 'repos/REditorSupport/vscode-R/actions/workflows/376461048/runs' ,
396+ ] ) ;
397+ assert . deepEqual ( runs . map ( ( item ) => item . id ) , [ 20 , 10 ] ) ;
398+
399+ const purgedLegacyApi = createGhApi ( ( _command , args ) => {
400+ const path = args . find ( ( arg ) => arg . startsWith ( 'repos/' ) ) ;
401+ if ( path . includes ( '/376461048/' ) ) {
402+ throw new Error ( 'gh: Not Found (HTTP 404)' ) ;
403+ }
404+ return `${ JSON . stringify ( {
405+ total_count : 1 ,
406+ workflow_runs : [ run ( 20 , '2026-11-10' , 'sha-a' ) ] ,
407+ } ) } \n`;
408+ } ) ;
409+ assert . deepEqual ( purgedLegacyApi . listRuns ( 'REditorSupport/vscode-R' ) . map ( ( item ) => item . id ) , [ 20 ] ) ;
410+
411+ const transientFailureApi = createGhApi ( ( _command , args ) => {
412+ const path = args . find ( ( arg ) => arg . startsWith ( 'repos/' ) ) ;
413+ if ( path . includes ( '/376461048/' ) ) {
414+ throw new Error ( 'gh: Bad Gateway (HTTP 502)' ) ;
415+ }
416+ return `${ JSON . stringify ( { total_count : 0 , workflow_runs : [ ] } ) } \n` ;
417+ } ) ;
418+ assert . throws ( ( ) => transientFailureApi . listRuns ( 'REditorSupport/vscode-R' ) , / H T T P 5 0 2 / ) ;
419+ } ) ;
420+
354421function splitJobs ( options ) {
355422 return publishJob ( options ) . steps . map ( ( step , index ) => ( {
356423 name : [ 'publish-vs-marketplace' , 'publish-open-vsx' ] [ index ] , steps : [ step ] ,
357424 } ) ) ;
358425}
359426
427+ function prereleaseSplitJobs ( options ) {
428+ return publishJob ( options ) . steps . map ( ( step , index ) => ( {
429+ name : [ 'prerelease-publish-vs-marketplace' , 'prerelease-publish-open-vsx' ] [ index ] , steps : [ step ] ,
430+ } ) ) ;
431+ }
432+
360433test ( 'split jobs: re-running only the failed registry job completes the publication' , async ( ) => {
361434 const prior = run ( 820 , '2026-10-05' , 'sha-a' ) ;
362435 const current = run ( 821 , '2026-10-06' , 'sha-a' ) ;
363- const firstAttempt = splitJobs ( { openVsxConclusion : 'failure' } ) ;
364- const retriedOpenVsx = { ...splitJobs ( ) [ 1 ] , run_attempt : 2 } ;
436+ const firstAttempt = prereleaseSplitJobs ( { openVsxConclusion : 'failure' } ) ;
437+ const retriedOpenVsx = { ...prereleaseSplitJobs ( ) [ 1 ] , run_attempt : 2 } ;
365438 const jobs = [ ...firstAttempt , retriedOpenVsx ] ;
366439 assert . equal ( completedBothRegistries ( firstAttempt ) , false ) ;
367440 assert . equal ( completedBothRegistries ( jobs ) , true ) ;
@@ -370,7 +443,11 @@ test('split jobs: re-running only the failed registry job completes the publicat
370443 } ) ;
371444} ) ;
372445
373- for ( const [ format , jobsFor ] of [ [ 'split jobs' , splitJobs ] , [ 'old combined job' , ( options ) => [ publishJob ( options ) ] ] ] ) {
446+ for ( const [ format , jobsFor ] of [
447+ [ 'prerelease split jobs' , prereleaseSplitJobs ] ,
448+ [ 'legacy split jobs' , splitJobs ] ,
449+ [ 'old combined job' , ( options ) => [ publishJob ( options ) ] ] ,
450+ ] ) {
374451 test ( `${ format } : both registries succeeded suppress unchanged SHA` , async ( ) => {
375452 const prior = run ( 800 , '2026-10-05' , 'sha-a' ) ;
376453 const current = run ( 801 , '2026-10-06' , 'sha-a' ) ;
0 commit comments