11import { spawn } from "node:child_process"
22import { createHash , createHmac } from "node:crypto"
3- import { mkdtemp , rm , writeFile } from "node:fs/promises"
3+ import { mkdtemp , readFile , rm , writeFile } from "node:fs/promises"
44import { tmpdir } from "node:os"
55import { join } from "node:path"
66import { stripVTControlCharacters } from "node:util"
@@ -11,7 +11,11 @@ const origin = `http://127.0.0.1:${port}`
1111const password = "cloudflare-runtime-test-password"
1212const authSecret = "cloudflare-runtime-test-auth-secret"
1313const operatorToken = "cloudflare-runtime-test-operator-token"
14+ const apiToken = "cloudflare-runtime-test-api-token"
15+ const administratorClaimSecret = "cloudflare-runtime-test-administrator-claim-secret"
1416const coordinator = process . argv . includes ( "--coordinator=d1" ) ? "d1" : "durable-object"
17+ const publicProvisioning = process . argv . includes ( "--public-provisioning" )
18+ const artifactPath = process . argv . find ( ( argument ) => argument . startsWith ( "--artifact=" ) ) ?. slice ( "--artifact=" . length )
1519const wranglerConfig = coordinator === "d1" ? "packages/runtime-cloudflare/wrangler.d1.jsonc" : "packages/runtime-cloudflare/wrangler.jsonc"
1620const stateDirectory = await mkdtemp ( join ( tmpdir ( ) , "wp-codebox-cloudflare-gate-" ) )
1721const cookies = [ ]
2933 await run ( "npm" , [ "run" , "provision:cloudflare-wordpress-runtime-corpus" , "--" , "--local" , "--persist-to" , stateDirectory ] )
3034 const staticArtifactImport = await provisionStaticArtifact ( )
3135 await startWorker ( )
36+ if ( publicProvisioning ) {
37+ await assertPublicProvisioning ( staticArtifactImport )
38+ console . log ( "Cloudflare public provisioning gate passed: authenticated artifact staging, idempotent site allocation, scheduled import, cold-restart site read, administrator claim, login, and editable native blocks." )
39+ } else {
3240 await assertFullBootProbe ( )
3341 await assertWordPressCronDisabled ( )
3442 await assertConcurrentMutations ( )
@@ -113,6 +121,7 @@ try {
113121 if ( conflicting . status !== "conflict" || conflictStateAfter . version !== duplicateStateBefore . version || conflictStateAfter . pointer ?. revision !== duplicateStateBefore . pointer ?. revision ) throw new Error ( "Conflicting static artifact replay changed canonical state." )
114122 await assertTwoSiteIsolation ( )
115123 console . log ( `Cloudflare local runtime gate passed with ${ coordinator } coordination: canonical full-boot probe, explanatory homepage, complete block styles, coordinator-free R2 publication reads, login, dashboard, post editor, concurrent canonical mutations, authenticated REST post and media creation, plugin ZIP installation and activation, direct R2 upload serving, frontend/admin/editor assets, cold-restart persistence, and bounded durable scheduled callback execution.` )
124+ }
116125} finally {
117126 await stopWorker ( )
118127 await rm ( stateDirectory , { recursive : true , force : true } )
@@ -127,7 +136,7 @@ async function run(command, args) {
127136}
128137
129138async function provisionStaticArtifact ( ) {
130- const artifact = {
139+ const fixture = {
131140 schema : "blocks-engine/php-transformer/site-artifact/v1" ,
132141 artifact_type : "website" ,
133142 version : 1 ,
@@ -149,18 +158,85 @@ async function provisionStaticArtifact() {
149158 content : "<!doctype html><html><head><meta charset=\"utf-8\"><title>About the Verified Artifact</title></head><body><main><h1>About the Verified Artifact</h1><p>A second imported page.</p></main></body></html>" ,
150159 } ] ,
151160 }
152- const serialized = JSON . stringify ( artifact )
161+ const serialized = artifactPath ? await readFile ( artifactPath , "utf8" ) : JSON . stringify ( fixture )
162+ const artifact = JSON . parse ( serialized )
163+ if ( artifact ?. schema !== "blocks-engine/php-transformer/site-artifact/v1" ) throw new Error ( `The provisioning artifact at ${ artifactPath } does not use the canonical portable schema.` )
153164 const sha256 = createHash ( "sha256" ) . update ( serialized ) . digest ( "hex" )
154165 const key = `sites/default/import-artifacts/${ sha256 } .json`
155- const path = join ( stateDirectory , "static-site-artifact.json" )
156- await writeFile ( path , serialized )
157- await run ( "npm" , [ "exec" , "--" , "wrangler" , "r2" , "object" , "put" , `wp-codebox-runtime-chubes/${ key } ` , "--file" , path , "--local" , "--persist-to" , stateDirectory ] )
158- return {
166+ if ( ! publicProvisioning ) {
167+ const path = join ( stateDirectory , "static-site-artifact.json" )
168+ await writeFile ( path , serialized )
169+ await run ( "npm" , [ "exec" , "--" , "wrangler" , "r2" , "object" , "put" , `wp-codebox-runtime-chubes/${ key } ` , "--file" , path , "--local" , "--persist-to" , stateDirectory ] )
170+ }
171+ const request = {
159172 schema : "wp-codebox/cloudflare-static-artifact-import-request/v1" ,
160173 idempotencyKey : `cloudflare-static-artifact-${ sha256 } ` ,
161174 artifact : { r2Key : key , sha256, size : Buffer . byteLength ( serialized ) } ,
162175 import : { slug : "verified-artifact-site" , name : "Verified Artifact Site" , siteTitle : "Verified Artifact Site" } ,
163176 }
177+ return publicProvisioning ? { ...request , serialized } : request
178+ }
179+
180+ async function assertPublicProvisioning ( input ) {
181+ const authorization = { authorization : `Bearer ${ apiToken } ` }
182+ const expectedPageCount = JSON . parse ( input . serialized ) . files . filter ( ( file ) => file . path . endsWith ( ".html" ) ) . length
183+ const stagedResponse = await fetch ( `${ origin } /v1/artifacts/${ input . artifact . sha256 } ` , {
184+ method : "PUT" ,
185+ headers : { ...authorization , "content-type" : "application/json" } ,
186+ body : input . serialized ,
187+ } )
188+ const stagedBody = await stagedResponse . text ( )
189+ if ( stagedResponse . status !== 200 ) throw new Error ( `Public artifact staging failed: ${ stagedResponse . status } ${ stagedBody } .` )
190+ const staged = JSON . parse ( stagedBody )
191+ if ( staged . schema !== "wp-codebox/provisioning-artifact/v1" || staged . artifact ?. sha256 !== input . artifact . sha256 || staged . artifact ?. size !== input . artifact . size
192+ || staged . artifact ?. r2Key !== `sites/provisioning/import-artifacts/${ input . artifact . sha256 } .json` ) {
193+ throw new Error ( `Public artifact staging returned an invalid reference: ${ stagedBody } .` )
194+ }
195+
196+ const idempotencyKey = `cloudflare-public-provisioning-${ input . artifact . sha256 } `
197+ const requestBody = JSON . stringify ( { schema : "wp-codebox/provisioning-create-request/v1" , idempotencyKey, artifact : staged . artifact , import : input . import } )
198+ const createSite = ( ) => fetch ( `${ origin } /v1/sites` , { method : "POST" , headers : { ...authorization , "content-type" : "application/json" , "idempotency-key" : idempotencyKey } , body : requestBody } )
199+ const createdResponse = await createSite ( )
200+ const createdBody = await createdResponse . text ( )
201+ if ( createdResponse . status !== 202 ) throw new Error ( `Public site creation failed: ${ createdResponse . status } ${ createdBody } .` )
202+ const created = JSON . parse ( createdBody )
203+ if ( created . schema !== "wp-codebox/provisioning-site/v1" || created . site ?. id !== "default" || typeof created . site . operation !== "string"
204+ || typeof created . site . administratorClaim ?. token !== "string" ) throw new Error ( `Public site creation returned an invalid resource: ${ createdBody } .` )
205+ const replayResponse = await createSite ( )
206+ const replayBody = await replayResponse . text ( )
207+ if ( replayResponse . status !== 202 || replayBody !== createdBody ) throw new Error ( `Public site creation replay did not converge: ${ replayResponse . status } ${ replayBody } .` )
208+
209+ let operation
210+ for ( let tick = 0 ; tick < 8 ; tick ++ ) {
211+ const response = await fetch ( new URL ( created . site . operation , origin ) , { headers : authorization } )
212+ const body = await response . text ( )
213+ if ( ! response . ok ) throw new Error ( `Public provisioning operation read failed: ${ response . status } ${ body } .` )
214+ operation = JSON . parse ( body ) . operation
215+ if ( operation ?. state === "succeeded" ) break
216+ if ( operation ?. state === "failed" ) throw new Error ( `Public provisioning operation failed: ${ body } .` )
217+ await runScheduledCron ( )
218+ }
219+ const imported = operation ?. receipt ?. ssiResult
220+ if ( operation ?. state !== "succeeded" || imported ?. status !== "imported" || imported . staticSiteImporterVersion !== "1.3.4" || ! imported . themeSlug
221+ || ! imported . pages || Object . keys ( imported . pages ) . length !== expectedPageCount || Object . values ( imported . quality ?? { } ) . some ( ( count ) => count !== 0 )
222+ || operation . receipt ?. publication ?. status !== "none" ) {
223+ throw new Error ( `Public provisioning did not produce a terminal import receipt: ${ JSON . stringify ( operation ) } .` )
224+ }
225+
226+ await stopWorker ( )
227+ await startWorker ( )
228+ const siteResponse = await fetch ( `${ origin } /v1/sites/${ created . site . id } ` , { headers : authorization } )
229+ const siteBody = await siteResponse . text ( )
230+ const site = JSON . parse ( siteBody )
231+ if ( ! siteResponse . ok || site . site ?. status !== "ready" || site . site ?. administratorClaim ?. state !== "pending" || siteBody . includes ( created . site . administratorClaim . token ) ) {
232+ throw new Error ( `Cold-restart public site read returned invalid state: ${ siteResponse . status } ${ siteBody } .` )
233+ }
234+ const claimResponse = await fetch ( new URL ( created . site . administratorClaim . url , origin ) , { method : "POST" , headers : { authorization : `Bearer ${ created . site . administratorClaim . token } ` } } )
235+ const claimBody = await claimResponse . text ( )
236+ const claim = JSON . parse ( claimBody )
237+ if ( ! claimResponse . ok || claim . credential ?. username !== "admin" || claim . credential ?. password !== password ) throw new Error ( `Administrator claim failed: ${ claimResponse . status } ${ claimBody } .` )
238+ const adminHtml = await login ( claim . credential . password )
239+ await assertImportedArtifactPages ( adminHtml , imported )
164240}
165241
166242async function assertTwoSiteIsolation ( ) {
@@ -238,7 +314,8 @@ async function assertTwoSiteIsolation() {
238314
239315async function startWorker ( ) {
240316 output = ""
241- child = spawn ( "npm" , [ "exec" , "--" , "wrangler" , "dev" , "--test-scheduled" , "--config" , wranglerConfig , "--port" , String ( port ) , "--persist-to" , stateDirectory , "--var" , `WORDPRESS_ADMIN_PASSWORD:${ password } ` , "--var" , `WORDPRESS_AUTH_SECRET:${ authSecret } ` , "--var" , `WORDPRESS_OPERATOR_TOKEN:${ operatorToken } ` , "--var" , `WORDPRESS_SITE_CONTEXTS:${ JSON . stringify ( siteContexts ) } ` ] , {
317+ const apiTokens = [ { id : "local-gate" , principal : "local-gate" , digest : createHash ( "sha256" ) . update ( apiToken ) . digest ( "hex" ) , scopes : [ "sites:create" , "sites:read" , "sites:import" , "operations:read" ] , expiresAt : "2099-01-01T00:00:00.000Z" , maxSites : 1 } ]
318+ child = spawn ( "npm" , [ "exec" , "--" , "wrangler" , "dev" , "--test-scheduled" , "--config" , wranglerConfig , "--port" , String ( port ) , "--persist-to" , stateDirectory , "--var" , `WORDPRESS_ADMIN_PASSWORD:${ password } ` , "--var" , `WORDPRESS_ADMIN_CLAIM_SECRET:${ administratorClaimSecret } ` , "--var" , `WORDPRESS_AUTH_SECRET:${ authSecret } ` , "--var" , `WORDPRESS_OPERATOR_TOKEN:${ operatorToken } ` , "--var" , `WORDPRESS_API_TOKENS:${ JSON . stringify ( apiTokens ) } ` , "--var" , `WORDPRESS_SITE_CONTEXTS:${ JSON . stringify ( siteContexts ) } ` ] , {
242319 cwd : process . cwd ( ) ,
243320 // The host PAC resolves these public archive hosts through an unavailable local proxy.
244321 env : { ...process . env , NO_PROXY : "wordpress.org,github.com,codeload.github.com" , no_proxy : "wordpress.org,github.com,codeload.github.com" } ,
@@ -277,9 +354,9 @@ async function assertLoginForm() {
277354 if ( ! / < f o r m [ ^ > ] + i d = [ " ' ] l o g i n f o r m [ " ' ] / i. test ( html ) ) throw new Error ( "wp-login.php did not return the login form." )
278355}
279356
280- async function login ( ) {
357+ async function login ( candidatePassword = password ) {
281358 await assertLoginForm ( )
282- const form = new URLSearchParams ( { log : "admin" , pwd : password , redirect_to : `${ origin } /wp-admin/` , testcookie : "1" , "wp-submit" : "Log In" } )
359+ const form = new URLSearchParams ( { log : "admin" , pwd : candidatePassword , redirect_to : `${ origin } /wp-admin/` , testcookie : "1" , "wp-submit" : "Log In" } )
283360 const response = await request ( `${ origin } /wp-login.php` , { method : "POST" , headers : { "content-type" : "application/x-www-form-urlencoded" } , body : form , redirect : "manual" } )
284361 if ( ! [ 301 , 302 ] . includes ( response . status ) ) throw new Error ( `Expected login redirect, received ${ response . status } : ${ await response . text ( ) } ` )
285362 if ( response . headers . get ( "x-wp-codebox-publication" ) === "queued" || response . headers . has ( "x-wp-codebox-publication-revision" ) ) throw new Error ( "Login must not enqueue or promote publication work." )
@@ -325,7 +402,7 @@ async function importStaticArtifact(input, expectedStatus = 201) {
325402
326403async function assertImportedArtifactPages ( adminHtml , imported ) {
327404 const pageIds = Object . values ( imported . pages ?? { } ) . filter ( Number . isInteger )
328- if ( pageIds . length !== 2 ) throw new Error ( `Static artifact import did not return two page IDs: ${ JSON . stringify ( imported ) } .` )
405+ if ( ! pageIds . length ) throw new Error ( `Static artifact import did not return page IDs: ${ JSON . stringify ( imported ) } .` )
329406 const pages = [ ]
330407 for ( const pageId of pageIds ) {
331408 const response = await request ( `${ origin } /wp-json/wp/v2/pages/${ pageId } ?context=edit` , { headers : { "x-wp-nonce" : restNonce ( adminHtml ) } } )
@@ -337,10 +414,10 @@ async function assertImportedArtifactPages(adminHtml, imported) {
337414 const link = new URL ( page . link )
338415 pages . push ( { id : pageId , route : `${ link . pathname } ${ link . search } ` , raw } )
339416 }
340- const primary = pages . find ( ( { raw } ) => raw . includes ( "Imported from a verified R2 artifact." ) )
341- const secondary = pages . find ( ( { raw } ) => raw . includes ( "A second imported page." ) )
342- if ( ! primary || ! secondary || secondary . route === "/" ) throw new Error ( `Imported artifact routes are invalid: ${ JSON . stringify ( pages ) } .` )
343- return { primary, secondary }
417+ const primary = pages . find ( ( { route } ) => route === "/" )
418+ const secondary = pages . find ( ( { route } ) => route !== "/" )
419+ if ( ! primary || ( pages . length > 1 && ! secondary ) ) throw new Error ( `Imported artifact routes are invalid: ${ JSON . stringify ( pages ) } .` )
420+ return { primary, secondary : secondary ?? primary }
344421}
345422
346423async function updatePost ( adminHtml , post , previousPublicationRevision ) {
0 commit comments