1
+ import { SupportedPHPVersions } from '@php-wasm/universal' ;
2
+ import { RecommendedPHPVersion } from '@wp-playground/common' ;
1
3
import fs from 'fs' ;
2
4
import path from 'path' ;
3
5
import yargs from 'yargs' ;
4
- import readline from 'readline' ;
5
- import { startServer } from './server' ;
6
- import {
7
- PHP ,
8
- PHPRequest ,
9
- PHPRequestHandler ,
10
- PHPResponse ,
11
- SupportedPHPVersion ,
12
- SupportedPHPVersions ,
13
- } from '@php-wasm/universal' ;
14
- import { logger , errorLogPath } from '@php-wasm/logger' ;
15
- import {
16
- Blueprint ,
17
- compileBlueprint ,
18
- runBlueprintSteps ,
19
- } from '@wp-playground/blueprints' ;
20
6
import { isValidWordPressSlug } from './is-valid-wordpress-slug' ;
21
- import { EmscriptenDownloadMonitor , ProgressTracker } from '@php-wasm/progress' ;
22
- import { createNodeFsMountHandler , loadNodeRuntime } from '@php-wasm/node' ;
23
- import { RecommendedPHPVersion , zipDirectory } from '@wp-playground/common' ;
24
- import { bootWordPress } from '@wp-playground/wordpress' ;
25
- import { rootCertificates } from 'tls' ;
26
- import {
27
- CACHE_FOLDER ,
28
- cachedDownload ,
29
- fetchSqliteIntegration ,
30
- readAsFile ,
31
- } from './download' ;
32
- import { resolveWordPressRelease } from '@wp-playground/wordpress' ;
7
+ import { runCLI , RunCLIArgs } from './run-cli' ;
8
+
33
9
export interface Mount {
34
10
hostPath : string ;
35
11
vfsPath : string ;
@@ -40,12 +16,12 @@ async function run() {
40
16
* @TODO This looks similar to Query API args https://wordpress.github.io/wordpress-playground/developers/apis/query-api/
41
17
* Perhaps the two could be handled by the same code?
42
18
*/
43
- const yargsObject = await yargs ( process . argv . slice ( 2 ) )
19
+ const yargsObject = yargs ( process . argv . slice ( 2 ) )
44
20
. usage ( 'Usage: wp-playground <command> [options]' )
45
21
. positional ( 'command' , {
46
22
describe : 'Command to run' ,
47
- type : 'string' ,
48
- choices : [ 'server' , 'run-blueprint' , 'build-snapshot' ] ,
23
+ choices : [ 'server' , 'run-blueprint' , 'build-snapshot' ] as const ,
24
+ demandOption : true ,
49
25
} )
50
26
. option ( 'outfile' , {
51
27
describe : 'When building, write to this output file.' ,
@@ -142,269 +118,16 @@ async function run() {
142
118
yargsObject . wrap ( yargsObject . terminalWidth ( ) ) ;
143
119
const args = await yargsObject . argv ;
144
120
145
- if ( args . quiet ) {
146
- // @ts -ignore
147
- logger . handlers = [ ] ;
148
- }
149
-
150
- /**
151
- * TODO: This exact feature will be provided in the PHP Blueprints library.
152
- * Let's use it when it ships. Let's also use it in the web Playground
153
- * app.
154
- */
155
- async function zipSite ( outfile : string ) {
156
- // Fake URL for the build
157
- const { php, reap } =
158
- await requestHandler . processManager . acquirePHPInstance ( ) ;
159
- try {
160
- await php . run ( {
161
- code : `<?php
162
- $zip = new ZipArchive();
163
- if(false === $zip->open('/tmp/build.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
164
- throw new Exception('Failed to create ZIP');
165
- }
166
- $files = new RecursiveIteratorIterator(
167
- new RecursiveDirectoryIterator('/wordpress')
168
- );
169
- foreach ($files as $file) {
170
- echo $file . PHP_EOL;
171
- if (!$file->isFile()) {
172
- continue;
173
- }
174
- $zip->addFile($file->getPathname(), $file->getPathname());
175
- }
176
- $zip->close();
177
-
178
- ` ,
179
- } ) ;
180
- const zip = php . readFileAsBuffer ( '/tmp/build.zip' ) ;
181
- fs . writeFileSync ( outfile , zip ) ;
182
- } finally {
183
- reap ( ) ;
184
- }
185
- }
186
-
187
- function mountResources ( php : PHP , rawMounts : string [ ] ) {
188
- const parsedMounts = rawMounts . map ( ( mount ) => {
189
- const [ source , vfsPath ] = mount . split ( ':' ) ;
190
- return {
191
- hostPath : path . resolve ( process . cwd ( ) , source ) ,
192
- vfsPath,
193
- } ;
194
- } ) ;
195
- for ( const mount of parsedMounts ) {
196
- php . mkdir ( mount . vfsPath ) ;
197
- php . mount ( mount . vfsPath , createNodeFsMountHandler ( mount . hostPath ) ) ;
198
- }
199
- }
200
-
201
- function compileInputBlueprint ( ) {
202
- /**
203
- * @TODO This looks similar to the resolveBlueprint() call in the website package:
204
- * https://github.com/WordPress/wordpress-playground/blob/ce586059e5885d185376184fdd2f52335cca32b0/packages/playground/website/src/main.tsx#L41
205
- *
206
- * Also the Blueprint Builder tool does something similar.
207
- * Perhaps all these cases could be handled by the same function?
208
- */
209
- let blueprint : Blueprint | undefined ;
210
- if ( args . blueprint ) {
211
- blueprint = args . blueprint as Blueprint ;
212
- } else {
213
- blueprint = {
214
- preferredVersions : {
215
- php : args . php as SupportedPHPVersion ,
216
- wp : args . wp ,
217
- } ,
218
- login : args . login ,
219
- } ;
220
- }
221
-
222
- const tracker = new ProgressTracker ( ) ;
223
- let lastCaption = '' ;
224
- let progress100 = false ;
225
- tracker . addEventListener ( 'progress' , ( e : any ) => {
226
- if ( progress100 ) {
227
- return ;
228
- } else if ( e . detail . progress === 100 ) {
229
- progress100 = true ;
230
- }
231
- lastCaption =
232
- e . detail . caption || lastCaption || 'Running the Blueprint' ;
233
- readline . clearLine ( process . stdout , 0 ) ;
234
- readline . cursorTo ( process . stdout , 0 ) ;
235
- process . stdout . write (
236
- '\r\x1b[K' + `${ lastCaption . trim ( ) } – ${ e . detail . progress } %`
237
- ) ;
238
- if ( progress100 ) {
239
- process . stdout . write ( '\n' ) ;
240
- }
241
- } ) ;
242
- return compileBlueprint ( blueprint as Blueprint , {
243
- progress : tracker ,
244
- } ) ;
245
- }
246
-
247
121
const command = args . _ [ 0 ] as string ;
122
+
248
123
if ( ! [ 'run-blueprint' , 'server' , 'build-snapshot' ] . includes ( command ) ) {
249
124
yargsObject . showHelp ( ) ;
250
125
process . exit ( 1 ) ;
251
126
}
252
127
253
- const compiledBlueprint = compileInputBlueprint ( ) ;
254
-
255
- let requestHandler : PHPRequestHandler ;
256
- let wordPressReady = false ;
128
+ args . command = args . _ [ 0 ] as any ;
257
129
258
- logger . log ( 'Starting a PHP server...' ) ;
259
-
260
- startServer ( {
261
- port : args [ 'port' ] as number ,
262
- onBind : async ( port : number ) => {
263
- const absoluteUrl = `http://127.0.0.1:${ port } ` ;
264
-
265
- logger . log ( `Setting up WordPress ${ args . wp } ` ) ;
266
- let wpDetails : any = undefined ;
267
- const monitor = new EmscriptenDownloadMonitor ( ) ;
268
- if ( ! args . skipWordPressSetup ) {
269
- // @TODO : Rename to FetchProgressMonitor. There's nothing Emscripten
270
- // about that class anymore.
271
- monitor . addEventListener ( 'progress' , ( (
272
- e : CustomEvent < ProgressEvent & { finished : boolean } >
273
- ) => {
274
- // @TODO Every progres bar will want percentages. The
275
- // download monitor should just provide that.
276
- const percentProgress = Math . round (
277
- Math . min ( 100 , ( 100 * e . detail . loaded ) / e . detail . total )
278
- ) ;
279
- if ( ! args . quiet ) {
280
- readline . clearLine ( process . stdout , 0 ) ;
281
- readline . cursorTo ( process . stdout , 0 ) ;
282
- process . stdout . write (
283
- `Downloading WordPress ${ percentProgress } %...`
284
- ) ;
285
- }
286
- } ) as any ) ;
287
-
288
- wpDetails = await resolveWordPressRelease ( args . wp ) ;
289
- }
290
- logger . log (
291
- `Resolved WordPress release URL: ${ wpDetails ?. releaseUrl } `
292
- ) ;
293
-
294
- const preinstalledWpContentPath =
295
- wpDetails &&
296
- path . join (
297
- CACHE_FOLDER ,
298
- `prebuilt-wp-content-for-wp-${ wpDetails . version } .zip`
299
- ) ;
300
- const wordPressZip = ! wpDetails
301
- ? undefined
302
- : fs . existsSync ( preinstalledWpContentPath )
303
- ? readAsFile ( preinstalledWpContentPath )
304
- : await cachedDownload (
305
- wpDetails . releaseUrl ,
306
- `${ wpDetails . version } .zip` ,
307
- monitor
308
- ) ;
309
-
310
- const constants : Record < string , string | number | boolean | null > =
311
- {
312
- WP_DEBUG : true ,
313
- WP_DEBUG_LOG : true ,
314
- WP_DEBUG_DISPLAY : false ,
315
- } ;
316
-
317
- logger . log ( `Booting WordPress...` ) ;
318
- requestHandler = await bootWordPress ( {
319
- siteUrl : absoluteUrl ,
320
- createPhpRuntime : async ( ) =>
321
- await loadNodeRuntime ( compiledBlueprint . versions . php ) ,
322
- wordPressZip,
323
- sqliteIntegrationPluginZip : fetchSqliteIntegration ( monitor ) ,
324
- sapiName : 'cli' ,
325
- createFiles : {
326
- '/internal/shared/ca-bundle.crt' :
327
- rootCertificates . join ( '\n' ) ,
328
- } ,
329
- constants,
330
- phpIniEntries : {
331
- 'openssl.cafile' : '/internal/shared/ca-bundle.crt' ,
332
- allow_url_fopen : '1' ,
333
- disable_functions : '' ,
334
- } ,
335
- hooks : {
336
- async beforeWordPressFiles ( php ) {
337
- if ( args . mountBeforeInstall ) {
338
- mountResources ( php , args . mountBeforeInstall ) ;
339
- }
340
- } ,
341
- } ,
342
- } ) ;
343
- logger . log ( `Booted!` ) ;
344
-
345
- const php = await requestHandler . getPrimaryPhp ( ) ;
346
- try {
347
- if (
348
- wpDetails &&
349
- ! args . mountBeforeInstall &&
350
- ! fs . existsSync ( preinstalledWpContentPath )
351
- ) {
352
- logger . log (
353
- `Caching preinstalled WordPress for the next boot...`
354
- ) ;
355
- fs . writeFileSync (
356
- preinstalledWpContentPath ,
357
- await zipDirectory ( php , '/wordpress' )
358
- ) ;
359
- logger . log ( `Cached!` ) ;
360
- }
361
-
362
- if ( args . mount ) {
363
- mountResources ( php , args . mount ) ;
364
- }
365
-
366
- wordPressReady = true ;
367
-
368
- if ( compiledBlueprint ) {
369
- const { php, reap } =
370
- await requestHandler . processManager . acquirePHPInstance ( ) ;
371
- try {
372
- logger . log ( `Running the Blueprint...` ) ;
373
- await runBlueprintSteps ( compiledBlueprint , php ) ;
374
- logger . log ( `Finished running the blueprint` ) ;
375
- } finally {
376
- reap ( ) ;
377
- }
378
- }
379
-
380
- if ( command === 'build-snapshot' ) {
381
- await zipSite ( args . outfile as string ) ;
382
- logger . log ( `WordPress exported to ${ args . outfile } ` ) ;
383
- process . exit ( 0 ) ;
384
- } else if ( command === 'run-blueprint' ) {
385
- logger . log ( `Blueprint executed` ) ;
386
- process . exit ( 0 ) ;
387
- } else {
388
- logger . log ( `WordPress is running on ${ absoluteUrl } ` ) ;
389
- }
390
- } catch ( error ) {
391
- if ( ! args . debug ) {
392
- throw error ;
393
- }
394
- const phpLogs = php . readFileAsText ( errorLogPath ) ;
395
- throw new Error ( phpLogs , { cause : error } ) ;
396
- }
397
- } ,
398
- async handleRequest ( request : PHPRequest ) {
399
- if ( ! wordPressReady ) {
400
- return PHPResponse . forHttpCode (
401
- 502 ,
402
- 'WordPress is not ready yet'
403
- ) ;
404
- }
405
- return await requestHandler . request ( request ) ;
406
- } ,
407
- } ) ;
130
+ return runCLI ( args as RunCLIArgs ) ;
408
131
}
409
132
410
133
run ( ) ;
0 commit comments