wp-studio 1.7.7-alpha1
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/LICENSE.md +257 -0
- package/README.md +87 -0
- package/dist/cli/_events-BeOo0LuG.js +116 -0
- package/dist/cli/appdata-07CF2rhg.js +21090 -0
- package/dist/cli/archive-xDmkN4wb.js +15942 -0
- package/dist/cli/browser-CgWK-yoe.js +44 -0
- package/dist/cli/certificate-manager-DdBumKZp.js +250 -0
- package/dist/cli/create-BHVhkvTx.js +80 -0
- package/dist/cli/create-ZS29BDDi.js +40999 -0
- package/dist/cli/delete-BgQn-elT.js +56 -0
- package/dist/cli/delete-g8pgaLna.js +132 -0
- package/dist/cli/get-wordpress-version-BwSCJujO.js +18 -0
- package/dist/cli/index-7pbG_s_U.js +434 -0
- package/dist/cli/index-BXRYeCYG.js +1393 -0
- package/dist/cli/index-T3F1GwxX.js +2668 -0
- package/dist/cli/is-errno-exception-t38xF2pB.js +6 -0
- package/dist/cli/list-BE_UBjL5.js +105 -0
- package/dist/cli/list-DKz0XxM7.js +1032 -0
- package/dist/cli/logger-actions-OaIvl-ai.js +45 -0
- package/dist/cli/login-B4PkfKOu.js +82 -0
- package/dist/cli/logout-BC9gKlTj.js +48 -0
- package/dist/cli/main.js +5 -0
- package/dist/cli/mu-plugins-GEfKsl5U.js +530 -0
- package/dist/cli/passwords-DyzWd9Xi.js +80 -0
- package/dist/cli/process-manager-daemon.js +327 -0
- package/dist/cli/process-manager-ipc-AUZeYYDT.js +454 -0
- package/dist/cli/proxy-daemon.js +197 -0
- package/dist/cli/run-wp-cli-command-BctnMDWG.js +88 -0
- package/dist/cli/sequential-BQFuixXz.js +46 -0
- package/dist/cli/server-files-C_oy-mnI.js +26 -0
- package/dist/cli/set-DknhAZpw.js +327 -0
- package/dist/cli/site-utils-CfsabjUn.js +243 -0
- package/dist/cli/snapshots-6XE53y_F.js +874 -0
- package/dist/cli/sqlite-integration-H4OwSlwR.js +83 -0
- package/dist/cli/start-CRJqm09_.js +90 -0
- package/dist/cli/status-CWNHIOaY.js +44 -0
- package/dist/cli/status-CWWx9jYF.js +110 -0
- package/dist/cli/stop-CQosmjqA.js +117 -0
- package/dist/cli/update-BgL2HKHW.js +101 -0
- package/dist/cli/validation-error-DqLxqQuA.js +40 -0
- package/dist/cli/wordpress-server-child.js +514 -0
- package/dist/cli/wordpress-server-ipc-Dwsg9jSb.js +140 -0
- package/dist/cli/wordpress-server-manager-CtiuJqEb.js +566 -0
- package/dist/cli/wordpress-version-utils-B6UVeTh_.js +51 -0
- package/dist/cli/wp-UGSnlkN0.js +103 -0
- package/package.json +73 -0
- package/patches/@wp-playground+wordpress+3.1.12.patch +28 -0
- package/scripts/postinstall-npm.mjs +38 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { T as lockFileAsync, U as unlockFileAsync, V as LOCKFILE_STALE_TIME, W as LOCKFILE_WAIT_TIME, X as PLAYGROUND_CLI_INACTIVITY_TIMEOUT, Y as PLAYGROUND_CLI_MAX_TIMEOUT, Z as PLAYGROUND_CLI_ACTIVITY_CHECK_INTERVAL, S as SITE_EVENTS } from "./appdata-07CF2rhg.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import crypto$1 from "crypto";
|
|
6
|
+
import { EventEmitter } from "events";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import { i as isErrnoException } from "./is-errno-exception-t38xF2pB.js";
|
|
9
|
+
import { e as SocketRequestClient, a as PROCESS_MANAGER_EVENTS_SOCKET_PATH, P as PROCESS_MANAGER_CONTROL_SOCKET_PATH, f as daemonResponseSchema, g as PROCESS_MANAGER_HOME, h as SocketMessageDecoder, i as SocketStreamClient, c as daemonEventSchema, p as processDescriptionSchema } from "./process-manager-ipc-AUZeYYDT.js";
|
|
10
|
+
const PROXY_PROCESS_NAME = "studio-proxy";
|
|
11
|
+
const CONNECTION_TIMEOUT_MS = 1e4;
|
|
12
|
+
const PROCESS_MANAGER_LOCKFILE_PATH = path.join(PROCESS_MANAGER_HOME, "pm-connection.lock");
|
|
13
|
+
const SITE_EVENTS_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\studio-events.sock" : path.join(PROCESS_MANAGER_HOME, "events.sock");
|
|
14
|
+
if (!fs.existsSync(PROCESS_MANAGER_HOME)) {
|
|
15
|
+
fs.mkdirSync(PROCESS_MANAGER_HOME, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
class DaemonBusEventEmitter extends EventEmitter {
|
|
18
|
+
on(event, listener) {
|
|
19
|
+
return super.on(event, listener);
|
|
20
|
+
}
|
|
21
|
+
emit(event, payload) {
|
|
22
|
+
return super.emit(event, payload);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
class DaemonBus extends DaemonBusEventEmitter {
|
|
26
|
+
constructor(endpoint) {
|
|
27
|
+
super();
|
|
28
|
+
this.decoder = new SocketMessageDecoder();
|
|
29
|
+
this.socketClient = new SocketStreamClient(endpoint, 2500);
|
|
30
|
+
this.socketClient.on("data", ({ chunk }) => {
|
|
31
|
+
try {
|
|
32
|
+
for (const packet of this.decoder.write(chunk)) {
|
|
33
|
+
this.handlePacket(packet);
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
this.decoder = new SocketMessageDecoder();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
this.socketClient.on("close", () => {
|
|
40
|
+
this.decoder = new SocketMessageDecoder();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
isConnected() {
|
|
44
|
+
return this.socketClient.isConnected();
|
|
45
|
+
}
|
|
46
|
+
handlePacket(packet) {
|
|
47
|
+
const result = daemonEventSchema.safeParse(packet);
|
|
48
|
+
if (!result.success) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
switch (result.data.type) {
|
|
52
|
+
case "process-message":
|
|
53
|
+
this.emit("process-message", result.data.payload);
|
|
54
|
+
return;
|
|
55
|
+
case "process-event":
|
|
56
|
+
this.emit("process-event", result.data.payload);
|
|
57
|
+
return;
|
|
58
|
+
case "daemon-kill":
|
|
59
|
+
this.emit("daemon-kill", result.data.payload);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async connect() {
|
|
64
|
+
await this.socketClient.connect();
|
|
65
|
+
}
|
|
66
|
+
async close() {
|
|
67
|
+
await this.socketClient.close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function sendDaemonRequest(request) {
|
|
71
|
+
const socketClient = new SocketRequestClient(
|
|
72
|
+
PROCESS_MANAGER_CONTROL_SOCKET_PATH,
|
|
73
|
+
CONNECTION_TIMEOUT_MS
|
|
74
|
+
);
|
|
75
|
+
const rawResponse = await socketClient.sendAndWaitForResponse({
|
|
76
|
+
...request,
|
|
77
|
+
requestId: crypto$1.randomUUID()
|
|
78
|
+
});
|
|
79
|
+
const response = daemonResponseSchema.parse(rawResponse);
|
|
80
|
+
if (response.type === "error") {
|
|
81
|
+
throw new Error(response.error.message);
|
|
82
|
+
}
|
|
83
|
+
return response.payload;
|
|
84
|
+
}
|
|
85
|
+
async function waitForDaemonReady() {
|
|
86
|
+
const start = Date.now();
|
|
87
|
+
let lastError;
|
|
88
|
+
while (Date.now() - start < CONNECTION_TIMEOUT_MS) {
|
|
89
|
+
try {
|
|
90
|
+
await sendDaemonRequest({ type: "ping" });
|
|
91
|
+
return;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
lastError = error;
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (lastError instanceof Error) {
|
|
98
|
+
throw lastError;
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`Daemon connection timeout after ${CONNECTION_TIMEOUT_MS / 1e3} seconds`);
|
|
101
|
+
}
|
|
102
|
+
function spawnDaemonProcess() {
|
|
103
|
+
const daemonScriptPath = path.resolve(import.meta.dirname, "process-manager-daemon.js");
|
|
104
|
+
const daemonProcess = spawn(process.execPath, [daemonScriptPath], {
|
|
105
|
+
detached: true,
|
|
106
|
+
stdio: "ignore",
|
|
107
|
+
windowsHide: true
|
|
108
|
+
});
|
|
109
|
+
daemonProcess.unref();
|
|
110
|
+
}
|
|
111
|
+
function isRecoverableConnectError(error) {
|
|
112
|
+
return isErrnoException(error) && (error.code === "ENOENT" || error.code === "ECONNREFUSED" || error.code === "EPIPE");
|
|
113
|
+
}
|
|
114
|
+
async function ensureDaemonIsRunning() {
|
|
115
|
+
try {
|
|
116
|
+
await sendDaemonRequest({ type: "ping" });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (!isRecoverableConnectError(error)) {
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
spawnDaemonProcess();
|
|
122
|
+
await waitForDaemonReady();
|
|
123
|
+
await sendDaemonRequest({ type: "ping" });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
let daemonBus = null;
|
|
127
|
+
async function ensureDaemonBus() {
|
|
128
|
+
if (daemonBus) {
|
|
129
|
+
if (!daemonBus.isConnected()) {
|
|
130
|
+
await daemonBus.connect();
|
|
131
|
+
}
|
|
132
|
+
return daemonBus;
|
|
133
|
+
}
|
|
134
|
+
const bus = new DaemonBus(PROCESS_MANAGER_EVENTS_SOCKET_PATH);
|
|
135
|
+
await bus.connect();
|
|
136
|
+
daemonBus = bus;
|
|
137
|
+
return bus;
|
|
138
|
+
}
|
|
139
|
+
async function cleanupDaemonBus() {
|
|
140
|
+
const busToClose = daemonBus;
|
|
141
|
+
daemonBus = null;
|
|
142
|
+
if (busToClose) {
|
|
143
|
+
await busToClose.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
let isConnected = false;
|
|
147
|
+
async function connectToDaemon() {
|
|
148
|
+
if (isConnected) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
await lockFileAsync(PROCESS_MANAGER_LOCKFILE_PATH, {
|
|
152
|
+
wait: LOCKFILE_WAIT_TIME,
|
|
153
|
+
stale: LOCKFILE_STALE_TIME
|
|
154
|
+
});
|
|
155
|
+
try {
|
|
156
|
+
await ensureDaemonIsRunning();
|
|
157
|
+
await ensureDaemonBus();
|
|
158
|
+
isConnected = true;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
await cleanupDaemonBus();
|
|
161
|
+
throw error;
|
|
162
|
+
} finally {
|
|
163
|
+
await unlockFileAsync(PROCESS_MANAGER_LOCKFILE_PATH);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function disconnectFromDaemon() {
|
|
167
|
+
isConnected = false;
|
|
168
|
+
await cleanupDaemonBus();
|
|
169
|
+
}
|
|
170
|
+
async function killDaemonAndChildren() {
|
|
171
|
+
await sendDaemonRequest({ type: "kill-daemon" });
|
|
172
|
+
}
|
|
173
|
+
const daemonListProcessesSuccessResponseSchema = z.object({
|
|
174
|
+
processes: z.array(processDescriptionSchema)
|
|
175
|
+
});
|
|
176
|
+
async function listProcesses() {
|
|
177
|
+
await connectToDaemon();
|
|
178
|
+
const response = await sendDaemonRequest({
|
|
179
|
+
type: "list-processes"
|
|
180
|
+
});
|
|
181
|
+
return daemonListProcessesSuccessResponseSchema.parse(response).processes;
|
|
182
|
+
}
|
|
183
|
+
async function getDaemonBus() {
|
|
184
|
+
if (!daemonBus) {
|
|
185
|
+
throw new Error("Daemon bus is not initialized");
|
|
186
|
+
}
|
|
187
|
+
return daemonBus;
|
|
188
|
+
}
|
|
189
|
+
async function sendMessageToProcess(processId, messageToProcess) {
|
|
190
|
+
await sendDaemonRequest({
|
|
191
|
+
type: "send-message-to-process",
|
|
192
|
+
processId,
|
|
193
|
+
message: messageToProcess
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async function startProxyProcess() {
|
|
197
|
+
const proxyDaemonPath = path.resolve(import.meta.dirname, "proxy-daemon.js");
|
|
198
|
+
return startProcess(PROXY_PROCESS_NAME, proxyDaemonPath);
|
|
199
|
+
}
|
|
200
|
+
async function isProxyProcessRunning() {
|
|
201
|
+
return isProcessRunning(PROXY_PROCESS_NAME);
|
|
202
|
+
}
|
|
203
|
+
async function stopProxyProcess() {
|
|
204
|
+
return stopProcess(PROXY_PROCESS_NAME);
|
|
205
|
+
}
|
|
206
|
+
async function isProcessRunning(processName) {
|
|
207
|
+
try {
|
|
208
|
+
const processes = await listProcesses();
|
|
209
|
+
return processes.find((p) => p.name === processName && p.status === "online");
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(`Error checking if process ${processName} is running:`, error);
|
|
212
|
+
return void 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const daemonStartProcessSuccessResponseSchema = z.object({
|
|
216
|
+
process: processDescriptionSchema
|
|
217
|
+
});
|
|
218
|
+
async function startProcess(processName, scriptPath, env = {}, args = []) {
|
|
219
|
+
const response = await sendDaemonRequest({
|
|
220
|
+
type: "start-process",
|
|
221
|
+
processName,
|
|
222
|
+
scriptPath,
|
|
223
|
+
env,
|
|
224
|
+
args
|
|
225
|
+
});
|
|
226
|
+
return daemonStartProcessSuccessResponseSchema.parse(response).process;
|
|
227
|
+
}
|
|
228
|
+
async function stopProcess(processName) {
|
|
229
|
+
const runningProcess = await isProcessRunning(processName);
|
|
230
|
+
if (!runningProcess) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
await connectToDaemon();
|
|
234
|
+
await sendDaemonRequest({
|
|
235
|
+
type: "stop-process",
|
|
236
|
+
processName
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const eventsSocketClient = new SocketRequestClient(SITE_EVENTS_SOCKET_PATH);
|
|
240
|
+
async function emitSiteEvent(event, data) {
|
|
241
|
+
try {
|
|
242
|
+
await eventsSocketClient.send({ event, data });
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const SITE_PROCESS_PREFIX = "studio-site-";
|
|
247
|
+
const abortController = new AbortController();
|
|
248
|
+
process.on("SIGINT", () => abortController.abort());
|
|
249
|
+
process.on("SIGTERM", () => abortController.abort());
|
|
250
|
+
function getProcessName(siteId) {
|
|
251
|
+
return `${SITE_PROCESS_PREFIX}${siteId}`;
|
|
252
|
+
}
|
|
253
|
+
async function isServerRunning(siteId) {
|
|
254
|
+
const processName = getProcessName(siteId);
|
|
255
|
+
return isProcessRunning(processName);
|
|
256
|
+
}
|
|
257
|
+
async function startWordPressServer(site, logger, options) {
|
|
258
|
+
const wordPressServerChildPath = path.resolve(import.meta.dirname, "wordpress-server-child.js");
|
|
259
|
+
const processName = getProcessName(site.id);
|
|
260
|
+
const serverConfig = {
|
|
261
|
+
siteId: site.id,
|
|
262
|
+
sitePath: site.path,
|
|
263
|
+
port: site.port,
|
|
264
|
+
phpVersion: site.phpVersion,
|
|
265
|
+
siteTitle: site.name
|
|
266
|
+
};
|
|
267
|
+
if (site.customDomain) {
|
|
268
|
+
const protocol = site.enableHttps ? "https" : "http";
|
|
269
|
+
serverConfig.absoluteUrl = `${protocol}://${site.customDomain}`;
|
|
270
|
+
}
|
|
271
|
+
if (site.adminUsername) {
|
|
272
|
+
serverConfig.adminUsername = site.adminUsername;
|
|
273
|
+
}
|
|
274
|
+
if (site.adminPassword) {
|
|
275
|
+
serverConfig.adminPassword = site.adminPassword;
|
|
276
|
+
}
|
|
277
|
+
if (site.adminEmail) {
|
|
278
|
+
serverConfig.adminEmail = site.adminEmail;
|
|
279
|
+
}
|
|
280
|
+
if (site.isWpAutoUpdating !== void 0) {
|
|
281
|
+
serverConfig.isWpAutoUpdating = site.isWpAutoUpdating;
|
|
282
|
+
}
|
|
283
|
+
if (options?.wpVersion) {
|
|
284
|
+
serverConfig.wpVersion = options.wpVersion;
|
|
285
|
+
}
|
|
286
|
+
if (options?.blueprint && options.blueprintUri) {
|
|
287
|
+
serverConfig.blueprint = {
|
|
288
|
+
contents: options.blueprint,
|
|
289
|
+
uri: options.blueprintUri
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (site.enableXdebug) {
|
|
293
|
+
serverConfig.enableXdebug = true;
|
|
294
|
+
}
|
|
295
|
+
if (site.enableDebugLog) {
|
|
296
|
+
serverConfig.enableDebugLog = true;
|
|
297
|
+
}
|
|
298
|
+
if (site.enableDebugDisplay) {
|
|
299
|
+
serverConfig.enableDebugDisplay = true;
|
|
300
|
+
}
|
|
301
|
+
const processDesc = await startProcess(processName, wordPressServerChildPath);
|
|
302
|
+
await waitForReadyMessage(processDesc.pmId);
|
|
303
|
+
await sendMessage(
|
|
304
|
+
processDesc.pmId,
|
|
305
|
+
processName,
|
|
306
|
+
{
|
|
307
|
+
topic: "start-server",
|
|
308
|
+
data: { config: serverConfig }
|
|
309
|
+
},
|
|
310
|
+
{ logger }
|
|
311
|
+
);
|
|
312
|
+
return processDesc;
|
|
313
|
+
}
|
|
314
|
+
async function waitForReadyMessage(pmId) {
|
|
315
|
+
const bus = await getDaemonBus();
|
|
316
|
+
let timeoutId;
|
|
317
|
+
let readyHandler;
|
|
318
|
+
let abortListener;
|
|
319
|
+
return new Promise((resolve, reject) => {
|
|
320
|
+
timeoutId = setTimeout(() => {
|
|
321
|
+
reject(new Error("Timeout waiting for ready message from WordPress server child"));
|
|
322
|
+
}, PLAYGROUND_CLI_INACTIVITY_TIMEOUT);
|
|
323
|
+
readyHandler = (packet) => {
|
|
324
|
+
if (packet.process.pm_id === pmId && packet.raw.topic === "ready") {
|
|
325
|
+
resolve();
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
abortListener = () => {
|
|
329
|
+
reject(new Error("Operation aborted"));
|
|
330
|
+
};
|
|
331
|
+
abortController.signal.addEventListener("abort", abortListener);
|
|
332
|
+
bus.on("process-message", readyHandler);
|
|
333
|
+
}).finally(() => {
|
|
334
|
+
clearTimeout(timeoutId);
|
|
335
|
+
abortController.signal.removeEventListener("abort", abortListener);
|
|
336
|
+
bus.off("process-message", readyHandler);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
const messageActivityTrackers = /* @__PURE__ */ new Map();
|
|
340
|
+
async function sendMessage(pmId, processName, message, options = {}) {
|
|
341
|
+
const { maxTotalElapsedTime = PLAYGROUND_CLI_MAX_TIMEOUT, logger } = options;
|
|
342
|
+
const bus = await getDaemonBus();
|
|
343
|
+
const messageId = crypto.randomUUID();
|
|
344
|
+
let responseHandler;
|
|
345
|
+
let processEventHandler;
|
|
346
|
+
let abortListener;
|
|
347
|
+
return new Promise((resolve, reject) => {
|
|
348
|
+
const startTime = Date.now();
|
|
349
|
+
let lastActivityTimestamp = Date.now();
|
|
350
|
+
const activityCheckIntervalId = setInterval(() => {
|
|
351
|
+
const now = Date.now();
|
|
352
|
+
const timeSinceLastActivity = now - lastActivityTimestamp;
|
|
353
|
+
const totalElapsedTime = now - startTime;
|
|
354
|
+
if (timeSinceLastActivity > PLAYGROUND_CLI_INACTIVITY_TIMEOUT || totalElapsedTime > maxTotalElapsedTime) {
|
|
355
|
+
const timeoutReason = totalElapsedTime > maxTotalElapsedTime ? `Maximum timeout of ${maxTotalElapsedTime / 1e3}s exceeded` : `No activity for ${PLAYGROUND_CLI_INACTIVITY_TIMEOUT / 1e3}s`;
|
|
356
|
+
reject(
|
|
357
|
+
new Error(
|
|
358
|
+
`Timeout waiting for response to message ${message.topic}: ${timeoutReason}`
|
|
359
|
+
)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}, PLAYGROUND_CLI_ACTIVITY_CHECK_INTERVAL);
|
|
363
|
+
messageActivityTrackers.set(messageId, {
|
|
364
|
+
activityCheckIntervalId
|
|
365
|
+
});
|
|
366
|
+
processEventHandler = (event) => {
|
|
367
|
+
if (event.process.name === processName && event.event === "exit") {
|
|
368
|
+
reject(new Error("WordPress server process exited unexpectedly"));
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
responseHandler = (packet) => {
|
|
372
|
+
if (packet.process.pm_id !== pmId) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (packet.raw.topic === "activity") {
|
|
376
|
+
lastActivityTimestamp = Date.now();
|
|
377
|
+
} else if (packet.raw.topic === "console-message") {
|
|
378
|
+
lastActivityTimestamp = Date.now();
|
|
379
|
+
logger?.reportProgress(packet.raw.message);
|
|
380
|
+
} else if (packet.raw.topic === "error" && packet.raw.originalMessageId === messageId) {
|
|
381
|
+
const error = new Error(packet.raw.errorMessage);
|
|
382
|
+
if (packet.raw.errorStack) {
|
|
383
|
+
error.stack = packet.raw.errorStack;
|
|
384
|
+
}
|
|
385
|
+
if (packet.raw.cliArgs) {
|
|
386
|
+
error.cliArgs = packet.raw.cliArgs;
|
|
387
|
+
}
|
|
388
|
+
reject(error);
|
|
389
|
+
} else if (packet.raw.topic === "result" && packet.raw.originalMessageId === messageId) {
|
|
390
|
+
resolve(packet.raw.result);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
abortListener = () => {
|
|
394
|
+
void sendMessageToProcess(pmId, { messageId, topic: "abort", data: {} });
|
|
395
|
+
reject(new Error("Operation aborted"));
|
|
396
|
+
};
|
|
397
|
+
abortController.signal.addEventListener("abort", abortListener);
|
|
398
|
+
bus.on("process-event", processEventHandler);
|
|
399
|
+
bus.on("process-message", responseHandler);
|
|
400
|
+
sendMessageToProcess(pmId, { ...message, messageId }).catch(reject);
|
|
401
|
+
}).finally(() => {
|
|
402
|
+
abortController.signal.removeEventListener("abort", abortListener);
|
|
403
|
+
bus.off("process-event", processEventHandler);
|
|
404
|
+
bus.off("process-message", responseHandler);
|
|
405
|
+
const tracker = messageActivityTrackers.get(messageId);
|
|
406
|
+
if (tracker) {
|
|
407
|
+
clearInterval(tracker.activityCheckIntervalId);
|
|
408
|
+
messageActivityTrackers.delete(messageId);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
const GRACEFUL_STOP_TIMEOUT = 5e3;
|
|
413
|
+
async function stopWordPressServer(siteId) {
|
|
414
|
+
const processName = getProcessName(siteId);
|
|
415
|
+
const runningProcess = await isProcessRunning(processName);
|
|
416
|
+
if (!runningProcess) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
const bus = await getDaemonBus();
|
|
421
|
+
let busExitEventListener;
|
|
422
|
+
const exitPromise = new Promise((resolve) => {
|
|
423
|
+
busExitEventListener = (event) => {
|
|
424
|
+
if (event.process.name === processName && event.event === "exit") {
|
|
425
|
+
resolve();
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
bus.on("process-event", busExitEventListener);
|
|
429
|
+
}).finally(() => {
|
|
430
|
+
bus.off("process-event", busExitEventListener);
|
|
431
|
+
});
|
|
432
|
+
await sendMessage(
|
|
433
|
+
runningProcess.pmId,
|
|
434
|
+
processName,
|
|
435
|
+
{ topic: "stop-server", data: {} },
|
|
436
|
+
{ maxTotalElapsedTime: GRACEFUL_STOP_TIMEOUT }
|
|
437
|
+
);
|
|
438
|
+
await Promise.race([
|
|
439
|
+
exitPromise,
|
|
440
|
+
new Promise((resolve, reject) => setTimeout(reject, 5e3))
|
|
441
|
+
]);
|
|
442
|
+
} catch {
|
|
443
|
+
return stopProcess(processName);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async function runBlueprint(site, logger, options) {
|
|
447
|
+
const wordPressServerChildPath = path.resolve(import.meta.dirname, "wordpress-server-child.js");
|
|
448
|
+
const processName = getProcessName(site.id);
|
|
449
|
+
const serverConfig = {
|
|
450
|
+
siteId: site.id,
|
|
451
|
+
sitePath: site.path,
|
|
452
|
+
port: site.port,
|
|
453
|
+
phpVersion: site.phpVersion,
|
|
454
|
+
siteTitle: site.name,
|
|
455
|
+
blueprint: {
|
|
456
|
+
contents: options.blueprint,
|
|
457
|
+
uri: options.blueprintUri
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
if (site.customDomain) {
|
|
461
|
+
const protocol = site.enableHttps ? "https" : "http";
|
|
462
|
+
serverConfig.absoluteUrl = `${protocol}://${site.customDomain}`;
|
|
463
|
+
}
|
|
464
|
+
if (site.adminUsername) {
|
|
465
|
+
serverConfig.adminUsername = site.adminUsername;
|
|
466
|
+
}
|
|
467
|
+
if (site.adminPassword) {
|
|
468
|
+
serverConfig.adminPassword = site.adminPassword;
|
|
469
|
+
}
|
|
470
|
+
if (site.adminEmail) {
|
|
471
|
+
serverConfig.adminEmail = site.adminEmail;
|
|
472
|
+
}
|
|
473
|
+
if (site.isWpAutoUpdating !== void 0) {
|
|
474
|
+
serverConfig.isWpAutoUpdating = site.isWpAutoUpdating;
|
|
475
|
+
}
|
|
476
|
+
if (options.wpVersion) {
|
|
477
|
+
serverConfig.wpVersion = options.wpVersion;
|
|
478
|
+
}
|
|
479
|
+
if (site.enableXdebug) {
|
|
480
|
+
serverConfig.enableXdebug = true;
|
|
481
|
+
}
|
|
482
|
+
if (site.enableDebugLog) {
|
|
483
|
+
serverConfig.enableDebugLog = true;
|
|
484
|
+
}
|
|
485
|
+
if (site.enableDebugDisplay) {
|
|
486
|
+
serverConfig.enableDebugDisplay = true;
|
|
487
|
+
}
|
|
488
|
+
const processDesc = await startProcess(processName, wordPressServerChildPath);
|
|
489
|
+
try {
|
|
490
|
+
await waitForReadyMessage(processDesc.pmId);
|
|
491
|
+
await sendMessage(
|
|
492
|
+
processDesc.pmId,
|
|
493
|
+
processName,
|
|
494
|
+
{
|
|
495
|
+
topic: "run-blueprint",
|
|
496
|
+
data: { config: serverConfig }
|
|
497
|
+
},
|
|
498
|
+
{ logger }
|
|
499
|
+
);
|
|
500
|
+
} finally {
|
|
501
|
+
await stopProcess(processName);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const wpCliResultSchema = z.object({
|
|
505
|
+
stdout: z.string(),
|
|
506
|
+
stderr: z.string(),
|
|
507
|
+
exitCode: z.number()
|
|
508
|
+
});
|
|
509
|
+
async function sendWpCliCommand(siteId, args) {
|
|
510
|
+
const processName = getProcessName(siteId);
|
|
511
|
+
const runningProcess = await isProcessRunning(processName);
|
|
512
|
+
if (!runningProcess) {
|
|
513
|
+
throw new Error(`WordPress server is not running`);
|
|
514
|
+
}
|
|
515
|
+
const result = await sendMessage(runningProcess.pmId, processName, {
|
|
516
|
+
topic: "wp-cli-command",
|
|
517
|
+
data: { args }
|
|
518
|
+
});
|
|
519
|
+
return wpCliResultSchema.parse(result);
|
|
520
|
+
}
|
|
521
|
+
async function subscribeSiteEvents(handler) {
|
|
522
|
+
const bus = await getDaemonBus();
|
|
523
|
+
const messageHandler = (message) => {
|
|
524
|
+
const processName = message.process.name;
|
|
525
|
+
if (!processName.startsWith(SITE_PROCESS_PREFIX)) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (message.raw.topic === "result") {
|
|
529
|
+
const siteId = processName.replace(SITE_PROCESS_PREFIX, "");
|
|
530
|
+
handler({ siteId, event: SITE_EVENTS.UPDATED, running: true });
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
bus.on("process-message", messageHandler);
|
|
534
|
+
const eventHandler = (event) => {
|
|
535
|
+
const processName = event.process.name;
|
|
536
|
+
if (!processName.startsWith(SITE_PROCESS_PREFIX)) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (event.event !== "online") {
|
|
540
|
+
const siteId = processName.replace(SITE_PROCESS_PREFIX, "");
|
|
541
|
+
handler({ siteId, event: SITE_EVENTS.UPDATED, running: false });
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
bus.on("process-event", eventHandler);
|
|
545
|
+
return () => {
|
|
546
|
+
bus.off("process-message", messageHandler);
|
|
547
|
+
bus.off("process-event", eventHandler);
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
export {
|
|
551
|
+
SITE_EVENTS_SOCKET_PATH as S,
|
|
552
|
+
stopWordPressServer as a,
|
|
553
|
+
sendWpCliCommand as b,
|
|
554
|
+
connectToDaemon as c,
|
|
555
|
+
disconnectFromDaemon as d,
|
|
556
|
+
emitSiteEvent as e,
|
|
557
|
+
subscribeSiteEvents as f,
|
|
558
|
+
getDaemonBus as g,
|
|
559
|
+
isProxyProcessRunning as h,
|
|
560
|
+
isServerRunning as i,
|
|
561
|
+
stopProxyProcess as j,
|
|
562
|
+
killDaemonAndChildren as k,
|
|
563
|
+
startProxyProcess as l,
|
|
564
|
+
runBlueprint as r,
|
|
565
|
+
startWordPressServer as s
|
|
566
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function isValidWordPressVersion(version) {
|
|
2
|
+
const versionPattern = /^latest$|^nightly$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/;
|
|
3
|
+
return versionPattern.test(version);
|
|
4
|
+
}
|
|
5
|
+
function isWordPressDevVersion(version) {
|
|
6
|
+
return version === "nightly" || /^\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9]+)*-\d+$/.test(version);
|
|
7
|
+
}
|
|
8
|
+
function isWordPressBetaVersion(version) {
|
|
9
|
+
return version.includes("beta") || version.includes("RC");
|
|
10
|
+
}
|
|
11
|
+
function getWordPressVersionUrl(version) {
|
|
12
|
+
if (isWordPressDevVersion(version)) {
|
|
13
|
+
return "https://wordpress.org/nightly-builds/wordpress-latest.zip";
|
|
14
|
+
}
|
|
15
|
+
if (!isValidWordPressVersion(version)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
'Unrecognized WordPress version. Please use "latest" or numeric versions such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1"'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return `https://wordpress.org/wordpress-${version}.zip`;
|
|
21
|
+
}
|
|
22
|
+
function isWordPressVersionAtLeast(version, minimumVersion) {
|
|
23
|
+
if (version === "latest" || version === "nightly") {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
let versionToCompare = version;
|
|
27
|
+
if (version.includes("-")) {
|
|
28
|
+
versionToCompare = version.split("-")[0];
|
|
29
|
+
}
|
|
30
|
+
const parseVersion = (v) => v.split(".").map(Number);
|
|
31
|
+
const provided = parseVersion(versionToCompare);
|
|
32
|
+
const minimum = parseVersion(minimumVersion);
|
|
33
|
+
for (let i = 0; i < Math.max(provided.length, minimum.length); i++) {
|
|
34
|
+
const p = provided[i] || 0;
|
|
35
|
+
const m = minimum[i] || 0;
|
|
36
|
+
if (p < m) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (p > m) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
isWordPressBetaVersion as a,
|
|
47
|
+
isValidWordPressVersion as b,
|
|
48
|
+
isWordPressVersionAtLeast as c,
|
|
49
|
+
getWordPressVersionUrl as g,
|
|
50
|
+
isWordPressDevVersion as i
|
|
51
|
+
};
|