@wp-operations/wp-app 0.1.0 → 0.2.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 +243 -489
- package/dist/main.js +340 -574
- package/package.json +8 -11
package/dist/main.js
CHANGED
|
@@ -1,89 +1,39 @@
|
|
|
1
1
|
// src/run-cli.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import fs13 from "fs";
|
|
3
|
+
import path11 from "path";
|
|
4
4
|
import yargs from "yargs";
|
|
5
5
|
import { hideBin } from "yargs/helpers";
|
|
6
6
|
import { spawn, execSync } from "child_process";
|
|
7
7
|
|
|
8
8
|
// src/start-server.ts
|
|
9
|
-
import
|
|
9
|
+
import fs11 from "fs";
|
|
10
|
+
import { Readable } from "stream";
|
|
11
|
+
import { pipeline } from "stream/promises";
|
|
10
12
|
import express from "express";
|
|
11
|
-
import fileUpload from "express-fileupload";
|
|
12
13
|
|
|
13
|
-
// src/
|
|
14
|
-
import
|
|
14
|
+
// src/engine.ts
|
|
15
|
+
import fs10 from "fs";
|
|
16
|
+
import path9 from "path";
|
|
17
|
+
import { createNodeFsMountHandler, loadNodeRuntime } from "@php-wasm/node";
|
|
18
|
+
import { bootWordPressAndRequestHandler } from "@wp-playground/wordpress";
|
|
15
19
|
|
|
16
20
|
// src/constants.ts
|
|
17
|
-
|
|
18
|
-
var
|
|
21
|
+
import { RecommendedPHPVersion } from "@wp-playground/common";
|
|
22
|
+
var WP_APP_HIDDEN_FOLDER = ".wp-app";
|
|
23
|
+
var WP_APP_HOME_ENV = "WP_APP_HOME";
|
|
19
24
|
var SQLITE_URL = "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/main.zip";
|
|
20
25
|
var CLASSICPRESS_LATEST_URL = "https://www.classicpress.net/latest.zip";
|
|
21
26
|
var DEFAULT_PORT = 8881;
|
|
22
|
-
var DEFAULT_PHP_VERSION =
|
|
27
|
+
var DEFAULT_PHP_VERSION = RecommendedPHPVersion;
|
|
23
28
|
var DEFAULT_WORDPRESS_VERSION = "latest";
|
|
24
|
-
|
|
25
|
-
// src/port-finder.ts
|
|
26
|
-
var PortFinder = class _PortFinder {
|
|
27
|
-
static #instance;
|
|
28
|
-
#searchPort = DEFAULT_PORT;
|
|
29
|
-
#openPort = null;
|
|
30
|
-
constructor() {
|
|
31
|
-
}
|
|
32
|
-
static getInstance() {
|
|
33
|
-
if (!_PortFinder.#instance) {
|
|
34
|
-
_PortFinder.#instance = new _PortFinder();
|
|
35
|
-
}
|
|
36
|
-
return _PortFinder.#instance;
|
|
37
|
-
}
|
|
38
|
-
#incrementPort() {
|
|
39
|
-
return this.#searchPort++;
|
|
40
|
-
}
|
|
41
|
-
#isPortFree() {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
const server = http.createServer();
|
|
44
|
-
server.listen(this.#searchPort, () => {
|
|
45
|
-
server.close();
|
|
46
|
-
resolve(true);
|
|
47
|
-
}).on("error", () => {
|
|
48
|
-
resolve(false);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Returns the first available open port, caching and reusing it for subsequent calls.
|
|
54
|
-
*
|
|
55
|
-
* @returns {Promise<number>} A promise that resolves to the open port number.
|
|
56
|
-
*/
|
|
57
|
-
async getOpenPort() {
|
|
58
|
-
if (this.#openPort) {
|
|
59
|
-
return this.#openPort;
|
|
60
|
-
}
|
|
61
|
-
while (!await this.#isPortFree()) {
|
|
62
|
-
this.#incrementPort();
|
|
63
|
-
}
|
|
64
|
-
this.#openPort = this.#searchPort;
|
|
65
|
-
return this.#openPort;
|
|
66
|
-
}
|
|
67
|
-
setPort(port) {
|
|
68
|
-
this.#openPort = port;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
var portFinder = PortFinder.getInstance();
|
|
72
|
-
|
|
73
|
-
// src/wp-now.ts
|
|
74
|
-
import fs9 from "fs-extra";
|
|
75
|
-
import { NodePHP } from "@php-wasm/node";
|
|
76
|
-
import path13 from "path";
|
|
29
|
+
var DOCROOT = "/wordpress";
|
|
77
30
|
|
|
78
31
|
// src/download.ts
|
|
79
|
-
import
|
|
80
|
-
import
|
|
81
|
-
import followRedirects from "follow-redirects";
|
|
82
|
-
import unzipper from "unzipper";
|
|
83
|
-
import os3 from "os";
|
|
32
|
+
import fs9 from "fs";
|
|
33
|
+
import path8 from "path";
|
|
84
34
|
|
|
85
35
|
// src/wp-playground-wordpress/has-index-file.ts
|
|
86
|
-
import fs from "fs
|
|
36
|
+
import fs from "fs";
|
|
87
37
|
import path from "path";
|
|
88
38
|
function hasIndexFile(projectPath) {
|
|
89
39
|
return fs.existsSync(path.join(projectPath, "index.php"));
|
|
@@ -100,11 +50,11 @@ function isClassicPressVersion(version) {
|
|
|
100
50
|
}
|
|
101
51
|
|
|
102
52
|
// src/wp-playground-wordpress/get-plugin-file.ts
|
|
103
|
-
import fs3 from "fs
|
|
53
|
+
import fs3 from "fs";
|
|
104
54
|
import path2, { basename } from "path";
|
|
105
55
|
|
|
106
56
|
// src/wp-playground-wordpress/read-file-head.ts
|
|
107
|
-
import fs2 from "fs
|
|
57
|
+
import fs2 from "fs";
|
|
108
58
|
function readFileHead(filePath, length = 8192) {
|
|
109
59
|
const buffer = Buffer.alloc(length);
|
|
110
60
|
const fd = fs2.openSync(filePath, "r");
|
|
@@ -145,7 +95,7 @@ function isPluginDirectory(projectPath) {
|
|
|
145
95
|
}
|
|
146
96
|
|
|
147
97
|
// src/wp-playground-wordpress/is-theme-directory.ts
|
|
148
|
-
import fs4 from "fs
|
|
98
|
+
import fs4 from "fs";
|
|
149
99
|
import path3 from "path";
|
|
150
100
|
function isThemeDirectory(projectPath) {
|
|
151
101
|
const styleCSSExists = fs4.existsSync(path3.join(projectPath, "style.css"));
|
|
@@ -158,7 +108,7 @@ function isThemeDirectory(projectPath) {
|
|
|
158
108
|
}
|
|
159
109
|
|
|
160
110
|
// src/wp-playground-wordpress/is-wp-content-directory.ts
|
|
161
|
-
import fs5 from "fs
|
|
111
|
+
import fs5 from "fs";
|
|
162
112
|
import path4 from "path";
|
|
163
113
|
function isWpContentDirectory(projectPath) {
|
|
164
114
|
const muPluginsExists = fs5.existsSync(path4.join(projectPath, "mu-plugins"));
|
|
@@ -171,14 +121,14 @@ function isWpContentDirectory(projectPath) {
|
|
|
171
121
|
}
|
|
172
122
|
|
|
173
123
|
// src/wp-playground-wordpress/is-wordpress-directory.ts
|
|
174
|
-
import fs6 from "fs
|
|
124
|
+
import fs6 from "fs";
|
|
175
125
|
import path5 from "path";
|
|
176
126
|
function isWordPressDirectory(projectPath) {
|
|
177
127
|
return fs6.existsSync(path5.join(projectPath, "wp-content")) && fs6.existsSync(path5.join(projectPath, "wp-includes")) && fs6.existsSync(path5.join(projectPath, "wp-load.php"));
|
|
178
128
|
}
|
|
179
129
|
|
|
180
130
|
// src/wp-playground-wordpress/is-wordpress-develop-directory.ts
|
|
181
|
-
import fs7 from "fs
|
|
131
|
+
import fs7 from "fs";
|
|
182
132
|
import path6 from "path";
|
|
183
133
|
function isWordPressDevelopDirectory(projectPath) {
|
|
184
134
|
const requiredFiles = [
|
|
@@ -201,44 +151,30 @@ function shouldOutput() {
|
|
|
201
151
|
return process.env.NODE_ENV !== "test";
|
|
202
152
|
}
|
|
203
153
|
var output = shouldOutput() ? console : null;
|
|
204
|
-
function disableOutput() {
|
|
205
|
-
output = null;
|
|
206
|
-
}
|
|
207
154
|
|
|
208
|
-
// src/
|
|
209
|
-
import
|
|
210
|
-
import
|
|
211
|
-
|
|
212
|
-
// src/get-wp-now-tmp-path.ts
|
|
213
|
-
import path7 from "path";
|
|
155
|
+
// src/paths.ts
|
|
156
|
+
import crypto from "crypto";
|
|
157
|
+
import fs8 from "fs";
|
|
214
158
|
import os from "os";
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return path7.join(
|
|
159
|
+
import path7 from "path";
|
|
160
|
+
function getWpAppHome() {
|
|
161
|
+
return process.env[WP_APP_HOME_ENV] || path7.join(os.homedir(), WP_APP_HIDDEN_FOLDER);
|
|
218
162
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
function getWpNowPath() {
|
|
222
|
-
if (process.env.NODE_ENV !== "test") {
|
|
223
|
-
return path8.join(os2.homedir(), WP_NOW_HIDDEN_FOLDER);
|
|
224
|
-
}
|
|
225
|
-
return getWpNowTmpPath();
|
|
163
|
+
function getCachePath() {
|
|
164
|
+
return ensureDir(path7.join(getWpAppHome(), "cache"));
|
|
226
165
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return path9.join(getWpNowPath(), "wordpress-versions");
|
|
166
|
+
function getSitePath(projectPath) {
|
|
167
|
+
const projectName = path7.basename(path7.resolve(projectPath));
|
|
168
|
+
const hash = crypto.createHash("sha1").update(path7.resolve(projectPath)).digest("hex");
|
|
169
|
+
return path7.join(getWpAppHome(), "sites", `${projectName}-${hash}`);
|
|
232
170
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
function getSqlitePath() {
|
|
237
|
-
return path10.join(getWpNowPath(), `${SQLITE_FILENAME}-main`);
|
|
171
|
+
function ensureDir(dir) {
|
|
172
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
173
|
+
return dir;
|
|
238
174
|
}
|
|
239
175
|
|
|
240
176
|
// src/download.ts
|
|
241
|
-
function getWordPressVersionUrl(version
|
|
177
|
+
function getWordPressVersionUrl(version) {
|
|
242
178
|
if (!isValidWordPressVersion(version)) {
|
|
243
179
|
throw new Error(
|
|
244
180
|
'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".'
|
|
@@ -253,129 +189,35 @@ function getClassicPressVersionUrl(version) {
|
|
|
253
189
|
const tag = version.slice("classicpress-".length);
|
|
254
190
|
return `https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${tag}.zip`;
|
|
255
191
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
itemName
|
|
263
|
-
}) {
|
|
264
|
-
if (fs8.existsSync(checkFinalPath)) {
|
|
265
|
-
output?.log(`${itemName} folder already exists. Skipping download.`);
|
|
266
|
-
return { downloaded: false, statusCode: 0 };
|
|
267
|
-
}
|
|
268
|
-
let statusCode = 0;
|
|
269
|
-
try {
|
|
270
|
-
fs8.ensureDirSync(path11.dirname(destinationFolder));
|
|
271
|
-
output?.log(`Downloading ${itemName}...`);
|
|
272
|
-
const response = await new Promise(
|
|
273
|
-
(resolve) => https.get(url, (response2) => resolve(response2))
|
|
274
|
-
);
|
|
275
|
-
statusCode = response.statusCode;
|
|
276
|
-
if (response.statusCode !== 200) {
|
|
192
|
+
async function cachedZip(url, cacheKey) {
|
|
193
|
+
const cacheFile = path8.join(getCachePath(), cacheKey);
|
|
194
|
+
if (!fs9.existsSync(cacheFile)) {
|
|
195
|
+
output?.log(`Downloading ${cacheKey}...`);
|
|
196
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
197
|
+
if (!response.ok) {
|
|
277
198
|
throw new Error(
|
|
278
|
-
`Failed to download
|
|
199
|
+
`Failed to download ${url} (HTTP ${response.status}).`
|
|
279
200
|
);
|
|
280
201
|
}
|
|
281
|
-
await response.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
entry.pipe(fs8.createWriteStream(filePath));
|
|
286
|
-
}
|
|
287
|
-
}).promise();
|
|
288
|
-
return { downloaded: true, statusCode };
|
|
289
|
-
} catch (err) {
|
|
290
|
-
output?.error(`Error downloading or unzipping ${itemName}:`, err);
|
|
291
|
-
}
|
|
292
|
-
return { downloaded: false, statusCode };
|
|
293
|
-
}
|
|
294
|
-
async function downloadWordPress(wordPressVersion = DEFAULT_WORDPRESS_VERSION) {
|
|
295
|
-
const finalFolder = path11.join(getWordpressVersionsPath(), wordPressVersion);
|
|
296
|
-
if (isClassicPressVersion(wordPressVersion)) {
|
|
297
|
-
return downloadClassicPress(wordPressVersion, finalFolder);
|
|
298
|
-
}
|
|
299
|
-
const tempFolder = os3.tmpdir();
|
|
300
|
-
const { downloaded, statusCode } = await downloadFileAndUnzip({
|
|
301
|
-
url: getWordPressVersionUrl(wordPressVersion),
|
|
302
|
-
destinationFolder: tempFolder,
|
|
303
|
-
checkFinalPath: finalFolder,
|
|
304
|
-
itemName: `WordPress ${wordPressVersion}`
|
|
305
|
-
});
|
|
306
|
-
if (downloaded) {
|
|
307
|
-
fs8.ensureDirSync(path11.dirname(finalFolder));
|
|
308
|
-
fs8.moveSync(path11.join(tempFolder, "wordpress"), finalFolder, {
|
|
309
|
-
overwrite: true
|
|
310
|
-
});
|
|
311
|
-
} else if (404 === statusCode) {
|
|
312
|
-
output?.log(
|
|
313
|
-
`WordPress ${wordPressVersion} not found. Check https://wordpress.org/download/releases/ for available versions.`
|
|
314
|
-
);
|
|
315
|
-
process.exit(1);
|
|
202
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
203
|
+
fs9.writeFileSync(`${cacheFile}.partial`, bytes);
|
|
204
|
+
fs9.renameSync(`${cacheFile}.partial`, cacheFile);
|
|
205
|
+
output?.log(`Cached ${cacheKey} (${bytes.length} bytes).`);
|
|
316
206
|
}
|
|
207
|
+
return new File([fs9.readFileSync(cacheFile)], cacheKey);
|
|
317
208
|
}
|
|
318
|
-
async function
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
url: getClassicPressVersionUrl(version),
|
|
322
|
-
destinationFolder: tempFolder,
|
|
323
|
-
checkFinalPath: finalFolder,
|
|
324
|
-
itemName: `ClassicPress ${version}`
|
|
325
|
-
});
|
|
326
|
-
if (downloaded) {
|
|
327
|
-
fs8.ensureDirSync(path11.dirname(finalFolder));
|
|
328
|
-
const entries = fs8.readdirSync(tempFolder);
|
|
329
|
-
const singleDirRoot = entries.length === 1 && fs8.statSync(path11.join(tempFolder, entries[0])).isDirectory() ? path11.join(tempFolder, entries[0]) : null;
|
|
330
|
-
fs8.moveSync(singleDirRoot ?? tempFolder, finalFolder, {
|
|
331
|
-
overwrite: true
|
|
332
|
-
});
|
|
333
|
-
fs8.removeSync(tempFolder);
|
|
334
|
-
} else if (404 === statusCode) {
|
|
335
|
-
output?.log(
|
|
336
|
-
`ClassicPress ${version} not found. Check https://www.classicpress.net for available releases.`
|
|
337
|
-
);
|
|
338
|
-
process.exit(1);
|
|
209
|
+
async function getCoreZip(version) {
|
|
210
|
+
if (isClassicPressVersion(version)) {
|
|
211
|
+
return cachedZip(getClassicPressVersionUrl(version), `${version}.zip`);
|
|
339
212
|
}
|
|
213
|
+
return cachedZip(getWordPressVersionUrl(version), `wordpress-${version}.zip`);
|
|
340
214
|
}
|
|
341
|
-
async function
|
|
342
|
-
return
|
|
343
|
-
url: SQLITE_URL,
|
|
344
|
-
destinationFolder: getWpNowPath(),
|
|
345
|
-
checkFinalPath: getSqlitePath(),
|
|
346
|
-
itemName: "SQLite"
|
|
347
|
-
});
|
|
215
|
+
async function getSqliteIntegrationZip() {
|
|
216
|
+
return cachedZip(SQLITE_URL, "sqlite-database-integration.zip");
|
|
348
217
|
}
|
|
349
|
-
async function downloadMuPlugins() {
|
|
350
|
-
fs8.ensureDirSync(path11.join(getWpNowPath(), "mu-plugins"));
|
|
351
|
-
fs8.writeFile(
|
|
352
|
-
path11.join(getWpNowPath(), "mu-plugins", "0-allow-wp-org.php"),
|
|
353
|
-
`<?php
|
|
354
|
-
// Needed because gethostbyname( 'wordpress.org' ) returns
|
|
355
|
-
// a private network IP address for some reason.
|
|
356
|
-
add_filter( 'allowed_redirect_hosts', function( $deprecated = '' ) {
|
|
357
|
-
return array(
|
|
358
|
-
'wordpress.org',
|
|
359
|
-
'api.wordpress.org',
|
|
360
|
-
'downloads.wordpress.org',
|
|
361
|
-
);
|
|
362
|
-
} );`
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// src/wp-now.ts
|
|
367
|
-
import {
|
|
368
|
-
activatePlugin,
|
|
369
|
-
activateTheme,
|
|
370
|
-
defineWpConfigConsts,
|
|
371
|
-
login
|
|
372
|
-
} from "@wp-playground/blueprints";
|
|
373
218
|
|
|
374
219
|
// src/config.ts
|
|
375
|
-
import {
|
|
376
|
-
SupportedPHPVersionsList
|
|
377
|
-
} from "@php-wasm/universal";
|
|
378
|
-
import crypto from "crypto";
|
|
220
|
+
import { SupportedPHPVersionsList } from "@php-wasm/universal";
|
|
379
221
|
|
|
380
222
|
// src/github-codespaces.ts
|
|
381
223
|
var isGitHubCodespace = Boolean(
|
|
@@ -385,15 +227,61 @@ function getCodeSpaceURL(port) {
|
|
|
385
227
|
return `https://${process.env.CODESPACE_NAME}-${port}.${process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}`;
|
|
386
228
|
}
|
|
387
229
|
|
|
230
|
+
// src/port-finder.ts
|
|
231
|
+
import http from "http";
|
|
232
|
+
var PortFinder = class _PortFinder {
|
|
233
|
+
static #instance;
|
|
234
|
+
#searchPort = DEFAULT_PORT;
|
|
235
|
+
#openPort = null;
|
|
236
|
+
constructor() {
|
|
237
|
+
}
|
|
238
|
+
static getInstance() {
|
|
239
|
+
if (!_PortFinder.#instance) {
|
|
240
|
+
_PortFinder.#instance = new _PortFinder();
|
|
241
|
+
}
|
|
242
|
+
return _PortFinder.#instance;
|
|
243
|
+
}
|
|
244
|
+
#incrementPort() {
|
|
245
|
+
return this.#searchPort++;
|
|
246
|
+
}
|
|
247
|
+
#isPortFree() {
|
|
248
|
+
return new Promise((resolve) => {
|
|
249
|
+
const server = http.createServer();
|
|
250
|
+
server.listen(this.#searchPort, () => {
|
|
251
|
+
server.close();
|
|
252
|
+
resolve(true);
|
|
253
|
+
}).on("error", () => {
|
|
254
|
+
resolve(false);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Returns the first available open port, caching and reusing it for subsequent calls.
|
|
260
|
+
*
|
|
261
|
+
* @returns {Promise<number>} A promise that resolves to the open port number.
|
|
262
|
+
*/
|
|
263
|
+
async getOpenPort() {
|
|
264
|
+
if (this.#openPort) {
|
|
265
|
+
return this.#openPort;
|
|
266
|
+
}
|
|
267
|
+
while (!await this.#isPortFree()) {
|
|
268
|
+
this.#incrementPort();
|
|
269
|
+
}
|
|
270
|
+
this.#openPort = this.#searchPort;
|
|
271
|
+
return this.#openPort;
|
|
272
|
+
}
|
|
273
|
+
setPort(port) {
|
|
274
|
+
this.#openPort = port;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var portFinder = PortFinder.getInstance();
|
|
278
|
+
|
|
388
279
|
// src/config.ts
|
|
389
|
-
import path12 from "path";
|
|
390
280
|
var DEFAULT_OPTIONS = {
|
|
391
281
|
phpVersion: DEFAULT_PHP_VERSION,
|
|
392
282
|
wordPressVersion: DEFAULT_WORDPRESS_VERSION,
|
|
393
|
-
documentRoot: "/var/www/html",
|
|
394
283
|
projectPath: process.cwd(),
|
|
395
|
-
mode: "auto" /* AUTO
|
|
396
|
-
numberOfPhpInstances: 1
|
|
284
|
+
mode: "auto" /* AUTO */
|
|
397
285
|
};
|
|
398
286
|
async function getAbsoluteURL() {
|
|
399
287
|
const port = await portFinder.getOpenPort();
|
|
@@ -402,13 +290,7 @@ async function getAbsoluteURL() {
|
|
|
402
290
|
}
|
|
403
291
|
return `http://localhost:${port}`;
|
|
404
292
|
}
|
|
405
|
-
function
|
|
406
|
-
const basename2 = path12.basename(projectPath);
|
|
407
|
-
const directoryHash = crypto.createHash("sha1").update(projectPath).digest("hex");
|
|
408
|
-
const projectDirectory = mode === "playground" /* PLAYGROUND */ ? "playground" : `${basename2}-${directoryHash}`;
|
|
409
|
-
return path12.join(getWpNowPath(), "wp-content", projectDirectory);
|
|
410
|
-
}
|
|
411
|
-
async function getWpNowConfig(args) {
|
|
293
|
+
async function getWpAppConfig(args) {
|
|
412
294
|
if (args.port) {
|
|
413
295
|
portFinder.setPort(args.port);
|
|
414
296
|
}
|
|
@@ -431,12 +313,6 @@ async function getWpNowConfig(args) {
|
|
|
431
313
|
if (!options.mode || options.mode === "auto") {
|
|
432
314
|
options.mode = inferMode(options.projectPath);
|
|
433
315
|
}
|
|
434
|
-
if (!options.wpContentPath) {
|
|
435
|
-
options.wpContentPath = getWpContentHomePath(
|
|
436
|
-
options.projectPath,
|
|
437
|
-
options.mode
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
316
|
if (!options.absoluteUrl) {
|
|
441
317
|
options.absoluteUrl = await getAbsoluteURL();
|
|
442
318
|
}
|
|
@@ -453,241 +329,145 @@ async function getWpNowConfig(args) {
|
|
|
453
329
|
return options;
|
|
454
330
|
}
|
|
455
331
|
|
|
456
|
-
// src/
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
async function applyToInstances(phpInstances, callback) {
|
|
461
|
-
for (let i = 0; i < phpInstances.length; i++) {
|
|
462
|
-
await callback(phpInstances[i]);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
async function startWPNow(options = {}) {
|
|
466
|
-
const { documentRoot } = options;
|
|
467
|
-
const nodePHPOptions = {
|
|
468
|
-
requestHandler: {
|
|
469
|
-
documentRoot,
|
|
470
|
-
absoluteUrl: options.absoluteUrl,
|
|
471
|
-
isStaticFilePath: (path15) => {
|
|
472
|
-
try {
|
|
473
|
-
const fullPath = options.documentRoot + path15;
|
|
474
|
-
return php.fileExists(fullPath) && !php.isDir(fullPath) && !seemsLikeAPHPFile(fullPath);
|
|
475
|
-
} catch (e) {
|
|
476
|
-
output?.error(e);
|
|
477
|
-
return false;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
const phpInstances = [];
|
|
483
|
-
for (let i = 0; i < Math.max(options.numberOfPhpInstances, 1); i++) {
|
|
484
|
-
phpInstances.push(
|
|
485
|
-
await NodePHP.load(options.phpVersion, nodePHPOptions)
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
const php = phpInstances[0];
|
|
489
|
-
phpInstances.forEach((_php) => {
|
|
490
|
-
_php.mkdirTree(documentRoot);
|
|
491
|
-
_php.chdir(documentRoot);
|
|
492
|
-
_php.writeFile(
|
|
493
|
-
`${documentRoot}/index.php`,
|
|
494
|
-
`<?php echo 'Hello wp-app!';`
|
|
495
|
-
);
|
|
496
|
-
});
|
|
332
|
+
// src/engine.ts
|
|
333
|
+
async function startWPApp(options) {
|
|
334
|
+
const projectPath = path9.resolve(options.projectPath);
|
|
335
|
+
const mode = options.mode;
|
|
497
336
|
output?.log(`directory: ${options.projectPath}`);
|
|
498
|
-
output?.log(`mode: ${
|
|
337
|
+
output?.log(`mode: ${mode}`);
|
|
499
338
|
output?.log(`php: ${options.phpVersion}`);
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
await
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
case "playground" /* PLAYGROUND */:
|
|
531
|
-
await runWpPlaygroundMode(_php, options);
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
await installationStep2(php);
|
|
536
|
-
await login(php, {
|
|
537
|
-
username: "admin",
|
|
538
|
-
password: "password"
|
|
339
|
+
const hostDocroot = mode === "index" /* INDEX */ || mode === "wordpress" /* WORDPRESS */ ? projectPath : mode === "wordpress-develop" /* WORDPRESS_DEVELOP */ ? path9.join(projectPath, "build") : ensureDir(getSitePath(projectPath));
|
|
340
|
+
const isWordPressBacked = mode !== "index" /* INDEX */;
|
|
341
|
+
const freshSite = isWordPressBacked && !fs10.existsSync(path9.join(hostDocroot, "wp-load.php"));
|
|
342
|
+
if (isWordPressBacked) {
|
|
343
|
+
output?.log(`wp: ${options.wordPressVersion}`);
|
|
344
|
+
output?.log(`site data: ${hostDocroot}`);
|
|
345
|
+
}
|
|
346
|
+
const requestHandler = await bootWordPressAndRequestHandler({
|
|
347
|
+
siteUrl: options.absoluteUrl,
|
|
348
|
+
documentRoot: DOCROOT,
|
|
349
|
+
phpVersion: options.phpVersion,
|
|
350
|
+
maxPhpInstances: 1,
|
|
351
|
+
sapiName: "cli",
|
|
352
|
+
cookieStore: false,
|
|
353
|
+
createPhpRuntime: () => loadNodeRuntime(options.phpVersion, {
|
|
354
|
+
emscriptenOptions: { processId: process.pid }
|
|
355
|
+
}),
|
|
356
|
+
hooks: {
|
|
357
|
+
beforeWordPressFiles: async (php2) => {
|
|
358
|
+
php2.mkdir(DOCROOT);
|
|
359
|
+
await php2.mount(
|
|
360
|
+
DOCROOT,
|
|
361
|
+
createNodeFsMountHandler(hostDocroot)
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
wordPressZip: freshSite ? await getCoreZip(options.wordPressVersion) : void 0,
|
|
366
|
+
sqliteIntegrationPluginZip: isWordPressBacked ? await getSqliteIntegrationZip() : void 0,
|
|
367
|
+
dataSqlPath: isWordPressBacked ? `${DOCROOT}/wp-content/database/.ht.sqlite` : void 0,
|
|
368
|
+
wordpressInstallMode: isWordPressBacked ? "install-from-existing-files-if-needed" : "do-not-attempt-installing"
|
|
539
369
|
});
|
|
540
|
-
|
|
541
|
-
|
|
370
|
+
const php = await requestHandler.getPrimaryPhp();
|
|
371
|
+
await applyProjectMounts(php, mode, projectPath);
|
|
372
|
+
if (freshSite) {
|
|
373
|
+
await activateProject(php, mode, projectPath);
|
|
374
|
+
}
|
|
375
|
+
return { requestHandler, php, options, freshSite };
|
|
376
|
+
}
|
|
377
|
+
async function applyProjectMounts(php, mode, projectPath) {
|
|
378
|
+
const projectName = path9.basename(projectPath);
|
|
379
|
+
switch (mode) {
|
|
380
|
+
case "theme" /* THEME */:
|
|
381
|
+
await mountAt(
|
|
382
|
+
php,
|
|
383
|
+
projectPath,
|
|
384
|
+
`${DOCROOT}/wp-content/themes/${projectName}`
|
|
385
|
+
);
|
|
386
|
+
break;
|
|
387
|
+
case "plugin" /* PLUGIN */:
|
|
388
|
+
await mountAt(
|
|
389
|
+
php,
|
|
390
|
+
projectPath,
|
|
391
|
+
`${DOCROOT}/wp-content/plugins/${projectName}`
|
|
392
|
+
);
|
|
393
|
+
break;
|
|
394
|
+
case "wp-content" /* WP_CONTENT */:
|
|
395
|
+
for (const entry of fs10.readdirSync(projectPath)) {
|
|
396
|
+
if (entry === "index.php") {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
await mountAt(
|
|
400
|
+
php,
|
|
401
|
+
path9.join(projectPath, entry),
|
|
402
|
+
`${DOCROOT}/wp-content/${entry}`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
default:
|
|
407
|
+
break;
|
|
542
408
|
}
|
|
543
|
-
return {
|
|
544
|
-
php,
|
|
545
|
-
phpInstances,
|
|
546
|
-
options
|
|
547
|
-
};
|
|
548
409
|
}
|
|
549
|
-
async function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
async function runWpContentMode(php, {
|
|
553
|
-
documentRoot,
|
|
554
|
-
wordPressVersion,
|
|
555
|
-
wpContentPath,
|
|
556
|
-
projectPath,
|
|
557
|
-
absoluteUrl
|
|
558
|
-
}) {
|
|
559
|
-
const wordPressPath = path13.join(
|
|
560
|
-
getWordpressVersionsPath(),
|
|
561
|
-
wordPressVersion
|
|
562
|
-
);
|
|
563
|
-
php.mount(wordPressPath, documentRoot);
|
|
564
|
-
await initWordPress(php, wordPressVersion, documentRoot, absoluteUrl);
|
|
565
|
-
fs9.ensureDirSync(wpContentPath);
|
|
566
|
-
php.mount(projectPath, `${documentRoot}/wp-content`);
|
|
567
|
-
mountSqlitePlugin(php, documentRoot);
|
|
568
|
-
mountSqliteDatabaseDirectory(php, documentRoot, wpContentPath);
|
|
569
|
-
mountMuPlugins(php, documentRoot);
|
|
570
|
-
}
|
|
571
|
-
async function runWordPressDevelopMode(php, { documentRoot, projectPath, absoluteUrl }) {
|
|
572
|
-
await runWordPressMode(php, {
|
|
573
|
-
documentRoot,
|
|
574
|
-
projectPath: projectPath + "/build",
|
|
575
|
-
absoluteUrl
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
async function runWordPressMode(php, { documentRoot, wpContentPath, projectPath, absoluteUrl }) {
|
|
579
|
-
php.mount(projectPath, documentRoot);
|
|
580
|
-
const { initializeDefaultDatabase } = await initWordPress(
|
|
581
|
-
php,
|
|
582
|
-
"user-provided",
|
|
583
|
-
documentRoot,
|
|
584
|
-
absoluteUrl
|
|
585
|
-
);
|
|
586
|
-
if (initializeDefaultDatabase || fs9.existsSync(path13.join(wpContentPath, "database"))) {
|
|
587
|
-
mountSqlitePlugin(php, documentRoot);
|
|
588
|
-
mountSqliteDatabaseDirectory(php, documentRoot, wpContentPath);
|
|
410
|
+
async function mountAt(php, hostPath, vfsPath) {
|
|
411
|
+
if (fs10.statSync(hostPath).isDirectory()) {
|
|
412
|
+
php.mkdir(vfsPath);
|
|
589
413
|
}
|
|
590
|
-
|
|
414
|
+
await php.mount(vfsPath, createNodeFsMountHandler(hostPath));
|
|
591
415
|
}
|
|
592
|
-
async function
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
);
|
|
618
|
-
mountSqlitePlugin(php, documentRoot);
|
|
619
|
-
mountMuPlugins(php, documentRoot);
|
|
620
|
-
}
|
|
621
|
-
async function runWpPlaygroundMode(php, { documentRoot, wordPressVersion, wpContentPath, absoluteUrl }) {
|
|
622
|
-
const wordPressPath = path13.join(
|
|
623
|
-
getWordpressVersionsPath(),
|
|
624
|
-
wordPressVersion
|
|
625
|
-
);
|
|
626
|
-
php.mount(wordPressPath, documentRoot);
|
|
627
|
-
await initWordPress(php, wordPressVersion, documentRoot, absoluteUrl);
|
|
628
|
-
fs9.ensureDirSync(wpContentPath);
|
|
629
|
-
fs9.copySync(
|
|
630
|
-
path13.join(getWordpressVersionsPath(), wordPressVersion, "wp-content"),
|
|
631
|
-
wpContentPath
|
|
632
|
-
);
|
|
633
|
-
php.mount(wpContentPath, `${documentRoot}/wp-content`);
|
|
634
|
-
mountSqlitePlugin(php, documentRoot);
|
|
635
|
-
mountMuPlugins(php, documentRoot);
|
|
636
|
-
}
|
|
637
|
-
async function initWordPress(php, wordPressVersion, vfsDocumentRoot, siteUrl) {
|
|
638
|
-
let initializeDefaultDatabase = false;
|
|
639
|
-
if (!php.fileExists(`${vfsDocumentRoot}/wp-config.php`)) {
|
|
640
|
-
php.writeFile(
|
|
641
|
-
`${vfsDocumentRoot}/wp-config.php`,
|
|
642
|
-
php.readFileAsText(`${vfsDocumentRoot}/wp-config-sample.php`)
|
|
416
|
+
async function activateProject(php, mode, projectPath) {
|
|
417
|
+
const projectName = path9.basename(projectPath);
|
|
418
|
+
if (mode === "theme" /* THEME */) {
|
|
419
|
+
await runWordPressCode(
|
|
420
|
+
php,
|
|
421
|
+
`switch_theme(${phpString(projectName)});`
|
|
422
|
+
);
|
|
423
|
+
} else if (mode === "plugin" /* PLUGIN */) {
|
|
424
|
+
const pluginFile = findPluginFile(projectPath);
|
|
425
|
+
if (pluginFile) {
|
|
426
|
+
await runWordPressCode(
|
|
427
|
+
php,
|
|
428
|
+
`activate_plugin(${phpString(`${projectName}/${pluginFile}`)});`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
} else if (mode === "wp-content" /* WP_CONTENT */) {
|
|
432
|
+
await runWordPressCode(
|
|
433
|
+
php,
|
|
434
|
+
`$theme = wp_get_theme();
|
|
435
|
+
if (!$theme->exists()) {
|
|
436
|
+
$themes = wp_get_themes();
|
|
437
|
+
if (count($themes) > 0) {
|
|
438
|
+
switch_theme(array_keys($themes)[0]);
|
|
439
|
+
}
|
|
440
|
+
}`
|
|
643
441
|
);
|
|
644
|
-
initializeDefaultDatabase = true;
|
|
645
|
-
}
|
|
646
|
-
const wpConfigConsts = {
|
|
647
|
-
WP_HOME: siteUrl,
|
|
648
|
-
WP_SITEURL: siteUrl
|
|
649
|
-
};
|
|
650
|
-
if (wordPressVersion !== "user-defined") {
|
|
651
|
-
wpConfigConsts["WP_AUTO_UPDATE_CORE"] = wordPressVersion === "latest";
|
|
652
442
|
}
|
|
653
|
-
await defineWpConfigConsts(php, {
|
|
654
|
-
consts: wpConfigConsts,
|
|
655
|
-
virtualize: true
|
|
656
|
-
});
|
|
657
|
-
return { initializeDefaultDatabase };
|
|
658
443
|
}
|
|
659
|
-
async function
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
444
|
+
async function runWordPressCode(php, code) {
|
|
445
|
+
const response = await php.run({
|
|
446
|
+
code: `<?php
|
|
447
|
+
require ${phpString(`${DOCROOT}/wp-load.php`)};
|
|
448
|
+
require_once ${phpString(`${DOCROOT}/wp-admin/includes/plugin.php`)};
|
|
449
|
+
${code}
|
|
450
|
+
`
|
|
451
|
+
});
|
|
452
|
+
if (response.exitCode !== 0) {
|
|
453
|
+
output?.error(`Activation step failed: ${response.errors}`);
|
|
666
454
|
}
|
|
667
455
|
}
|
|
668
|
-
function
|
|
669
|
-
|
|
670
|
-
path13.join(getWpNowPath(), "mu-plugins"),
|
|
671
|
-
// VFS paths are always POSIX — path.join would break them on Windows.
|
|
672
|
-
`${vfsDocumentRoot}/wp-content/mu-plugins`
|
|
673
|
-
);
|
|
456
|
+
function phpString(value) {
|
|
457
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
674
458
|
}
|
|
675
|
-
function
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
)
|
|
459
|
+
function findPluginFile(projectPath) {
|
|
460
|
+
const pluginNameRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;
|
|
461
|
+
for (const file of fs10.readdirSync(projectPath)) {
|
|
462
|
+
if (!file.endsWith(".php")) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const content = fs10.readFileSync(path9.join(projectPath, file), "utf8");
|
|
466
|
+
if (pluginNameRegex.test(content)) {
|
|
467
|
+
return file;
|
|
468
|
+
}
|
|
683
469
|
}
|
|
684
|
-
|
|
685
|
-
function mountSqliteDatabaseDirectory(php, vfsDocumentRoot, wpContentPath) {
|
|
686
|
-
fs9.ensureDirSync(path13.join(wpContentPath, "database"));
|
|
687
|
-
php.mount(
|
|
688
|
-
path13.join(wpContentPath, "database"),
|
|
689
|
-
`${vfsDocumentRoot}/wp-content/database`
|
|
690
|
-
);
|
|
470
|
+
return null;
|
|
691
471
|
}
|
|
692
472
|
function inferMode(projectPath) {
|
|
693
473
|
if (isWordPressDevelopDirectory(projectPath)) {
|
|
@@ -705,136 +485,107 @@ function inferMode(projectPath) {
|
|
|
705
485
|
}
|
|
706
486
|
return "playground" /* PLAYGROUND */;
|
|
707
487
|
}
|
|
708
|
-
async function installationStep2(php) {
|
|
709
|
-
const fields = new URLSearchParams({
|
|
710
|
-
language: "en",
|
|
711
|
-
prefix: "wp_",
|
|
712
|
-
weblog_title: "My WordPress Website",
|
|
713
|
-
user_name: "admin",
|
|
714
|
-
admin_password: "password",
|
|
715
|
-
admin_password2: "password",
|
|
716
|
-
Submit: "Install WordPress",
|
|
717
|
-
pw_weak: "1",
|
|
718
|
-
admin_email: "admin@localhost.com"
|
|
719
|
-
});
|
|
720
|
-
return php.request({
|
|
721
|
-
url: "/wp-admin/install.php?step=2",
|
|
722
|
-
method: "POST",
|
|
723
|
-
headers: {
|
|
724
|
-
"content-type": "application/x-www-form-urlencoded"
|
|
725
|
-
},
|
|
726
|
-
body: fields.toString()
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
488
|
|
|
730
489
|
// src/start-server.ts
|
|
731
|
-
function requestBodyToMultipartFormData(json, boundary) {
|
|
732
|
-
let multipartData = "";
|
|
733
|
-
const eol = "\r\n";
|
|
734
|
-
for (const key in json) {
|
|
735
|
-
multipartData += `--${boundary}${eol}`;
|
|
736
|
-
multipartData += `Content-Disposition: form-data; name="${key}"${eol}${eol}`;
|
|
737
|
-
multipartData += `${json[key]}${eol}`;
|
|
738
|
-
}
|
|
739
|
-
multipartData += `--${boundary}--${eol}`;
|
|
740
|
-
return multipartData;
|
|
741
|
-
}
|
|
742
|
-
var requestBodyToString = async (req) => await new Promise((resolve) => {
|
|
743
|
-
let body = "";
|
|
744
|
-
req.on("data", (chunk) => {
|
|
745
|
-
body += chunk.toString();
|
|
746
|
-
});
|
|
747
|
-
req.on("end", () => {
|
|
748
|
-
resolve(body);
|
|
749
|
-
});
|
|
750
|
-
});
|
|
751
490
|
async function startServer(options = {}) {
|
|
752
|
-
if (!
|
|
491
|
+
if (!fs11.existsSync(options.projectPath)) {
|
|
753
492
|
throw new Error(
|
|
754
493
|
`The given path "${options.projectPath}" does not exist.`
|
|
755
494
|
);
|
|
756
495
|
}
|
|
757
|
-
const app = express();
|
|
758
|
-
app.use(fileUpload());
|
|
759
496
|
const port = await portFinder.getOpenPort();
|
|
760
|
-
const
|
|
497
|
+
const site = await startWPApp(options);
|
|
498
|
+
const app = express();
|
|
761
499
|
app.use("/", async (req, res) => {
|
|
762
500
|
try {
|
|
763
|
-
const
|
|
764
|
-
if (req.rawHeaders && req.rawHeaders.length) {
|
|
765
|
-
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
766
|
-
requestHeaders[req.rawHeaders[i].toLowerCase()] = req.rawHeaders[i + 1];
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
const body = requestHeaders["content-type"]?.startsWith(
|
|
770
|
-
"multipart/form-data"
|
|
771
|
-
) ? requestBodyToMultipartFormData(
|
|
772
|
-
req.body,
|
|
773
|
-
requestHeaders["content-type"].split("; boundary=")[1]
|
|
774
|
-
) : await requestBodyToString(req);
|
|
775
|
-
const data = {
|
|
501
|
+
const phpRequest = {
|
|
776
502
|
url: req.url,
|
|
777
|
-
headers: requestHeaders,
|
|
778
503
|
method: req.method,
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
([key, file]) => [
|
|
782
|
-
key,
|
|
783
|
-
{
|
|
784
|
-
key,
|
|
785
|
-
name: file.name,
|
|
786
|
-
size: file.size,
|
|
787
|
-
type: file.mimetype,
|
|
788
|
-
arrayBuffer: () => file.data.buffer
|
|
789
|
-
}
|
|
790
|
-
]
|
|
791
|
-
)
|
|
792
|
-
),
|
|
793
|
-
body
|
|
504
|
+
headers: parseHeaders(req),
|
|
505
|
+
body: await bufferRequestBody(req)
|
|
794
506
|
};
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
507
|
+
const response = await site.requestHandler.requestStreamed(phpRequest);
|
|
508
|
+
await sendStreamedResponse(response, res);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
output?.trace(error);
|
|
511
|
+
if (!res.headersSent) {
|
|
512
|
+
res.statusCode = 500;
|
|
513
|
+
res.end("Internal Server Error");
|
|
514
|
+
}
|
|
803
515
|
}
|
|
804
516
|
});
|
|
805
517
|
const url = options.absoluteUrl;
|
|
806
518
|
app.listen(port, () => {
|
|
807
519
|
output?.log(`Server running at ${url}`);
|
|
808
520
|
});
|
|
809
|
-
return {
|
|
810
|
-
url,
|
|
811
|
-
php,
|
|
812
|
-
options: wpNowOptions
|
|
813
|
-
};
|
|
521
|
+
return { ...site, url };
|
|
814
522
|
}
|
|
523
|
+
async function sendStreamedResponse(streamedResponse, res) {
|
|
524
|
+
const [headers, httpStatusCode] = await Promise.all([
|
|
525
|
+
streamedResponse.headers,
|
|
526
|
+
streamedResponse.httpStatusCode
|
|
527
|
+
]);
|
|
528
|
+
res.statusCode = httpStatusCode;
|
|
529
|
+
for (const key in headers) {
|
|
530
|
+
res.setHeader(key, headers[key]);
|
|
531
|
+
}
|
|
532
|
+
const nodeStream = Readable.fromWeb(streamedResponse.stdout);
|
|
533
|
+
try {
|
|
534
|
+
await pipeline(nodeStream, res);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
if (error instanceof Error && "code" in error && (error.code === "ERR_STREAM_PREMATURE_CLOSE" || error.code === "ERR_STREAM_UNABLE_TO_PIPE")) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
var bufferRequestBody = async (req) => await new Promise((resolve) => {
|
|
543
|
+
const body = [];
|
|
544
|
+
req.on("data", (chunk) => {
|
|
545
|
+
body.push(chunk);
|
|
546
|
+
});
|
|
547
|
+
req.on("end", () => {
|
|
548
|
+
resolve(new Uint8Array(Buffer.concat(body)));
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
var parseHeaders = (req) => {
|
|
552
|
+
const requestHeaders = {};
|
|
553
|
+
if (req.rawHeaders && req.rawHeaders.length) {
|
|
554
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
555
|
+
requestHeaders[req.rawHeaders[i].toLowerCase()] = req.rawHeaders[i + 1];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return requestHeaders;
|
|
559
|
+
};
|
|
815
560
|
|
|
816
561
|
// src/execute-php.ts
|
|
817
|
-
|
|
818
|
-
|
|
562
|
+
import fs12 from "fs";
|
|
563
|
+
import path10 from "path";
|
|
564
|
+
import { loadNodeRuntime as loadNodeRuntime2 } from "@php-wasm/node";
|
|
565
|
+
import { PHP } from "@php-wasm/universal";
|
|
566
|
+
async function executePHP(phpArgs, options) {
|
|
567
|
+
const args = phpArgs.filter((arg) => arg !== "php" && arg !== "--");
|
|
568
|
+
let code;
|
|
569
|
+
if (args[0] === "-r" && typeof args[1] === "string") {
|
|
570
|
+
code = `<?php ${args[1]}`;
|
|
571
|
+
} else if (args[0] && fs12.existsSync(path10.resolve(args[0]))) {
|
|
572
|
+
code = fs12.readFileSync(path10.resolve(args[0]), "utf8");
|
|
573
|
+
} else {
|
|
819
574
|
throw new Error(
|
|
820
|
-
'
|
|
575
|
+
'Usage: wp-app php -- -r "<code>" | wp-app php -- <script.php>'
|
|
821
576
|
);
|
|
822
577
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
} catch (resultOrError) {
|
|
833
|
-
const success = resultOrError.name === "ExitStatus" && resultOrError.status === 0;
|
|
834
|
-
if (!success) {
|
|
835
|
-
throw resultOrError;
|
|
836
|
-
}
|
|
578
|
+
const php = new PHP(
|
|
579
|
+
await loadNodeRuntime2(options.phpVersion, {
|
|
580
|
+
emscriptenOptions: { processId: process.pid }
|
|
581
|
+
})
|
|
582
|
+
);
|
|
583
|
+
const response = await php.run({ code });
|
|
584
|
+
process.stdout.write(response.bytes);
|
|
585
|
+
if (response.errors) {
|
|
586
|
+
process.stderr.write(response.errors);
|
|
837
587
|
}
|
|
588
|
+
process.exitCode = response.exitCode;
|
|
838
589
|
}
|
|
839
590
|
|
|
840
591
|
// src/run-cli.ts
|
|
@@ -900,7 +651,7 @@ async function runCli() {
|
|
|
900
651
|
config.mode = argv.mode;
|
|
901
652
|
}
|
|
902
653
|
try {
|
|
903
|
-
await
|
|
654
|
+
await getWpAppConfig(config);
|
|
904
655
|
} catch (error) {
|
|
905
656
|
return error.message;
|
|
906
657
|
}
|
|
@@ -911,7 +662,7 @@ async function runCli() {
|
|
|
911
662
|
(yargs2) => {
|
|
912
663
|
serverParameters(yargs2);
|
|
913
664
|
yargs2.option("reset", {
|
|
914
|
-
describe: "Create a new
|
|
665
|
+
describe: "Create a new site environment, destroying the old one (WordPress files and database).",
|
|
915
666
|
type: "boolean",
|
|
916
667
|
default: false
|
|
917
668
|
});
|
|
@@ -924,7 +675,7 @@ async function runCli() {
|
|
|
924
675
|
async (argv) => {
|
|
925
676
|
const spinner = startSpinner("Starting the server...");
|
|
926
677
|
try {
|
|
927
|
-
const options = await
|
|
678
|
+
const options = await getWpAppConfig({
|
|
928
679
|
path: argv.path,
|
|
929
680
|
php: argv.php,
|
|
930
681
|
wp: argv.wp,
|
|
@@ -933,8 +684,21 @@ async function runCli() {
|
|
|
933
684
|
});
|
|
934
685
|
portFinder.setPort(options.port);
|
|
935
686
|
if (argv.reset) {
|
|
936
|
-
|
|
937
|
-
|
|
687
|
+
if (["wordpress", "wordpress-develop", "index"].includes(
|
|
688
|
+
options.mode
|
|
689
|
+
)) {
|
|
690
|
+
output?.log(
|
|
691
|
+
"--reset only applies to wp-app managed sites; your project files are never touched."
|
|
692
|
+
);
|
|
693
|
+
} else {
|
|
694
|
+
fs13.rmSync(getSitePath(options.projectPath), {
|
|
695
|
+
recursive: true,
|
|
696
|
+
force: true,
|
|
697
|
+
maxRetries: 10,
|
|
698
|
+
retryDelay: 100
|
|
699
|
+
});
|
|
700
|
+
output?.log("Site environment reset.");
|
|
701
|
+
}
|
|
938
702
|
}
|
|
939
703
|
const { url } = await startServer(options);
|
|
940
704
|
if (argv.open) {
|
|
@@ -957,7 +721,7 @@ async function runCli() {
|
|
|
957
721
|
async (argv) => {
|
|
958
722
|
try {
|
|
959
723
|
const args = process.argv.slice(2);
|
|
960
|
-
const options = await
|
|
724
|
+
const options = await getWpAppConfig({
|
|
961
725
|
path: argv.path,
|
|
962
726
|
php: argv.php
|
|
963
727
|
});
|
|
@@ -976,9 +740,9 @@ async function runCli() {
|
|
|
976
740
|
commonParameters(yargs2);
|
|
977
741
|
},
|
|
978
742
|
async (argv) => {
|
|
979
|
-
const projectPath = argv.path ?
|
|
743
|
+
const projectPath = argv.path ? path11.resolve(argv.path) : process.cwd();
|
|
980
744
|
console.log(`Building project at ${projectPath}`);
|
|
981
|
-
if (
|
|
745
|
+
if (fs13.existsSync(path11.join(projectPath, "composer.json"))) {
|
|
982
746
|
console.log("Running composer install...");
|
|
983
747
|
try {
|
|
984
748
|
execSync(
|
|
@@ -991,9 +755,11 @@ async function runCli() {
|
|
|
991
755
|
);
|
|
992
756
|
}
|
|
993
757
|
}
|
|
994
|
-
const packageJsonPath =
|
|
995
|
-
if (
|
|
996
|
-
const pkg =
|
|
758
|
+
const packageJsonPath = path11.join(projectPath, "package.json");
|
|
759
|
+
if (fs13.existsSync(packageJsonPath)) {
|
|
760
|
+
const pkg = JSON.parse(
|
|
761
|
+
fs13.readFileSync(packageJsonPath, "utf8")
|
|
762
|
+
);
|
|
997
763
|
if (pkg.scripts?.build) {
|
|
998
764
|
console.log("Running npm build...");
|
|
999
765
|
execSync("npm run build", {
|
|
@@ -1012,7 +778,7 @@ async function runCli() {
|
|
|
1012
778
|
},
|
|
1013
779
|
async (argv) => {
|
|
1014
780
|
const selfScript = process.argv[1];
|
|
1015
|
-
const watchDir =
|
|
781
|
+
const watchDir = path11.resolve(
|
|
1016
782
|
argv.path || process.cwd()
|
|
1017
783
|
);
|
|
1018
784
|
const IGNORED = ["node_modules", "vendor", ".git", "dist"];
|
|
@@ -1033,7 +799,7 @@ async function runCli() {
|
|
|
1033
799
|
});
|
|
1034
800
|
};
|
|
1035
801
|
startChild();
|
|
1036
|
-
|
|
802
|
+
fs13.watch(
|
|
1037
803
|
watchDir,
|
|
1038
804
|
{ recursive: true },
|
|
1039
805
|
(_eventType, filename) => {
|
|
@@ -1086,11 +852,11 @@ function openInDefaultBrowser(url) {
|
|
|
1086
852
|
}
|
|
1087
853
|
|
|
1088
854
|
// src/main.ts
|
|
1089
|
-
var requiredMajorVersion =
|
|
855
|
+
var requiredMajorVersion = 20;
|
|
1090
856
|
var currentNodeVersion = parseInt(process.versions?.node?.split(".")?.[0]);
|
|
1091
857
|
if (currentNodeVersion < requiredMajorVersion) {
|
|
1092
858
|
console.warn(
|
|
1093
|
-
`You are running Node.js version ${currentNodeVersion}, but this application
|
|
859
|
+
`You are running Node.js version ${currentNodeVersion}, but this application requires at least Node.js ${requiredMajorVersion}. Please upgrade your Node.js version.`
|
|
1094
860
|
);
|
|
1095
861
|
}
|
|
1096
862
|
runCli();
|