@wp-operations/wp-app 0.2.0 → 0.3.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/README.md +1 -1
- package/dist/index.js +202 -45
- package/dist/main.js +204 -47
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -59,4 +59,4 @@ Auto-detected from the project directory, or forced with `--mode`:
|
|
|
59
59
|
|
|
60
60
|
## Credits & License
|
|
61
61
|
|
|
62
|
-
GPL-2.0-or-later. Continues the workflow of [`@wp-now/wp-now`](https://github.com/WordPress/playground-tools) by the WordPress contributors, rebuilt on
|
|
62
|
+
GPL-2.0-or-later. Continues the workflow of [`@wp-now/wp-now`](https://github.com/WordPress/playground-tools) by the WordPress contributors, rebuilt on `@php-wasm/*`.
|
package/dist/index.js
CHANGED
|
@@ -12,17 +12,165 @@ function getCodeSpaceURL(port) {
|
|
|
12
12
|
// src/engine.ts
|
|
13
13
|
import fs10 from "fs";
|
|
14
14
|
import path9 from "path";
|
|
15
|
-
import { createNodeFsMountHandler
|
|
16
|
-
|
|
15
|
+
import { createNodeFsMountHandler } from "@php-wasm/node";
|
|
16
|
+
|
|
17
|
+
// src/boot.ts
|
|
18
|
+
import { rootCertificates } from "tls";
|
|
19
|
+
import { loadNodeRuntime } from "@php-wasm/node";
|
|
20
|
+
import { PHP, PHPRequestHandler, setPhpIniEntries } from "@php-wasm/universal";
|
|
21
|
+
var CA_BUNDLE_PATH = "/internal/ca-bundle.crt";
|
|
22
|
+
async function createPhp(phpVersion) {
|
|
23
|
+
const php = new PHP(
|
|
24
|
+
await loadNodeRuntime(phpVersion, {
|
|
25
|
+
emscriptenOptions: { processId: process.pid }
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
php.setSapiName("cli");
|
|
29
|
+
php.mkdir("/internal");
|
|
30
|
+
php.writeFile(CA_BUNDLE_PATH, rootCertificates.join("\n"));
|
|
31
|
+
await setPhpIniEntries(php, {
|
|
32
|
+
"openssl.cafile": CA_BUNDLE_PATH,
|
|
33
|
+
"curl.cainfo": CA_BUNDLE_PATH,
|
|
34
|
+
allow_url_fopen: "1",
|
|
35
|
+
disable_functions: ""
|
|
36
|
+
});
|
|
37
|
+
return php;
|
|
38
|
+
}
|
|
39
|
+
function createRequestHandler(php, documentRoot, absoluteUrl) {
|
|
40
|
+
return new PHPRequestHandler({ php, documentRoot, absoluteUrl });
|
|
41
|
+
}
|
|
42
|
+
async function runPhp(php, code) {
|
|
43
|
+
const response = await php.run({ code: `<?php ${code}` });
|
|
44
|
+
if (response.exitCode !== 0) {
|
|
45
|
+
throw new Error(response.errors || `PHP exited ${response.exitCode}`);
|
|
46
|
+
}
|
|
47
|
+
return response;
|
|
48
|
+
}
|
|
49
|
+
async function unzipTo(php, zip, destination) {
|
|
50
|
+
const tmpZip = `/internal/upload-${Date.now()}.zip`;
|
|
51
|
+
php.writeFile(tmpZip, new Uint8Array(await zip.arrayBuffer()));
|
|
52
|
+
await runPhp(
|
|
53
|
+
php,
|
|
54
|
+
`
|
|
55
|
+
$zip = new ZipArchive();
|
|
56
|
+
if ($zip->open(${str(tmpZip)}) !== true) exit(1);
|
|
57
|
+
if (!is_dir(${str(destination)})) mkdir(${str(destination)}, 0777, true);
|
|
58
|
+
if (!$zip->extractTo(${str(destination)})) exit(1);
|
|
59
|
+
$zip->close();
|
|
60
|
+
unlink(${str(tmpZip)});
|
|
61
|
+
`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
async function installCore(php, coreZip, docroot) {
|
|
65
|
+
const staging = `${docroot}/.wpapp-staging`;
|
|
66
|
+
await unzipTo(php, coreZip, staging);
|
|
67
|
+
await runPhp(
|
|
68
|
+
php,
|
|
69
|
+
`
|
|
70
|
+
function wpapp_core_root($dir) {
|
|
71
|
+
if (file_exists("$dir/wp-config-sample.php")) return $dir;
|
|
72
|
+
foreach (scandir($dir) as $entry) {
|
|
73
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
74
|
+
$candidate = "$dir/$entry";
|
|
75
|
+
if (is_dir($candidate) && file_exists("$candidate/wp-config-sample.php")) {
|
|
76
|
+
return $candidate;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exit(1);
|
|
80
|
+
}
|
|
81
|
+
function wpapp_rrmdir($dir) {
|
|
82
|
+
foreach (scandir($dir) as $entry) {
|
|
83
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
84
|
+
$path = "$dir/$entry";
|
|
85
|
+
is_dir($path) ? wpapp_rrmdir($path) : unlink($path);
|
|
86
|
+
}
|
|
87
|
+
rmdir($dir);
|
|
88
|
+
}
|
|
89
|
+
$root = wpapp_core_root(${str(staging)});
|
|
90
|
+
foreach (scandir($root) as $entry) {
|
|
91
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
92
|
+
rename("$root/$entry", ${str(docroot)} . "/$entry");
|
|
93
|
+
}
|
|
94
|
+
wpapp_rrmdir(${str(staging)});
|
|
95
|
+
if (!file_exists(${str(docroot)} . '/wp-config.php')) {
|
|
96
|
+
copy(
|
|
97
|
+
${str(docroot)} . '/wp-config-sample.php',
|
|
98
|
+
${str(docroot)} . '/wp-config.php'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
async function installSqliteIntegration(php, pluginZip, docroot) {
|
|
105
|
+
const pluginDir = `${docroot}/wp-content/plugins/sqlite-database-integration`;
|
|
106
|
+
const staging = `${docroot}/wp-content/.wpapp-sqlite-staging`;
|
|
107
|
+
await unzipTo(php, pluginZip, staging);
|
|
108
|
+
await runPhp(
|
|
109
|
+
php,
|
|
110
|
+
`
|
|
111
|
+
$root = ${str(staging)};
|
|
112
|
+
$entries = array_values(array_diff(scandir($root), ['.', '..']));
|
|
113
|
+
if (count($entries) === 1 && is_dir("$root/$entries[0]")) {
|
|
114
|
+
$root = "$root/$entries[0]";
|
|
115
|
+
}
|
|
116
|
+
if (!is_dir(${str(pluginDir)})) {
|
|
117
|
+
rename($root, ${str(pluginDir)});
|
|
118
|
+
}
|
|
119
|
+
if (is_dir(${str(staging)})) {
|
|
120
|
+
@rmdir(${str(staging)});
|
|
121
|
+
}
|
|
122
|
+
$dropIn = file_get_contents(${str(pluginDir)} . '/db.copy');
|
|
123
|
+
$dropIn = str_replace(
|
|
124
|
+
'{SQLITE_IMPLEMENTATION_FOLDER_PATH}',
|
|
125
|
+
${str(pluginDir)},
|
|
126
|
+
$dropIn
|
|
127
|
+
);
|
|
128
|
+
$dropIn = str_replace(
|
|
129
|
+
'{SQLITE_PLUGIN}',
|
|
130
|
+
'sqlite-database-integration/load.php',
|
|
131
|
+
$dropIn
|
|
132
|
+
);
|
|
133
|
+
file_put_contents(${str(docroot)} . '/wp-content/db.php', $dropIn);
|
|
134
|
+
`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
function defineSiteConstants(php, siteUrl, databaseDir) {
|
|
138
|
+
php.defineConstant("WP_HOME", siteUrl);
|
|
139
|
+
php.defineConstant("WP_SITEURL", siteUrl);
|
|
140
|
+
php.defineConstant("DB_DIR", databaseDir);
|
|
141
|
+
php.defineConstant("DB_FILE", ".ht.sqlite");
|
|
142
|
+
php.defineConstant("WP_SQLITE_AST_DRIVER", true);
|
|
143
|
+
}
|
|
144
|
+
async function runInstaller(requestHandler) {
|
|
145
|
+
const fields = new URLSearchParams({
|
|
146
|
+
language: "en",
|
|
147
|
+
prefix: "wp_",
|
|
148
|
+
weblog_title: "My WordPress Website",
|
|
149
|
+
user_name: "admin",
|
|
150
|
+
admin_password: "password",
|
|
151
|
+
admin_password2: "password",
|
|
152
|
+
Submit: "Install WordPress",
|
|
153
|
+
pw_weak: "1",
|
|
154
|
+
admin_email: "admin@localhost.com"
|
|
155
|
+
});
|
|
156
|
+
return requestHandler.request({
|
|
157
|
+
url: "/wp-admin/install.php?step=2",
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
160
|
+
body: new TextEncoder().encode(fields.toString())
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function str(value) {
|
|
164
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
165
|
+
}
|
|
17
166
|
|
|
18
167
|
// src/constants.ts
|
|
19
|
-
import { RecommendedPHPVersion } from "@wp-playground/common";
|
|
20
168
|
var WP_APP_HIDDEN_FOLDER = ".wp-app";
|
|
21
169
|
var WP_APP_HOME_ENV = "WP_APP_HOME";
|
|
22
170
|
var SQLITE_URL = "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/main.zip";
|
|
23
171
|
var CLASSICPRESS_LATEST_URL = "https://www.classicpress.net/latest.zip";
|
|
24
172
|
var DEFAULT_PORT = 8881;
|
|
25
|
-
var DEFAULT_PHP_VERSION =
|
|
173
|
+
var DEFAULT_PHP_VERSION = "8.3";
|
|
26
174
|
var DEFAULT_WORDPRESS_VERSION = "latest";
|
|
27
175
|
var DOCROOT = "/wordpress";
|
|
28
176
|
|
|
@@ -30,14 +178,14 @@ var DOCROOT = "/wordpress";
|
|
|
30
178
|
import fs9 from "fs";
|
|
31
179
|
import path8 from "path";
|
|
32
180
|
|
|
33
|
-
// src/
|
|
181
|
+
// src/detect/has-index-file.ts
|
|
34
182
|
import fs from "fs";
|
|
35
183
|
import path from "path";
|
|
36
184
|
function hasIndexFile(projectPath) {
|
|
37
185
|
return fs.existsSync(path.join(projectPath, "index.php"));
|
|
38
186
|
}
|
|
39
187
|
|
|
40
|
-
// src/
|
|
188
|
+
// src/detect/is-valid-wordpress-version.ts
|
|
41
189
|
function isValidWordPressVersion(version) {
|
|
42
190
|
const versionPattern = /^latest$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/;
|
|
43
191
|
const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?)?$/;
|
|
@@ -47,11 +195,11 @@ function isClassicPressVersion(version) {
|
|
|
47
195
|
return version === "classicpress" || version.startsWith("classicpress-");
|
|
48
196
|
}
|
|
49
197
|
|
|
50
|
-
// src/
|
|
198
|
+
// src/detect/get-plugin-file.ts
|
|
51
199
|
import fs3 from "fs";
|
|
52
200
|
import path2, { basename } from "path";
|
|
53
201
|
|
|
54
|
-
// src/
|
|
202
|
+
// src/detect/read-file-head.ts
|
|
55
203
|
import fs2 from "fs";
|
|
56
204
|
function readFileHead(filePath, length = 8192) {
|
|
57
205
|
const buffer = Buffer.alloc(length);
|
|
@@ -62,7 +210,7 @@ function readFileHead(filePath, length = 8192) {
|
|
|
62
210
|
return fileContentBuffer.toString();
|
|
63
211
|
}
|
|
64
212
|
|
|
65
|
-
// src/
|
|
213
|
+
// src/detect/get-plugin-file.ts
|
|
66
214
|
function heuristicSort(files, projectPath) {
|
|
67
215
|
const heuristicsBestGuess = `${basename(projectPath)}.php`;
|
|
68
216
|
const heuristicsBestGuessIndex = files.indexOf(heuristicsBestGuess);
|
|
@@ -86,13 +234,13 @@ function getPluginFile(projectPath) {
|
|
|
86
234
|
return null;
|
|
87
235
|
}
|
|
88
236
|
|
|
89
|
-
// src/
|
|
237
|
+
// src/detect/is-plugin-directory.ts
|
|
90
238
|
function isPluginDirectory(projectPath) {
|
|
91
239
|
const pluginFile = getPluginFile(projectPath);
|
|
92
240
|
return pluginFile !== null;
|
|
93
241
|
}
|
|
94
242
|
|
|
95
|
-
// src/
|
|
243
|
+
// src/detect/is-theme-directory.ts
|
|
96
244
|
import fs4 from "fs";
|
|
97
245
|
import path3 from "path";
|
|
98
246
|
function isThemeDirectory(projectPath) {
|
|
@@ -105,7 +253,7 @@ function isThemeDirectory(projectPath) {
|
|
|
105
253
|
return themeNameRegex.test(styleCSS);
|
|
106
254
|
}
|
|
107
255
|
|
|
108
|
-
// src/
|
|
256
|
+
// src/detect/is-wp-content-directory.ts
|
|
109
257
|
import fs5 from "fs";
|
|
110
258
|
import path4 from "path";
|
|
111
259
|
function isWpContentDirectory(projectPath) {
|
|
@@ -118,14 +266,14 @@ function isWpContentDirectory(projectPath) {
|
|
|
118
266
|
return false;
|
|
119
267
|
}
|
|
120
268
|
|
|
121
|
-
// src/
|
|
269
|
+
// src/detect/is-wordpress-directory.ts
|
|
122
270
|
import fs6 from "fs";
|
|
123
271
|
import path5 from "path";
|
|
124
272
|
function isWordPressDirectory(projectPath) {
|
|
125
273
|
return fs6.existsSync(path5.join(projectPath, "wp-content")) && fs6.existsSync(path5.join(projectPath, "wp-includes")) && fs6.existsSync(path5.join(projectPath, "wp-load.php"));
|
|
126
274
|
}
|
|
127
275
|
|
|
128
|
-
// src/
|
|
276
|
+
// src/detect/is-wordpress-develop-directory.ts
|
|
129
277
|
import fs7 from "fs";
|
|
130
278
|
import path6 from "path";
|
|
131
279
|
function isWordPressDevelopDirectory(projectPath) {
|
|
@@ -228,31 +376,38 @@ async function startWPApp(options) {
|
|
|
228
376
|
output?.log(`wp: ${options.wordPressVersion}`);
|
|
229
377
|
output?.log(`site data: ${hostDocroot}`);
|
|
230
378
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
379
|
+
const php = await createPhp(options.phpVersion);
|
|
380
|
+
php.mkdir(DOCROOT);
|
|
381
|
+
await php.mount(DOCROOT, createNodeFsMountHandler(hostDocroot));
|
|
382
|
+
const requestHandler = createRequestHandler(
|
|
383
|
+
php,
|
|
384
|
+
DOCROOT,
|
|
385
|
+
options.absoluteUrl
|
|
386
|
+
);
|
|
387
|
+
if (freshSite) {
|
|
388
|
+
await installCore(
|
|
389
|
+
php,
|
|
390
|
+
await getCoreZip(options.wordPressVersion),
|
|
391
|
+
DOCROOT
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
if (isWordPressBacked) {
|
|
395
|
+
if (!php.fileExists(`${DOCROOT}/wp-content/db.php`)) {
|
|
396
|
+
await installSqliteIntegration(
|
|
397
|
+
php,
|
|
398
|
+
await getSqliteIntegrationZip(),
|
|
399
|
+
DOCROOT
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
defineSiteConstants(
|
|
403
|
+
php,
|
|
404
|
+
options.absoluteUrl,
|
|
405
|
+
`${DOCROOT}/wp-content/database`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (freshSite) {
|
|
409
|
+
await runInstaller(requestHandler);
|
|
410
|
+
}
|
|
256
411
|
await applyProjectMounts(php, mode, projectPath);
|
|
257
412
|
if (freshSite) {
|
|
258
413
|
await activateProject(php, mode, projectPath);
|
|
@@ -327,15 +482,17 @@ async function activateProject(php, mode, projectPath) {
|
|
|
327
482
|
}
|
|
328
483
|
}
|
|
329
484
|
async function runWordPressCode(php, code) {
|
|
330
|
-
|
|
331
|
-
|
|
485
|
+
try {
|
|
486
|
+
await runPhp(
|
|
487
|
+
php,
|
|
488
|
+
`
|
|
332
489
|
require ${phpString(`${DOCROOT}/wp-load.php`)};
|
|
333
490
|
require_once ${phpString(`${DOCROOT}/wp-admin/includes/plugin.php`)};
|
|
334
491
|
${code}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
output?.error(`Activation step failed: ${
|
|
492
|
+
`
|
|
493
|
+
);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
output?.error(`Activation step failed: ${error.message}`);
|
|
339
496
|
}
|
|
340
497
|
}
|
|
341
498
|
function phpString(value) {
|
package/dist/main.js
CHANGED
|
@@ -14,17 +14,165 @@ import express from "express";
|
|
|
14
14
|
// src/engine.ts
|
|
15
15
|
import fs10 from "fs";
|
|
16
16
|
import path9 from "path";
|
|
17
|
-
import { createNodeFsMountHandler
|
|
18
|
-
|
|
17
|
+
import { createNodeFsMountHandler } from "@php-wasm/node";
|
|
18
|
+
|
|
19
|
+
// src/boot.ts
|
|
20
|
+
import { rootCertificates } from "tls";
|
|
21
|
+
import { loadNodeRuntime } from "@php-wasm/node";
|
|
22
|
+
import { PHP, PHPRequestHandler, setPhpIniEntries } from "@php-wasm/universal";
|
|
23
|
+
var CA_BUNDLE_PATH = "/internal/ca-bundle.crt";
|
|
24
|
+
async function createPhp(phpVersion) {
|
|
25
|
+
const php = new PHP(
|
|
26
|
+
await loadNodeRuntime(phpVersion, {
|
|
27
|
+
emscriptenOptions: { processId: process.pid }
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
php.setSapiName("cli");
|
|
31
|
+
php.mkdir("/internal");
|
|
32
|
+
php.writeFile(CA_BUNDLE_PATH, rootCertificates.join("\n"));
|
|
33
|
+
await setPhpIniEntries(php, {
|
|
34
|
+
"openssl.cafile": CA_BUNDLE_PATH,
|
|
35
|
+
"curl.cainfo": CA_BUNDLE_PATH,
|
|
36
|
+
allow_url_fopen: "1",
|
|
37
|
+
disable_functions: ""
|
|
38
|
+
});
|
|
39
|
+
return php;
|
|
40
|
+
}
|
|
41
|
+
function createRequestHandler(php, documentRoot, absoluteUrl) {
|
|
42
|
+
return new PHPRequestHandler({ php, documentRoot, absoluteUrl });
|
|
43
|
+
}
|
|
44
|
+
async function runPhp(php, code) {
|
|
45
|
+
const response = await php.run({ code: `<?php ${code}` });
|
|
46
|
+
if (response.exitCode !== 0) {
|
|
47
|
+
throw new Error(response.errors || `PHP exited ${response.exitCode}`);
|
|
48
|
+
}
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
async function unzipTo(php, zip, destination) {
|
|
52
|
+
const tmpZip = `/internal/upload-${Date.now()}.zip`;
|
|
53
|
+
php.writeFile(tmpZip, new Uint8Array(await zip.arrayBuffer()));
|
|
54
|
+
await runPhp(
|
|
55
|
+
php,
|
|
56
|
+
`
|
|
57
|
+
$zip = new ZipArchive();
|
|
58
|
+
if ($zip->open(${str(tmpZip)}) !== true) exit(1);
|
|
59
|
+
if (!is_dir(${str(destination)})) mkdir(${str(destination)}, 0777, true);
|
|
60
|
+
if (!$zip->extractTo(${str(destination)})) exit(1);
|
|
61
|
+
$zip->close();
|
|
62
|
+
unlink(${str(tmpZip)});
|
|
63
|
+
`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
async function installCore(php, coreZip, docroot) {
|
|
67
|
+
const staging = `${docroot}/.wpapp-staging`;
|
|
68
|
+
await unzipTo(php, coreZip, staging);
|
|
69
|
+
await runPhp(
|
|
70
|
+
php,
|
|
71
|
+
`
|
|
72
|
+
function wpapp_core_root($dir) {
|
|
73
|
+
if (file_exists("$dir/wp-config-sample.php")) return $dir;
|
|
74
|
+
foreach (scandir($dir) as $entry) {
|
|
75
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
76
|
+
$candidate = "$dir/$entry";
|
|
77
|
+
if (is_dir($candidate) && file_exists("$candidate/wp-config-sample.php")) {
|
|
78
|
+
return $candidate;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exit(1);
|
|
82
|
+
}
|
|
83
|
+
function wpapp_rrmdir($dir) {
|
|
84
|
+
foreach (scandir($dir) as $entry) {
|
|
85
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
86
|
+
$path = "$dir/$entry";
|
|
87
|
+
is_dir($path) ? wpapp_rrmdir($path) : unlink($path);
|
|
88
|
+
}
|
|
89
|
+
rmdir($dir);
|
|
90
|
+
}
|
|
91
|
+
$root = wpapp_core_root(${str(staging)});
|
|
92
|
+
foreach (scandir($root) as $entry) {
|
|
93
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
94
|
+
rename("$root/$entry", ${str(docroot)} . "/$entry");
|
|
95
|
+
}
|
|
96
|
+
wpapp_rrmdir(${str(staging)});
|
|
97
|
+
if (!file_exists(${str(docroot)} . '/wp-config.php')) {
|
|
98
|
+
copy(
|
|
99
|
+
${str(docroot)} . '/wp-config-sample.php',
|
|
100
|
+
${str(docroot)} . '/wp-config.php'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
async function installSqliteIntegration(php, pluginZip, docroot) {
|
|
107
|
+
const pluginDir = `${docroot}/wp-content/plugins/sqlite-database-integration`;
|
|
108
|
+
const staging = `${docroot}/wp-content/.wpapp-sqlite-staging`;
|
|
109
|
+
await unzipTo(php, pluginZip, staging);
|
|
110
|
+
await runPhp(
|
|
111
|
+
php,
|
|
112
|
+
`
|
|
113
|
+
$root = ${str(staging)};
|
|
114
|
+
$entries = array_values(array_diff(scandir($root), ['.', '..']));
|
|
115
|
+
if (count($entries) === 1 && is_dir("$root/$entries[0]")) {
|
|
116
|
+
$root = "$root/$entries[0]";
|
|
117
|
+
}
|
|
118
|
+
if (!is_dir(${str(pluginDir)})) {
|
|
119
|
+
rename($root, ${str(pluginDir)});
|
|
120
|
+
}
|
|
121
|
+
if (is_dir(${str(staging)})) {
|
|
122
|
+
@rmdir(${str(staging)});
|
|
123
|
+
}
|
|
124
|
+
$dropIn = file_get_contents(${str(pluginDir)} . '/db.copy');
|
|
125
|
+
$dropIn = str_replace(
|
|
126
|
+
'{SQLITE_IMPLEMENTATION_FOLDER_PATH}',
|
|
127
|
+
${str(pluginDir)},
|
|
128
|
+
$dropIn
|
|
129
|
+
);
|
|
130
|
+
$dropIn = str_replace(
|
|
131
|
+
'{SQLITE_PLUGIN}',
|
|
132
|
+
'sqlite-database-integration/load.php',
|
|
133
|
+
$dropIn
|
|
134
|
+
);
|
|
135
|
+
file_put_contents(${str(docroot)} . '/wp-content/db.php', $dropIn);
|
|
136
|
+
`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
function defineSiteConstants(php, siteUrl, databaseDir) {
|
|
140
|
+
php.defineConstant("WP_HOME", siteUrl);
|
|
141
|
+
php.defineConstant("WP_SITEURL", siteUrl);
|
|
142
|
+
php.defineConstant("DB_DIR", databaseDir);
|
|
143
|
+
php.defineConstant("DB_FILE", ".ht.sqlite");
|
|
144
|
+
php.defineConstant("WP_SQLITE_AST_DRIVER", true);
|
|
145
|
+
}
|
|
146
|
+
async function runInstaller(requestHandler) {
|
|
147
|
+
const fields = new URLSearchParams({
|
|
148
|
+
language: "en",
|
|
149
|
+
prefix: "wp_",
|
|
150
|
+
weblog_title: "My WordPress Website",
|
|
151
|
+
user_name: "admin",
|
|
152
|
+
admin_password: "password",
|
|
153
|
+
admin_password2: "password",
|
|
154
|
+
Submit: "Install WordPress",
|
|
155
|
+
pw_weak: "1",
|
|
156
|
+
admin_email: "admin@localhost.com"
|
|
157
|
+
});
|
|
158
|
+
return requestHandler.request({
|
|
159
|
+
url: "/wp-admin/install.php?step=2",
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
162
|
+
body: new TextEncoder().encode(fields.toString())
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function str(value) {
|
|
166
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
167
|
+
}
|
|
19
168
|
|
|
20
169
|
// src/constants.ts
|
|
21
|
-
import { RecommendedPHPVersion } from "@wp-playground/common";
|
|
22
170
|
var WP_APP_HIDDEN_FOLDER = ".wp-app";
|
|
23
171
|
var WP_APP_HOME_ENV = "WP_APP_HOME";
|
|
24
172
|
var SQLITE_URL = "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/main.zip";
|
|
25
173
|
var CLASSICPRESS_LATEST_URL = "https://www.classicpress.net/latest.zip";
|
|
26
174
|
var DEFAULT_PORT = 8881;
|
|
27
|
-
var DEFAULT_PHP_VERSION =
|
|
175
|
+
var DEFAULT_PHP_VERSION = "8.3";
|
|
28
176
|
var DEFAULT_WORDPRESS_VERSION = "latest";
|
|
29
177
|
var DOCROOT = "/wordpress";
|
|
30
178
|
|
|
@@ -32,14 +180,14 @@ var DOCROOT = "/wordpress";
|
|
|
32
180
|
import fs9 from "fs";
|
|
33
181
|
import path8 from "path";
|
|
34
182
|
|
|
35
|
-
// src/
|
|
183
|
+
// src/detect/has-index-file.ts
|
|
36
184
|
import fs from "fs";
|
|
37
185
|
import path from "path";
|
|
38
186
|
function hasIndexFile(projectPath) {
|
|
39
187
|
return fs.existsSync(path.join(projectPath, "index.php"));
|
|
40
188
|
}
|
|
41
189
|
|
|
42
|
-
// src/
|
|
190
|
+
// src/detect/is-valid-wordpress-version.ts
|
|
43
191
|
function isValidWordPressVersion(version) {
|
|
44
192
|
const versionPattern = /^latest$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/;
|
|
45
193
|
const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?)?$/;
|
|
@@ -49,11 +197,11 @@ function isClassicPressVersion(version) {
|
|
|
49
197
|
return version === "classicpress" || version.startsWith("classicpress-");
|
|
50
198
|
}
|
|
51
199
|
|
|
52
|
-
// src/
|
|
200
|
+
// src/detect/get-plugin-file.ts
|
|
53
201
|
import fs3 from "fs";
|
|
54
202
|
import path2, { basename } from "path";
|
|
55
203
|
|
|
56
|
-
// src/
|
|
204
|
+
// src/detect/read-file-head.ts
|
|
57
205
|
import fs2 from "fs";
|
|
58
206
|
function readFileHead(filePath, length = 8192) {
|
|
59
207
|
const buffer = Buffer.alloc(length);
|
|
@@ -64,7 +212,7 @@ function readFileHead(filePath, length = 8192) {
|
|
|
64
212
|
return fileContentBuffer.toString();
|
|
65
213
|
}
|
|
66
214
|
|
|
67
|
-
// src/
|
|
215
|
+
// src/detect/get-plugin-file.ts
|
|
68
216
|
function heuristicSort(files, projectPath) {
|
|
69
217
|
const heuristicsBestGuess = `${basename(projectPath)}.php`;
|
|
70
218
|
const heuristicsBestGuessIndex = files.indexOf(heuristicsBestGuess);
|
|
@@ -88,13 +236,13 @@ function getPluginFile(projectPath) {
|
|
|
88
236
|
return null;
|
|
89
237
|
}
|
|
90
238
|
|
|
91
|
-
// src/
|
|
239
|
+
// src/detect/is-plugin-directory.ts
|
|
92
240
|
function isPluginDirectory(projectPath) {
|
|
93
241
|
const pluginFile = getPluginFile(projectPath);
|
|
94
242
|
return pluginFile !== null;
|
|
95
243
|
}
|
|
96
244
|
|
|
97
|
-
// src/
|
|
245
|
+
// src/detect/is-theme-directory.ts
|
|
98
246
|
import fs4 from "fs";
|
|
99
247
|
import path3 from "path";
|
|
100
248
|
function isThemeDirectory(projectPath) {
|
|
@@ -107,7 +255,7 @@ function isThemeDirectory(projectPath) {
|
|
|
107
255
|
return themeNameRegex.test(styleCSS);
|
|
108
256
|
}
|
|
109
257
|
|
|
110
|
-
// src/
|
|
258
|
+
// src/detect/is-wp-content-directory.ts
|
|
111
259
|
import fs5 from "fs";
|
|
112
260
|
import path4 from "path";
|
|
113
261
|
function isWpContentDirectory(projectPath) {
|
|
@@ -120,14 +268,14 @@ function isWpContentDirectory(projectPath) {
|
|
|
120
268
|
return false;
|
|
121
269
|
}
|
|
122
270
|
|
|
123
|
-
// src/
|
|
271
|
+
// src/detect/is-wordpress-directory.ts
|
|
124
272
|
import fs6 from "fs";
|
|
125
273
|
import path5 from "path";
|
|
126
274
|
function isWordPressDirectory(projectPath) {
|
|
127
275
|
return fs6.existsSync(path5.join(projectPath, "wp-content")) && fs6.existsSync(path5.join(projectPath, "wp-includes")) && fs6.existsSync(path5.join(projectPath, "wp-load.php"));
|
|
128
276
|
}
|
|
129
277
|
|
|
130
|
-
// src/
|
|
278
|
+
// src/detect/is-wordpress-develop-directory.ts
|
|
131
279
|
import fs7 from "fs";
|
|
132
280
|
import path6 from "path";
|
|
133
281
|
function isWordPressDevelopDirectory(projectPath) {
|
|
@@ -343,31 +491,38 @@ async function startWPApp(options) {
|
|
|
343
491
|
output?.log(`wp: ${options.wordPressVersion}`);
|
|
344
492
|
output?.log(`site data: ${hostDocroot}`);
|
|
345
493
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
494
|
+
const php = await createPhp(options.phpVersion);
|
|
495
|
+
php.mkdir(DOCROOT);
|
|
496
|
+
await php.mount(DOCROOT, createNodeFsMountHandler(hostDocroot));
|
|
497
|
+
const requestHandler = createRequestHandler(
|
|
498
|
+
php,
|
|
499
|
+
DOCROOT,
|
|
500
|
+
options.absoluteUrl
|
|
501
|
+
);
|
|
502
|
+
if (freshSite) {
|
|
503
|
+
await installCore(
|
|
504
|
+
php,
|
|
505
|
+
await getCoreZip(options.wordPressVersion),
|
|
506
|
+
DOCROOT
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
if (isWordPressBacked) {
|
|
510
|
+
if (!php.fileExists(`${DOCROOT}/wp-content/db.php`)) {
|
|
511
|
+
await installSqliteIntegration(
|
|
512
|
+
php,
|
|
513
|
+
await getSqliteIntegrationZip(),
|
|
514
|
+
DOCROOT
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
defineSiteConstants(
|
|
518
|
+
php,
|
|
519
|
+
options.absoluteUrl,
|
|
520
|
+
`${DOCROOT}/wp-content/database`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
if (freshSite) {
|
|
524
|
+
await runInstaller(requestHandler);
|
|
525
|
+
}
|
|
371
526
|
await applyProjectMounts(php, mode, projectPath);
|
|
372
527
|
if (freshSite) {
|
|
373
528
|
await activateProject(php, mode, projectPath);
|
|
@@ -442,15 +597,17 @@ async function activateProject(php, mode, projectPath) {
|
|
|
442
597
|
}
|
|
443
598
|
}
|
|
444
599
|
async function runWordPressCode(php, code) {
|
|
445
|
-
|
|
446
|
-
|
|
600
|
+
try {
|
|
601
|
+
await runPhp(
|
|
602
|
+
php,
|
|
603
|
+
`
|
|
447
604
|
require ${phpString(`${DOCROOT}/wp-load.php`)};
|
|
448
605
|
require_once ${phpString(`${DOCROOT}/wp-admin/includes/plugin.php`)};
|
|
449
606
|
${code}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
output?.error(`Activation step failed: ${
|
|
607
|
+
`
|
|
608
|
+
);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
output?.error(`Activation step failed: ${error.message}`);
|
|
454
611
|
}
|
|
455
612
|
}
|
|
456
613
|
function phpString(value) {
|
|
@@ -562,7 +719,7 @@ var parseHeaders = (req) => {
|
|
|
562
719
|
import fs12 from "fs";
|
|
563
720
|
import path10 from "path";
|
|
564
721
|
import { loadNodeRuntime as loadNodeRuntime2 } from "@php-wasm/node";
|
|
565
|
-
import { PHP } from "@php-wasm/universal";
|
|
722
|
+
import { PHP as PHP2 } from "@php-wasm/universal";
|
|
566
723
|
async function executePHP(phpArgs, options) {
|
|
567
724
|
const args = phpArgs.filter((arg) => arg !== "php" && arg !== "--");
|
|
568
725
|
let code;
|
|
@@ -575,7 +732,7 @@ async function executePHP(phpArgs, options) {
|
|
|
575
732
|
'Usage: wp-app php -- -r "<code>" | wp-app php -- <script.php>'
|
|
576
733
|
);
|
|
577
734
|
}
|
|
578
|
-
const php = new
|
|
735
|
+
const php = new PHP2(
|
|
579
736
|
await loadNodeRuntime2(options.phpVersion, {
|
|
580
737
|
emscriptenOptions: { processId: process.pid }
|
|
581
738
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-operations/wp-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The wp-now revival. Zero-config local WordPress dev server on @php-wasm/node.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,8 +41,6 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@php-wasm/node": "3.1.44",
|
|
43
43
|
"@php-wasm/universal": "3.1.44",
|
|
44
|
-
"@wp-playground/common": "3.1.44",
|
|
45
|
-
"@wp-playground/wordpress": "3.1.44",
|
|
46
44
|
"express": "4.21.2",
|
|
47
45
|
"yargs": "17.7.2"
|
|
48
46
|
},
|