@wp-operations/wp-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,802 @@
1
+ // src/config.ts
2
+ import {
3
+ SupportedPHPVersionsList
4
+ } from "@php-wasm/universal";
5
+ import crypto from "crypto";
6
+
7
+ // src/github-codespaces.ts
8
+ var isGitHubCodespace = Boolean(
9
+ process.env.CODESPACE_NAME && process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
10
+ );
11
+ function getCodeSpaceURL(port) {
12
+ return `https://${process.env.CODESPACE_NAME}-${port}.${process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}`;
13
+ }
14
+
15
+ // src/wp-now.ts
16
+ import fs9 from "fs-extra";
17
+ import { NodePHP } from "@php-wasm/node";
18
+ import path12 from "path";
19
+
20
+ // src/constants.ts
21
+ var WP_NOW_HIDDEN_FOLDER = ".wp-app";
22
+ var SQLITE_FILENAME = "sqlite-database-integration";
23
+ var SQLITE_URL = "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/main.zip";
24
+ var CLASSICPRESS_LATEST_URL = "https://www.classicpress.net/latest.zip";
25
+ var DEFAULT_PORT = 8881;
26
+ var DEFAULT_PHP_VERSION = "8.0";
27
+ var DEFAULT_WORDPRESS_VERSION = "latest";
28
+
29
+ // src/download.ts
30
+ import fs8 from "fs-extra";
31
+ import path11 from "path";
32
+ import followRedirects from "follow-redirects";
33
+ import unzipper from "unzipper";
34
+ import os3 from "os";
35
+
36
+ // src/wp-playground-wordpress/has-index-file.ts
37
+ import fs from "fs-extra";
38
+ import path from "path";
39
+ function hasIndexFile(projectPath) {
40
+ return fs.existsSync(path.join(projectPath, "index.php"));
41
+ }
42
+
43
+ // src/wp-playground-wordpress/is-valid-wordpress-version.ts
44
+ function isValidWordPressVersion(version) {
45
+ const versionPattern = /^latest$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/;
46
+ const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?)?$/;
47
+ return versionPattern.test(version) || classicPressPattern.test(version);
48
+ }
49
+ function isClassicPressVersion(version) {
50
+ return version === "classicpress" || version.startsWith("classicpress-");
51
+ }
52
+
53
+ // src/wp-playground-wordpress/get-plugin-file.ts
54
+ import fs3 from "fs-extra";
55
+ import path2, { basename } from "path";
56
+
57
+ // src/wp-playground-wordpress/read-file-head.ts
58
+ import fs2 from "fs-extra";
59
+ function readFileHead(filePath, length = 8192) {
60
+ const buffer = Buffer.alloc(length);
61
+ const fd = fs2.openSync(filePath, "r");
62
+ fs2.readSync(fd, buffer, 0, buffer.length, 0);
63
+ const fileContentBuffer = buffer.toString("utf8");
64
+ fs2.closeSync(fd);
65
+ return fileContentBuffer.toString();
66
+ }
67
+
68
+ // src/wp-playground-wordpress/get-plugin-file.ts
69
+ function heuristicSort(files, projectPath) {
70
+ const heuristicsBestGuess = `${basename(projectPath)}.php`;
71
+ const heuristicsBestGuessIndex = files.indexOf(heuristicsBestGuess);
72
+ if (heuristicsBestGuessIndex !== -1) {
73
+ files.splice(heuristicsBestGuessIndex, 1);
74
+ files.unshift(heuristicsBestGuess);
75
+ }
76
+ return files;
77
+ }
78
+ function getPluginFile(projectPath) {
79
+ const files = heuristicSort(fs3.readdirSync(projectPath), projectPath);
80
+ for (const file of files) {
81
+ if (file.endsWith(".php")) {
82
+ const fileContent = readFileHead(path2.join(projectPath, file));
83
+ const pluginNameRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;
84
+ if (pluginNameRegex.test(fileContent)) {
85
+ return path2.join(path2.basename(projectPath), file);
86
+ }
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+
92
+ // src/wp-playground-wordpress/is-plugin-directory.ts
93
+ function isPluginDirectory(projectPath) {
94
+ const pluginFile = getPluginFile(projectPath);
95
+ return pluginFile !== null;
96
+ }
97
+
98
+ // src/wp-playground-wordpress/is-theme-directory.ts
99
+ import fs4 from "fs-extra";
100
+ import path3 from "path";
101
+ function isThemeDirectory(projectPath) {
102
+ const styleCSSExists = fs4.existsSync(path3.join(projectPath, "style.css"));
103
+ if (!styleCSSExists) {
104
+ return false;
105
+ }
106
+ const styleCSS = readFileHead(path3.join(projectPath, "style.css"));
107
+ const themeNameRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Theme Name:(.*)$/im;
108
+ return themeNameRegex.test(styleCSS);
109
+ }
110
+
111
+ // src/wp-playground-wordpress/is-wp-content-directory.ts
112
+ import fs5 from "fs-extra";
113
+ import path4 from "path";
114
+ function isWpContentDirectory(projectPath) {
115
+ const muPluginsExists = fs5.existsSync(path4.join(projectPath, "mu-plugins"));
116
+ const pluginsExists = fs5.existsSync(path4.join(projectPath, "plugins"));
117
+ const themesExists = fs5.existsSync(path4.join(projectPath, "themes"));
118
+ if (muPluginsExists || pluginsExists || themesExists) {
119
+ return true;
120
+ }
121
+ return false;
122
+ }
123
+
124
+ // src/wp-playground-wordpress/is-wordpress-directory.ts
125
+ import fs6 from "fs-extra";
126
+ import path5 from "path";
127
+ function isWordPressDirectory(projectPath) {
128
+ return fs6.existsSync(path5.join(projectPath, "wp-content")) && fs6.existsSync(path5.join(projectPath, "wp-includes")) && fs6.existsSync(path5.join(projectPath, "wp-load.php"));
129
+ }
130
+
131
+ // src/wp-playground-wordpress/is-wordpress-develop-directory.ts
132
+ import fs7 from "fs-extra";
133
+ import path6 from "path";
134
+ function isWordPressDevelopDirectory(projectPath) {
135
+ const requiredFiles = [
136
+ "src",
137
+ "src/wp-content",
138
+ "src/wp-includes",
139
+ "src/wp-load.php",
140
+ "build",
141
+ "build/wp-content",
142
+ "build/wp-includes",
143
+ "build/wp-load.php"
144
+ ];
145
+ return requiredFiles.every(
146
+ (file) => fs7.existsSync(path6.join(projectPath, file))
147
+ );
148
+ }
149
+
150
+ // src/output.ts
151
+ function shouldOutput() {
152
+ return process.env.NODE_ENV !== "test";
153
+ }
154
+ var output = shouldOutput() ? console : null;
155
+
156
+ // src/get-wp-now-path.ts
157
+ import path8 from "path";
158
+ import os2 from "os";
159
+
160
+ // src/get-wp-now-tmp-path.ts
161
+ import path7 from "path";
162
+ import os from "os";
163
+ function getWpNowTmpPath() {
164
+ const tmpDirectory = os.tmpdir();
165
+ return path7.join(tmpDirectory, `wp-now-tests-hidden-folder`);
166
+ }
167
+
168
+ // src/get-wp-now-path.ts
169
+ function getWpNowPath() {
170
+ if (process.env.NODE_ENV !== "test") {
171
+ return path8.join(os2.homedir(), WP_NOW_HIDDEN_FOLDER);
172
+ }
173
+ return getWpNowTmpPath();
174
+ }
175
+
176
+ // src/get-wordpress-versions-path.ts
177
+ import path9 from "path";
178
+ function getWordpressVersionsPath() {
179
+ return path9.join(getWpNowPath(), "wordpress-versions");
180
+ }
181
+
182
+ // src/get-sqlite-path.ts
183
+ import path10 from "path";
184
+ function getSqlitePath() {
185
+ return path10.join(getWpNowPath(), `${SQLITE_FILENAME}-main`);
186
+ }
187
+
188
+ // src/download.ts
189
+ function getWordPressVersionUrl(version = DEFAULT_WORDPRESS_VERSION) {
190
+ if (!isValidWordPressVersion(version)) {
191
+ throw new Error(
192
+ 'Unrecognized WordPress version. Please use "latest", numeric versions such as "6.2", "6.0.1", "6.2-beta1", "6.2-RC1", or "classicpress" / "classicpress-2.4.1".'
193
+ );
194
+ }
195
+ return `https://wordpress.org/wordpress-${version}.zip`;
196
+ }
197
+ function getClassicPressVersionUrl(version) {
198
+ if (version === "classicpress" || version === "classicpress-latest") {
199
+ return CLASSICPRESS_LATEST_URL;
200
+ }
201
+ const tag = version.slice("classicpress-".length);
202
+ return `https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${tag}.zip`;
203
+ }
204
+ followRedirects.maxRedirects = 5;
205
+ var { https } = followRedirects;
206
+ async function downloadFileAndUnzip({
207
+ url,
208
+ destinationFolder,
209
+ checkFinalPath,
210
+ itemName
211
+ }) {
212
+ if (fs8.existsSync(checkFinalPath)) {
213
+ output?.log(`${itemName} folder already exists. Skipping download.`);
214
+ return { downloaded: false, statusCode: 0 };
215
+ }
216
+ let statusCode = 0;
217
+ try {
218
+ fs8.ensureDirSync(path11.dirname(destinationFolder));
219
+ output?.log(`Downloading ${itemName}...`);
220
+ const response = await new Promise(
221
+ (resolve) => https.get(url, (response2) => resolve(response2))
222
+ );
223
+ statusCode = response.statusCode;
224
+ if (response.statusCode !== 200) {
225
+ throw new Error(
226
+ `Failed to download file (Status code ${response.statusCode}).`
227
+ );
228
+ }
229
+ await response.pipe(unzipper.Parse()).on("entry", (entry) => {
230
+ const filePath = path11.join(destinationFolder, entry.path);
231
+ fs8.ensureDirSync(path11.dirname(filePath));
232
+ if (entry.type !== "Directory") {
233
+ entry.pipe(fs8.createWriteStream(filePath));
234
+ }
235
+ }).promise();
236
+ return { downloaded: true, statusCode };
237
+ } catch (err) {
238
+ output?.error(`Error downloading or unzipping ${itemName}:`, err);
239
+ }
240
+ return { downloaded: false, statusCode };
241
+ }
242
+ async function downloadWordPress(wordPressVersion = DEFAULT_WORDPRESS_VERSION) {
243
+ const finalFolder = path11.join(getWordpressVersionsPath(), wordPressVersion);
244
+ if (isClassicPressVersion(wordPressVersion)) {
245
+ return downloadClassicPress(wordPressVersion, finalFolder);
246
+ }
247
+ const tempFolder = os3.tmpdir();
248
+ const { downloaded, statusCode } = await downloadFileAndUnzip({
249
+ url: getWordPressVersionUrl(wordPressVersion),
250
+ destinationFolder: tempFolder,
251
+ checkFinalPath: finalFolder,
252
+ itemName: `WordPress ${wordPressVersion}`
253
+ });
254
+ if (downloaded) {
255
+ fs8.ensureDirSync(path11.dirname(finalFolder));
256
+ fs8.moveSync(path11.join(tempFolder, "wordpress"), finalFolder, {
257
+ overwrite: true
258
+ });
259
+ } else if (404 === statusCode) {
260
+ output?.log(
261
+ `WordPress ${wordPressVersion} not found. Check https://wordpress.org/download/releases/ for available versions.`
262
+ );
263
+ process.exit(1);
264
+ }
265
+ }
266
+ async function downloadClassicPress(version, finalFolder) {
267
+ const tempFolder = path11.join(os3.tmpdir(), `wp-app-classicpress-${Date.now()}`);
268
+ const { downloaded, statusCode } = await downloadFileAndUnzip({
269
+ url: getClassicPressVersionUrl(version),
270
+ destinationFolder: tempFolder,
271
+ checkFinalPath: finalFolder,
272
+ itemName: `ClassicPress ${version}`
273
+ });
274
+ if (downloaded) {
275
+ fs8.ensureDirSync(path11.dirname(finalFolder));
276
+ const entries = fs8.readdirSync(tempFolder);
277
+ const singleDirRoot = entries.length === 1 && fs8.statSync(path11.join(tempFolder, entries[0])).isDirectory() ? path11.join(tempFolder, entries[0]) : null;
278
+ fs8.moveSync(singleDirRoot ?? tempFolder, finalFolder, {
279
+ overwrite: true
280
+ });
281
+ fs8.removeSync(tempFolder);
282
+ } else if (404 === statusCode) {
283
+ output?.log(
284
+ `ClassicPress ${version} not found. Check https://www.classicpress.net for available releases.`
285
+ );
286
+ process.exit(1);
287
+ }
288
+ }
289
+ async function downloadSqliteIntegrationPlugin() {
290
+ return downloadFileAndUnzip({
291
+ url: SQLITE_URL,
292
+ destinationFolder: getWpNowPath(),
293
+ checkFinalPath: getSqlitePath(),
294
+ itemName: "SQLite"
295
+ });
296
+ }
297
+ async function downloadMuPlugins() {
298
+ fs8.ensureDirSync(path11.join(getWpNowPath(), "mu-plugins"));
299
+ fs8.writeFile(
300
+ path11.join(getWpNowPath(), "mu-plugins", "0-allow-wp-org.php"),
301
+ `<?php
302
+ // Needed because gethostbyname( 'wordpress.org' ) returns
303
+ // a private network IP address for some reason.
304
+ add_filter( 'allowed_redirect_hosts', function( $deprecated = '' ) {
305
+ return array(
306
+ 'wordpress.org',
307
+ 'api.wordpress.org',
308
+ 'downloads.wordpress.org',
309
+ );
310
+ } );`
311
+ );
312
+ }
313
+
314
+ // src/wp-now.ts
315
+ import {
316
+ activatePlugin,
317
+ activateTheme,
318
+ defineWpConfigConsts,
319
+ login
320
+ } from "@wp-playground/blueprints";
321
+ function seemsLikeAPHPFile(path14) {
322
+ return path14.endsWith(".php") || path14.includes(".php/");
323
+ }
324
+ async function applyToInstances(phpInstances, callback) {
325
+ for (let i = 0; i < phpInstances.length; i++) {
326
+ await callback(phpInstances[i]);
327
+ }
328
+ }
329
+ async function startWPNow(options = {}) {
330
+ const { documentRoot } = options;
331
+ const nodePHPOptions = {
332
+ requestHandler: {
333
+ documentRoot,
334
+ absoluteUrl: options.absoluteUrl,
335
+ isStaticFilePath: (path14) => {
336
+ try {
337
+ const fullPath = options.documentRoot + path14;
338
+ return php.fileExists(fullPath) && !php.isDir(fullPath) && !seemsLikeAPHPFile(fullPath);
339
+ } catch (e) {
340
+ output?.error(e);
341
+ return false;
342
+ }
343
+ }
344
+ }
345
+ };
346
+ const phpInstances = [];
347
+ for (let i = 0; i < Math.max(options.numberOfPhpInstances, 1); i++) {
348
+ phpInstances.push(
349
+ await NodePHP.load(options.phpVersion, nodePHPOptions)
350
+ );
351
+ }
352
+ const php = phpInstances[0];
353
+ phpInstances.forEach((_php) => {
354
+ _php.mkdirTree(documentRoot);
355
+ _php.chdir(documentRoot);
356
+ _php.writeFile(
357
+ `${documentRoot}/index.php`,
358
+ `<?php echo 'Hello wp-app!';`
359
+ );
360
+ });
361
+ output?.log(`directory: ${options.projectPath}`);
362
+ output?.log(`mode: ${options.mode}`);
363
+ output?.log(`php: ${options.phpVersion}`);
364
+ if (options.mode === "index" /* INDEX */) {
365
+ await applyToInstances(phpInstances, async (_php) => {
366
+ runIndexMode(_php, options);
367
+ });
368
+ return { php, phpInstances, options };
369
+ }
370
+ output?.log(`wp: ${options.wordPressVersion}`);
371
+ await Promise.all([
372
+ downloadWordPress(options.wordPressVersion),
373
+ downloadSqliteIntegrationPlugin(),
374
+ downloadMuPlugins()
375
+ ]);
376
+ const isFirstTimeProject = !fs9.existsSync(options.wpContentPath);
377
+ await applyToInstances(phpInstances, async (_php) => {
378
+ switch (options.mode) {
379
+ case "wp-content" /* WP_CONTENT */:
380
+ await runWpContentMode(_php, options);
381
+ break;
382
+ case "wordpress-develop" /* WORDPRESS_DEVELOP */:
383
+ await runWordPressDevelopMode(_php, options);
384
+ break;
385
+ case "wordpress" /* WORDPRESS */:
386
+ await runWordPressMode(_php, options);
387
+ break;
388
+ case "plugin" /* PLUGIN */:
389
+ await runPluginOrThemeMode(_php, options);
390
+ break;
391
+ case "theme" /* THEME */:
392
+ await runPluginOrThemeMode(_php, options);
393
+ break;
394
+ case "playground" /* PLAYGROUND */:
395
+ await runWpPlaygroundMode(_php, options);
396
+ break;
397
+ }
398
+ });
399
+ await installationStep2(php);
400
+ await login(php, {
401
+ username: "admin",
402
+ password: "password"
403
+ });
404
+ if (isFirstTimeProject && ["plugin" /* PLUGIN */, "theme" /* THEME */].includes(options.mode)) {
405
+ await activatePluginOrTheme(php, options);
406
+ }
407
+ return {
408
+ php,
409
+ phpInstances,
410
+ options
411
+ };
412
+ }
413
+ async function runIndexMode(php, { documentRoot, projectPath }) {
414
+ php.mount(projectPath, documentRoot);
415
+ }
416
+ async function runWpContentMode(php, {
417
+ documentRoot,
418
+ wordPressVersion,
419
+ wpContentPath,
420
+ projectPath,
421
+ absoluteUrl
422
+ }) {
423
+ const wordPressPath = path12.join(
424
+ getWordpressVersionsPath(),
425
+ wordPressVersion
426
+ );
427
+ php.mount(wordPressPath, documentRoot);
428
+ await initWordPress(php, wordPressVersion, documentRoot, absoluteUrl);
429
+ fs9.ensureDirSync(wpContentPath);
430
+ php.mount(projectPath, `${documentRoot}/wp-content`);
431
+ mountSqlitePlugin(php, documentRoot);
432
+ mountSqliteDatabaseDirectory(php, documentRoot, wpContentPath);
433
+ mountMuPlugins(php, documentRoot);
434
+ }
435
+ async function runWordPressDevelopMode(php, { documentRoot, projectPath, absoluteUrl }) {
436
+ await runWordPressMode(php, {
437
+ documentRoot,
438
+ projectPath: projectPath + "/build",
439
+ absoluteUrl
440
+ });
441
+ }
442
+ async function runWordPressMode(php, { documentRoot, wpContentPath, projectPath, absoluteUrl }) {
443
+ php.mount(projectPath, documentRoot);
444
+ const { initializeDefaultDatabase } = await initWordPress(
445
+ php,
446
+ "user-provided",
447
+ documentRoot,
448
+ absoluteUrl
449
+ );
450
+ if (initializeDefaultDatabase || fs9.existsSync(path12.join(wpContentPath, "database"))) {
451
+ mountSqlitePlugin(php, documentRoot);
452
+ mountSqliteDatabaseDirectory(php, documentRoot, wpContentPath);
453
+ }
454
+ mountMuPlugins(php, documentRoot);
455
+ }
456
+ async function runPluginOrThemeMode(php, {
457
+ wordPressVersion,
458
+ documentRoot,
459
+ projectPath,
460
+ wpContentPath,
461
+ absoluteUrl,
462
+ mode
463
+ }) {
464
+ const wordPressPath = path12.join(
465
+ getWordpressVersionsPath(),
466
+ wordPressVersion
467
+ );
468
+ php.mount(wordPressPath, documentRoot);
469
+ await initWordPress(php, wordPressVersion, documentRoot, absoluteUrl);
470
+ fs9.ensureDirSync(wpContentPath);
471
+ fs9.copySync(
472
+ path12.join(getWordpressVersionsPath(), wordPressVersion, "wp-content"),
473
+ wpContentPath
474
+ );
475
+ php.mount(wpContentPath, `${documentRoot}/wp-content`);
476
+ const pluginName = path12.basename(projectPath);
477
+ const directoryName = mode === "plugin" /* PLUGIN */ ? "plugins" : "themes";
478
+ php.mount(
479
+ projectPath,
480
+ `${documentRoot}/wp-content/${directoryName}/${pluginName}`
481
+ );
482
+ mountSqlitePlugin(php, documentRoot);
483
+ mountMuPlugins(php, documentRoot);
484
+ }
485
+ async function runWpPlaygroundMode(php, { documentRoot, wordPressVersion, wpContentPath, absoluteUrl }) {
486
+ const wordPressPath = path12.join(
487
+ getWordpressVersionsPath(),
488
+ wordPressVersion
489
+ );
490
+ php.mount(wordPressPath, documentRoot);
491
+ await initWordPress(php, wordPressVersion, documentRoot, absoluteUrl);
492
+ fs9.ensureDirSync(wpContentPath);
493
+ fs9.copySync(
494
+ path12.join(getWordpressVersionsPath(), wordPressVersion, "wp-content"),
495
+ wpContentPath
496
+ );
497
+ php.mount(wpContentPath, `${documentRoot}/wp-content`);
498
+ mountSqlitePlugin(php, documentRoot);
499
+ mountMuPlugins(php, documentRoot);
500
+ }
501
+ async function initWordPress(php, wordPressVersion, vfsDocumentRoot, siteUrl) {
502
+ let initializeDefaultDatabase = false;
503
+ if (!php.fileExists(`${vfsDocumentRoot}/wp-config.php`)) {
504
+ php.writeFile(
505
+ `${vfsDocumentRoot}/wp-config.php`,
506
+ php.readFileAsText(`${vfsDocumentRoot}/wp-config-sample.php`)
507
+ );
508
+ initializeDefaultDatabase = true;
509
+ }
510
+ const wpConfigConsts = {
511
+ WP_HOME: siteUrl,
512
+ WP_SITEURL: siteUrl
513
+ };
514
+ if (wordPressVersion !== "user-defined") {
515
+ wpConfigConsts["WP_AUTO_UPDATE_CORE"] = wordPressVersion === "latest";
516
+ }
517
+ await defineWpConfigConsts(php, {
518
+ consts: wpConfigConsts,
519
+ virtualize: true
520
+ });
521
+ return { initializeDefaultDatabase };
522
+ }
523
+ async function activatePluginOrTheme(php, { projectPath, mode }) {
524
+ if (mode === "plugin" /* PLUGIN */) {
525
+ const pluginFile = getPluginFile(projectPath);
526
+ await activatePlugin(php, { pluginPath: pluginFile });
527
+ } else if (mode === "theme" /* THEME */) {
528
+ const themeFolderName = path12.basename(projectPath);
529
+ await activateTheme(php, { themeFolderName });
530
+ }
531
+ }
532
+ function mountMuPlugins(php, vfsDocumentRoot) {
533
+ php.mount(
534
+ path12.join(getWpNowPath(), "mu-plugins"),
535
+ // VFS paths are always POSIX — path.join would break them on Windows.
536
+ `${vfsDocumentRoot}/wp-content/mu-plugins`
537
+ );
538
+ }
539
+ function mountSqlitePlugin(php, vfsDocumentRoot) {
540
+ const sqlitePluginPath = `${vfsDocumentRoot}/wp-content/plugins/${SQLITE_FILENAME}`;
541
+ if (php.listFiles(sqlitePluginPath).length === 0) {
542
+ php.mount(getSqlitePath(), sqlitePluginPath);
543
+ php.mount(
544
+ path12.join(getSqlitePath(), "db.copy"),
545
+ `${vfsDocumentRoot}/wp-content/db.php`
546
+ );
547
+ }
548
+ }
549
+ function mountSqliteDatabaseDirectory(php, vfsDocumentRoot, wpContentPath) {
550
+ fs9.ensureDirSync(path12.join(wpContentPath, "database"));
551
+ php.mount(
552
+ path12.join(wpContentPath, "database"),
553
+ `${vfsDocumentRoot}/wp-content/database`
554
+ );
555
+ }
556
+ function inferMode(projectPath) {
557
+ if (isWordPressDevelopDirectory(projectPath)) {
558
+ return "wordpress-develop" /* WORDPRESS_DEVELOP */;
559
+ } else if (isWordPressDirectory(projectPath)) {
560
+ return "wordpress" /* WORDPRESS */;
561
+ } else if (isWpContentDirectory(projectPath)) {
562
+ return "wp-content" /* WP_CONTENT */;
563
+ } else if (isPluginDirectory(projectPath)) {
564
+ return "plugin" /* PLUGIN */;
565
+ } else if (isThemeDirectory(projectPath)) {
566
+ return "theme" /* THEME */;
567
+ } else if (hasIndexFile(projectPath)) {
568
+ return "index" /* INDEX */;
569
+ }
570
+ return "playground" /* PLAYGROUND */;
571
+ }
572
+ async function installationStep2(php) {
573
+ const fields = new URLSearchParams({
574
+ language: "en",
575
+ prefix: "wp_",
576
+ weblog_title: "My WordPress Website",
577
+ user_name: "admin",
578
+ admin_password: "password",
579
+ admin_password2: "password",
580
+ Submit: "Install WordPress",
581
+ pw_weak: "1",
582
+ admin_email: "admin@localhost.com"
583
+ });
584
+ return php.request({
585
+ url: "/wp-admin/install.php?step=2",
586
+ method: "POST",
587
+ headers: {
588
+ "content-type": "application/x-www-form-urlencoded"
589
+ },
590
+ body: fields.toString()
591
+ });
592
+ }
593
+
594
+ // src/port-finder.ts
595
+ import http from "http";
596
+ var PortFinder = class _PortFinder {
597
+ static #instance;
598
+ #searchPort = DEFAULT_PORT;
599
+ #openPort = null;
600
+ constructor() {
601
+ }
602
+ static getInstance() {
603
+ if (!_PortFinder.#instance) {
604
+ _PortFinder.#instance = new _PortFinder();
605
+ }
606
+ return _PortFinder.#instance;
607
+ }
608
+ #incrementPort() {
609
+ return this.#searchPort++;
610
+ }
611
+ #isPortFree() {
612
+ return new Promise((resolve) => {
613
+ const server = http.createServer();
614
+ server.listen(this.#searchPort, () => {
615
+ server.close();
616
+ resolve(true);
617
+ }).on("error", () => {
618
+ resolve(false);
619
+ });
620
+ });
621
+ }
622
+ /**
623
+ * Returns the first available open port, caching and reusing it for subsequent calls.
624
+ *
625
+ * @returns {Promise<number>} A promise that resolves to the open port number.
626
+ */
627
+ async getOpenPort() {
628
+ if (this.#openPort) {
629
+ return this.#openPort;
630
+ }
631
+ while (!await this.#isPortFree()) {
632
+ this.#incrementPort();
633
+ }
634
+ this.#openPort = this.#searchPort;
635
+ return this.#openPort;
636
+ }
637
+ setPort(port) {
638
+ this.#openPort = port;
639
+ }
640
+ };
641
+ var portFinder = PortFinder.getInstance();
642
+
643
+ // src/config.ts
644
+ import path13 from "path";
645
+ var DEFAULT_OPTIONS = {
646
+ phpVersion: DEFAULT_PHP_VERSION,
647
+ wordPressVersion: DEFAULT_WORDPRESS_VERSION,
648
+ documentRoot: "/var/www/html",
649
+ projectPath: process.cwd(),
650
+ mode: "auto" /* AUTO */,
651
+ numberOfPhpInstances: 1
652
+ };
653
+ async function getAbsoluteURL() {
654
+ const port = await portFinder.getOpenPort();
655
+ if (isGitHubCodespace) {
656
+ return getCodeSpaceURL(port);
657
+ }
658
+ return `http://localhost:${port}`;
659
+ }
660
+ function getWpContentHomePath(projectPath, mode) {
661
+ const basename2 = path13.basename(projectPath);
662
+ const directoryHash = crypto.createHash("sha1").update(projectPath).digest("hex");
663
+ const projectDirectory = mode === "playground" /* PLAYGROUND */ ? "playground" : `${basename2}-${directoryHash}`;
664
+ return path13.join(getWpNowPath(), "wp-content", projectDirectory);
665
+ }
666
+ async function getWpNowConfig(args) {
667
+ if (args.port) {
668
+ portFinder.setPort(args.port);
669
+ }
670
+ const port = await portFinder.getOpenPort();
671
+ const optionsFromCli = {
672
+ phpVersion: args.php,
673
+ projectPath: args.path,
674
+ wordPressVersion: args.wp,
675
+ mode: args.mode,
676
+ port
677
+ };
678
+ const options = {};
679
+ [optionsFromCli, DEFAULT_OPTIONS].forEach((config) => {
680
+ for (const key in config) {
681
+ if (!options[key]) {
682
+ options[key] = config[key];
683
+ }
684
+ }
685
+ });
686
+ if (!options.mode || options.mode === "auto") {
687
+ options.mode = inferMode(options.projectPath);
688
+ }
689
+ if (!options.wpContentPath) {
690
+ options.wpContentPath = getWpContentHomePath(
691
+ options.projectPath,
692
+ options.mode
693
+ );
694
+ }
695
+ if (!options.absoluteUrl) {
696
+ options.absoluteUrl = await getAbsoluteURL();
697
+ }
698
+ if (!isValidWordPressVersion(options.wordPressVersion)) {
699
+ throw new Error(
700
+ 'Unrecognized WordPress version. Please use "latest", numeric versions such as "6.2", "6.0.1", "6.2-beta1", "6.2-RC1", or "classicpress" / "classicpress-2.4.1"'
701
+ );
702
+ }
703
+ if (options.phpVersion && !SupportedPHPVersionsList.includes(options.phpVersion)) {
704
+ throw new Error(
705
+ `Unsupported PHP version: ${options.phpVersion}. Supported versions: ${SupportedPHPVersionsList.join(", ")}`
706
+ );
707
+ }
708
+ return options;
709
+ }
710
+
711
+ // src/start-server.ts
712
+ import fs10 from "fs";
713
+ import express from "express";
714
+ import fileUpload from "express-fileupload";
715
+ function requestBodyToMultipartFormData(json, boundary) {
716
+ let multipartData = "";
717
+ const eol = "\r\n";
718
+ for (const key in json) {
719
+ multipartData += `--${boundary}${eol}`;
720
+ multipartData += `Content-Disposition: form-data; name="${key}"${eol}${eol}`;
721
+ multipartData += `${json[key]}${eol}`;
722
+ }
723
+ multipartData += `--${boundary}--${eol}`;
724
+ return multipartData;
725
+ }
726
+ var requestBodyToString = async (req) => await new Promise((resolve) => {
727
+ let body = "";
728
+ req.on("data", (chunk) => {
729
+ body += chunk.toString();
730
+ });
731
+ req.on("end", () => {
732
+ resolve(body);
733
+ });
734
+ });
735
+ async function startServer(options = {}) {
736
+ if (!fs10.existsSync(options.projectPath)) {
737
+ throw new Error(
738
+ `The given path "${options.projectPath}" does not exist.`
739
+ );
740
+ }
741
+ const app = express();
742
+ app.use(fileUpload());
743
+ const port = await portFinder.getOpenPort();
744
+ const { php, options: wpNowOptions } = await startWPNow(options);
745
+ app.use("/", async (req, res) => {
746
+ try {
747
+ const requestHeaders = {};
748
+ if (req.rawHeaders && req.rawHeaders.length) {
749
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
750
+ requestHeaders[req.rawHeaders[i].toLowerCase()] = req.rawHeaders[i + 1];
751
+ }
752
+ }
753
+ const body = requestHeaders["content-type"]?.startsWith(
754
+ "multipart/form-data"
755
+ ) ? requestBodyToMultipartFormData(
756
+ req.body,
757
+ requestHeaders["content-type"].split("; boundary=")[1]
758
+ ) : await requestBodyToString(req);
759
+ const data = {
760
+ url: req.url,
761
+ headers: requestHeaders,
762
+ method: req.method,
763
+ files: Object.fromEntries(
764
+ Object.entries(req.files || {}).map(
765
+ ([key, file]) => [
766
+ key,
767
+ {
768
+ key,
769
+ name: file.name,
770
+ size: file.size,
771
+ type: file.mimetype,
772
+ arrayBuffer: () => file.data.buffer
773
+ }
774
+ ]
775
+ )
776
+ ),
777
+ body
778
+ };
779
+ const resp = await php.request(data);
780
+ res.statusCode = resp.httpStatusCode;
781
+ Object.keys(resp.headers).forEach((key) => {
782
+ res.setHeader(key, resp.headers[key]);
783
+ });
784
+ res.end(resp.bytes);
785
+ } catch (e) {
786
+ output?.trace(e);
787
+ }
788
+ });
789
+ const url = options.absoluteUrl;
790
+ app.listen(port, () => {
791
+ output?.log(`Server running at ${url}`);
792
+ });
793
+ return {
794
+ url,
795
+ php,
796
+ options: wpNowOptions
797
+ };
798
+ }
799
+ export {
800
+ getWpNowConfig,
801
+ startServer
802
+ };