@@ -56,95 +56,115 @@ export default function PreviewPRForm({
5656 await previewPr ( value ) ;
5757 }
5858
59- function renderRetryIn ( retryIn : number ) {
59+ function renderRetryIn ( retryIn : number , isBranch : boolean ) {
6060 setError (
61- `Waiting for GitHub to finish building PR ${ value } . This might take 15 minutes or more! Retrying in ${
61+ `Waiting for GitHub to finish building ${
62+ isBranch ? 'branch' : 'PR'
63+ } ${ value } . This might take 15 minutes or more! Retrying in ${
6264 retryIn / 1000
6365 } ...`
6466 ) ;
6567 }
6668
69+ function buildArtifactUrl ( ref : string , isBranch : boolean ) : string {
70+ const refType = isBranch ? 'branch' : 'pr' ;
71+ // For WordPress PRs: artifact name is wordpress-build-{PR_NUMBER}
72+ // For WordPress branches: artifact name is wordpress-build-{COMMIT_HASH}
73+ // We use wordpress-build- (with trailing dash) to trigger prefix matching
74+ // For Gutenberg: artifact name is always gutenberg-plugin
75+ let artifactSuffix = '' ;
76+ if ( target === 'wordpress' ) {
77+ artifactSuffix = isBranch ? '-' : ref ;
78+ }
79+ return `https://playground.wordpress.net/plugin-proxy.php?org=WordPress&repo=${ targetParams [ target ] . repo } &workflow=${ targetParams [ target ] . workflow } &artifact=${ targetParams [ target ] . artifact } ${ artifactSuffix } &${ refType } =${ ref } ` ;
80+ }
81+
6782 async function previewPr ( prValue : string ) {
6883 let cleanupRetry = ( ) => { } ;
6984 if ( cleanupRetry ) {
7085 cleanupRetry ( ) ;
7186 }
7287
7388 let prNumber : string = prValue ;
89+ let branchName : string | null = null ;
7490 setSubmitting ( true ) ;
7591
7692 // Extract number from a GitHub URL
7793 if ( prNumber . toLowerCase ( ) . includes ( targetParams [ target ] . pull ) ) {
7894 prNumber = prNumber . match ( / \/ p u l l \/ ( \d + ) / ) ! [ 1 ] ;
95+ } else if ( ! / ^ \d + $ / . test ( prNumber ) ) {
96+ // If it's not a number and not a PR URL, treat it as a branch name
97+ branchName = prNumber ;
7998 }
8099
81- // Verify that the PR exists and that GitHub CI finished building it
82- const zipArtifactUrl = `https://playground.wordpress.net/plugin-proxy.php?org=WordPress&repo=${
83- targetParams [ target ] . repo
84- } &workflow=${ targetParams [ target ] . workflow } &artifact=${
85- targetParams [ target ] . artifact
86- } ${ target === 'wordpress' ? prNumber : '' } &pr=${ prNumber } `;
87- // Send the HEAD request to zipArtifactUrl to confirm the PR and the artifact both exist
88- const response = await fetch ( zipArtifactUrl + '&verify_only=true' ) ;
89- if ( response . status !== 200 ) {
90- let error = 'invalid_pr_number' ;
91- try {
92- const json = await response . json ( ) ;
93- if ( json . error ) {
94- error = json . error ;
100+ const ref = branchName || prNumber ;
101+ const isBranch = ! ! branchName ;
102+
103+ // For branches, skip verification since we'll use the most recent artifact with prefix matching
104+ // For PRs, verify that the specific PR build exists
105+ if ( ! isBranch ) {
106+ const zipArtifactUrl = buildArtifactUrl ( ref , isBranch ) ;
107+ const response = await fetch ( zipArtifactUrl + '&verify_only=true' ) ;
108+ if ( response . status !== 200 ) {
109+ let error = 'invalid_pr_number' ;
110+ try {
111+ const json = await response . json ( ) ;
112+ if ( json . error ) {
113+ error = json . error ;
114+ }
115+ } catch ( e ) {
116+ logger . error ( e ) ;
117+ setError ( 'An unexpected error occurred. Please try again.' ) ;
118+ return ;
95119 }
96- } catch ( e ) {
97- logger . error ( e ) ;
98- setError ( 'An unexpected error occurred. Please try again.' ) ;
99- return ;
100- }
101120
102- if ( error === 'invalid_pr_number' ) {
103- setError ( `The PR ${ prNumber } does not exist.` ) ;
104- } else if (
105- error === 'artifact_not_found' ||
106- error === 'artifact_not_available'
107- ) {
108- if ( parseInt ( prNumber ) < 5749 ) {
121+ if ( error === 'invalid_pr_number' || error === 'no_ci_runs' ) {
122+ setError ( `The PR ${ ref } does not exist.` ) ;
123+ } else if (
124+ error === 'artifact_not_found' ||
125+ error === 'artifact_not_available'
126+ ) {
127+ if ( parseInt ( ref ) < 5749 ) {
128+ setError (
129+ `The PR ${ ref } predates the Pull Request previewer and requires a rebase before it can be previewed.`
130+ ) ;
131+ } else {
132+ // For PRs, retry since we expect a specific build to complete
133+ let retryIn = 30000 ;
134+ renderRetryIn ( retryIn , false ) ;
135+ const timerInterval = setInterval ( ( ) => {
136+ retryIn -= 1000 ;
137+ if ( retryIn <= 0 ) {
138+ retryIn = 0 ;
139+ }
140+ renderRetryIn ( retryIn , false ) ;
141+ } , 1000 ) ;
142+ const scheduledRetry = setTimeout ( ( ) => {
143+ previewPr ( ref ) ;
144+ } , retryIn ) ;
145+ cleanupRetry = ( ) => {
146+ clearInterval ( timerInterval ) ;
147+ clearTimeout ( scheduledRetry ) ;
148+ cleanupRetry = ( ) => { } ;
149+ } ;
150+ }
151+ } else if ( error === 'artifact_invalid' ) {
109152 setError (
110- `The PR ${ prNumber } predates the Pull Request previewer and requires a rebase before it can be previewed.`
153+ `The PR ${ ref } requires a rebase before it can be previewed.`
111154 ) ;
112155 } else {
113- let retryIn = 30000 ;
114- renderRetryIn ( retryIn ) ;
115- const timerInterval = setInterval ( ( ) => {
116- retryIn -= 1000 ;
117- if ( retryIn <= 0 ) {
118- retryIn = 0 ;
119- }
120- renderRetryIn ( retryIn ) ;
121- } , 1000 ) ;
122- const scheduledRetry = setTimeout ( ( ) => {
123- previewPr ( prNumber ) ;
124- } , retryIn ) ;
125- cleanupRetry = ( ) => {
126- clearInterval ( timerInterval ) ;
127- clearTimeout ( scheduledRetry ) ;
128- cleanupRetry = ( ) => { } ;
129- } ;
156+ setError (
157+ `The PR ${ ref } couldn't be previewed due to an unexpected error. Please try again later or fill an issue in the WordPress Playground repository.`
158+ ) ;
159+ // https://github.com/WordPress/wordpress-playground/issues/new
130160 }
131- } else if ( error === 'artifact_invalid' ) {
132- setError (
133- `The PR ${ prNumber } requires a rebase before it can be previewed.`
134- ) ;
135- } else {
136- setError (
137- `The PR ${ prNumber } couldn't be previewed due to an unexpected error. Please try again later or fill an issue in the WordPress Playground repository.`
138- ) ;
139- // https://github.com/WordPress/wordpress-playground/issues/new
140- }
141-
142- setSubmitting ( false ) ;
143161
144- return ;
162+ setSubmitting ( false ) ;
163+ return ;
164+ }
145165 }
146166
147- // Redirect to the Playground site with the Blueprint to download and apply the PR
167+ // Redirect to the Playground site with the Blueprint to download and apply the PR/branch
148168 const blueprint : BlueprintV1Declaration = {
149169 landingPage : urlParams . get ( 'url' ) || '/wp-admin' ,
150170 login : true ,
@@ -154,26 +174,25 @@ export default function PreviewPRForm({
154174 steps : [ ] ,
155175 } ;
156176
177+ const refParam = isBranch
178+ ? `${ target === 'wordpress' ? 'core' : 'gutenberg' } -branch`
179+ : `${ target === 'wordpress' ? 'core' : 'gutenberg' } -pr` ;
180+ const urlWithPreview = new URL (
181+ window . location . pathname ,
182+ window . location . href
183+ ) ;
184+
157185 if ( target === 'wordpress' ) {
158186 // [wordpress] Passthrough the mode query parameter if it exists
159- const targetParams = new URLSearchParams ( ) ;
160187 if ( urlParams . has ( 'mode' ) ) {
161- targetParams . set ( 'mode' , urlParams . get ( 'mode' ) as string ) ;
188+ urlWithPreview . searchParams . set (
189+ 'mode' ,
190+ urlParams . get ( 'mode' ) as string
191+ ) ;
162192 }
163- targetParams . set ( 'core-pr' , prNumber ) ;
164-
165- const blueprintJson = JSON . stringify ( blueprint ) ;
166- const urlWithPreview = new URL (
167- window . location . pathname ,
168- window . location . href
169- ) ;
170- urlWithPreview . search = targetParams . toString ( ) ;
171- urlWithPreview . hash = encodeURI ( blueprintJson ) ;
172-
173- window . location . href = urlWithPreview . toString ( ) ;
193+ urlWithPreview . searchParams . set ( refParam , ref ) ;
174194 } else if ( target === 'gutenberg' ) {
175195 // [gutenberg] If there's a import-site query parameter, pass that to the blueprint
176- const urlParams = new URLSearchParams ( window . location . search ) ;
177196 try {
178197 const importSite = new URL (
179198 urlParams . get ( 'import-site' ) as string
@@ -191,18 +210,11 @@ export default function PreviewPRForm({
191210 } catch {
192211 logger . error ( 'Invalid import-site URL' ) ;
193212 }
194-
195- const blueprintJson = JSON . stringify ( blueprint ) ;
196-
197- const urlWithPreview = new URL (
198- window . location . pathname ,
199- window . location . href
200- ) ;
201- urlWithPreview . searchParams . set ( 'gutenberg-pr' , prNumber ) ;
202- urlWithPreview . hash = encodeURI ( blueprintJson ) ;
203-
204- window . location . href = urlWithPreview . toString ( ) ;
213+ urlWithPreview . searchParams . set ( refParam , ref ) ;
205214 }
215+
216+ urlWithPreview . hash = encodeURI ( JSON . stringify ( blueprint ) ) ;
217+ window . location . href = urlWithPreview . toString ( ) ;
206218 }
207219
208220 return (
@@ -215,7 +227,7 @@ export default function PreviewPRForm({
215227 ) }
216228 < TextControl
217229 disabled = { submitting }
218- label = "Pull request number or URL "
230+ label = "PR number, URL, or a branch name "
219231 value = { value }
220232 autoFocus
221233 onChange = { ( e ) => {
0 commit comments