@wp-operations/wp-app 0.1.1 → 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 +24 -14
- package/dist/index.js +407 -496
- package/dist/main.js +504 -581
- package/package.json +5 -10
package/dist/index.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
|
-
import {
|
|
3
|
-
SupportedPHPVersionsList
|
|
4
|
-
} from "@php-wasm/universal";
|
|
5
|
-
import crypto from "crypto";
|
|
2
|
+
import { SupportedPHPVersionsList } from "@php-wasm/universal";
|
|
6
3
|
|
|
7
4
|
// src/github-codespaces.ts
|
|
8
5
|
var isGitHubCodespace = Boolean(
|
|
@@ -12,35 +9,183 @@ function getCodeSpaceURL(port) {
|
|
|
12
9
|
return `https://${process.env.CODESPACE_NAME}-${port}.${process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}`;
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
// src/
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
12
|
+
// src/engine.ts
|
|
13
|
+
import fs10 from "fs";
|
|
14
|
+
import path9 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({ 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
|
+
}
|
|
19
166
|
|
|
20
167
|
// src/constants.ts
|
|
21
|
-
var
|
|
22
|
-
var
|
|
168
|
+
var WP_APP_HIDDEN_FOLDER = ".wp-app";
|
|
169
|
+
var WP_APP_HOME_ENV = "WP_APP_HOME";
|
|
23
170
|
var SQLITE_URL = "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/main.zip";
|
|
24
171
|
var CLASSICPRESS_LATEST_URL = "https://www.classicpress.net/latest.zip";
|
|
25
172
|
var DEFAULT_PORT = 8881;
|
|
26
|
-
var DEFAULT_PHP_VERSION = "8.
|
|
173
|
+
var DEFAULT_PHP_VERSION = "8.3";
|
|
27
174
|
var DEFAULT_WORDPRESS_VERSION = "latest";
|
|
175
|
+
var DOCROOT = "/wordpress";
|
|
28
176
|
|
|
29
177
|
// src/download.ts
|
|
30
|
-
import
|
|
31
|
-
import
|
|
32
|
-
import followRedirects from "follow-redirects";
|
|
33
|
-
import unzipper from "unzipper";
|
|
34
|
-
import os3 from "os";
|
|
178
|
+
import fs9 from "fs";
|
|
179
|
+
import path8 from "path";
|
|
35
180
|
|
|
36
|
-
// src/
|
|
37
|
-
import fs from "fs
|
|
181
|
+
// src/detect/has-index-file.ts
|
|
182
|
+
import fs from "fs";
|
|
38
183
|
import path from "path";
|
|
39
184
|
function hasIndexFile(projectPath) {
|
|
40
185
|
return fs.existsSync(path.join(projectPath, "index.php"));
|
|
41
186
|
}
|
|
42
187
|
|
|
43
|
-
// src/
|
|
188
|
+
// src/detect/is-valid-wordpress-version.ts
|
|
44
189
|
function isValidWordPressVersion(version) {
|
|
45
190
|
const versionPattern = /^latest$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/;
|
|
46
191
|
const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?)?$/;
|
|
@@ -50,12 +195,12 @@ function isClassicPressVersion(version) {
|
|
|
50
195
|
return version === "classicpress" || version.startsWith("classicpress-");
|
|
51
196
|
}
|
|
52
197
|
|
|
53
|
-
// src/
|
|
54
|
-
import fs3 from "fs
|
|
198
|
+
// src/detect/get-plugin-file.ts
|
|
199
|
+
import fs3 from "fs";
|
|
55
200
|
import path2, { basename } from "path";
|
|
56
201
|
|
|
57
|
-
// src/
|
|
58
|
-
import fs2 from "fs
|
|
202
|
+
// src/detect/read-file-head.ts
|
|
203
|
+
import fs2 from "fs";
|
|
59
204
|
function readFileHead(filePath, length = 8192) {
|
|
60
205
|
const buffer = Buffer.alloc(length);
|
|
61
206
|
const fd = fs2.openSync(filePath, "r");
|
|
@@ -65,7 +210,7 @@ function readFileHead(filePath, length = 8192) {
|
|
|
65
210
|
return fileContentBuffer.toString();
|
|
66
211
|
}
|
|
67
212
|
|
|
68
|
-
// src/
|
|
213
|
+
// src/detect/get-plugin-file.ts
|
|
69
214
|
function heuristicSort(files, projectPath) {
|
|
70
215
|
const heuristicsBestGuess = `${basename(projectPath)}.php`;
|
|
71
216
|
const heuristicsBestGuessIndex = files.indexOf(heuristicsBestGuess);
|
|
@@ -89,14 +234,14 @@ function getPluginFile(projectPath) {
|
|
|
89
234
|
return null;
|
|
90
235
|
}
|
|
91
236
|
|
|
92
|
-
// src/
|
|
237
|
+
// src/detect/is-plugin-directory.ts
|
|
93
238
|
function isPluginDirectory(projectPath) {
|
|
94
239
|
const pluginFile = getPluginFile(projectPath);
|
|
95
240
|
return pluginFile !== null;
|
|
96
241
|
}
|
|
97
242
|
|
|
98
|
-
// src/
|
|
99
|
-
import fs4 from "fs
|
|
243
|
+
// src/detect/is-theme-directory.ts
|
|
244
|
+
import fs4 from "fs";
|
|
100
245
|
import path3 from "path";
|
|
101
246
|
function isThemeDirectory(projectPath) {
|
|
102
247
|
const styleCSSExists = fs4.existsSync(path3.join(projectPath, "style.css"));
|
|
@@ -108,8 +253,8 @@ function isThemeDirectory(projectPath) {
|
|
|
108
253
|
return themeNameRegex.test(styleCSS);
|
|
109
254
|
}
|
|
110
255
|
|
|
111
|
-
// src/
|
|
112
|
-
import fs5 from "fs
|
|
256
|
+
// src/detect/is-wp-content-directory.ts
|
|
257
|
+
import fs5 from "fs";
|
|
113
258
|
import path4 from "path";
|
|
114
259
|
function isWpContentDirectory(projectPath) {
|
|
115
260
|
const muPluginsExists = fs5.existsSync(path4.join(projectPath, "mu-plugins"));
|
|
@@ -121,15 +266,15 @@ function isWpContentDirectory(projectPath) {
|
|
|
121
266
|
return false;
|
|
122
267
|
}
|
|
123
268
|
|
|
124
|
-
// src/
|
|
125
|
-
import fs6 from "fs
|
|
269
|
+
// src/detect/is-wordpress-directory.ts
|
|
270
|
+
import fs6 from "fs";
|
|
126
271
|
import path5 from "path";
|
|
127
272
|
function isWordPressDirectory(projectPath) {
|
|
128
273
|
return fs6.existsSync(path5.join(projectPath, "wp-content")) && fs6.existsSync(path5.join(projectPath, "wp-includes")) && fs6.existsSync(path5.join(projectPath, "wp-load.php"));
|
|
129
274
|
}
|
|
130
275
|
|
|
131
|
-
// src/
|
|
132
|
-
import fs7 from "fs
|
|
276
|
+
// src/detect/is-wordpress-develop-directory.ts
|
|
277
|
+
import fs7 from "fs";
|
|
133
278
|
import path6 from "path";
|
|
134
279
|
function isWordPressDevelopDirectory(projectPath) {
|
|
135
280
|
const requiredFiles = [
|
|
@@ -153,40 +298,29 @@ function shouldOutput() {
|
|
|
153
298
|
}
|
|
154
299
|
var output = shouldOutput() ? console : null;
|
|
155
300
|
|
|
156
|
-
// src/
|
|
157
|
-
import
|
|
158
|
-
import
|
|
159
|
-
|
|
160
|
-
// src/get-wp-now-tmp-path.ts
|
|
161
|
-
import path7 from "path";
|
|
301
|
+
// src/paths.ts
|
|
302
|
+
import crypto from "crypto";
|
|
303
|
+
import fs8 from "fs";
|
|
162
304
|
import os from "os";
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return path7.join(
|
|
305
|
+
import path7 from "path";
|
|
306
|
+
function getWpAppHome() {
|
|
307
|
+
return process.env[WP_APP_HOME_ENV] || path7.join(os.homedir(), WP_APP_HIDDEN_FOLDER);
|
|
166
308
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
function getWpNowPath() {
|
|
170
|
-
if (process.env.NODE_ENV !== "test") {
|
|
171
|
-
return path8.join(os2.homedir(), WP_NOW_HIDDEN_FOLDER);
|
|
172
|
-
}
|
|
173
|
-
return getWpNowTmpPath();
|
|
309
|
+
function getCachePath() {
|
|
310
|
+
return ensureDir(path7.join(getWpAppHome(), "cache"));
|
|
174
311
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return path9.join(getWpNowPath(), "wordpress-versions");
|
|
312
|
+
function getSitePath(projectPath) {
|
|
313
|
+
const projectName = path7.basename(path7.resolve(projectPath));
|
|
314
|
+
const hash = crypto.createHash("sha1").update(path7.resolve(projectPath)).digest("hex");
|
|
315
|
+
return path7.join(getWpAppHome(), "sites", `${projectName}-${hash}`);
|
|
180
316
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
function getSqlitePath() {
|
|
185
|
-
return path10.join(getWpNowPath(), `${SQLITE_FILENAME}-main`);
|
|
317
|
+
function ensureDir(dir) {
|
|
318
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
319
|
+
return dir;
|
|
186
320
|
}
|
|
187
321
|
|
|
188
322
|
// src/download.ts
|
|
189
|
-
function getWordPressVersionUrl(version
|
|
323
|
+
function getWordPressVersionUrl(version) {
|
|
190
324
|
if (!isValidWordPressVersion(version)) {
|
|
191
325
|
throw new Error(
|
|
192
326
|
'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".'
|
|
@@ -201,357 +335,181 @@ function getClassicPressVersionUrl(version) {
|
|
|
201
335
|
const tag = version.slice("classicpress-".length);
|
|
202
336
|
return `https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${tag}.zip`;
|
|
203
337
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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) {
|
|
338
|
+
async function cachedZip(url, cacheKey) {
|
|
339
|
+
const cacheFile = path8.join(getCachePath(), cacheKey);
|
|
340
|
+
if (!fs9.existsSync(cacheFile)) {
|
|
341
|
+
output?.log(`Downloading ${cacheKey}...`);
|
|
342
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
343
|
+
if (!response.ok) {
|
|
225
344
|
throw new Error(
|
|
226
|
-
`Failed to download
|
|
345
|
+
`Failed to download ${url} (HTTP ${response.status}).`
|
|
227
346
|
);
|
|
228
347
|
}
|
|
229
|
-
await response.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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);
|
|
348
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
349
|
+
fs9.writeFileSync(`${cacheFile}.partial`, bytes);
|
|
350
|
+
fs9.renameSync(`${cacheFile}.partial`, cacheFile);
|
|
351
|
+
output?.log(`Cached ${cacheKey} (${bytes.length} bytes).`);
|
|
264
352
|
}
|
|
353
|
+
return new File([fs9.readFileSync(cacheFile)], cacheKey);
|
|
265
354
|
}
|
|
266
|
-
async function
|
|
267
|
-
|
|
268
|
-
|
|
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);
|
|
355
|
+
async function getCoreZip(version) {
|
|
356
|
+
if (isClassicPressVersion(version)) {
|
|
357
|
+
return cachedZip(getClassicPressVersionUrl(version), `${version}.zip`);
|
|
287
358
|
}
|
|
359
|
+
return cachedZip(getWordPressVersionUrl(version), `wordpress-${version}.zip`);
|
|
288
360
|
}
|
|
289
|
-
async function
|
|
290
|
-
return
|
|
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
|
-
);
|
|
361
|
+
async function getSqliteIntegrationZip() {
|
|
362
|
+
return cachedZip(SQLITE_URL, "sqlite-database-integration.zip");
|
|
312
363
|
}
|
|
313
364
|
|
|
314
|
-
// src/
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
});
|
|
365
|
+
// src/engine.ts
|
|
366
|
+
async function startWPApp(options) {
|
|
367
|
+
const projectPath = path9.resolve(options.projectPath);
|
|
368
|
+
const mode = options.mode;
|
|
361
369
|
output?.log(`directory: ${options.projectPath}`);
|
|
362
|
-
output?.log(`mode: ${
|
|
370
|
+
output?.log(`mode: ${mode}`);
|
|
363
371
|
output?.log(`php: ${options.phpVersion}`);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
372
|
+
const hostDocroot = mode === "index" /* INDEX */ || mode === "wordpress" /* WORDPRESS */ ? projectPath : mode === "wordpress-develop" /* WORDPRESS_DEVELOP */ ? path9.join(projectPath, "build") : ensureDir(getSitePath(projectPath));
|
|
373
|
+
const isWordPressBacked = mode !== "index" /* INDEX */;
|
|
374
|
+
const freshSite = isWordPressBacked && !fs10.existsSync(path9.join(hostDocroot, "wp-load.php"));
|
|
375
|
+
if (isWordPressBacked) {
|
|
376
|
+
output?.log(`wp: ${options.wordPressVersion}`);
|
|
377
|
+
output?.log(`site data: ${hostDocroot}`);
|
|
378
|
+
}
|
|
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
|
+
);
|
|
369
393
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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;
|
|
394
|
+
if (isWordPressBacked) {
|
|
395
|
+
if (!php.fileExists(`${DOCROOT}/wp-content/db.php`)) {
|
|
396
|
+
await installSqliteIntegration(
|
|
397
|
+
php,
|
|
398
|
+
await getSqliteIntegrationZip(),
|
|
399
|
+
DOCROOT
|
|
400
|
+
);
|
|
397
401
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
});
|
|
404
|
-
if (isFirstTimeProject && ["plugin" /* PLUGIN */, "theme" /* THEME */].includes(options.mode)) {
|
|
405
|
-
await activatePluginOrTheme(php, options);
|
|
402
|
+
defineSiteConstants(
|
|
403
|
+
php,
|
|
404
|
+
options.absoluteUrl,
|
|
405
|
+
`${DOCROOT}/wp-content/database`
|
|
406
|
+
);
|
|
406
407
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
408
|
+
if (freshSite) {
|
|
409
|
+
await runInstaller(requestHandler);
|
|
410
|
+
}
|
|
411
|
+
await applyProjectMounts(php, mode, projectPath);
|
|
412
|
+
if (freshSite) {
|
|
413
|
+
await activateProject(php, mode, projectPath);
|
|
414
|
+
}
|
|
415
|
+
return { requestHandler, php, options, freshSite };
|
|
412
416
|
}
|
|
413
|
-
async function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
417
|
+
async function applyProjectMounts(php, mode, projectPath) {
|
|
418
|
+
const projectName = path9.basename(projectPath);
|
|
419
|
+
switch (mode) {
|
|
420
|
+
case "theme" /* THEME */:
|
|
421
|
+
await mountAt(
|
|
422
|
+
php,
|
|
423
|
+
projectPath,
|
|
424
|
+
`${DOCROOT}/wp-content/themes/${projectName}`
|
|
425
|
+
);
|
|
426
|
+
break;
|
|
427
|
+
case "plugin" /* PLUGIN */:
|
|
428
|
+
await mountAt(
|
|
429
|
+
php,
|
|
430
|
+
projectPath,
|
|
431
|
+
`${DOCROOT}/wp-content/plugins/${projectName}`
|
|
432
|
+
);
|
|
433
|
+
break;
|
|
434
|
+
case "wp-content" /* WP_CONTENT */:
|
|
435
|
+
for (const entry of fs10.readdirSync(projectPath)) {
|
|
436
|
+
if (entry === "index.php") {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
await mountAt(
|
|
440
|
+
php,
|
|
441
|
+
path9.join(projectPath, entry),
|
|
442
|
+
`${DOCROOT}/wp-content/${entry}`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
446
|
+
default:
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
441
449
|
}
|
|
442
|
-
async function
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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);
|
|
450
|
+
async function mountAt(php, hostPath, vfsPath) {
|
|
451
|
+
if (fs10.statSync(hostPath).isDirectory()) {
|
|
452
|
+
php.mkdir(vfsPath);
|
|
453
|
+
}
|
|
454
|
+
await php.mount(vfsPath, createNodeFsMountHandler(hostPath));
|
|
484
455
|
}
|
|
485
|
-
async function
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
456
|
+
async function activateProject(php, mode, projectPath) {
|
|
457
|
+
const projectName = path9.basename(projectPath);
|
|
458
|
+
if (mode === "theme" /* THEME */) {
|
|
459
|
+
await runWordPressCode(
|
|
460
|
+
php,
|
|
461
|
+
`switch_theme(${phpString(projectName)});`
|
|
462
|
+
);
|
|
463
|
+
} else if (mode === "plugin" /* PLUGIN */) {
|
|
464
|
+
const pluginFile = findPluginFile(projectPath);
|
|
465
|
+
if (pluginFile) {
|
|
466
|
+
await runWordPressCode(
|
|
467
|
+
php,
|
|
468
|
+
`activate_plugin(${phpString(`${projectName}/${pluginFile}`)});`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
} else if (mode === "wp-content" /* WP_CONTENT */) {
|
|
472
|
+
await runWordPressCode(
|
|
473
|
+
php,
|
|
474
|
+
`$theme = wp_get_theme();
|
|
475
|
+
if (!$theme->exists()) {
|
|
476
|
+
$themes = wp_get_themes();
|
|
477
|
+
if (count($themes) > 0) {
|
|
478
|
+
switch_theme(array_keys($themes)[0]);
|
|
479
|
+
}
|
|
480
|
+
}`
|
|
507
481
|
);
|
|
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
482
|
}
|
|
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
483
|
}
|
|
539
|
-
function
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
484
|
+
async function runWordPressCode(php, code) {
|
|
485
|
+
try {
|
|
486
|
+
await runPhp(
|
|
487
|
+
php,
|
|
488
|
+
`
|
|
489
|
+
require ${phpString(`${DOCROOT}/wp-load.php`)};
|
|
490
|
+
require_once ${phpString(`${DOCROOT}/wp-admin/includes/plugin.php`)};
|
|
491
|
+
${code}
|
|
492
|
+
`
|
|
546
493
|
);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
output?.error(`Activation step failed: ${error.message}`);
|
|
547
496
|
}
|
|
548
497
|
}
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
)
|
|
498
|
+
function phpString(value) {
|
|
499
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
500
|
+
}
|
|
501
|
+
function findPluginFile(projectPath) {
|
|
502
|
+
const pluginNameRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;
|
|
503
|
+
for (const file of fs10.readdirSync(projectPath)) {
|
|
504
|
+
if (!file.endsWith(".php")) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const content = fs10.readFileSync(path9.join(projectPath, file), "utf8");
|
|
508
|
+
if (pluginNameRegex.test(content)) {
|
|
509
|
+
return file;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
555
513
|
}
|
|
556
514
|
function inferMode(projectPath) {
|
|
557
515
|
if (isWordPressDevelopDirectory(projectPath)) {
|
|
@@ -569,27 +527,6 @@ function inferMode(projectPath) {
|
|
|
569
527
|
}
|
|
570
528
|
return "playground" /* PLAYGROUND */;
|
|
571
529
|
}
|
|
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: new TextEncoder().encode(fields.toString())
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
530
|
|
|
594
531
|
// src/port-finder.ts
|
|
595
532
|
import http from "http";
|
|
@@ -641,14 +578,11 @@ var PortFinder = class _PortFinder {
|
|
|
641
578
|
var portFinder = PortFinder.getInstance();
|
|
642
579
|
|
|
643
580
|
// src/config.ts
|
|
644
|
-
import path13 from "path";
|
|
645
581
|
var DEFAULT_OPTIONS = {
|
|
646
582
|
phpVersion: DEFAULT_PHP_VERSION,
|
|
647
583
|
wordPressVersion: DEFAULT_WORDPRESS_VERSION,
|
|
648
|
-
documentRoot: "/var/www/html",
|
|
649
584
|
projectPath: process.cwd(),
|
|
650
|
-
mode: "auto" /* AUTO
|
|
651
|
-
numberOfPhpInstances: 1
|
|
585
|
+
mode: "auto" /* AUTO */
|
|
652
586
|
};
|
|
653
587
|
async function getAbsoluteURL() {
|
|
654
588
|
const port = await portFinder.getOpenPort();
|
|
@@ -657,13 +591,7 @@ async function getAbsoluteURL() {
|
|
|
657
591
|
}
|
|
658
592
|
return `http://localhost:${port}`;
|
|
659
593
|
}
|
|
660
|
-
function
|
|
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) {
|
|
594
|
+
async function getWpAppConfig(args) {
|
|
667
595
|
if (args.port) {
|
|
668
596
|
portFinder.setPort(args.port);
|
|
669
597
|
}
|
|
@@ -686,12 +614,6 @@ async function getWpNowConfig(args) {
|
|
|
686
614
|
if (!options.mode || options.mode === "auto") {
|
|
687
615
|
options.mode = inferMode(options.projectPath);
|
|
688
616
|
}
|
|
689
|
-
if (!options.wpContentPath) {
|
|
690
|
-
options.wpContentPath = getWpContentHomePath(
|
|
691
|
-
options.projectPath,
|
|
692
|
-
options.mode
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
617
|
if (!options.absoluteUrl) {
|
|
696
618
|
options.absoluteUrl = await getAbsoluteURL();
|
|
697
619
|
}
|
|
@@ -709,94 +631,83 @@ async function getWpNowConfig(args) {
|
|
|
709
631
|
}
|
|
710
632
|
|
|
711
633
|
// src/start-server.ts
|
|
712
|
-
import
|
|
634
|
+
import fs11 from "fs";
|
|
635
|
+
import { Readable } from "stream";
|
|
636
|
+
import { pipeline } from "stream/promises";
|
|
713
637
|
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
638
|
async function startServer(options = {}) {
|
|
736
|
-
if (!
|
|
639
|
+
if (!fs11.existsSync(options.projectPath)) {
|
|
737
640
|
throw new Error(
|
|
738
641
|
`The given path "${options.projectPath}" does not exist.`
|
|
739
642
|
);
|
|
740
643
|
}
|
|
741
|
-
const app = express();
|
|
742
|
-
app.use(fileUpload());
|
|
743
644
|
const port = await portFinder.getOpenPort();
|
|
744
|
-
const
|
|
645
|
+
const site = await startWPApp(options);
|
|
646
|
+
const app = express();
|
|
745
647
|
app.use("/", async (req, res) => {
|
|
746
648
|
try {
|
|
747
|
-
const
|
|
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 = {
|
|
649
|
+
const phpRequest = {
|
|
760
650
|
url: req.url,
|
|
761
|
-
headers: requestHeaders,
|
|
762
651
|
method: req.method,
|
|
763
|
-
|
|
764
|
-
|
|
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
|
|
652
|
+
headers: parseHeaders(req),
|
|
653
|
+
body: await bufferRequestBody(req)
|
|
778
654
|
};
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
655
|
+
const response = await site.requestHandler.requestStreamed(phpRequest);
|
|
656
|
+
await sendStreamedResponse(response, res);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
output?.trace(error);
|
|
659
|
+
if (!res.headersSent) {
|
|
660
|
+
res.statusCode = 500;
|
|
661
|
+
res.end("Internal Server Error");
|
|
662
|
+
}
|
|
787
663
|
}
|
|
788
664
|
});
|
|
789
665
|
const url = options.absoluteUrl;
|
|
790
666
|
app.listen(port, () => {
|
|
791
667
|
output?.log(`Server running at ${url}`);
|
|
792
668
|
});
|
|
793
|
-
return {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
669
|
+
return { ...site, url };
|
|
670
|
+
}
|
|
671
|
+
async function sendStreamedResponse(streamedResponse, res) {
|
|
672
|
+
const [headers, httpStatusCode] = await Promise.all([
|
|
673
|
+
streamedResponse.headers,
|
|
674
|
+
streamedResponse.httpStatusCode
|
|
675
|
+
]);
|
|
676
|
+
res.statusCode = httpStatusCode;
|
|
677
|
+
for (const key in headers) {
|
|
678
|
+
res.setHeader(key, headers[key]);
|
|
679
|
+
}
|
|
680
|
+
const nodeStream = Readable.fromWeb(streamedResponse.stdout);
|
|
681
|
+
try {
|
|
682
|
+
await pipeline(nodeStream, res);
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (error instanceof Error && "code" in error && (error.code === "ERR_STREAM_PREMATURE_CLOSE" || error.code === "ERR_STREAM_UNABLE_TO_PIPE")) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
798
689
|
}
|
|
690
|
+
var bufferRequestBody = async (req) => await new Promise((resolve) => {
|
|
691
|
+
const body = [];
|
|
692
|
+
req.on("data", (chunk) => {
|
|
693
|
+
body.push(chunk);
|
|
694
|
+
});
|
|
695
|
+
req.on("end", () => {
|
|
696
|
+
resolve(new Uint8Array(Buffer.concat(body)));
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
var parseHeaders = (req) => {
|
|
700
|
+
const requestHeaders = {};
|
|
701
|
+
if (req.rawHeaders && req.rawHeaders.length) {
|
|
702
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
703
|
+
requestHeaders[req.rawHeaders[i].toLowerCase()] = req.rawHeaders[i + 1];
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return requestHeaders;
|
|
707
|
+
};
|
|
799
708
|
export {
|
|
800
|
-
|
|
801
|
-
|
|
709
|
+
getWpAppConfig,
|
|
710
|
+
inferMode,
|
|
711
|
+
startServer,
|
|
712
|
+
startWPApp
|
|
802
713
|
};
|