@wp-operations/wp-app 0.1.1 → 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 +7 -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,29 +9,29 @@ 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, loadNodeRuntime } from "@php-wasm/node";
|
|
16
|
+
import { bootWordPressAndRequestHandler } from "@wp-playground/wordpress";
|
|
19
17
|
|
|
20
18
|
// src/constants.ts
|
|
21
|
-
|
|
22
|
-
var
|
|
19
|
+
import { RecommendedPHPVersion } from "@wp-playground/common";
|
|
20
|
+
var WP_APP_HIDDEN_FOLDER = ".wp-app";
|
|
21
|
+
var WP_APP_HOME_ENV = "WP_APP_HOME";
|
|
23
22
|
var SQLITE_URL = "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/main.zip";
|
|
24
23
|
var CLASSICPRESS_LATEST_URL = "https://www.classicpress.net/latest.zip";
|
|
25
24
|
var DEFAULT_PORT = 8881;
|
|
26
|
-
var DEFAULT_PHP_VERSION =
|
|
25
|
+
var DEFAULT_PHP_VERSION = RecommendedPHPVersion;
|
|
27
26
|
var DEFAULT_WORDPRESS_VERSION = "latest";
|
|
27
|
+
var DOCROOT = "/wordpress";
|
|
28
28
|
|
|
29
29
|
// src/download.ts
|
|
30
|
-
import
|
|
31
|
-
import
|
|
32
|
-
import followRedirects from "follow-redirects";
|
|
33
|
-
import unzipper from "unzipper";
|
|
34
|
-
import os3 from "os";
|
|
30
|
+
import fs9 from "fs";
|
|
31
|
+
import path8 from "path";
|
|
35
32
|
|
|
36
33
|
// src/wp-playground-wordpress/has-index-file.ts
|
|
37
|
-
import fs from "fs
|
|
34
|
+
import fs from "fs";
|
|
38
35
|
import path from "path";
|
|
39
36
|
function hasIndexFile(projectPath) {
|
|
40
37
|
return fs.existsSync(path.join(projectPath, "index.php"));
|
|
@@ -51,11 +48,11 @@ function isClassicPressVersion(version) {
|
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
// src/wp-playground-wordpress/get-plugin-file.ts
|
|
54
|
-
import fs3 from "fs
|
|
51
|
+
import fs3 from "fs";
|
|
55
52
|
import path2, { basename } from "path";
|
|
56
53
|
|
|
57
54
|
// src/wp-playground-wordpress/read-file-head.ts
|
|
58
|
-
import fs2 from "fs
|
|
55
|
+
import fs2 from "fs";
|
|
59
56
|
function readFileHead(filePath, length = 8192) {
|
|
60
57
|
const buffer = Buffer.alloc(length);
|
|
61
58
|
const fd = fs2.openSync(filePath, "r");
|
|
@@ -96,7 +93,7 @@ function isPluginDirectory(projectPath) {
|
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
// src/wp-playground-wordpress/is-theme-directory.ts
|
|
99
|
-
import fs4 from "fs
|
|
96
|
+
import fs4 from "fs";
|
|
100
97
|
import path3 from "path";
|
|
101
98
|
function isThemeDirectory(projectPath) {
|
|
102
99
|
const styleCSSExists = fs4.existsSync(path3.join(projectPath, "style.css"));
|
|
@@ -109,7 +106,7 @@ function isThemeDirectory(projectPath) {
|
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
// src/wp-playground-wordpress/is-wp-content-directory.ts
|
|
112
|
-
import fs5 from "fs
|
|
109
|
+
import fs5 from "fs";
|
|
113
110
|
import path4 from "path";
|
|
114
111
|
function isWpContentDirectory(projectPath) {
|
|
115
112
|
const muPluginsExists = fs5.existsSync(path4.join(projectPath, "mu-plugins"));
|
|
@@ -122,14 +119,14 @@ function isWpContentDirectory(projectPath) {
|
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
// src/wp-playground-wordpress/is-wordpress-directory.ts
|
|
125
|
-
import fs6 from "fs
|
|
122
|
+
import fs6 from "fs";
|
|
126
123
|
import path5 from "path";
|
|
127
124
|
function isWordPressDirectory(projectPath) {
|
|
128
125
|
return fs6.existsSync(path5.join(projectPath, "wp-content")) && fs6.existsSync(path5.join(projectPath, "wp-includes")) && fs6.existsSync(path5.join(projectPath, "wp-load.php"));
|
|
129
126
|
}
|
|
130
127
|
|
|
131
128
|
// src/wp-playground-wordpress/is-wordpress-develop-directory.ts
|
|
132
|
-
import fs7 from "fs
|
|
129
|
+
import fs7 from "fs";
|
|
133
130
|
import path6 from "path";
|
|
134
131
|
function isWordPressDevelopDirectory(projectPath) {
|
|
135
132
|
const requiredFiles = [
|
|
@@ -153,40 +150,29 @@ function shouldOutput() {
|
|
|
153
150
|
}
|
|
154
151
|
var output = shouldOutput() ? console : null;
|
|
155
152
|
|
|
156
|
-
// src/
|
|
157
|
-
import
|
|
158
|
-
import
|
|
159
|
-
|
|
160
|
-
// src/get-wp-now-tmp-path.ts
|
|
161
|
-
import path7 from "path";
|
|
153
|
+
// src/paths.ts
|
|
154
|
+
import crypto from "crypto";
|
|
155
|
+
import fs8 from "fs";
|
|
162
156
|
import os from "os";
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return path7.join(
|
|
157
|
+
import path7 from "path";
|
|
158
|
+
function getWpAppHome() {
|
|
159
|
+
return process.env[WP_APP_HOME_ENV] || path7.join(os.homedir(), WP_APP_HIDDEN_FOLDER);
|
|
166
160
|
}
|
|
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();
|
|
161
|
+
function getCachePath() {
|
|
162
|
+
return ensureDir(path7.join(getWpAppHome(), "cache"));
|
|
174
163
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return path9.join(getWpNowPath(), "wordpress-versions");
|
|
164
|
+
function getSitePath(projectPath) {
|
|
165
|
+
const projectName = path7.basename(path7.resolve(projectPath));
|
|
166
|
+
const hash = crypto.createHash("sha1").update(path7.resolve(projectPath)).digest("hex");
|
|
167
|
+
return path7.join(getWpAppHome(), "sites", `${projectName}-${hash}`);
|
|
180
168
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
function getSqlitePath() {
|
|
185
|
-
return path10.join(getWpNowPath(), `${SQLITE_FILENAME}-main`);
|
|
169
|
+
function ensureDir(dir) {
|
|
170
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
171
|
+
return dir;
|
|
186
172
|
}
|
|
187
173
|
|
|
188
174
|
// src/download.ts
|
|
189
|
-
function getWordPressVersionUrl(version
|
|
175
|
+
function getWordPressVersionUrl(version) {
|
|
190
176
|
if (!isValidWordPressVersion(version)) {
|
|
191
177
|
throw new Error(
|
|
192
178
|
'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 +187,172 @@ function getClassicPressVersionUrl(version) {
|
|
|
201
187
|
const tag = version.slice("classicpress-".length);
|
|
202
188
|
return `https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${tag}.zip`;
|
|
203
189
|
}
|
|
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) {
|
|
190
|
+
async function cachedZip(url, cacheKey) {
|
|
191
|
+
const cacheFile = path8.join(getCachePath(), cacheKey);
|
|
192
|
+
if (!fs9.existsSync(cacheFile)) {
|
|
193
|
+
output?.log(`Downloading ${cacheKey}...`);
|
|
194
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
195
|
+
if (!response.ok) {
|
|
225
196
|
throw new Error(
|
|
226
|
-
`Failed to download
|
|
197
|
+
`Failed to download ${url} (HTTP ${response.status}).`
|
|
227
198
|
);
|
|
228
199
|
}
|
|
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);
|
|
200
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
201
|
+
fs9.writeFileSync(`${cacheFile}.partial`, bytes);
|
|
202
|
+
fs9.renameSync(`${cacheFile}.partial`, cacheFile);
|
|
203
|
+
output?.log(`Cached ${cacheKey} (${bytes.length} bytes).`);
|
|
264
204
|
}
|
|
205
|
+
return new File([fs9.readFileSync(cacheFile)], cacheKey);
|
|
265
206
|
}
|
|
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);
|
|
207
|
+
async function getCoreZip(version) {
|
|
208
|
+
if (isClassicPressVersion(version)) {
|
|
209
|
+
return cachedZip(getClassicPressVersionUrl(version), `${version}.zip`);
|
|
287
210
|
}
|
|
211
|
+
return cachedZip(getWordPressVersionUrl(version), `wordpress-${version}.zip`);
|
|
288
212
|
}
|
|
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
|
-
);
|
|
213
|
+
async function getSqliteIntegrationZip() {
|
|
214
|
+
return cachedZip(SQLITE_URL, "sqlite-database-integration.zip");
|
|
312
215
|
}
|
|
313
216
|
|
|
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
|
-
});
|
|
217
|
+
// src/engine.ts
|
|
218
|
+
async function startWPApp(options) {
|
|
219
|
+
const projectPath = path9.resolve(options.projectPath);
|
|
220
|
+
const mode = options.mode;
|
|
361
221
|
output?.log(`directory: ${options.projectPath}`);
|
|
362
|
-
output?.log(`mode: ${
|
|
222
|
+
output?.log(`mode: ${mode}`);
|
|
363
223
|
output?.log(`php: ${options.phpVersion}`);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
await
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
case "playground" /* PLAYGROUND */:
|
|
395
|
-
await runWpPlaygroundMode(_php, options);
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
await installationStep2(php);
|
|
400
|
-
await login(php, {
|
|
401
|
-
username: "admin",
|
|
402
|
-
password: "password"
|
|
224
|
+
const hostDocroot = mode === "index" /* INDEX */ || mode === "wordpress" /* WORDPRESS */ ? projectPath : mode === "wordpress-develop" /* WORDPRESS_DEVELOP */ ? path9.join(projectPath, "build") : ensureDir(getSitePath(projectPath));
|
|
225
|
+
const isWordPressBacked = mode !== "index" /* INDEX */;
|
|
226
|
+
const freshSite = isWordPressBacked && !fs10.existsSync(path9.join(hostDocroot, "wp-load.php"));
|
|
227
|
+
if (isWordPressBacked) {
|
|
228
|
+
output?.log(`wp: ${options.wordPressVersion}`);
|
|
229
|
+
output?.log(`site data: ${hostDocroot}`);
|
|
230
|
+
}
|
|
231
|
+
const requestHandler = await bootWordPressAndRequestHandler({
|
|
232
|
+
siteUrl: options.absoluteUrl,
|
|
233
|
+
documentRoot: DOCROOT,
|
|
234
|
+
phpVersion: options.phpVersion,
|
|
235
|
+
maxPhpInstances: 1,
|
|
236
|
+
sapiName: "cli",
|
|
237
|
+
cookieStore: false,
|
|
238
|
+
createPhpRuntime: () => loadNodeRuntime(options.phpVersion, {
|
|
239
|
+
emscriptenOptions: { processId: process.pid }
|
|
240
|
+
}),
|
|
241
|
+
hooks: {
|
|
242
|
+
beforeWordPressFiles: async (php2) => {
|
|
243
|
+
php2.mkdir(DOCROOT);
|
|
244
|
+
await php2.mount(
|
|
245
|
+
DOCROOT,
|
|
246
|
+
createNodeFsMountHandler(hostDocroot)
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
wordPressZip: freshSite ? await getCoreZip(options.wordPressVersion) : void 0,
|
|
251
|
+
sqliteIntegrationPluginZip: isWordPressBacked ? await getSqliteIntegrationZip() : void 0,
|
|
252
|
+
dataSqlPath: isWordPressBacked ? `${DOCROOT}/wp-content/database/.ht.sqlite` : void 0,
|
|
253
|
+
wordpressInstallMode: isWordPressBacked ? "install-from-existing-files-if-needed" : "do-not-attempt-installing"
|
|
403
254
|
});
|
|
404
|
-
|
|
405
|
-
|
|
255
|
+
const php = await requestHandler.getPrimaryPhp();
|
|
256
|
+
await applyProjectMounts(php, mode, projectPath);
|
|
257
|
+
if (freshSite) {
|
|
258
|
+
await activateProject(php, mode, projectPath);
|
|
259
|
+
}
|
|
260
|
+
return { requestHandler, php, options, freshSite };
|
|
261
|
+
}
|
|
262
|
+
async function applyProjectMounts(php, mode, projectPath) {
|
|
263
|
+
const projectName = path9.basename(projectPath);
|
|
264
|
+
switch (mode) {
|
|
265
|
+
case "theme" /* THEME */:
|
|
266
|
+
await mountAt(
|
|
267
|
+
php,
|
|
268
|
+
projectPath,
|
|
269
|
+
`${DOCROOT}/wp-content/themes/${projectName}`
|
|
270
|
+
);
|
|
271
|
+
break;
|
|
272
|
+
case "plugin" /* PLUGIN */:
|
|
273
|
+
await mountAt(
|
|
274
|
+
php,
|
|
275
|
+
projectPath,
|
|
276
|
+
`${DOCROOT}/wp-content/plugins/${projectName}`
|
|
277
|
+
);
|
|
278
|
+
break;
|
|
279
|
+
case "wp-content" /* WP_CONTENT */:
|
|
280
|
+
for (const entry of fs10.readdirSync(projectPath)) {
|
|
281
|
+
if (entry === "index.php") {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
await mountAt(
|
|
285
|
+
php,
|
|
286
|
+
path9.join(projectPath, entry),
|
|
287
|
+
`${DOCROOT}/wp-content/${entry}`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
default:
|
|
292
|
+
break;
|
|
406
293
|
}
|
|
407
|
-
return {
|
|
408
|
-
php,
|
|
409
|
-
phpInstances,
|
|
410
|
-
options
|
|
411
|
-
};
|
|
412
294
|
}
|
|
413
|
-
async function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
wordPressVersion,
|
|
419
|
-
wpContentPath,
|
|
420
|
-
projectPath,
|
|
421
|
-
absoluteUrl
|
|
422
|
-
}) {
|
|
423
|
-
const wordPressPath = path12.join(
|
|
424
|
-
getWordpressVersionsPath(),
|
|
425
|
-
wordPressVersion
|
|
426
|
-
);
|
|
427
|
-
php.mount(wordPressPath, documentRoot);
|
|
428
|
-
await initWordPress(php, wordPressVersion, documentRoot, absoluteUrl);
|
|
429
|
-
fs9.ensureDirSync(wpContentPath);
|
|
430
|
-
php.mount(projectPath, `${documentRoot}/wp-content`);
|
|
431
|
-
mountSqlitePlugin(php, documentRoot);
|
|
432
|
-
mountSqliteDatabaseDirectory(php, documentRoot, wpContentPath);
|
|
433
|
-
mountMuPlugins(php, documentRoot);
|
|
434
|
-
}
|
|
435
|
-
async function runWordPressDevelopMode(php, { documentRoot, projectPath, absoluteUrl }) {
|
|
436
|
-
await runWordPressMode(php, {
|
|
437
|
-
documentRoot,
|
|
438
|
-
projectPath: projectPath + "/build",
|
|
439
|
-
absoluteUrl
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
async function runWordPressMode(php, { documentRoot, wpContentPath, projectPath, absoluteUrl }) {
|
|
443
|
-
php.mount(projectPath, documentRoot);
|
|
444
|
-
const { initializeDefaultDatabase } = await initWordPress(
|
|
445
|
-
php,
|
|
446
|
-
"user-provided",
|
|
447
|
-
documentRoot,
|
|
448
|
-
absoluteUrl
|
|
449
|
-
);
|
|
450
|
-
if (initializeDefaultDatabase || fs9.existsSync(path12.join(wpContentPath, "database"))) {
|
|
451
|
-
mountSqlitePlugin(php, documentRoot);
|
|
452
|
-
mountSqliteDatabaseDirectory(php, documentRoot, wpContentPath);
|
|
453
|
-
}
|
|
454
|
-
mountMuPlugins(php, documentRoot);
|
|
455
|
-
}
|
|
456
|
-
async function runPluginOrThemeMode(php, {
|
|
457
|
-
wordPressVersion,
|
|
458
|
-
documentRoot,
|
|
459
|
-
projectPath,
|
|
460
|
-
wpContentPath,
|
|
461
|
-
absoluteUrl,
|
|
462
|
-
mode
|
|
463
|
-
}) {
|
|
464
|
-
const wordPressPath = path12.join(
|
|
465
|
-
getWordpressVersionsPath(),
|
|
466
|
-
wordPressVersion
|
|
467
|
-
);
|
|
468
|
-
php.mount(wordPressPath, documentRoot);
|
|
469
|
-
await initWordPress(php, wordPressVersion, documentRoot, absoluteUrl);
|
|
470
|
-
fs9.ensureDirSync(wpContentPath);
|
|
471
|
-
fs9.copySync(
|
|
472
|
-
path12.join(getWordpressVersionsPath(), wordPressVersion, "wp-content"),
|
|
473
|
-
wpContentPath
|
|
474
|
-
);
|
|
475
|
-
php.mount(wpContentPath, `${documentRoot}/wp-content`);
|
|
476
|
-
const pluginName = path12.basename(projectPath);
|
|
477
|
-
const directoryName = mode === "plugin" /* PLUGIN */ ? "plugins" : "themes";
|
|
478
|
-
php.mount(
|
|
479
|
-
projectPath,
|
|
480
|
-
`${documentRoot}/wp-content/${directoryName}/${pluginName}`
|
|
481
|
-
);
|
|
482
|
-
mountSqlitePlugin(php, documentRoot);
|
|
483
|
-
mountMuPlugins(php, documentRoot);
|
|
295
|
+
async function mountAt(php, hostPath, vfsPath) {
|
|
296
|
+
if (fs10.statSync(hostPath).isDirectory()) {
|
|
297
|
+
php.mkdir(vfsPath);
|
|
298
|
+
}
|
|
299
|
+
await php.mount(vfsPath, createNodeFsMountHandler(hostPath));
|
|
484
300
|
}
|
|
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
|
-
|
|
301
|
+
async function activateProject(php, mode, projectPath) {
|
|
302
|
+
const projectName = path9.basename(projectPath);
|
|
303
|
+
if (mode === "theme" /* THEME */) {
|
|
304
|
+
await runWordPressCode(
|
|
305
|
+
php,
|
|
306
|
+
`switch_theme(${phpString(projectName)});`
|
|
307
|
+
);
|
|
308
|
+
} else if (mode === "plugin" /* PLUGIN */) {
|
|
309
|
+
const pluginFile = findPluginFile(projectPath);
|
|
310
|
+
if (pluginFile) {
|
|
311
|
+
await runWordPressCode(
|
|
312
|
+
php,
|
|
313
|
+
`activate_plugin(${phpString(`${projectName}/${pluginFile}`)});`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
} else if (mode === "wp-content" /* WP_CONTENT */) {
|
|
317
|
+
await runWordPressCode(
|
|
318
|
+
php,
|
|
319
|
+
`$theme = wp_get_theme();
|
|
320
|
+
if (!$theme->exists()) {
|
|
321
|
+
$themes = wp_get_themes();
|
|
322
|
+
if (count($themes) > 0) {
|
|
323
|
+
switch_theme(array_keys($themes)[0]);
|
|
324
|
+
}
|
|
325
|
+
}`
|
|
507
326
|
);
|
|
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
327
|
}
|
|
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
328
|
}
|
|
539
|
-
function
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
329
|
+
async function runWordPressCode(php, code) {
|
|
330
|
+
const response = await php.run({
|
|
331
|
+
code: `<?php
|
|
332
|
+
require ${phpString(`${DOCROOT}/wp-load.php`)};
|
|
333
|
+
require_once ${phpString(`${DOCROOT}/wp-admin/includes/plugin.php`)};
|
|
334
|
+
${code}
|
|
335
|
+
`
|
|
336
|
+
});
|
|
337
|
+
if (response.exitCode !== 0) {
|
|
338
|
+
output?.error(`Activation step failed: ${response.errors}`);
|
|
547
339
|
}
|
|
548
340
|
}
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
)
|
|
341
|
+
function phpString(value) {
|
|
342
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
343
|
+
}
|
|
344
|
+
function findPluginFile(projectPath) {
|
|
345
|
+
const pluginNameRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;
|
|
346
|
+
for (const file of fs10.readdirSync(projectPath)) {
|
|
347
|
+
if (!file.endsWith(".php")) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const content = fs10.readFileSync(path9.join(projectPath, file), "utf8");
|
|
351
|
+
if (pluginNameRegex.test(content)) {
|
|
352
|
+
return file;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
555
356
|
}
|
|
556
357
|
function inferMode(projectPath) {
|
|
557
358
|
if (isWordPressDevelopDirectory(projectPath)) {
|
|
@@ -569,27 +370,6 @@ function inferMode(projectPath) {
|
|
|
569
370
|
}
|
|
570
371
|
return "playground" /* PLAYGROUND */;
|
|
571
372
|
}
|
|
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
373
|
|
|
594
374
|
// src/port-finder.ts
|
|
595
375
|
import http from "http";
|
|
@@ -641,14 +421,11 @@ var PortFinder = class _PortFinder {
|
|
|
641
421
|
var portFinder = PortFinder.getInstance();
|
|
642
422
|
|
|
643
423
|
// src/config.ts
|
|
644
|
-
import path13 from "path";
|
|
645
424
|
var DEFAULT_OPTIONS = {
|
|
646
425
|
phpVersion: DEFAULT_PHP_VERSION,
|
|
647
426
|
wordPressVersion: DEFAULT_WORDPRESS_VERSION,
|
|
648
|
-
documentRoot: "/var/www/html",
|
|
649
427
|
projectPath: process.cwd(),
|
|
650
|
-
mode: "auto" /* AUTO
|
|
651
|
-
numberOfPhpInstances: 1
|
|
428
|
+
mode: "auto" /* AUTO */
|
|
652
429
|
};
|
|
653
430
|
async function getAbsoluteURL() {
|
|
654
431
|
const port = await portFinder.getOpenPort();
|
|
@@ -657,13 +434,7 @@ async function getAbsoluteURL() {
|
|
|
657
434
|
}
|
|
658
435
|
return `http://localhost:${port}`;
|
|
659
436
|
}
|
|
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) {
|
|
437
|
+
async function getWpAppConfig(args) {
|
|
667
438
|
if (args.port) {
|
|
668
439
|
portFinder.setPort(args.port);
|
|
669
440
|
}
|
|
@@ -686,12 +457,6 @@ async function getWpNowConfig(args) {
|
|
|
686
457
|
if (!options.mode || options.mode === "auto") {
|
|
687
458
|
options.mode = inferMode(options.projectPath);
|
|
688
459
|
}
|
|
689
|
-
if (!options.wpContentPath) {
|
|
690
|
-
options.wpContentPath = getWpContentHomePath(
|
|
691
|
-
options.projectPath,
|
|
692
|
-
options.mode
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
460
|
if (!options.absoluteUrl) {
|
|
696
461
|
options.absoluteUrl = await getAbsoluteURL();
|
|
697
462
|
}
|
|
@@ -709,94 +474,83 @@ async function getWpNowConfig(args) {
|
|
|
709
474
|
}
|
|
710
475
|
|
|
711
476
|
// src/start-server.ts
|
|
712
|
-
import
|
|
477
|
+
import fs11 from "fs";
|
|
478
|
+
import { Readable } from "stream";
|
|
479
|
+
import { pipeline } from "stream/promises";
|
|
713
480
|
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
481
|
async function startServer(options = {}) {
|
|
736
|
-
if (!
|
|
482
|
+
if (!fs11.existsSync(options.projectPath)) {
|
|
737
483
|
throw new Error(
|
|
738
484
|
`The given path "${options.projectPath}" does not exist.`
|
|
739
485
|
);
|
|
740
486
|
}
|
|
741
|
-
const app = express();
|
|
742
|
-
app.use(fileUpload());
|
|
743
487
|
const port = await portFinder.getOpenPort();
|
|
744
|
-
const
|
|
488
|
+
const site = await startWPApp(options);
|
|
489
|
+
const app = express();
|
|
745
490
|
app.use("/", async (req, res) => {
|
|
746
491
|
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 = {
|
|
492
|
+
const phpRequest = {
|
|
760
493
|
url: req.url,
|
|
761
|
-
headers: requestHeaders,
|
|
762
494
|
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
|
|
495
|
+
headers: parseHeaders(req),
|
|
496
|
+
body: await bufferRequestBody(req)
|
|
778
497
|
};
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
498
|
+
const response = await site.requestHandler.requestStreamed(phpRequest);
|
|
499
|
+
await sendStreamedResponse(response, res);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
output?.trace(error);
|
|
502
|
+
if (!res.headersSent) {
|
|
503
|
+
res.statusCode = 500;
|
|
504
|
+
res.end("Internal Server Error");
|
|
505
|
+
}
|
|
787
506
|
}
|
|
788
507
|
});
|
|
789
508
|
const url = options.absoluteUrl;
|
|
790
509
|
app.listen(port, () => {
|
|
791
510
|
output?.log(`Server running at ${url}`);
|
|
792
511
|
});
|
|
793
|
-
return {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
512
|
+
return { ...site, url };
|
|
513
|
+
}
|
|
514
|
+
async function sendStreamedResponse(streamedResponse, res) {
|
|
515
|
+
const [headers, httpStatusCode] = await Promise.all([
|
|
516
|
+
streamedResponse.headers,
|
|
517
|
+
streamedResponse.httpStatusCode
|
|
518
|
+
]);
|
|
519
|
+
res.statusCode = httpStatusCode;
|
|
520
|
+
for (const key in headers) {
|
|
521
|
+
res.setHeader(key, headers[key]);
|
|
522
|
+
}
|
|
523
|
+
const nodeStream = Readable.fromWeb(streamedResponse.stdout);
|
|
524
|
+
try {
|
|
525
|
+
await pipeline(nodeStream, res);
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if (error instanceof Error && "code" in error && (error.code === "ERR_STREAM_PREMATURE_CLOSE" || error.code === "ERR_STREAM_UNABLE_TO_PIPE")) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
798
532
|
}
|
|
533
|
+
var bufferRequestBody = async (req) => await new Promise((resolve) => {
|
|
534
|
+
const body = [];
|
|
535
|
+
req.on("data", (chunk) => {
|
|
536
|
+
body.push(chunk);
|
|
537
|
+
});
|
|
538
|
+
req.on("end", () => {
|
|
539
|
+
resolve(new Uint8Array(Buffer.concat(body)));
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
var parseHeaders = (req) => {
|
|
543
|
+
const requestHeaders = {};
|
|
544
|
+
if (req.rawHeaders && req.rawHeaders.length) {
|
|
545
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
546
|
+
requestHeaders[req.rawHeaders[i].toLowerCase()] = req.rawHeaders[i + 1];
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return requestHeaders;
|
|
550
|
+
};
|
|
799
551
|
export {
|
|
800
|
-
|
|
801
|
-
|
|
552
|
+
getWpAppConfig,
|
|
553
|
+
inferMode,
|
|
554
|
+
startServer,
|
|
555
|
+
startWPApp
|
|
802
556
|
};
|