@wp-operations/wp-app 0.2.0 → 0.4.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 +21 -34
- package/dist/index.js +345 -109
- package/dist/main.js +896 -322
- package/package.json +5 -8
package/dist/index.js
CHANGED
|
@@ -10,48 +10,211 @@ function getCodeSpaceURL(port) {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
// src/engine.ts
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import { createNodeFsMountHandler
|
|
16
|
-
|
|
13
|
+
import fs11 from "fs";
|
|
14
|
+
import path10 from "path";
|
|
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({
|
|
41
|
+
php,
|
|
42
|
+
documentRoot,
|
|
43
|
+
absoluteUrl,
|
|
44
|
+
// Route unmatched URLs (permalinks, admin-ajax) to index.php, not 404.
|
|
45
|
+
getFileNotFoundAction: () => ({
|
|
46
|
+
type: "internal-redirect",
|
|
47
|
+
uri: "/index.php"
|
|
48
|
+
}),
|
|
49
|
+
// Let Set-Cookie pass through to the real HTTP server (express).
|
|
50
|
+
cookieStore: false
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function runPhp(php, code) {
|
|
54
|
+
const response = await php.run({ code: `<?php ${code}` });
|
|
55
|
+
if (response.exitCode !== 0) {
|
|
56
|
+
throw new Error(response.errors || `PHP exited ${response.exitCode}`);
|
|
57
|
+
}
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
60
|
+
async function unzipTo(php, zip, destination) {
|
|
61
|
+
const tmpZip = `/internal/upload-${Date.now()}.zip`;
|
|
62
|
+
php.writeFile(tmpZip, new Uint8Array(await zip.arrayBuffer()));
|
|
63
|
+
await runPhp(
|
|
64
|
+
php,
|
|
65
|
+
`
|
|
66
|
+
$zip = new ZipArchive();
|
|
67
|
+
if ($zip->open(${str(tmpZip)}) !== true) exit(1);
|
|
68
|
+
if (!is_dir(${str(destination)})) mkdir(${str(destination)}, 0777, true);
|
|
69
|
+
if (!$zip->extractTo(${str(destination)})) exit(1);
|
|
70
|
+
$zip->close();
|
|
71
|
+
unlink(${str(tmpZip)});
|
|
72
|
+
`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
async function installCore(php, coreZip, docroot) {
|
|
76
|
+
const staging = `${docroot}/.wpapp-staging`;
|
|
77
|
+
await unzipTo(php, coreZip, staging);
|
|
78
|
+
await runPhp(
|
|
79
|
+
php,
|
|
80
|
+
`
|
|
81
|
+
function wpapp_core_root($dir) {
|
|
82
|
+
if (file_exists("$dir/wp-config-sample.php")) return $dir;
|
|
83
|
+
foreach (scandir($dir) as $entry) {
|
|
84
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
85
|
+
$candidate = "$dir/$entry";
|
|
86
|
+
if (is_dir($candidate) && file_exists("$candidate/wp-config-sample.php")) {
|
|
87
|
+
return $candidate;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exit(1);
|
|
91
|
+
}
|
|
92
|
+
function wpapp_rrmdir($dir) {
|
|
93
|
+
foreach (scandir($dir) as $entry) {
|
|
94
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
95
|
+
$path = "$dir/$entry";
|
|
96
|
+
is_dir($path) ? wpapp_rrmdir($path) : unlink($path);
|
|
97
|
+
}
|
|
98
|
+
rmdir($dir);
|
|
99
|
+
}
|
|
100
|
+
$root = wpapp_core_root(${str(staging)});
|
|
101
|
+
foreach (scandir($root) as $entry) {
|
|
102
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
103
|
+
rename("$root/$entry", ${str(docroot)} . "/$entry");
|
|
104
|
+
}
|
|
105
|
+
wpapp_rrmdir(${str(staging)});
|
|
106
|
+
if (!file_exists(${str(docroot)} . '/wp-config.php')) {
|
|
107
|
+
copy(
|
|
108
|
+
${str(docroot)} . '/wp-config-sample.php',
|
|
109
|
+
${str(docroot)} . '/wp-config.php'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
async function installSqliteIntegration(php, pluginZip, docroot) {
|
|
116
|
+
const pluginDir = `${docroot}/wp-content/plugins/sqlite-database-integration`;
|
|
117
|
+
const staging = `${docroot}/wp-content/.wpapp-sqlite-staging`;
|
|
118
|
+
await unzipTo(php, pluginZip, staging);
|
|
119
|
+
await runPhp(
|
|
120
|
+
php,
|
|
121
|
+
`
|
|
122
|
+
$root = ${str(staging)};
|
|
123
|
+
$entries = array_values(array_diff(scandir($root), ['.', '..']));
|
|
124
|
+
if (count($entries) === 1 && is_dir("$root/$entries[0]")) {
|
|
125
|
+
$root = "$root/$entries[0]";
|
|
126
|
+
}
|
|
127
|
+
if (!is_dir(${str(pluginDir)})) {
|
|
128
|
+
rename($root, ${str(pluginDir)});
|
|
129
|
+
}
|
|
130
|
+
if (is_dir(${str(staging)})) {
|
|
131
|
+
@rmdir(${str(staging)});
|
|
132
|
+
}
|
|
133
|
+
$dropIn = file_get_contents(${str(pluginDir)} . '/db.copy');
|
|
134
|
+
$dropIn = str_replace(
|
|
135
|
+
'{SQLITE_IMPLEMENTATION_FOLDER_PATH}',
|
|
136
|
+
${str(pluginDir)},
|
|
137
|
+
$dropIn
|
|
138
|
+
);
|
|
139
|
+
$dropIn = str_replace(
|
|
140
|
+
'{SQLITE_PLUGIN}',
|
|
141
|
+
'sqlite-database-integration/load.php',
|
|
142
|
+
$dropIn
|
|
143
|
+
);
|
|
144
|
+
file_put_contents(${str(docroot)} . '/wp-content/db.php', $dropIn);
|
|
145
|
+
`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function defineSiteConstants(php, siteUrl, databaseDir) {
|
|
149
|
+
php.defineConstant("WP_HOME", siteUrl);
|
|
150
|
+
php.defineConstant("WP_SITEURL", siteUrl);
|
|
151
|
+
php.defineConstant("DB_DIR", databaseDir);
|
|
152
|
+
php.defineConstant("DB_FILE", ".ht.sqlite");
|
|
153
|
+
php.defineConstant("WP_SQLITE_AST_DRIVER", true);
|
|
154
|
+
php.defineConstant("DISABLE_WP_CRON", true);
|
|
155
|
+
}
|
|
156
|
+
async function runInstaller(requestHandler) {
|
|
157
|
+
const adminUser = process.env.WP_ADMIN_USER || "admin";
|
|
158
|
+
const adminPassword = process.env.WP_ADMIN_PASSWORD || "password";
|
|
159
|
+
const adminEmail = process.env.WP_ADMIN_EMAIL || "admin@localhost.com";
|
|
160
|
+
const fields = new URLSearchParams({
|
|
161
|
+
language: "en",
|
|
162
|
+
prefix: "wp_",
|
|
163
|
+
weblog_title: "My WordPress Website",
|
|
164
|
+
user_name: adminUser,
|
|
165
|
+
admin_password: adminPassword,
|
|
166
|
+
admin_password2: adminPassword,
|
|
167
|
+
Submit: "Install WordPress",
|
|
168
|
+
pw_weak: "1",
|
|
169
|
+
admin_email: adminEmail
|
|
170
|
+
});
|
|
171
|
+
return requestHandler.request({
|
|
172
|
+
url: "/wp-admin/install.php?step=2",
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
175
|
+
body: new TextEncoder().encode(fields.toString())
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function str(value) {
|
|
179
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
180
|
+
}
|
|
17
181
|
|
|
18
182
|
// src/constants.ts
|
|
19
|
-
import { RecommendedPHPVersion } from "@wp-playground/common";
|
|
20
183
|
var WP_APP_HIDDEN_FOLDER = ".wp-app";
|
|
21
184
|
var WP_APP_HOME_ENV = "WP_APP_HOME";
|
|
22
185
|
var SQLITE_URL = "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/main.zip";
|
|
23
186
|
var CLASSICPRESS_LATEST_URL = "https://www.classicpress.net/latest.zip";
|
|
24
187
|
var DEFAULT_PORT = 8881;
|
|
25
|
-
var DEFAULT_PHP_VERSION =
|
|
188
|
+
var DEFAULT_PHP_VERSION = "8.3";
|
|
26
189
|
var DEFAULT_WORDPRESS_VERSION = "latest";
|
|
27
190
|
var DOCROOT = "/wordpress";
|
|
28
191
|
|
|
29
192
|
// src/download.ts
|
|
30
|
-
import
|
|
31
|
-
import
|
|
193
|
+
import fs10 from "fs";
|
|
194
|
+
import path9 from "path";
|
|
32
195
|
|
|
33
|
-
// src/
|
|
196
|
+
// src/detect/has-index-file.ts
|
|
34
197
|
import fs from "fs";
|
|
35
198
|
import path from "path";
|
|
36
199
|
function hasIndexFile(projectPath) {
|
|
37
200
|
return fs.existsSync(path.join(projectPath, "index.php"));
|
|
38
201
|
}
|
|
39
202
|
|
|
40
|
-
// src/
|
|
203
|
+
// src/detect/is-valid-wordpress-version.ts
|
|
41
204
|
function isValidWordPressVersion(version) {
|
|
42
205
|
const versionPattern = /^latest$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/;
|
|
43
|
-
const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?)?$/;
|
|
206
|
+
const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?(?:-rc(?:\d+)?)?)?$/;
|
|
44
207
|
return versionPattern.test(version) || classicPressPattern.test(version);
|
|
45
208
|
}
|
|
46
209
|
function isClassicPressVersion(version) {
|
|
47
210
|
return version === "classicpress" || version.startsWith("classicpress-");
|
|
48
211
|
}
|
|
49
212
|
|
|
50
|
-
// src/
|
|
213
|
+
// src/detect/get-plugin-file.ts
|
|
51
214
|
import fs3 from "fs";
|
|
52
215
|
import path2, { basename } from "path";
|
|
53
216
|
|
|
54
|
-
// src/
|
|
217
|
+
// src/detect/read-file-head.ts
|
|
55
218
|
import fs2 from "fs";
|
|
56
219
|
function readFileHead(filePath, length = 8192) {
|
|
57
220
|
const buffer = Buffer.alloc(length);
|
|
@@ -62,7 +225,7 @@ function readFileHead(filePath, length = 8192) {
|
|
|
62
225
|
return fileContentBuffer.toString();
|
|
63
226
|
}
|
|
64
227
|
|
|
65
|
-
// src/
|
|
228
|
+
// src/detect/get-plugin-file.ts
|
|
66
229
|
function heuristicSort(files, projectPath) {
|
|
67
230
|
const heuristicsBestGuess = `${basename(projectPath)}.php`;
|
|
68
231
|
const heuristicsBestGuessIndex = files.indexOf(heuristicsBestGuess);
|
|
@@ -86,13 +249,13 @@ function getPluginFile(projectPath) {
|
|
|
86
249
|
return null;
|
|
87
250
|
}
|
|
88
251
|
|
|
89
|
-
// src/
|
|
252
|
+
// src/detect/is-plugin-directory.ts
|
|
90
253
|
function isPluginDirectory(projectPath) {
|
|
91
254
|
const pluginFile = getPluginFile(projectPath);
|
|
92
255
|
return pluginFile !== null;
|
|
93
256
|
}
|
|
94
257
|
|
|
95
|
-
// src/
|
|
258
|
+
// src/detect/is-theme-directory.ts
|
|
96
259
|
import fs4 from "fs";
|
|
97
260
|
import path3 from "path";
|
|
98
261
|
function isThemeDirectory(projectPath) {
|
|
@@ -105,43 +268,54 @@ function isThemeDirectory(projectPath) {
|
|
|
105
268
|
return themeNameRegex.test(styleCSS);
|
|
106
269
|
}
|
|
107
270
|
|
|
108
|
-
// src/
|
|
271
|
+
// src/detect/is-wp-app-project.ts
|
|
109
272
|
import fs5 from "fs";
|
|
110
273
|
import path4 from "path";
|
|
274
|
+
function isWpAppProject(projectPath) {
|
|
275
|
+
const composerJsonPath = path4.join(projectPath, "composer.json");
|
|
276
|
+
if (!fs5.existsSync(composerJsonPath)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
const composerJson = JSON.parse(fs5.readFileSync(composerJsonPath, "utf8"));
|
|
281
|
+
return composerJson.type === "wp-app";
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/detect/is-wp-content-directory.ts
|
|
288
|
+
import fs6 from "fs";
|
|
289
|
+
import path5 from "path";
|
|
111
290
|
function isWpContentDirectory(projectPath) {
|
|
112
|
-
const muPluginsExists =
|
|
113
|
-
const pluginsExists =
|
|
114
|
-
const themesExists =
|
|
291
|
+
const muPluginsExists = fs6.existsSync(path5.join(projectPath, "mu-plugins"));
|
|
292
|
+
const pluginsExists = fs6.existsSync(path5.join(projectPath, "plugins"));
|
|
293
|
+
const themesExists = fs6.existsSync(path5.join(projectPath, "themes"));
|
|
115
294
|
if (muPluginsExists || pluginsExists || themesExists) {
|
|
116
295
|
return true;
|
|
117
296
|
}
|
|
118
297
|
return false;
|
|
119
298
|
}
|
|
120
299
|
|
|
121
|
-
// src/
|
|
122
|
-
import
|
|
123
|
-
import
|
|
300
|
+
// src/detect/is-wordpress-directory.ts
|
|
301
|
+
import fs7 from "fs";
|
|
302
|
+
import path6 from "path";
|
|
124
303
|
function isWordPressDirectory(projectPath) {
|
|
125
|
-
return
|
|
304
|
+
return fs7.existsSync(path6.join(projectPath, "wp-content")) && fs7.existsSync(path6.join(projectPath, "wp-includes")) && fs7.existsSync(path6.join(projectPath, "wp-load.php"));
|
|
126
305
|
}
|
|
127
306
|
|
|
128
|
-
// src/
|
|
129
|
-
import
|
|
130
|
-
import
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"build/wp-load.php"
|
|
141
|
-
];
|
|
142
|
-
return requiredFiles.every(
|
|
143
|
-
(file) => fs7.existsSync(path6.join(projectPath, file))
|
|
144
|
-
);
|
|
307
|
+
// src/detect/is-classicpress-directory.ts
|
|
308
|
+
import fs8 from "fs";
|
|
309
|
+
import path7 from "path";
|
|
310
|
+
function isClassicPressDirectory(projectPath) {
|
|
311
|
+
if (!isWordPressDirectory(projectPath)) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
const versionFile = path7.join(projectPath, "wp-includes", "version.php");
|
|
315
|
+
if (!fs8.existsSync(versionFile)) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return fs8.readFileSync(versionFile, "utf8").includes("classicpress_version");
|
|
145
319
|
}
|
|
146
320
|
|
|
147
321
|
// src/output.ts
|
|
@@ -152,24 +326,54 @@ var output = shouldOutput() ? console : null;
|
|
|
152
326
|
|
|
153
327
|
// src/paths.ts
|
|
154
328
|
import crypto from "crypto";
|
|
155
|
-
import
|
|
329
|
+
import fs9 from "fs";
|
|
156
330
|
import os from "os";
|
|
157
|
-
import
|
|
331
|
+
import path8 from "path";
|
|
158
332
|
function getWpAppHome() {
|
|
159
|
-
|
|
333
|
+
const home = process.env[WP_APP_HOME_ENV] || path8.join(os.homedir(), WP_APP_HIDDEN_FOLDER);
|
|
334
|
+
cleanupLegacyHome(home);
|
|
335
|
+
return home;
|
|
160
336
|
}
|
|
161
337
|
function getCachePath() {
|
|
162
|
-
return ensureDir(
|
|
338
|
+
return ensureDir(path8.join(getWpAppHome(), "cache"));
|
|
339
|
+
}
|
|
340
|
+
function getCorePath(wordPressVersion) {
|
|
341
|
+
const key = wordPressVersion.replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
342
|
+
return path8.join(getWpAppHome(), "core", key);
|
|
163
343
|
}
|
|
164
344
|
function getSitePath(projectPath) {
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
|
|
345
|
+
const resolved = path8.resolve(projectPath);
|
|
346
|
+
const identity = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
347
|
+
const projectName = path8.basename(resolved);
|
|
348
|
+
const hash = crypto.createHash("sha1").update(identity).digest("hex");
|
|
349
|
+
return path8.join(getWpAppHome(), "sites", `${projectName}-${hash}`);
|
|
350
|
+
}
|
|
351
|
+
function getProjectLocalPath(projectPath) {
|
|
352
|
+
return path8.join(path8.resolve(projectPath), ".local", "wp-app");
|
|
168
353
|
}
|
|
169
354
|
function ensureDir(dir) {
|
|
170
|
-
|
|
355
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
171
356
|
return dir;
|
|
172
357
|
}
|
|
358
|
+
var LEGACY_TOP_LEVEL_DIRS = [
|
|
359
|
+
"wordpress-versions",
|
|
360
|
+
"wp-content",
|
|
361
|
+
"mu-plugins",
|
|
362
|
+
"sqlite-database-integration-main"
|
|
363
|
+
];
|
|
364
|
+
var legacyCleanupDone = false;
|
|
365
|
+
function cleanupLegacyHome(home) {
|
|
366
|
+
if (legacyCleanupDone) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
legacyCleanupDone = true;
|
|
370
|
+
for (const name of LEGACY_TOP_LEVEL_DIRS) {
|
|
371
|
+
const dir = path8.join(home, name);
|
|
372
|
+
if (fs9.existsSync(dir)) {
|
|
373
|
+
fs9.rmSync(dir, { recursive: true, force: true });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
173
377
|
|
|
174
378
|
// src/download.ts
|
|
175
379
|
function getWordPressVersionUrl(version) {
|
|
@@ -188,8 +392,8 @@ function getClassicPressVersionUrl(version) {
|
|
|
188
392
|
return `https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${tag}.zip`;
|
|
189
393
|
}
|
|
190
394
|
async function cachedZip(url, cacheKey) {
|
|
191
|
-
const cacheFile =
|
|
192
|
-
if (!
|
|
395
|
+
const cacheFile = path9.join(getCachePath(), cacheKey);
|
|
396
|
+
if (!fs10.existsSync(cacheFile)) {
|
|
193
397
|
output?.log(`Downloading ${cacheKey}...`);
|
|
194
398
|
const response = await fetch(url, { redirect: "follow" });
|
|
195
399
|
if (!response.ok) {
|
|
@@ -198,11 +402,11 @@ async function cachedZip(url, cacheKey) {
|
|
|
198
402
|
);
|
|
199
403
|
}
|
|
200
404
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
201
|
-
|
|
202
|
-
|
|
405
|
+
fs10.writeFileSync(`${cacheFile}.partial`, bytes);
|
|
406
|
+
fs10.renameSync(`${cacheFile}.partial`, cacheFile);
|
|
203
407
|
output?.log(`Cached ${cacheKey} (${bytes.length} bytes).`);
|
|
204
408
|
}
|
|
205
|
-
return new File([
|
|
409
|
+
return new File([fs10.readFileSync(cacheFile)], cacheKey);
|
|
206
410
|
}
|
|
207
411
|
async function getCoreZip(version) {
|
|
208
412
|
if (isClassicPressVersion(version)) {
|
|
@@ -216,43 +420,71 @@ async function getSqliteIntegrationZip() {
|
|
|
216
420
|
|
|
217
421
|
// src/engine.ts
|
|
218
422
|
async function startWPApp(options) {
|
|
219
|
-
const projectPath =
|
|
423
|
+
const projectPath = path10.resolve(options.projectPath);
|
|
220
424
|
const mode = options.mode;
|
|
221
425
|
output?.log(`directory: ${options.projectPath}`);
|
|
222
426
|
output?.log(`mode: ${mode}`);
|
|
223
427
|
output?.log(`php: ${options.phpVersion}`);
|
|
224
|
-
const
|
|
428
|
+
const sharesCore = mode === "wp-app" /* WP_APP */ || mode === "theme" /* THEME */ || mode === "plugin" /* PLUGIN */ || mode === "wp-content" /* WP_CONTENT */;
|
|
429
|
+
const hostDocroot = mode === "index" /* INDEX */ || mode === "wordpress" /* WORDPRESS */ || mode === "classicpress" /* CLASSICPRESS */ ? projectPath : ensureDir(getCorePath(options.wordPressVersion));
|
|
430
|
+
const projectDataRoot = sharesCore ? ensureDir(
|
|
431
|
+
mode === "wp-app" /* WP_APP */ ? getProjectLocalPath(projectPath) : getSitePath(projectPath)
|
|
432
|
+
) : null;
|
|
225
433
|
const isWordPressBacked = mode !== "index" /* INDEX */;
|
|
226
|
-
const
|
|
434
|
+
const freshCore = isWordPressBacked && !fs11.existsSync(path10.join(hostDocroot, "wp-load.php"));
|
|
435
|
+
const freshSite = sharesCore ? !fs11.existsSync(path10.join(projectDataRoot, "database", ".ht.sqlite")) : freshCore;
|
|
227
436
|
if (isWordPressBacked) {
|
|
228
437
|
output?.log(`wp: ${options.wordPressVersion}`);
|
|
229
|
-
output?.log(`site data: ${hostDocroot}`);
|
|
230
|
-
}
|
|
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
|
-
|
|
438
|
+
output?.log(`site data: ${projectDataRoot ?? hostDocroot}`);
|
|
439
|
+
}
|
|
440
|
+
const php = await createPhp(options.phpVersion);
|
|
441
|
+
php.mkdir(DOCROOT);
|
|
442
|
+
await php.mount(DOCROOT, createNodeFsMountHandler(hostDocroot));
|
|
443
|
+
const requestHandler = createRequestHandler(
|
|
444
|
+
php,
|
|
445
|
+
DOCROOT,
|
|
446
|
+
options.absoluteUrl
|
|
447
|
+
);
|
|
448
|
+
if (freshCore) {
|
|
449
|
+
await installCore(
|
|
450
|
+
php,
|
|
451
|
+
await getCoreZip(options.wordPressVersion),
|
|
452
|
+
DOCROOT
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
if (isWordPressBacked) {
|
|
456
|
+
if (!php.fileExists(`${DOCROOT}/wp-content/db.php`)) {
|
|
457
|
+
await installSqliteIntegration(
|
|
458
|
+
php,
|
|
459
|
+
await getSqliteIntegrationZip(),
|
|
460
|
+
DOCROOT
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (projectDataRoot) {
|
|
465
|
+
const databaseDir = ensureDir(path10.join(projectDataRoot, "database"));
|
|
466
|
+
const uploadsDir = ensureDir(path10.join(projectDataRoot, "uploads"));
|
|
467
|
+
php.mkdir(`${DOCROOT}/wp-content/database`);
|
|
468
|
+
await php.mount(
|
|
469
|
+
`${DOCROOT}/wp-content/database`,
|
|
470
|
+
createNodeFsMountHandler(databaseDir)
|
|
471
|
+
);
|
|
472
|
+
php.mkdir(`${DOCROOT}/wp-content/uploads`);
|
|
473
|
+
await php.mount(
|
|
474
|
+
`${DOCROOT}/wp-content/uploads`,
|
|
475
|
+
createNodeFsMountHandler(uploadsDir)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
if (isWordPressBacked) {
|
|
479
|
+
defineSiteConstants(
|
|
480
|
+
php,
|
|
481
|
+
options.absoluteUrl,
|
|
482
|
+
`${DOCROOT}/wp-content/database`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
if (freshSite) {
|
|
486
|
+
await runInstaller(requestHandler);
|
|
487
|
+
}
|
|
256
488
|
await applyProjectMounts(php, mode, projectPath);
|
|
257
489
|
if (freshSite) {
|
|
258
490
|
await activateProject(php, mode, projectPath);
|
|
@@ -260,8 +492,9 @@ async function startWPApp(options) {
|
|
|
260
492
|
return { requestHandler, php, options, freshSite };
|
|
261
493
|
}
|
|
262
494
|
async function applyProjectMounts(php, mode, projectPath) {
|
|
263
|
-
const projectName =
|
|
495
|
+
const projectName = path10.basename(projectPath);
|
|
264
496
|
switch (mode) {
|
|
497
|
+
case "wp-app" /* WP_APP */:
|
|
265
498
|
case "theme" /* THEME */:
|
|
266
499
|
await mountAt(
|
|
267
500
|
php,
|
|
@@ -277,13 +510,13 @@ async function applyProjectMounts(php, mode, projectPath) {
|
|
|
277
510
|
);
|
|
278
511
|
break;
|
|
279
512
|
case "wp-content" /* WP_CONTENT */:
|
|
280
|
-
for (const entry of
|
|
513
|
+
for (const entry of fs11.readdirSync(projectPath)) {
|
|
281
514
|
if (entry === "index.php") {
|
|
282
515
|
continue;
|
|
283
516
|
}
|
|
284
517
|
await mountAt(
|
|
285
518
|
php,
|
|
286
|
-
|
|
519
|
+
path10.join(projectPath, entry),
|
|
287
520
|
`${DOCROOT}/wp-content/${entry}`
|
|
288
521
|
);
|
|
289
522
|
}
|
|
@@ -293,14 +526,14 @@ async function applyProjectMounts(php, mode, projectPath) {
|
|
|
293
526
|
}
|
|
294
527
|
}
|
|
295
528
|
async function mountAt(php, hostPath, vfsPath) {
|
|
296
|
-
if (
|
|
529
|
+
if (fs11.statSync(hostPath).isDirectory()) {
|
|
297
530
|
php.mkdir(vfsPath);
|
|
298
531
|
}
|
|
299
532
|
await php.mount(vfsPath, createNodeFsMountHandler(hostPath));
|
|
300
533
|
}
|
|
301
534
|
async function activateProject(php, mode, projectPath) {
|
|
302
|
-
const projectName =
|
|
303
|
-
if (mode === "theme" /* THEME */) {
|
|
535
|
+
const projectName = path10.basename(projectPath);
|
|
536
|
+
if (mode === "wp-app" /* WP_APP */ || mode === "theme" /* THEME */) {
|
|
304
537
|
await runWordPressCode(
|
|
305
538
|
php,
|
|
306
539
|
`switch_theme(${phpString(projectName)});`
|
|
@@ -327,15 +560,17 @@ async function activateProject(php, mode, projectPath) {
|
|
|
327
560
|
}
|
|
328
561
|
}
|
|
329
562
|
async function runWordPressCode(php, code) {
|
|
330
|
-
|
|
331
|
-
|
|
563
|
+
try {
|
|
564
|
+
await runPhp(
|
|
565
|
+
php,
|
|
566
|
+
`
|
|
332
567
|
require ${phpString(`${DOCROOT}/wp-load.php`)};
|
|
333
568
|
require_once ${phpString(`${DOCROOT}/wp-admin/includes/plugin.php`)};
|
|
334
569
|
${code}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
output?.error(`Activation step failed: ${
|
|
570
|
+
`
|
|
571
|
+
);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
output?.error(`Activation step failed: ${error.message}`);
|
|
339
574
|
}
|
|
340
575
|
}
|
|
341
576
|
function phpString(value) {
|
|
@@ -343,11 +578,11 @@ function phpString(value) {
|
|
|
343
578
|
}
|
|
344
579
|
function findPluginFile(projectPath) {
|
|
345
580
|
const pluginNameRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;
|
|
346
|
-
for (const file of
|
|
581
|
+
for (const file of fs11.readdirSync(projectPath)) {
|
|
347
582
|
if (!file.endsWith(".php")) {
|
|
348
583
|
continue;
|
|
349
584
|
}
|
|
350
|
-
const content =
|
|
585
|
+
const content = fs11.readFileSync(path10.join(projectPath, file), "utf8");
|
|
351
586
|
if (pluginNameRegex.test(content)) {
|
|
352
587
|
return file;
|
|
353
588
|
}
|
|
@@ -355,8 +590,10 @@ function findPluginFile(projectPath) {
|
|
|
355
590
|
return null;
|
|
356
591
|
}
|
|
357
592
|
function inferMode(projectPath) {
|
|
358
|
-
if (
|
|
359
|
-
return "
|
|
593
|
+
if (isWpAppProject(projectPath)) {
|
|
594
|
+
return "wp-app" /* WP_APP */;
|
|
595
|
+
} else if (isClassicPressDirectory(projectPath)) {
|
|
596
|
+
return "classicpress" /* CLASSICPRESS */;
|
|
360
597
|
} else if (isWordPressDirectory(projectPath)) {
|
|
361
598
|
return "wordpress" /* WORDPRESS */;
|
|
362
599
|
} else if (isWpContentDirectory(projectPath)) {
|
|
@@ -368,7 +605,7 @@ function inferMode(projectPath) {
|
|
|
368
605
|
} else if (hasIndexFile(projectPath)) {
|
|
369
606
|
return "index" /* INDEX */;
|
|
370
607
|
}
|
|
371
|
-
return
|
|
608
|
+
return null;
|
|
372
609
|
}
|
|
373
610
|
|
|
374
611
|
// src/port-finder.ts
|
|
@@ -399,11 +636,6 @@ var PortFinder = class _PortFinder {
|
|
|
399
636
|
});
|
|
400
637
|
});
|
|
401
638
|
}
|
|
402
|
-
/**
|
|
403
|
-
* Returns the first available open port, caching and reusing it for subsequent calls.
|
|
404
|
-
*
|
|
405
|
-
* @returns {Promise<number>} A promise that resolves to the open port number.
|
|
406
|
-
*/
|
|
407
639
|
async getOpenPort() {
|
|
408
640
|
if (this.#openPort) {
|
|
409
641
|
return this.#openPort;
|
|
@@ -455,7 +687,11 @@ async function getWpAppConfig(args) {
|
|
|
455
687
|
}
|
|
456
688
|
});
|
|
457
689
|
if (!options.mode || options.mode === "auto") {
|
|
458
|
-
|
|
690
|
+
const inferred = inferMode(options.projectPath);
|
|
691
|
+
if (!inferred) {
|
|
692
|
+
process.exit(1);
|
|
693
|
+
}
|
|
694
|
+
options.mode = inferred;
|
|
459
695
|
}
|
|
460
696
|
if (!options.absoluteUrl) {
|
|
461
697
|
options.absoluteUrl = await getAbsoluteURL();
|
|
@@ -474,12 +710,12 @@ async function getWpAppConfig(args) {
|
|
|
474
710
|
}
|
|
475
711
|
|
|
476
712
|
// src/start-server.ts
|
|
477
|
-
import
|
|
713
|
+
import fs12 from "fs";
|
|
478
714
|
import { Readable } from "stream";
|
|
479
715
|
import { pipeline } from "stream/promises";
|
|
480
716
|
import express from "express";
|
|
481
717
|
async function startServer(options = {}) {
|
|
482
|
-
if (!
|
|
718
|
+
if (!fs12.existsSync(options.projectPath)) {
|
|
483
719
|
throw new Error(
|
|
484
720
|
`The given path "${options.projectPath}" does not exist.`
|
|
485
721
|
);
|