@wp-playground/cli 3.0.40 → 3.0.42
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/blueprints-v1/blueprints-v1-handler.d.ts +3 -3
- package/blueprints-v2/blueprints-v2-handler.d.ts +3 -3
- package/cli-output.d.ts +114 -0
- package/cli.cjs +1 -1
- package/cli.js +1 -1
- package/index.cjs +1 -1
- package/index.js +1 -1
- package/mounts.d.ts +1 -1
- package/package.json +13 -13
- package/run-cli-B8Dv6R-o.cjs +64 -0
- package/run-cli-B8Dv6R-o.cjs.map +1 -0
- package/run-cli-NcKUE5gJ.js +1543 -0
- package/run-cli-NcKUE5gJ.js.map +1 -0
- package/run-cli.d.ts +8 -1
- package/sqlite-database-integration-develop.zip +0 -0
- package/worker-thread-v1.cjs +1 -1
- package/worker-thread-v1.js +1 -1
- package/worker-thread-v2.cjs +5 -5
- package/worker-thread-v2.cjs.map +1 -1
- package/worker-thread-v2.js +7 -7
- package/worker-thread-v2.js.map +1 -1
- package/run-cli-D8BNUM1f.cjs +0 -42
- package/run-cli-D8BNUM1f.cjs.map +0 -1
- package/run-cli-pLJHMn5d.js +0 -1234
- package/run-cli-pLJHMn5d.js.map +0 -1
|
@@ -0,0 +1,1543 @@
|
|
|
1
|
+
import { logger as g, LogSeverity as j, errorLogPath as ee } from "@php-wasm/logger";
|
|
2
|
+
import { PHPResponse as Y, consumeAPI as F, SupportedPHPVersions as te, printDebugDetails as ye, exposeAPI as be, exposeSyncAPI as ve } from "@php-wasm/universal";
|
|
3
|
+
import { resolveRemoteBlueprint as Pe, resolveRuntimeConfiguration as re, compileBlueprintV1 as Se, isBlueprintBundle as xe, runBlueprintV1Steps as ke } from "@wp-playground/blueprints";
|
|
4
|
+
import { zipDirectory as Ie, RecommendedPHPVersion as U } from "@wp-playground/common";
|
|
5
|
+
import p, { existsSync as X, rmdirSync as $e, mkdirSync as z, readdirSync as Te } from "fs";
|
|
6
|
+
import { MessageChannel as Be, Worker as oe } from "worker_threads";
|
|
7
|
+
import { createNodeFsMountHandler as Ee, FileLockManagerForNode as Le } from "@php-wasm/node";
|
|
8
|
+
import h, { basename as A, join as ue } from "path";
|
|
9
|
+
import We from "express";
|
|
10
|
+
import O, { cpus as Ce } from "os";
|
|
11
|
+
import { jspi as Me } from "wasm-feature-detect";
|
|
12
|
+
import Re from "yargs";
|
|
13
|
+
import { NodeJsFilesystem as De, OverlayFilesystem as Ae, InMemoryFilesystem as Fe, ZipFilesystem as Ue } from "@wp-playground/storage";
|
|
14
|
+
import { EmscriptenDownloadMonitor as Oe, ProgressTracker as He } from "@php-wasm/progress";
|
|
15
|
+
import { resolveWordPressRelease as Ve } from "@wp-playground/wordpress";
|
|
16
|
+
import B from "fs-extra";
|
|
17
|
+
import { startBridge as Ne } from "@php-wasm/xdebug-bridge";
|
|
18
|
+
import { exec as je } from "child_process";
|
|
19
|
+
import { dir as _e, setGracefulCleanup as qe } from "tmp-promise";
|
|
20
|
+
import Ye from "ps-man";
|
|
21
|
+
import { removeTempDirSymlink as ze, createTempDirSymlink as Qe, clearXdebugIDEConfig as Xe, addXdebugIDEConfig as Ze } from "@php-wasm/cli-util";
|
|
22
|
+
import { createHash as Ge } from "crypto";
|
|
23
|
+
function _(e) {
|
|
24
|
+
const t = [];
|
|
25
|
+
for (const r of e) {
|
|
26
|
+
const s = r.split(":");
|
|
27
|
+
if (s.length !== 2)
|
|
28
|
+
throw new Error(`Invalid mount format: ${r}.
|
|
29
|
+
Expected format: /host/path:/vfs/path.
|
|
30
|
+
If your path contains a colon, e.g. C:\\myplugin, use the --mount-dir option instead.
|
|
31
|
+
Example: --mount-dir C:\\my-plugin /wordpress/wp-content/plugins/my-plugin`);
|
|
32
|
+
const [o, i] = s;
|
|
33
|
+
if (!X(o))
|
|
34
|
+
throw new Error(`Host path does not exist: ${o}`);
|
|
35
|
+
t.push({ hostPath: o, vfsPath: i });
|
|
36
|
+
}
|
|
37
|
+
return t;
|
|
38
|
+
}
|
|
39
|
+
function se(e) {
|
|
40
|
+
if (e.length % 2 !== 0)
|
|
41
|
+
throw new Error("Invalid mount format. Expected: /host/path /vfs/path");
|
|
42
|
+
const t = [];
|
|
43
|
+
for (let r = 0; r < e.length; r += 2) {
|
|
44
|
+
const s = e[r], o = e[r + 1];
|
|
45
|
+
if (!X(s))
|
|
46
|
+
throw new Error(`Host path does not exist: ${s}`);
|
|
47
|
+
t.push({
|
|
48
|
+
hostPath: h.resolve(process.cwd(), s),
|
|
49
|
+
vfsPath: o
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return t;
|
|
53
|
+
}
|
|
54
|
+
async function Gt(e, t) {
|
|
55
|
+
for (const r of t)
|
|
56
|
+
await e.mount(
|
|
57
|
+
r.vfsPath,
|
|
58
|
+
Ee(r.hostPath)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const ie = {
|
|
62
|
+
step: "runPHP",
|
|
63
|
+
code: {
|
|
64
|
+
filename: "activate-theme.php",
|
|
65
|
+
// @TODO: Remove DOCROOT check after moving totally to Blueprints v2.
|
|
66
|
+
content: `<?php
|
|
67
|
+
$docroot = getenv('DOCROOT') ? getenv('DOCROOT') : '/wordpress';
|
|
68
|
+
require_once "$docroot/wp-load.php";
|
|
69
|
+
$theme = wp_get_theme();
|
|
70
|
+
if (!$theme->exists()) {
|
|
71
|
+
$themes = wp_get_themes();
|
|
72
|
+
if (count($themes) > 0) {
|
|
73
|
+
$themeName = array_keys($themes)[0];
|
|
74
|
+
switch_theme($themeName);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
`
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
function de(e) {
|
|
81
|
+
const t = e.autoMount, r = [...e.mount || []], s = [...e["mount-before-install"] || []], o = {
|
|
82
|
+
...e,
|
|
83
|
+
mount: r,
|
|
84
|
+
"mount-before-install": s,
|
|
85
|
+
"additional-blueprint-steps": [
|
|
86
|
+
...e["additional-blueprint-steps"] || []
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
if (tt(t)) {
|
|
90
|
+
const n = `/wordpress/wp-content/plugins/${A(t)}`;
|
|
91
|
+
r.push({
|
|
92
|
+
hostPath: t,
|
|
93
|
+
vfsPath: n,
|
|
94
|
+
autoMounted: !0
|
|
95
|
+
}), o["additional-blueprint-steps"].push({
|
|
96
|
+
step: "activatePlugin",
|
|
97
|
+
pluginPath: `/wordpress/wp-content/plugins/${A(t)}`
|
|
98
|
+
});
|
|
99
|
+
} else if (et(t)) {
|
|
100
|
+
const i = A(t), n = `/wordpress/wp-content/themes/${i}`;
|
|
101
|
+
r.push({
|
|
102
|
+
hostPath: t,
|
|
103
|
+
vfsPath: n,
|
|
104
|
+
autoMounted: !0
|
|
105
|
+
}), o["additional-blueprint-steps"].push(
|
|
106
|
+
e["experimental-blueprints-v2-runner"] ? {
|
|
107
|
+
step: "activateTheme",
|
|
108
|
+
themeDirectoryName: i
|
|
109
|
+
} : {
|
|
110
|
+
step: "activateTheme",
|
|
111
|
+
themeFolderName: i
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
} else if (Ke(t)) {
|
|
115
|
+
const i = p.readdirSync(t);
|
|
116
|
+
for (const n of i)
|
|
117
|
+
n !== "index.php" && r.push({
|
|
118
|
+
hostPath: `${t}/${n}`,
|
|
119
|
+
vfsPath: `/wordpress/wp-content/${n}`,
|
|
120
|
+
autoMounted: !0
|
|
121
|
+
});
|
|
122
|
+
o["additional-blueprint-steps"].push(ie);
|
|
123
|
+
} else Je(t) && (s.push({
|
|
124
|
+
hostPath: t,
|
|
125
|
+
vfsPath: "/wordpress",
|
|
126
|
+
autoMounted: !0
|
|
127
|
+
}), o.mode = "apply-to-existing-site", o["additional-blueprint-steps"].push(ie), o.wordpressInstallMode || (o.wordpressInstallMode = "install-from-existing-files-if-needed"));
|
|
128
|
+
return o;
|
|
129
|
+
}
|
|
130
|
+
function Je(e) {
|
|
131
|
+
const t = p.readdirSync(e);
|
|
132
|
+
return t.includes("wp-admin") && t.includes("wp-includes") && t.includes("wp-content");
|
|
133
|
+
}
|
|
134
|
+
function Ke(e) {
|
|
135
|
+
const t = p.readdirSync(e);
|
|
136
|
+
return t.includes("themes") || t.includes("plugins") || t.includes("mu-plugins") || t.includes("uploads");
|
|
137
|
+
}
|
|
138
|
+
function et(e) {
|
|
139
|
+
if (!p.readdirSync(e).includes("style.css"))
|
|
140
|
+
return !1;
|
|
141
|
+
const r = p.readFileSync(ue(e, "style.css"), "utf8");
|
|
142
|
+
return !!/^(?:[ \t]*<\?php)?[ \t/*#@]*Theme Name:(.*)$/im.exec(r);
|
|
143
|
+
}
|
|
144
|
+
function tt(e) {
|
|
145
|
+
const t = p.readdirSync(e), r = /^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;
|
|
146
|
+
return !!t.filter((o) => o.endsWith(".php")).find((o) => {
|
|
147
|
+
const i = p.readFileSync(ue(e, o), "utf8");
|
|
148
|
+
return !!r.exec(i);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async function rt(e) {
|
|
152
|
+
const t = We(), r = await new Promise((i, n) => {
|
|
153
|
+
const l = t.listen(e.port, () => {
|
|
154
|
+
const u = l.address();
|
|
155
|
+
u === null || typeof u == "string" ? n(new Error("Server address is not available")) : i(l);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
t.use("/", async (i, n) => {
|
|
159
|
+
let l;
|
|
160
|
+
try {
|
|
161
|
+
l = await e.handleRequest({
|
|
162
|
+
url: i.url,
|
|
163
|
+
headers: st(i),
|
|
164
|
+
method: i.method,
|
|
165
|
+
body: await ot(i)
|
|
166
|
+
});
|
|
167
|
+
} catch (u) {
|
|
168
|
+
g.error(u), l = Y.forHttpCode(500);
|
|
169
|
+
}
|
|
170
|
+
n.statusCode = l.httpStatusCode;
|
|
171
|
+
for (const u in l.headers)
|
|
172
|
+
n.setHeader(u, l.headers[u]);
|
|
173
|
+
n.end(l.bytes);
|
|
174
|
+
});
|
|
175
|
+
const o = r.address().port;
|
|
176
|
+
return await e.onBind(r, o);
|
|
177
|
+
}
|
|
178
|
+
const ot = async (e) => await new Promise((t) => {
|
|
179
|
+
const r = [];
|
|
180
|
+
e.on("data", (s) => {
|
|
181
|
+
r.push(s);
|
|
182
|
+
}), e.on("end", () => {
|
|
183
|
+
t(new Uint8Array(Buffer.concat(r)));
|
|
184
|
+
});
|
|
185
|
+
}), st = (e) => {
|
|
186
|
+
const t = {};
|
|
187
|
+
if (e.rawHeaders && e.rawHeaders.length)
|
|
188
|
+
for (let r = 0; r < e.rawHeaders.length; r += 2)
|
|
189
|
+
t[e.rawHeaders[r].toLowerCase()] = e.rawHeaders[r + 1];
|
|
190
|
+
return t;
|
|
191
|
+
};
|
|
192
|
+
class it {
|
|
193
|
+
constructor(t) {
|
|
194
|
+
this.workerLoads = [], this.addWorker(t);
|
|
195
|
+
}
|
|
196
|
+
addWorker(t) {
|
|
197
|
+
this.workerLoads.push({
|
|
198
|
+
worker: t,
|
|
199
|
+
activeRequests: /* @__PURE__ */ new Set()
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
async removeWorker(t) {
|
|
203
|
+
const r = this.workerLoads.findIndex(
|
|
204
|
+
(o) => o.worker === t
|
|
205
|
+
);
|
|
206
|
+
if (r === -1)
|
|
207
|
+
return;
|
|
208
|
+
const [s] = this.workerLoads.splice(r, 1);
|
|
209
|
+
await Promise.allSettled(s.activeRequests);
|
|
210
|
+
}
|
|
211
|
+
async handleRequest(t) {
|
|
212
|
+
let r = this.workerLoads[0];
|
|
213
|
+
for (let o = 1; o < this.workerLoads.length; o++) {
|
|
214
|
+
const i = this.workerLoads[o];
|
|
215
|
+
i.activeRequests.size < r.activeRequests.size && (r = i);
|
|
216
|
+
}
|
|
217
|
+
const s = r.worker.request(t);
|
|
218
|
+
return r.activeRequests.add(s), s.url = t.url, s.finally(() => {
|
|
219
|
+
r.activeRequests.delete(s);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function nt(e) {
|
|
224
|
+
return /^latest$|^trunk$|^nightly$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/.test(e);
|
|
225
|
+
}
|
|
226
|
+
async function at({
|
|
227
|
+
sourceString: e,
|
|
228
|
+
blueprintMayReadAdjacentFiles: t
|
|
229
|
+
}) {
|
|
230
|
+
if (!e)
|
|
231
|
+
return;
|
|
232
|
+
if (e.startsWith("http://") || e.startsWith("https://"))
|
|
233
|
+
return await Pe(e);
|
|
234
|
+
let r = h.resolve(process.cwd(), e);
|
|
235
|
+
if (!p.existsSync(r))
|
|
236
|
+
throw new Error(`Blueprint file does not exist: ${r}`);
|
|
237
|
+
const s = p.statSync(r);
|
|
238
|
+
if (s.isDirectory() && (r = h.join(r, "blueprint.json")), !s.isFile() && s.isSymbolicLink())
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Blueprint path is neither a file nor a directory: ${r}`
|
|
241
|
+
);
|
|
242
|
+
const o = h.extname(r);
|
|
243
|
+
switch (o) {
|
|
244
|
+
case ".zip":
|
|
245
|
+
return Ue.fromArrayBuffer(
|
|
246
|
+
p.readFileSync(r).buffer
|
|
247
|
+
);
|
|
248
|
+
case ".json": {
|
|
249
|
+
const i = p.readFileSync(r, "utf-8");
|
|
250
|
+
try {
|
|
251
|
+
JSON.parse(i);
|
|
252
|
+
} catch {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`Blueprint file at ${r} is not a valid JSON file`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const n = h.dirname(r), l = new De(n);
|
|
258
|
+
return new Ae([
|
|
259
|
+
new Fe({
|
|
260
|
+
"blueprint.json": i
|
|
261
|
+
}),
|
|
262
|
+
/**
|
|
263
|
+
* Wrap the NodeJS filesystem to prevent access to local files
|
|
264
|
+
* unless the user explicitly allowed it.
|
|
265
|
+
*/
|
|
266
|
+
{
|
|
267
|
+
read(u) {
|
|
268
|
+
if (!t)
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Error: Blueprint contained tried to read a local file at path "${u}" (via a resource of type "bundled"). Playground restricts access to local resources by default as a security measure.
|
|
271
|
+
|
|
272
|
+
You can allow this Blueprint to read files from the same parent directory by explicitly adding the --blueprint-may-read-adjacent-files option to your command.`
|
|
273
|
+
);
|
|
274
|
+
return l.read(u);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
default:
|
|
280
|
+
throw new Error(
|
|
281
|
+
`Unsupported blueprint file extension: ${o}. Only .zip and .json files are supported.`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
class lt {
|
|
286
|
+
constructor(t, r) {
|
|
287
|
+
this.args = t, this.siteUrl = r.siteUrl, this.processIdSpaceLength = r.processIdSpaceLength, this.phpVersion = t.php, this.cliOutput = r.cliOutput;
|
|
288
|
+
}
|
|
289
|
+
getWorkerType() {
|
|
290
|
+
return "v2";
|
|
291
|
+
}
|
|
292
|
+
async bootAndSetUpInitialPlayground(t, r, s) {
|
|
293
|
+
const o = F(t);
|
|
294
|
+
await o.useFileLockManager(r);
|
|
295
|
+
const i = {
|
|
296
|
+
...this.args,
|
|
297
|
+
phpVersion: this.phpVersion,
|
|
298
|
+
siteUrl: this.siteUrl,
|
|
299
|
+
firstProcessId: 1,
|
|
300
|
+
processIdSpaceLength: this.processIdSpaceLength,
|
|
301
|
+
trace: this.args.verbosity === "debug",
|
|
302
|
+
blueprint: this.args.blueprint,
|
|
303
|
+
withIntl: this.args.intl,
|
|
304
|
+
// We do not enable Xdebug by default for the initial worker
|
|
305
|
+
// because we do not imagine users expect to hit breakpoints
|
|
306
|
+
// until Playground has fully booted.
|
|
307
|
+
// TODO: Consider supporting Xdebug for the initial worker via a dedicated flag.
|
|
308
|
+
withXdebug: !1,
|
|
309
|
+
xdebug: void 0,
|
|
310
|
+
nativeInternalDirPath: s,
|
|
311
|
+
mountsBeforeWpInstall: this.args["mount-before-install"] || [],
|
|
312
|
+
mountsAfterWpInstall: this.args.mount || []
|
|
313
|
+
};
|
|
314
|
+
return await o.bootAndSetUpInitialWorker(i), o;
|
|
315
|
+
}
|
|
316
|
+
async bootPlayground({
|
|
317
|
+
worker: t,
|
|
318
|
+
fileLockManagerPort: r,
|
|
319
|
+
firstProcessId: s,
|
|
320
|
+
nativeInternalDirPath: o
|
|
321
|
+
}) {
|
|
322
|
+
const i = F(t.phpPort);
|
|
323
|
+
await i.useFileLockManager(r);
|
|
324
|
+
const n = {
|
|
325
|
+
...this.args,
|
|
326
|
+
phpVersion: this.phpVersion,
|
|
327
|
+
siteUrl: this.siteUrl,
|
|
328
|
+
firstProcessId: s,
|
|
329
|
+
processIdSpaceLength: this.processIdSpaceLength,
|
|
330
|
+
trace: this.args.verbosity === "debug",
|
|
331
|
+
withIntl: this.args.intl,
|
|
332
|
+
withXdebug: !!this.args.xdebug,
|
|
333
|
+
nativeInternalDirPath: o,
|
|
334
|
+
mountsBeforeWpInstall: this.args["mount-before-install"] || [],
|
|
335
|
+
mountsAfterWpInstall: this.args.mount || []
|
|
336
|
+
};
|
|
337
|
+
return await i.bootWorker(n), i;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const Q = h.join(O.homedir(), ".wordpress-playground");
|
|
341
|
+
async function ut(e) {
|
|
342
|
+
return await pe(
|
|
343
|
+
"https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/develop.zip",
|
|
344
|
+
"sqlite.zip",
|
|
345
|
+
e
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
async function pe(e, t, r) {
|
|
349
|
+
const s = h.join(Q, t);
|
|
350
|
+
return B.existsSync(s) || (B.ensureDirSync(Q), await dt(e, s, r)), ce(s);
|
|
351
|
+
}
|
|
352
|
+
async function dt(e, t, r) {
|
|
353
|
+
const o = (await r.monitorFetch(fetch(e))).body.getReader(), i = `${t}.partial`, n = B.createWriteStream(i);
|
|
354
|
+
for (; ; ) {
|
|
355
|
+
const { done: l, value: u } = await o.read();
|
|
356
|
+
if (u && n.write(u), l)
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
n.close(), n.closed || await new Promise((l, u) => {
|
|
360
|
+
n.on("finish", () => {
|
|
361
|
+
B.renameSync(i, t), l(null);
|
|
362
|
+
}), n.on("error", (b) => {
|
|
363
|
+
B.removeSync(i), u(b);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function ce(e, t) {
|
|
368
|
+
return new File([B.readFileSync(e)], A(e));
|
|
369
|
+
}
|
|
370
|
+
class pt {
|
|
371
|
+
constructor(t, r) {
|
|
372
|
+
this.args = t, this.siteUrl = r.siteUrl, this.processIdSpaceLength = r.processIdSpaceLength, this.cliOutput = r.cliOutput;
|
|
373
|
+
}
|
|
374
|
+
getWorkerType() {
|
|
375
|
+
return "v1";
|
|
376
|
+
}
|
|
377
|
+
async bootAndSetUpInitialPlayground(t, r, s) {
|
|
378
|
+
let o, i, n;
|
|
379
|
+
const l = new Oe();
|
|
380
|
+
if (this.args.wordpressInstallMode === "download-and-install") {
|
|
381
|
+
let E = !1;
|
|
382
|
+
l.addEventListener("progress", (k) => {
|
|
383
|
+
if (E)
|
|
384
|
+
return;
|
|
385
|
+
const { loaded: W, total: Z } = k.detail, C = Math.floor(
|
|
386
|
+
Math.min(100, 100 * W / Z)
|
|
387
|
+
);
|
|
388
|
+
E = C === 100, this.cliOutput.updateProgress(
|
|
389
|
+
"Downloading WordPress",
|
|
390
|
+
C
|
|
391
|
+
);
|
|
392
|
+
}), o = await Ve(this.args.wp), n = h.join(
|
|
393
|
+
Q,
|
|
394
|
+
`prebuilt-wp-content-for-wp-${o.version}.zip`
|
|
395
|
+
), i = p.existsSync(n) ? ce(n) : await pe(
|
|
396
|
+
o.releaseUrl,
|
|
397
|
+
`${o.version}.zip`,
|
|
398
|
+
l
|
|
399
|
+
), g.debug(
|
|
400
|
+
`Resolved WordPress release URL: ${o?.releaseUrl}`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
let u;
|
|
404
|
+
this.args.skipSqliteSetup ? (g.debug("Skipping SQLite integration plugin setup..."), u = void 0) : (this.cliOutput.updateProgress("Preparing SQLite database"), u = await ut(l));
|
|
405
|
+
const b = this.args.followSymlinks === !0, v = this.args.experimentalTrace === !0, a = this.args["mount-before-install"] || [], f = this.args.mount || [], c = F(t);
|
|
406
|
+
await c.isConnected(), this.cliOutput.updateProgress("Booting WordPress");
|
|
407
|
+
const S = await re(
|
|
408
|
+
this.getEffectiveBlueprint()
|
|
409
|
+
);
|
|
410
|
+
return await c.useFileLockManager(r), await c.bootAndSetUpInitialWorker({
|
|
411
|
+
phpVersion: S.phpVersion,
|
|
412
|
+
wpVersion: S.wpVersion,
|
|
413
|
+
siteUrl: this.siteUrl,
|
|
414
|
+
mountsBeforeWpInstall: a,
|
|
415
|
+
mountsAfterWpInstall: f,
|
|
416
|
+
wordpressInstallMode: this.args.wordpressInstallMode || "download-and-install",
|
|
417
|
+
wordPressZip: i && await i.arrayBuffer(),
|
|
418
|
+
sqliteIntegrationPluginZip: await u?.arrayBuffer(),
|
|
419
|
+
firstProcessId: 0,
|
|
420
|
+
processIdSpaceLength: this.processIdSpaceLength,
|
|
421
|
+
followSymlinks: b,
|
|
422
|
+
trace: v,
|
|
423
|
+
internalCookieStore: this.args.internalCookieStore,
|
|
424
|
+
withIntl: this.args.intl,
|
|
425
|
+
// We do not enable Xdebug by default for the initial worker
|
|
426
|
+
// because we do not imagine users expect to hit breakpoints
|
|
427
|
+
// until Playground has fully booted.
|
|
428
|
+
// TODO: Consider supporting Xdebug for the initial worker via a dedicated flag.
|
|
429
|
+
withXdebug: !1,
|
|
430
|
+
nativeInternalDirPath: s
|
|
431
|
+
}), n && !this.args["mount-before-install"] && !p.existsSync(n) && (this.cliOutput.updateProgress("Caching WordPress for next boot"), p.writeFileSync(
|
|
432
|
+
n,
|
|
433
|
+
await Ie(c, "/wordpress")
|
|
434
|
+
)), c;
|
|
435
|
+
}
|
|
436
|
+
async bootPlayground({
|
|
437
|
+
worker: t,
|
|
438
|
+
fileLockManagerPort: r,
|
|
439
|
+
firstProcessId: s,
|
|
440
|
+
nativeInternalDirPath: o
|
|
441
|
+
}) {
|
|
442
|
+
const i = F(
|
|
443
|
+
t.phpPort
|
|
444
|
+
);
|
|
445
|
+
await i.isConnected();
|
|
446
|
+
const n = await re(
|
|
447
|
+
this.getEffectiveBlueprint()
|
|
448
|
+
);
|
|
449
|
+
return await i.useFileLockManager(r), await i.bootWorker({
|
|
450
|
+
phpVersion: n.phpVersion,
|
|
451
|
+
siteUrl: this.siteUrl,
|
|
452
|
+
mountsBeforeWpInstall: this.args["mount-before-install"] || [],
|
|
453
|
+
mountsAfterWpInstall: this.args.mount || [],
|
|
454
|
+
firstProcessId: s,
|
|
455
|
+
processIdSpaceLength: this.processIdSpaceLength,
|
|
456
|
+
followSymlinks: this.args.followSymlinks === !0,
|
|
457
|
+
trace: this.args.experimentalTrace === !0,
|
|
458
|
+
// @TODO: Move this to the request handler or else every worker
|
|
459
|
+
// will have a separate cookie store.
|
|
460
|
+
internalCookieStore: this.args.internalCookieStore,
|
|
461
|
+
withIntl: this.args.intl,
|
|
462
|
+
withXdebug: !!this.args.xdebug,
|
|
463
|
+
nativeInternalDirPath: o
|
|
464
|
+
}), await i.isReady(), i;
|
|
465
|
+
}
|
|
466
|
+
async compileInputBlueprint(t) {
|
|
467
|
+
const r = this.getEffectiveBlueprint(), s = new He();
|
|
468
|
+
let o = "", i = !1;
|
|
469
|
+
return s.addEventListener("progress", (n) => {
|
|
470
|
+
if (i)
|
|
471
|
+
return;
|
|
472
|
+
i = n.detail.progress === 100;
|
|
473
|
+
const l = Math.floor(n.detail.progress);
|
|
474
|
+
o = n.detail.caption || o || "Running Blueprint", this.cliOutput.updateProgress(o.trim(), l);
|
|
475
|
+
}), await Se(r, {
|
|
476
|
+
progress: s,
|
|
477
|
+
additionalSteps: t
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
getEffectiveBlueprint() {
|
|
481
|
+
const t = this.args.blueprint;
|
|
482
|
+
return xe(t) ? t : {
|
|
483
|
+
login: this.args.login,
|
|
484
|
+
...t || {},
|
|
485
|
+
preferredVersions: {
|
|
486
|
+
php: this.args.php ?? t?.preferredVersions?.php ?? U,
|
|
487
|
+
wp: this.args.wp ?? t?.preferredVersions?.wp ?? "latest",
|
|
488
|
+
...t?.preferredVersions || {}
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
async function ct(e, t = !0) {
|
|
494
|
+
const s = `${h.basename(process.argv0)}${e}${process.pid}-`, o = await _e({
|
|
495
|
+
prefix: s,
|
|
496
|
+
/*
|
|
497
|
+
* Allow recursive cleanup on process exit.
|
|
498
|
+
*
|
|
499
|
+
* NOTE: I worried about whether this cleanup would follow symlinks
|
|
500
|
+
* and delete target files instead of unlinking the symlink,
|
|
501
|
+
* but this feature uses rimraf under the hood which respects symlinks:
|
|
502
|
+
* https://github.com/raszi/node-tmp/blob/3d2fe387f3f91b13830b9182faa02c3231ea8258/lib/tmp.js#L318
|
|
503
|
+
*/
|
|
504
|
+
unsafeCleanup: !0
|
|
505
|
+
});
|
|
506
|
+
return t && qe(), o;
|
|
507
|
+
}
|
|
508
|
+
async function ht(e, t, r) {
|
|
509
|
+
const o = (await mt(
|
|
510
|
+
e,
|
|
511
|
+
t,
|
|
512
|
+
r
|
|
513
|
+
)).map(
|
|
514
|
+
(i) => new Promise((n) => {
|
|
515
|
+
p.rm(i, { recursive: !0 }, (l) => {
|
|
516
|
+
l ? g.warn(
|
|
517
|
+
`Failed to delete stale Playground temp dir: ${i}`,
|
|
518
|
+
l
|
|
519
|
+
) : g.info(
|
|
520
|
+
`Deleted stale Playground temp dir: ${i}`
|
|
521
|
+
), n();
|
|
522
|
+
});
|
|
523
|
+
})
|
|
524
|
+
);
|
|
525
|
+
await Promise.all(o);
|
|
526
|
+
}
|
|
527
|
+
async function mt(e, t, r) {
|
|
528
|
+
try {
|
|
529
|
+
const s = p.readdirSync(r).map((i) => h.join(r, i)), o = [];
|
|
530
|
+
for (const i of s)
|
|
531
|
+
await ft(
|
|
532
|
+
e,
|
|
533
|
+
t,
|
|
534
|
+
i
|
|
535
|
+
) && o.push(i);
|
|
536
|
+
return o;
|
|
537
|
+
} catch (s) {
|
|
538
|
+
return g.warn(`Failed to find stale Playground temp dirs: ${s}`), [];
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async function ft(e, t, r) {
|
|
542
|
+
if (!p.lstatSync(r).isDirectory())
|
|
543
|
+
return !1;
|
|
544
|
+
const o = h.basename(r);
|
|
545
|
+
if (!o.includes(e))
|
|
546
|
+
return !1;
|
|
547
|
+
const i = o.match(
|
|
548
|
+
new RegExp(`^(.+)${e}(\\d+)-`)
|
|
549
|
+
);
|
|
550
|
+
if (!i)
|
|
551
|
+
return !1;
|
|
552
|
+
const n = {
|
|
553
|
+
executableName: i[1],
|
|
554
|
+
pid: i[2]
|
|
555
|
+
};
|
|
556
|
+
if (await wt(n.pid, n.executableName))
|
|
557
|
+
return !1;
|
|
558
|
+
const l = Date.now() - t;
|
|
559
|
+
return p.statSync(r).mtime.getTime() < l;
|
|
560
|
+
}
|
|
561
|
+
async function wt(e, t) {
|
|
562
|
+
const [r] = await new Promise(
|
|
563
|
+
(s, o) => {
|
|
564
|
+
Ye.list(
|
|
565
|
+
{
|
|
566
|
+
pid: e,
|
|
567
|
+
name: t,
|
|
568
|
+
// Remove path from executable name in the results.
|
|
569
|
+
clean: !0
|
|
570
|
+
},
|
|
571
|
+
(i, n) => {
|
|
572
|
+
i ? o(i) : s(n);
|
|
573
|
+
}
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
return !!r && r.pid === e && r.command === t;
|
|
578
|
+
}
|
|
579
|
+
function gt(e) {
|
|
580
|
+
return process.env.CI === "true" || process.env.CI === "1" || process.env.GITHUB_ACTIONS === "true" || process.env.GITHUB_ACTIONS === "1" || (process.env.TERM || "").toLowerCase() === "dumb" ? !1 : e ? !!e.isTTY : process.stdout.isTTY;
|
|
581
|
+
}
|
|
582
|
+
class yt {
|
|
583
|
+
constructor(t) {
|
|
584
|
+
this.lastProgressLine = "", this.progressActive = !1, this.verbosity = t.verbosity, this.writeStream = t.writeStream || process.stdout;
|
|
585
|
+
}
|
|
586
|
+
get isTTY() {
|
|
587
|
+
return !!this.writeStream.isTTY;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Determines if progress updates should be rendered.
|
|
591
|
+
*
|
|
592
|
+
* Returns false when output is piped, redirected, or in CI environments.
|
|
593
|
+
* This prevents progress spam in logs - users only see the final outcome.
|
|
594
|
+
*/
|
|
595
|
+
get shouldRender() {
|
|
596
|
+
return gt(this.writeStream);
|
|
597
|
+
}
|
|
598
|
+
get isQuiet() {
|
|
599
|
+
return this.verbosity === "quiet";
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* ANSI formatting helpers.
|
|
603
|
+
*
|
|
604
|
+
* These only apply color codes when outputting to a terminal (TTY).
|
|
605
|
+
* When piped to files or non-TTY streams, they return plain text to
|
|
606
|
+
* avoid polluting logs with escape sequences.
|
|
607
|
+
*/
|
|
608
|
+
bold(t) {
|
|
609
|
+
return this.isTTY ? `\x1B[1m${t}\x1B[0m` : t;
|
|
610
|
+
}
|
|
611
|
+
dim(t) {
|
|
612
|
+
return this.isTTY ? `\x1B[2m${t}\x1B[0m` : t;
|
|
613
|
+
}
|
|
614
|
+
green(t) {
|
|
615
|
+
return this.isTTY ? `\x1B[32m${t}\x1B[0m` : t;
|
|
616
|
+
}
|
|
617
|
+
cyan(t) {
|
|
618
|
+
return this.isTTY ? `\x1B[36m${t}\x1B[0m` : t;
|
|
619
|
+
}
|
|
620
|
+
yellow(t) {
|
|
621
|
+
return this.isTTY ? `\x1B[33m${t}\x1B[0m` : t;
|
|
622
|
+
}
|
|
623
|
+
red(t) {
|
|
624
|
+
return this.isTTY ? `\x1B[31m${t}\x1B[0m` : t;
|
|
625
|
+
}
|
|
626
|
+
printBanner() {
|
|
627
|
+
if (this.isQuiet) return;
|
|
628
|
+
const t = this.bold("WordPress Playground CLI");
|
|
629
|
+
this.writeStream.write(`
|
|
630
|
+
${t}
|
|
631
|
+
|
|
632
|
+
`);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Prints the configuration summary before starting the server.
|
|
636
|
+
*
|
|
637
|
+
* Displays PHP/WordPress versions, enabled extensions, all mounts
|
|
638
|
+
* (with auto-mounts labeled), and any loaded blueprint. This gives
|
|
639
|
+
* users a clear view of what's configured before the server boots.
|
|
640
|
+
*/
|
|
641
|
+
printConfig(t) {
|
|
642
|
+
if (this.isQuiet) return;
|
|
643
|
+
const r = [];
|
|
644
|
+
r.push(
|
|
645
|
+
`${this.dim("PHP")} ${this.cyan(t.phpVersion)} ${this.dim("WordPress")} ${this.cyan(t.wpVersion)}`
|
|
646
|
+
);
|
|
647
|
+
const s = [];
|
|
648
|
+
if (t.intl && s.push("intl"), t.xdebug && s.push(this.yellow("xdebug")), s.length > 0 && r.push(`${this.dim("Extensions")} ${s.join(", ")}`), t.mounts.length > 0)
|
|
649
|
+
for (const o of t.mounts) {
|
|
650
|
+
const i = o.autoMounted ? ` ${this.dim("(auto-mount)")}` : "";
|
|
651
|
+
r.push(
|
|
652
|
+
`${this.dim("Mount")} ${o.hostPath} ${this.dim("→")} ${o.vfsPath}${i}`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
t.blueprint && r.push(`${this.dim("Blueprint")} ${t.blueprint}`), this.writeStream.write(r.join(`
|
|
656
|
+
`) + `
|
|
657
|
+
|
|
658
|
+
`);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Starts a progress indicator that updates in-place.
|
|
662
|
+
*
|
|
663
|
+
* Subsequent updateProgress() calls rewrite the same line in TTY mode.
|
|
664
|
+
* When output is piped or redirected, progress is completely skipped.
|
|
665
|
+
*/
|
|
666
|
+
startProgress(t) {
|
|
667
|
+
this.isQuiet || this.shouldRender && (this.progressActive = !0, this.updateProgress(t));
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Updates the current progress message and optional percentage.
|
|
671
|
+
*
|
|
672
|
+
* Rewrites the current line using ANSI cursor control in TTY mode.
|
|
673
|
+
* Identical messages are skipped to prevent flickering. When piped,
|
|
674
|
+
* this method does nothing (early return via shouldRender check).
|
|
675
|
+
*/
|
|
676
|
+
updateProgress(t, r) {
|
|
677
|
+
if (this.isQuiet || !this.shouldRender) return;
|
|
678
|
+
this.progressActive || (this.progressActive = !0);
|
|
679
|
+
let s = `${t}`;
|
|
680
|
+
r !== void 0 && (s = `${t} ${this.dim(`${r}%`)}`), s !== this.lastProgressLine && (this.lastProgressLine = s, this.isTTY ? (this.writeStream.cursorTo(0), this.writeStream.write(s), this.writeStream.clearLine(1)) : this.writeStream.write(`${s}
|
|
681
|
+
`));
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Completes the progress indicator and moves to a new line.
|
|
685
|
+
*
|
|
686
|
+
* Optionally displays a final message before finishing. In TTY mode,
|
|
687
|
+
* this ensures the cursor moves to the next line after the progress.
|
|
688
|
+
*/
|
|
689
|
+
finishProgress(t) {
|
|
690
|
+
this.isQuiet || this.shouldRender && (t && (this.isTTY ? (this.writeStream.cursorTo(0), this.writeStream.write(`${t}`), this.writeStream.clearLine(1)) : this.writeStream.write(`${t}
|
|
691
|
+
`)), this.isTTY && this.writeStream.write(`
|
|
692
|
+
`), this.progressActive = !1, this.lastProgressLine = "");
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Prints a status message, interrupting any active progress.
|
|
696
|
+
*
|
|
697
|
+
* Unlike progress updates, status messages are always printed on their
|
|
698
|
+
* own line. Any active progress indicator is cleared before the message.
|
|
699
|
+
*/
|
|
700
|
+
printStatus(t) {
|
|
701
|
+
this.isQuiet || (this.progressActive && this.isTTY && (this.writeStream.cursorTo(0), this.writeStream.clearLine(0)), this.writeStream.write(`${t}
|
|
702
|
+
`), this.progressActive = !1, this.lastProgressLine = "");
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Prints an error message.
|
|
706
|
+
*
|
|
707
|
+
* Errors are always shown, even in quiet mode, and interrupt any
|
|
708
|
+
* active progress display to ensure visibility.
|
|
709
|
+
*/
|
|
710
|
+
printError(t) {
|
|
711
|
+
this.progressActive && this.isTTY && (this.writeStream.cursorTo(0), this.writeStream.clearLine(0), this.progressActive = !1), this.writeStream.write(`${this.red("Error:")} ${t}
|
|
712
|
+
`);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Prints the final "server ready" message with the URL.
|
|
716
|
+
*
|
|
717
|
+
* Note: The exact wording "WordPress is running on" is checked by
|
|
718
|
+
* CI tests, so changes to this string will break test assertions.
|
|
719
|
+
*/
|
|
720
|
+
printReady(t, r) {
|
|
721
|
+
if (this.isQuiet) return;
|
|
722
|
+
const s = r === 1 ? "worker" : "workers";
|
|
723
|
+
this.writeStream.write(
|
|
724
|
+
`
|
|
725
|
+
${this.green("Ready!")} WordPress is running on ${this.bold(t)} ${this.dim(`(${r} ${s})`)}
|
|
726
|
+
|
|
727
|
+
`
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
printWarning(t) {
|
|
731
|
+
this.isQuiet || this.writeStream.write(`${this.yellow("Warning:")} ${t}
|
|
732
|
+
`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
const he = {
|
|
736
|
+
Quiet: { name: "quiet", severity: j.Fatal },
|
|
737
|
+
Normal: { name: "normal", severity: j.Info },
|
|
738
|
+
Debug: { name: "debug", severity: j.Debug }
|
|
739
|
+
};
|
|
740
|
+
async function Jt(e) {
|
|
741
|
+
try {
|
|
742
|
+
const t = {
|
|
743
|
+
"site-url": {
|
|
744
|
+
describe: "Site URL to use for WordPress. Defaults to http://127.0.0.1:{port}",
|
|
745
|
+
type: "string"
|
|
746
|
+
},
|
|
747
|
+
php: {
|
|
748
|
+
describe: "PHP version to use.",
|
|
749
|
+
type: "string",
|
|
750
|
+
default: U,
|
|
751
|
+
choices: te
|
|
752
|
+
},
|
|
753
|
+
wp: {
|
|
754
|
+
describe: "WordPress version to use.",
|
|
755
|
+
type: "string",
|
|
756
|
+
default: "latest"
|
|
757
|
+
},
|
|
758
|
+
// @TODO: Support read-only mounts, e.g. via WORKERFS, a custom
|
|
759
|
+
// ReadOnlyNODEFS, or by copying the files into MEMFS
|
|
760
|
+
mount: {
|
|
761
|
+
describe: "Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path",
|
|
762
|
+
type: "array",
|
|
763
|
+
string: !0,
|
|
764
|
+
coerce: _
|
|
765
|
+
},
|
|
766
|
+
"mount-before-install": {
|
|
767
|
+
describe: "Mount a directory to the PHP runtime before WordPress installation (can be used multiple times). Format: /host/path:/vfs/path",
|
|
768
|
+
type: "array",
|
|
769
|
+
string: !0,
|
|
770
|
+
coerce: _
|
|
771
|
+
},
|
|
772
|
+
"mount-dir": {
|
|
773
|
+
describe: 'Mount a directory to the PHP runtime (can be used multiple times). Format: "/host/path" "/vfs/path"',
|
|
774
|
+
type: "array",
|
|
775
|
+
nargs: 2,
|
|
776
|
+
array: !0,
|
|
777
|
+
coerce: se
|
|
778
|
+
},
|
|
779
|
+
"mount-dir-before-install": {
|
|
780
|
+
describe: 'Mount a directory before WordPress installation (can be used multiple times). Format: "/host/path" "/vfs/path"',
|
|
781
|
+
type: "string",
|
|
782
|
+
nargs: 2,
|
|
783
|
+
array: !0,
|
|
784
|
+
coerce: se
|
|
785
|
+
},
|
|
786
|
+
login: {
|
|
787
|
+
describe: "Should log the user in",
|
|
788
|
+
type: "boolean",
|
|
789
|
+
default: !1
|
|
790
|
+
},
|
|
791
|
+
blueprint: {
|
|
792
|
+
describe: "Blueprint to execute.",
|
|
793
|
+
type: "string"
|
|
794
|
+
},
|
|
795
|
+
"blueprint-may-read-adjacent-files": {
|
|
796
|
+
describe: 'Consent flag: Allow "bundled" resources in a local blueprint to read files in the same directory as the blueprint file.',
|
|
797
|
+
type: "boolean",
|
|
798
|
+
default: !1
|
|
799
|
+
},
|
|
800
|
+
"wordpress-install-mode": {
|
|
801
|
+
describe: "Control how Playground prepares WordPress before booting.",
|
|
802
|
+
type: "string",
|
|
803
|
+
default: "download-and-install",
|
|
804
|
+
choices: [
|
|
805
|
+
"download-and-install",
|
|
806
|
+
"install-from-existing-files",
|
|
807
|
+
"install-from-existing-files-if-needed",
|
|
808
|
+
"do-not-attempt-installing"
|
|
809
|
+
]
|
|
810
|
+
},
|
|
811
|
+
"skip-wordpress-install": {
|
|
812
|
+
describe: "[Deprecated] Use --wordpress-install-mode instead.",
|
|
813
|
+
type: "boolean",
|
|
814
|
+
hidden: !0
|
|
815
|
+
},
|
|
816
|
+
"skip-sqlite-setup": {
|
|
817
|
+
describe: "Skip the SQLite integration plugin setup to allow the WordPress site to use MySQL.",
|
|
818
|
+
type: "boolean",
|
|
819
|
+
default: !1
|
|
820
|
+
},
|
|
821
|
+
// Hidden - Deprecated in favor of verbosity
|
|
822
|
+
quiet: {
|
|
823
|
+
describe: "Do not output logs and progress messages.",
|
|
824
|
+
type: "boolean",
|
|
825
|
+
default: !1,
|
|
826
|
+
hidden: !0
|
|
827
|
+
},
|
|
828
|
+
verbosity: {
|
|
829
|
+
describe: "Output logs and progress messages.",
|
|
830
|
+
type: "string",
|
|
831
|
+
choices: Object.values(he).map(
|
|
832
|
+
(a) => a.name
|
|
833
|
+
),
|
|
834
|
+
default: "normal"
|
|
835
|
+
},
|
|
836
|
+
debug: {
|
|
837
|
+
describe: "Print PHP error log content if an error occurs during Playground boot.",
|
|
838
|
+
type: "boolean",
|
|
839
|
+
default: !1,
|
|
840
|
+
// Hide this deprecated option. Use verbosity=debug instead.
|
|
841
|
+
hidden: !0
|
|
842
|
+
},
|
|
843
|
+
"auto-mount": {
|
|
844
|
+
describe: "Automatically mount the specified directory. If no path is provided, mount the current working directory. You can mount a WordPress directory, a plugin directory, a theme directory, a wp-content directory, or any directory containing PHP and HTML files.",
|
|
845
|
+
type: "string"
|
|
846
|
+
},
|
|
847
|
+
"follow-symlinks": {
|
|
848
|
+
describe: `Allow Playground to follow symlinks by automatically mounting symlinked directories and files encountered in mounted directories.
|
|
849
|
+
Warning: Following symlinks will expose files outside mounted directories to Playground and could be a security risk.`,
|
|
850
|
+
type: "boolean",
|
|
851
|
+
default: !1
|
|
852
|
+
},
|
|
853
|
+
"experimental-trace": {
|
|
854
|
+
describe: "Print detailed messages about system behavior to the console. Useful for troubleshooting.",
|
|
855
|
+
type: "boolean",
|
|
856
|
+
default: !1,
|
|
857
|
+
// Hide this option because we want to replace with a more general log-level flag.
|
|
858
|
+
hidden: !0
|
|
859
|
+
},
|
|
860
|
+
"internal-cookie-store": {
|
|
861
|
+
describe: "Enable internal cookie handling. When enabled, Playground will manage cookies internally using an HttpCookieStore that persists cookies across requests. When disabled, cookies are handled externally (e.g., by a browser in Node.js environments).",
|
|
862
|
+
type: "boolean",
|
|
863
|
+
default: !1
|
|
864
|
+
},
|
|
865
|
+
intl: {
|
|
866
|
+
describe: "Enable Intl.",
|
|
867
|
+
type: "boolean",
|
|
868
|
+
default: !0
|
|
869
|
+
},
|
|
870
|
+
xdebug: {
|
|
871
|
+
describe: "Enable Xdebug.",
|
|
872
|
+
type: "boolean",
|
|
873
|
+
default: !1
|
|
874
|
+
},
|
|
875
|
+
"experimental-unsafe-ide-integration": {
|
|
876
|
+
describe: "Enable experimental IDE development tools. This option edits IDE config files to set Xdebug path mappings and web server details. CAUTION: If there are bugs, this feature may break your IDE config files. Please consider backing up your IDE configs before using this feature.",
|
|
877
|
+
type: "string",
|
|
878
|
+
// The empty value means the option is enabled for all
|
|
879
|
+
// supported IDEs and, if needed, will create the relevant
|
|
880
|
+
// config file for each.
|
|
881
|
+
choices: ["", "vscode", "phpstorm"],
|
|
882
|
+
coerce: (a) => a === "" ? ["vscode", "phpstorm"] : [a]
|
|
883
|
+
},
|
|
884
|
+
"experimental-blueprints-v2-runner": {
|
|
885
|
+
describe: "Use the experimental Blueprint V2 runner.",
|
|
886
|
+
type: "boolean",
|
|
887
|
+
default: !1,
|
|
888
|
+
// Remove the "hidden" flag once Blueprint V2 is fully supported
|
|
889
|
+
hidden: !0
|
|
890
|
+
},
|
|
891
|
+
mode: {
|
|
892
|
+
describe: "Blueprints v2 runner mode to use. This option is required when using the --experimental-blueprints-v2-runner flag with a blueprint.",
|
|
893
|
+
type: "string",
|
|
894
|
+
choices: ["create-new-site", "apply-to-existing-site"],
|
|
895
|
+
// Remove the "hidden" flag once Blueprint V2 is fully supported
|
|
896
|
+
hidden: !0
|
|
897
|
+
}
|
|
898
|
+
}, r = {
|
|
899
|
+
port: {
|
|
900
|
+
describe: "Port to listen on when serving.",
|
|
901
|
+
type: "number",
|
|
902
|
+
default: 9400
|
|
903
|
+
},
|
|
904
|
+
"experimental-multi-worker": {
|
|
905
|
+
describe: "Enable experimental multi-worker support which requires a /wordpress directory backed by a real filesystem. Pass a positive number to specify the number of workers to use. Otherwise, default to the number of CPUs minus 1.",
|
|
906
|
+
type: "number",
|
|
907
|
+
coerce: (a) => a ?? Ce().length - 1
|
|
908
|
+
},
|
|
909
|
+
"experimental-devtools": {
|
|
910
|
+
describe: "Enable experimental browser development tools.",
|
|
911
|
+
type: "boolean"
|
|
912
|
+
}
|
|
913
|
+
}, s = {
|
|
914
|
+
path: {
|
|
915
|
+
describe: "Path to the project directory. Playground will auto-detect if this is a plugin, theme, wp-content, or WordPress directory.",
|
|
916
|
+
type: "string",
|
|
917
|
+
default: process.cwd()
|
|
918
|
+
},
|
|
919
|
+
php: {
|
|
920
|
+
describe: "PHP version to use.",
|
|
921
|
+
type: "string",
|
|
922
|
+
default: U,
|
|
923
|
+
choices: te
|
|
924
|
+
},
|
|
925
|
+
wp: {
|
|
926
|
+
describe: "WordPress version to use.",
|
|
927
|
+
type: "string",
|
|
928
|
+
default: "latest"
|
|
929
|
+
},
|
|
930
|
+
port: {
|
|
931
|
+
describe: "Port to listen on.",
|
|
932
|
+
type: "number",
|
|
933
|
+
default: 9400
|
|
934
|
+
},
|
|
935
|
+
blueprint: {
|
|
936
|
+
describe: "Path to a Blueprint JSON file to execute on startup.",
|
|
937
|
+
type: "string"
|
|
938
|
+
},
|
|
939
|
+
login: {
|
|
940
|
+
describe: "Auto-login as the admin user.",
|
|
941
|
+
type: "boolean",
|
|
942
|
+
default: !0
|
|
943
|
+
},
|
|
944
|
+
xdebug: {
|
|
945
|
+
describe: "Enable Xdebug for debugging.",
|
|
946
|
+
type: "boolean",
|
|
947
|
+
default: !1
|
|
948
|
+
},
|
|
949
|
+
"experimental-unsafe-ide-integration": t["experimental-unsafe-ide-integration"],
|
|
950
|
+
"skip-browser": {
|
|
951
|
+
describe: "Do not open the site in your default browser on startup.",
|
|
952
|
+
type: "boolean",
|
|
953
|
+
default: !1
|
|
954
|
+
},
|
|
955
|
+
quiet: {
|
|
956
|
+
describe: "Suppress non-essential output.",
|
|
957
|
+
type: "boolean",
|
|
958
|
+
default: !1
|
|
959
|
+
},
|
|
960
|
+
// Advanced options for power users who need more control
|
|
961
|
+
"site-url": {
|
|
962
|
+
describe: "Override the site URL. By default, derived from the port (http://127.0.0.1:<port>).",
|
|
963
|
+
type: "string"
|
|
964
|
+
},
|
|
965
|
+
mount: {
|
|
966
|
+
describe: "Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path. Use this for additional mounts beyond auto-detection.",
|
|
967
|
+
type: "array",
|
|
968
|
+
string: !0,
|
|
969
|
+
coerce: _
|
|
970
|
+
},
|
|
971
|
+
reset: {
|
|
972
|
+
describe: "Deletes the stored site directory and starts a new site from scratch.",
|
|
973
|
+
type: "boolean",
|
|
974
|
+
default: !1
|
|
975
|
+
},
|
|
976
|
+
"no-auto-mount": {
|
|
977
|
+
describe: "Disable automatic project type detection. Use --mount to manually specify mounts instead.",
|
|
978
|
+
type: "boolean",
|
|
979
|
+
default: !1
|
|
980
|
+
}
|
|
981
|
+
}, o = {
|
|
982
|
+
outfile: {
|
|
983
|
+
describe: "When building, write to this output file.",
|
|
984
|
+
type: "string",
|
|
985
|
+
default: "wordpress.zip"
|
|
986
|
+
}
|
|
987
|
+
}, i = Re(e).usage("Usage: wp-playground <command> [options]").command(
|
|
988
|
+
"start",
|
|
989
|
+
"Start a local WordPress server with automatic project detection (recommended)",
|
|
990
|
+
(a) => a.usage(
|
|
991
|
+
`Usage: wp-playground start [options]
|
|
992
|
+
|
|
993
|
+
The easiest way to run WordPress locally. Automatically detects
|
|
994
|
+
if your directory contains a plugin, theme, wp-content, or
|
|
995
|
+
WordPress installation and configures everything for you.
|
|
996
|
+
|
|
997
|
+
Examples:
|
|
998
|
+
wp-playground start # Start in current directory
|
|
999
|
+
wp-playground start --path=./my-plugin # Start with a specific path
|
|
1000
|
+
wp-playground start --wp=6.7 --php=8.3 # Use specific versions
|
|
1001
|
+
wp-playground start --skip-browser # Skip opening browser
|
|
1002
|
+
wp-playground start --no-auto-mount # Disable auto-detection`
|
|
1003
|
+
).options(s)
|
|
1004
|
+
).command(
|
|
1005
|
+
"server",
|
|
1006
|
+
"Start a local WordPress server (advanced, low-level)",
|
|
1007
|
+
(a) => a.options({
|
|
1008
|
+
...t,
|
|
1009
|
+
...r
|
|
1010
|
+
})
|
|
1011
|
+
).command(
|
|
1012
|
+
"run-blueprint",
|
|
1013
|
+
"Execute a Blueprint without starting a server",
|
|
1014
|
+
(a) => a.options({ ...t })
|
|
1015
|
+
).command(
|
|
1016
|
+
"build-snapshot",
|
|
1017
|
+
"Build a ZIP snapshot of a WordPress site based on a Blueprint",
|
|
1018
|
+
(a) => a.options({
|
|
1019
|
+
...t,
|
|
1020
|
+
...o
|
|
1021
|
+
})
|
|
1022
|
+
).demandCommand(1, "Please specify a command").strictCommands().conflicts(
|
|
1023
|
+
"experimental-unsafe-ide-integration",
|
|
1024
|
+
"experimental-devtools"
|
|
1025
|
+
).showHelpOnFail(!1).fail((a, f, c) => {
|
|
1026
|
+
if (f)
|
|
1027
|
+
throw f;
|
|
1028
|
+
a && a.includes("Please specify a command") && (c.showHelp(), console.error(`
|
|
1029
|
+
` + a), process.exit(1)), console.error(a), process.exit(1);
|
|
1030
|
+
}).strictOptions().check(async (a) => {
|
|
1031
|
+
if (a["skip-wordpress-install"] === !0 && (a["wordpress-install-mode"] = "do-not-attempt-installing", a.wordpressInstallMode = "do-not-attempt-installing"), a.wp !== void 0 && typeof a.wp == "string" && !nt(a.wp))
|
|
1032
|
+
try {
|
|
1033
|
+
new URL(a.wp);
|
|
1034
|
+
} catch {
|
|
1035
|
+
throw new Error(
|
|
1036
|
+
'Unrecognized WordPress version. Please use "latest", a URL, or a numeric version such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1"'
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
const f = a["site-url"];
|
|
1040
|
+
if (typeof f == "string" && f.trim() !== "")
|
|
1041
|
+
try {
|
|
1042
|
+
new URL(f);
|
|
1043
|
+
} catch {
|
|
1044
|
+
throw new Error(
|
|
1045
|
+
`Invalid site-url "${f}". Please provide a valid URL (e.g., http://localhost:8080 or https://example.com)`
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
if (a["auto-mount"]) {
|
|
1049
|
+
let c = !1;
|
|
1050
|
+
try {
|
|
1051
|
+
c = p.statSync(
|
|
1052
|
+
a["auto-mount"]
|
|
1053
|
+
).isDirectory();
|
|
1054
|
+
} catch {
|
|
1055
|
+
c = !1;
|
|
1056
|
+
}
|
|
1057
|
+
if (!c)
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
`The specified --auto-mount path is not a directory: '${a["auto-mount"]}'.`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
if (a["experimental-multi-worker"] !== void 0) {
|
|
1063
|
+
if (a._[0] !== "server")
|
|
1064
|
+
throw new Error(
|
|
1065
|
+
"The --experimental-multi-worker flag is only supported when running the server command."
|
|
1066
|
+
);
|
|
1067
|
+
if (a["experimental-multi-worker"] !== void 0 && typeof a["experimental-multi-worker"] == "number" && a["experimental-multi-worker"] <= 1)
|
|
1068
|
+
throw new Error(
|
|
1069
|
+
"The --experimental-multi-worker flag must be a positive integer greater than 1."
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
if (a["experimental-blueprints-v2-runner"] === !0) {
|
|
1073
|
+
if (a.mode !== void 0) {
|
|
1074
|
+
if (a["wordpress-install-mode"] !== void 0)
|
|
1075
|
+
throw new Error(
|
|
1076
|
+
"The --wordpress-install-mode option cannot be used with the --mode option. Use one or the other."
|
|
1077
|
+
);
|
|
1078
|
+
if ("skip-sqlite-setup" in a)
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
"The --skipSqliteSetup option is not supported in Blueprint V2 mode."
|
|
1081
|
+
);
|
|
1082
|
+
if (a["auto-mount"] !== void 0)
|
|
1083
|
+
throw new Error(
|
|
1084
|
+
"The --mode option cannot be used with --auto-mount because --auto-mount automatically sets the mode."
|
|
1085
|
+
);
|
|
1086
|
+
} else
|
|
1087
|
+
a["wordpress-install-mode"] === "do-not-attempt-installing" ? a.mode = "apply-to-existing-site" : a.mode = "create-new-site";
|
|
1088
|
+
const c = a.allow || [];
|
|
1089
|
+
a.followSymlinks === !0 && c.push("follow-symlinks"), a["blueprint-may-read-adjacent-files"] === !0 && c.push("read-local-fs"), a.allow = c;
|
|
1090
|
+
} else if (a.mode !== void 0)
|
|
1091
|
+
throw new Error(
|
|
1092
|
+
"The --mode option requires the --experimentalBlueprintsV2Runner flag."
|
|
1093
|
+
);
|
|
1094
|
+
return !0;
|
|
1095
|
+
});
|
|
1096
|
+
i.wrap(i.terminalWidth());
|
|
1097
|
+
const n = await i.argv, l = n._[0];
|
|
1098
|
+
["start", "run-blueprint", "server", "build-snapshot"].includes(
|
|
1099
|
+
l
|
|
1100
|
+
) || (i.showHelp(), process.exit(1));
|
|
1101
|
+
const u = {
|
|
1102
|
+
...n,
|
|
1103
|
+
command: l,
|
|
1104
|
+
mount: [
|
|
1105
|
+
...n.mount || [],
|
|
1106
|
+
...n["mount-dir"] || []
|
|
1107
|
+
],
|
|
1108
|
+
"mount-before-install": [
|
|
1109
|
+
...n["mount-before-install"] || [],
|
|
1110
|
+
...n["mount-dir-before-install"] || []
|
|
1111
|
+
]
|
|
1112
|
+
}, b = await St(u);
|
|
1113
|
+
b === void 0 && process.exit(0);
|
|
1114
|
+
const v = /* @__PURE__ */ (() => {
|
|
1115
|
+
let a;
|
|
1116
|
+
return async () => {
|
|
1117
|
+
a !== void 0 && (a = b[Symbol.asyncDispose]()), await a, process.exit(0);
|
|
1118
|
+
};
|
|
1119
|
+
})();
|
|
1120
|
+
process.on("SIGINT", v), process.on("SIGTERM", v);
|
|
1121
|
+
} catch (t) {
|
|
1122
|
+
if (console.error(t), !(t instanceof Error))
|
|
1123
|
+
throw t;
|
|
1124
|
+
if (process.argv.includes("--debug"))
|
|
1125
|
+
ye(t);
|
|
1126
|
+
else {
|
|
1127
|
+
const s = [];
|
|
1128
|
+
let o = t;
|
|
1129
|
+
do
|
|
1130
|
+
s.push(o.message), o = o.cause;
|
|
1131
|
+
while (o instanceof Error);
|
|
1132
|
+
console.error(
|
|
1133
|
+
"\x1B[1m" + s.join(" caused by: ") + "\x1B[0m"
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
process.exit(1);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
function ne(e, t) {
|
|
1140
|
+
return e.find(
|
|
1141
|
+
(r) => r.vfsPath.replace(/\/$/, "") === t.replace(/\/$/, "")
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
const bt = Symbol("playground-cli-testing"), T = (e) => process.stdout.isTTY ? "\x1B[1m" + e + "\x1B[0m" : e, vt = (e) => process.stdout.isTTY ? "\x1B[31m" + e + "\x1B[0m" : e, Pt = (e) => process.stdout.isTTY ? `\x1B[2m${e}\x1B[0m` : e, q = (e) => process.stdout.isTTY ? `\x1B[3m${e}\x1B[0m` : e, ae = (e) => process.stdout.isTTY ? `\x1B[33m${e}\x1B[0m` : e;
|
|
1145
|
+
async function St(e) {
|
|
1146
|
+
let t, r;
|
|
1147
|
+
const s = /* @__PURE__ */ new Map();
|
|
1148
|
+
if (e.command === "start" && (e = xt(e)), e.autoMount !== void 0 && (e.autoMount === "" && (e = { ...e, autoMount: process.cwd() }), e = de(e)), e.wordpressInstallMode === void 0 && (e.wordpressInstallMode = "download-and-install"), e.quiet && (e.verbosity = "quiet", delete e.quiet), e.debug && (e.verbosity = "debug", delete e.debug), e.verbosity) {
|
|
1149
|
+
const a = Object.values(he).find(
|
|
1150
|
+
(f) => f.name === e.verbosity
|
|
1151
|
+
).severity;
|
|
1152
|
+
g.setSeverityFilterLevel(a);
|
|
1153
|
+
}
|
|
1154
|
+
e.intl || (e.intl = !0);
|
|
1155
|
+
const o = new yt({
|
|
1156
|
+
verbosity: e.verbosity || "normal"
|
|
1157
|
+
});
|
|
1158
|
+
e.command === "server" && (o.printBanner(), o.printConfig({
|
|
1159
|
+
phpVersion: e.php || U,
|
|
1160
|
+
wpVersion: e.wp || "latest",
|
|
1161
|
+
port: e.port || 9400,
|
|
1162
|
+
xdebug: !!e.xdebug,
|
|
1163
|
+
intl: !!e.intl,
|
|
1164
|
+
mounts: [
|
|
1165
|
+
...e.mount || [],
|
|
1166
|
+
...e["mount-before-install"] || []
|
|
1167
|
+
],
|
|
1168
|
+
blueprint: typeof e.blueprint == "string" ? e.blueprint : void 0
|
|
1169
|
+
}));
|
|
1170
|
+
const i = e.command === "server" ? e.port ?? 9400 : 0, n = O.platform() === "win32" ? (
|
|
1171
|
+
// @TODO: Enable fs-ext here when it works with Windows.
|
|
1172
|
+
void 0
|
|
1173
|
+
) : await import("fs-ext").then((a) => a.flockSync).catch(() => {
|
|
1174
|
+
g.debug(
|
|
1175
|
+
"The fs-ext package is not installed. Internal file locking will not be integrated with host OS file locking."
|
|
1176
|
+
);
|
|
1177
|
+
}), l = new Le(n);
|
|
1178
|
+
let u = !1, b = !0;
|
|
1179
|
+
const v = await rt({
|
|
1180
|
+
port: i,
|
|
1181
|
+
onBind: async (a, f) => {
|
|
1182
|
+
const c = "127.0.0.1", S = `http://${c}:${f}`, E = e["site-url"] || S, k = e.command === "server" ? e.experimentalMultiWorker ?? 1 : 1, W = e.command === "server" ? (
|
|
1183
|
+
// Account for the initial worker which is discarded by the server after setup.
|
|
1184
|
+
k + 1
|
|
1185
|
+
) : k, C = 2 ** 31 - 1, L = Math.floor(
|
|
1186
|
+
C / W
|
|
1187
|
+
), G = "-playground-cli-site-", I = await ct(G);
|
|
1188
|
+
g.debug(`Native temp dir for VFS root: ${I.path}`);
|
|
1189
|
+
const M = "WP Playground CLI - Listen for Xdebug", J = ".playground-xdebug-root", K = h.join(process.cwd(), J);
|
|
1190
|
+
if (await ze(K), e.xdebug && e.experimentalUnsafeIdeIntegration) {
|
|
1191
|
+
await Qe(
|
|
1192
|
+
I.path,
|
|
1193
|
+
K,
|
|
1194
|
+
process.platform
|
|
1195
|
+
);
|
|
1196
|
+
const d = {
|
|
1197
|
+
hostPath: h.join(".", h.sep, J),
|
|
1198
|
+
vfsPath: "/"
|
|
1199
|
+
};
|
|
1200
|
+
try {
|
|
1201
|
+
await Xe(M, process.cwd());
|
|
1202
|
+
const w = typeof e.xdebug == "object" ? e.xdebug : void 0, P = await Ze({
|
|
1203
|
+
name: M,
|
|
1204
|
+
host: c,
|
|
1205
|
+
port: f,
|
|
1206
|
+
ides: e.experimentalUnsafeIdeIntegration,
|
|
1207
|
+
cwd: process.cwd(),
|
|
1208
|
+
mounts: [
|
|
1209
|
+
d,
|
|
1210
|
+
...e["mount-before-install"] || [],
|
|
1211
|
+
...e.mount || []
|
|
1212
|
+
],
|
|
1213
|
+
ideKey: w?.ideKey
|
|
1214
|
+
}), m = e.experimentalUnsafeIdeIntegration, y = m.includes("vscode"), x = m.includes("phpstorm"), D = Object.values(P);
|
|
1215
|
+
console.log(""), D.length > 0 ? (console.log(T("Xdebug configured successfully")), console.log(
|
|
1216
|
+
ae("Updated IDE config: ") + D.join(" ")
|
|
1217
|
+
), console.log(
|
|
1218
|
+
ae("Playground source root: ") + ".playground-xdebug-root" + q(
|
|
1219
|
+
Pt(
|
|
1220
|
+
" – you can set breakpoints and preview Playground's VFS structure in there."
|
|
1221
|
+
)
|
|
1222
|
+
)
|
|
1223
|
+
)) : (console.log(T("Xdebug configuration failed.")), console.log(
|
|
1224
|
+
"No IDE-specific project settings directory was found in the current working directory."
|
|
1225
|
+
)), console.log(""), y && P.vscode && (console.log(T("VS Code / Cursor instructions:")), console.log(
|
|
1226
|
+
" 1. Ensure you have installed an IDE extension for PHP Debugging"
|
|
1227
|
+
), console.log(
|
|
1228
|
+
` (The ${T("PHP Debug")} extension by ${T(
|
|
1229
|
+
"Xdebug"
|
|
1230
|
+
)} has been a solid option)`
|
|
1231
|
+
), console.log(
|
|
1232
|
+
" 2. Open the Run and Debug panel on the left sidebar"
|
|
1233
|
+
), console.log(
|
|
1234
|
+
` 3. Select "${q(
|
|
1235
|
+
M
|
|
1236
|
+
)}" from the dropdown`
|
|
1237
|
+
), console.log(' 3. Click "start debugging"'), console.log(
|
|
1238
|
+
" 5. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"
|
|
1239
|
+
), console.log(
|
|
1240
|
+
" 6. Visit Playground in your browser to hit the breakpoint"
|
|
1241
|
+
), x && console.log("")), x && P.phpstorm && (console.log(T("PhpStorm instructions:")), console.log(
|
|
1242
|
+
` 1. Choose "${q(
|
|
1243
|
+
M
|
|
1244
|
+
)}" debug configuration in the toolbar`
|
|
1245
|
+
), console.log(" 2. Click the debug button (bug icon)`"), console.log(
|
|
1246
|
+
" 3. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"
|
|
1247
|
+
), console.log(
|
|
1248
|
+
" 4. Visit Playground in your browser to hit the breakpoint"
|
|
1249
|
+
)), console.log("");
|
|
1250
|
+
} catch (w) {
|
|
1251
|
+
throw new Error("Could not configure Xdebug", {
|
|
1252
|
+
cause: w
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
const me = h.dirname(I.path), fe = 2 * 24 * 60 * 60 * 1e3;
|
|
1257
|
+
ht(
|
|
1258
|
+
G,
|
|
1259
|
+
fe,
|
|
1260
|
+
me
|
|
1261
|
+
);
|
|
1262
|
+
const H = h.join(I.path, "internal");
|
|
1263
|
+
z(H);
|
|
1264
|
+
const we = [
|
|
1265
|
+
"wordpress",
|
|
1266
|
+
// Note: These dirs are from Emscripten's "default dirs" list:
|
|
1267
|
+
// https://github.com/emscripten-core/emscripten/blob/f431ec220e472e1f8d3db6b52fe23fb377facf30/src/lib/libfs.js#L1400-L1402
|
|
1268
|
+
//
|
|
1269
|
+
// Any Playground process with multiple workers may assume
|
|
1270
|
+
// these are part of a shared filesystem, so let's recognize
|
|
1271
|
+
// them explicitly here.
|
|
1272
|
+
"tmp",
|
|
1273
|
+
"home"
|
|
1274
|
+
];
|
|
1275
|
+
for (const d of we) {
|
|
1276
|
+
const w = (m) => m.vfsPath === `/${d}`;
|
|
1277
|
+
if (!(e["mount-before-install"]?.some(w) || e.mount?.some(w))) {
|
|
1278
|
+
const m = h.join(
|
|
1279
|
+
I.path,
|
|
1280
|
+
d
|
|
1281
|
+
);
|
|
1282
|
+
z(m), e["mount-before-install"] === void 0 && (e["mount-before-install"] = []), e["mount-before-install"].unshift({
|
|
1283
|
+
vfsPath: `/${d}`,
|
|
1284
|
+
hostPath: m
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (e["mount-before-install"])
|
|
1289
|
+
for (const d of e["mount-before-install"])
|
|
1290
|
+
g.debug(
|
|
1291
|
+
`Mount before WP install: ${d.vfsPath} -> ${d.hostPath}`
|
|
1292
|
+
);
|
|
1293
|
+
if (e.mount)
|
|
1294
|
+
for (const d of e.mount)
|
|
1295
|
+
g.debug(
|
|
1296
|
+
`Mount after WP install: ${d.vfsPath} -> ${d.hostPath}`
|
|
1297
|
+
);
|
|
1298
|
+
let $;
|
|
1299
|
+
e["experimental-blueprints-v2-runner"] ? $ = new lt(e, {
|
|
1300
|
+
siteUrl: E,
|
|
1301
|
+
processIdSpaceLength: L,
|
|
1302
|
+
cliOutput: o
|
|
1303
|
+
}) : ($ = new pt(e, {
|
|
1304
|
+
siteUrl: E,
|
|
1305
|
+
processIdSpaceLength: L,
|
|
1306
|
+
cliOutput: o
|
|
1307
|
+
}), typeof e.blueprint == "string" && (e.blueprint = await at({
|
|
1308
|
+
sourceString: e.blueprint,
|
|
1309
|
+
blueprintMayReadAdjacentFiles: e["blueprint-may-read-adjacent-files"] === !0
|
|
1310
|
+
})));
|
|
1311
|
+
let V = !1;
|
|
1312
|
+
const R = async function() {
|
|
1313
|
+
V || (V = !0, await Promise.all(
|
|
1314
|
+
[...s].map(
|
|
1315
|
+
async ([w, P]) => {
|
|
1316
|
+
await P.dispose(), await w.terminate();
|
|
1317
|
+
}
|
|
1318
|
+
)
|
|
1319
|
+
), a && await new Promise((w) => a.close(w)), await I.cleanup());
|
|
1320
|
+
}, ge = kt(
|
|
1321
|
+
W,
|
|
1322
|
+
$.getWorkerType(),
|
|
1323
|
+
({ exitCode: d, workerIndex: w }) => {
|
|
1324
|
+
V || d === 0 && g.error(
|
|
1325
|
+
`Worker ${w} exited with code ${d}
|
|
1326
|
+
`
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
);
|
|
1330
|
+
o.startProgress("Starting...");
|
|
1331
|
+
try {
|
|
1332
|
+
const d = await ge, w = await le(l);
|
|
1333
|
+
{
|
|
1334
|
+
const m = d.shift(), y = await $.bootAndSetUpInitialPlayground(
|
|
1335
|
+
m.phpPort,
|
|
1336
|
+
w,
|
|
1337
|
+
H
|
|
1338
|
+
);
|
|
1339
|
+
if (s.set(
|
|
1340
|
+
m.worker,
|
|
1341
|
+
y
|
|
1342
|
+
), await y.isReady(), u = !0, t = new it(y), !e["experimental-blueprints-v2-runner"]) {
|
|
1343
|
+
const x = await $.compileInputBlueprint(
|
|
1344
|
+
e["additional-blueprint-steps"] || []
|
|
1345
|
+
);
|
|
1346
|
+
x && await ke(
|
|
1347
|
+
x,
|
|
1348
|
+
y
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
if (e.command === "build-snapshot") {
|
|
1352
|
+
await Tt(r, e.outfile), o.printStatus(`Exported to ${e.outfile}`), await R();
|
|
1353
|
+
return;
|
|
1354
|
+
} else if (e.command === "run-blueprint") {
|
|
1355
|
+
o.finishProgress("Done"), await R();
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
await t.removeWorker(y), await y.dispose(), await m.worker.terminate(), s.delete(m.worker);
|
|
1359
|
+
}
|
|
1360
|
+
const P = L;
|
|
1361
|
+
return [r] = await Promise.all(
|
|
1362
|
+
d.map(async (m, y) => {
|
|
1363
|
+
const x = P + y * L, D = await le(l), N = await $.bootPlayground({
|
|
1364
|
+
worker: m,
|
|
1365
|
+
fileLockManagerPort: D,
|
|
1366
|
+
firstProcessId: x,
|
|
1367
|
+
nativeInternalDirPath: H
|
|
1368
|
+
});
|
|
1369
|
+
return s.set(
|
|
1370
|
+
m.worker,
|
|
1371
|
+
N
|
|
1372
|
+
), t.addWorker(N), N;
|
|
1373
|
+
})
|
|
1374
|
+
), o.finishProgress(), o.printReady(S, k), e.xdebug && e.experimentalDevtools && (await Ne({
|
|
1375
|
+
phpInstance: r,
|
|
1376
|
+
phpRoot: "/wordpress"
|
|
1377
|
+
})).start(), {
|
|
1378
|
+
playground: r,
|
|
1379
|
+
server: a,
|
|
1380
|
+
serverUrl: S,
|
|
1381
|
+
[Symbol.asyncDispose]: R,
|
|
1382
|
+
[bt]: {
|
|
1383
|
+
workerThreadCount: k,
|
|
1384
|
+
getWorkerNumberFromProcessId: (m) => Math.floor(m / L)
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
} catch (d) {
|
|
1388
|
+
if (e.verbosity !== "debug")
|
|
1389
|
+
throw d;
|
|
1390
|
+
let w = "";
|
|
1391
|
+
throw await r?.fileExists(ee) && (w = await r.readFileAsText(ee)), await R(), new Error(w, { cause: d });
|
|
1392
|
+
}
|
|
1393
|
+
},
|
|
1394
|
+
async handleRequest(a) {
|
|
1395
|
+
if (!u)
|
|
1396
|
+
return Y.forHttpCode(
|
|
1397
|
+
502,
|
|
1398
|
+
"WordPress is not ready yet"
|
|
1399
|
+
);
|
|
1400
|
+
if (b) {
|
|
1401
|
+
b = !1;
|
|
1402
|
+
const f = {
|
|
1403
|
+
"Content-Type": ["text/plain"],
|
|
1404
|
+
"Content-Length": ["0"],
|
|
1405
|
+
Location: [a.url]
|
|
1406
|
+
};
|
|
1407
|
+
return a.headers?.cookie?.includes(
|
|
1408
|
+
"playground_auto_login_already_happened"
|
|
1409
|
+
) && (f["Set-Cookie"] = [
|
|
1410
|
+
"playground_auto_login_already_happened=1; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/"
|
|
1411
|
+
]), new Y(302, f, new Uint8Array());
|
|
1412
|
+
}
|
|
1413
|
+
return await t.handleRequest(a);
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
return v && e.command === "start" && !e.skipBrowser && $t(v.serverUrl), v;
|
|
1417
|
+
}
|
|
1418
|
+
function xt(e) {
|
|
1419
|
+
let t = { ...e, command: "server" };
|
|
1420
|
+
e.noAutoMount || (t.autoMount = h.resolve(process.cwd(), t.path ?? ""), t = de(t), delete t.autoMount);
|
|
1421
|
+
const r = ne(
|
|
1422
|
+
t["mount-before-install"] || [],
|
|
1423
|
+
"/wordpress"
|
|
1424
|
+
) || ne(t.mount || [], "/wordpress");
|
|
1425
|
+
if (r)
|
|
1426
|
+
console.log("Site files stored at:", r?.hostPath), e.reset && (console.log(""), console.log(
|
|
1427
|
+
vt(
|
|
1428
|
+
"This site is not managed by Playground CLI and cannot be reset."
|
|
1429
|
+
)
|
|
1430
|
+
), console.log(
|
|
1431
|
+
"(It's not stored in the ~/.wordpress-playground/sites/<site-id> directory.)"
|
|
1432
|
+
), console.log(""), console.log(
|
|
1433
|
+
"You may still remove the site's directory manually if you wish."
|
|
1434
|
+
), process.exit(1));
|
|
1435
|
+
else {
|
|
1436
|
+
const s = t.autoMount || process.cwd(), o = Ge("sha256").update(s).digest("hex"), i = O.homedir(), n = h.join(
|
|
1437
|
+
i,
|
|
1438
|
+
".wordpress-playground/sites",
|
|
1439
|
+
o
|
|
1440
|
+
);
|
|
1441
|
+
console.log("Site files stored at:", n), X(n) && e.reset && (console.log("Resetting site..."), $e(n, { recursive: !0 })), z(n, { recursive: !0 }), t["mount-before-install"] = [
|
|
1442
|
+
...t["mount-before-install"] || [],
|
|
1443
|
+
{ vfsPath: "/wordpress", hostPath: n }
|
|
1444
|
+
], t.wordpressInstallMode = Te(n).length === 0 ? (
|
|
1445
|
+
// Only download WordPress on the first run when the site directory is still
|
|
1446
|
+
// empty.
|
|
1447
|
+
"download-and-install"
|
|
1448
|
+
) : (
|
|
1449
|
+
// After that, reuse the WordPress installation from the initial run.
|
|
1450
|
+
"install-from-existing-files-if-needed"
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
return t;
|
|
1454
|
+
}
|
|
1455
|
+
async function kt(e, t, r) {
|
|
1456
|
+
const s = [];
|
|
1457
|
+
for (let o = 0; o < e; o++) {
|
|
1458
|
+
const n = It(t, { onExit: (l) => {
|
|
1459
|
+
r({
|
|
1460
|
+
exitCode: l,
|
|
1461
|
+
workerIndex: o
|
|
1462
|
+
});
|
|
1463
|
+
} });
|
|
1464
|
+
s.push(n);
|
|
1465
|
+
}
|
|
1466
|
+
return Promise.all(s);
|
|
1467
|
+
}
|
|
1468
|
+
function It(e, { onExit: t } = {}) {
|
|
1469
|
+
let r;
|
|
1470
|
+
return e === "v1" ? r = new oe(new URL("./worker-thread-v1.js", import.meta.url)) : r = new oe(new URL("./worker-thread-v2.js", import.meta.url)), new Promise((s, o) => {
|
|
1471
|
+
r.once("message", function(n) {
|
|
1472
|
+
n.command === "worker-script-initialized" && s({ worker: r, phpPort: n.phpPort });
|
|
1473
|
+
}), r.once("error", function(n) {
|
|
1474
|
+
console.error(n);
|
|
1475
|
+
const l = new Error(
|
|
1476
|
+
`Worker failed to load worker. ${n.message ? `Original error: ${n.message}` : ""}`
|
|
1477
|
+
);
|
|
1478
|
+
o(l);
|
|
1479
|
+
});
|
|
1480
|
+
let i = !1;
|
|
1481
|
+
r.once("spawn", () => {
|
|
1482
|
+
i = !0;
|
|
1483
|
+
}), r.once("exit", (n) => {
|
|
1484
|
+
i || o(new Error(`Worker exited before spawning: ${n}`)), t?.(n);
|
|
1485
|
+
});
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
async function le(e) {
|
|
1489
|
+
const { port1: t, port2: r } = new Be();
|
|
1490
|
+
return await Me() ? be(e, null, t) : await ve(e, t), r;
|
|
1491
|
+
}
|
|
1492
|
+
function $t(e) {
|
|
1493
|
+
const t = O.platform();
|
|
1494
|
+
let r;
|
|
1495
|
+
switch (t) {
|
|
1496
|
+
case "darwin":
|
|
1497
|
+
r = `open "${e}"`;
|
|
1498
|
+
break;
|
|
1499
|
+
case "win32":
|
|
1500
|
+
r = `start "" "${e}"`;
|
|
1501
|
+
break;
|
|
1502
|
+
default:
|
|
1503
|
+
r = `xdg-open "${e}"`;
|
|
1504
|
+
break;
|
|
1505
|
+
}
|
|
1506
|
+
je(r, (s) => {
|
|
1507
|
+
s && g.debug(`Could not open browser: ${s.message}`);
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
async function Tt(e, t) {
|
|
1511
|
+
await e.run({
|
|
1512
|
+
code: `<?php
|
|
1513
|
+
$zip = new ZipArchive();
|
|
1514
|
+
if(false === $zip->open('/tmp/build.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
|
|
1515
|
+
throw new Exception('Failed to create ZIP');
|
|
1516
|
+
}
|
|
1517
|
+
$files = new RecursiveIteratorIterator(
|
|
1518
|
+
new RecursiveDirectoryIterator('/wordpress')
|
|
1519
|
+
);
|
|
1520
|
+
foreach ($files as $file) {
|
|
1521
|
+
echo $file . PHP_EOL;
|
|
1522
|
+
if (!$file->isFile()) {
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
$zip->addFile($file->getPathname(), $file->getPathname());
|
|
1526
|
+
}
|
|
1527
|
+
$zip->close();
|
|
1528
|
+
|
|
1529
|
+
`
|
|
1530
|
+
});
|
|
1531
|
+
const r = await e.readFileAsBuffer("/tmp/build.zip");
|
|
1532
|
+
p.writeFileSync(t, r);
|
|
1533
|
+
}
|
|
1534
|
+
export {
|
|
1535
|
+
he as L,
|
|
1536
|
+
gt as a,
|
|
1537
|
+
bt as i,
|
|
1538
|
+
Gt as m,
|
|
1539
|
+
Jt as p,
|
|
1540
|
+
St as r,
|
|
1541
|
+
It as s
|
|
1542
|
+
};
|
|
1543
|
+
//# sourceMappingURL=run-cli-NcKUE5gJ.js.map
|