@wp-operations/wp-app 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -34
- package/dist/index.js +345 -109
- package/dist/main.js +896 -322
- package/package.json +5 -8
package/dist/main.js
CHANGED
|
@@ -1,59 +1,527 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res, err) => function __init() {
|
|
4
|
+
if (err) throw err[0];
|
|
5
|
+
try {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw err = [e], e;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/exec-command.ts
|
|
17
|
+
import { execFileSync } from "child_process";
|
|
18
|
+
function runCommand(command, args, options) {
|
|
19
|
+
return execFileSync(command, args, {
|
|
20
|
+
...options,
|
|
21
|
+
shell: process.platform === "win32"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
var init_exec_command = __esm({
|
|
25
|
+
"src/exec-command.ts"() {
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// src/create.ts
|
|
30
|
+
var create_exports = {};
|
|
31
|
+
__export(create_exports, {
|
|
32
|
+
runCreateCommand: () => runCreateCommand
|
|
33
|
+
});
|
|
34
|
+
import fs14 from "fs";
|
|
35
|
+
import path12 from "path";
|
|
36
|
+
import readline from "readline/promises";
|
|
37
|
+
async function ask(question, defaultValue = "") {
|
|
38
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
39
|
+
const answer = (await rl.question(`? ${question}${suffix}: `)).trim();
|
|
40
|
+
return answer || defaultValue;
|
|
41
|
+
}
|
|
42
|
+
async function askChoice(question, choices, defaultIndex = 0) {
|
|
43
|
+
console.log(`? ${question}`);
|
|
44
|
+
choices.forEach((choice, i) => {
|
|
45
|
+
const marker = i === defaultIndex ? ">" : " ";
|
|
46
|
+
console.log(` ${marker} ${i + 1}) ${choice}`);
|
|
47
|
+
});
|
|
48
|
+
const answer = (await rl.question(` Choice (${defaultIndex + 1}): `)).trim();
|
|
49
|
+
const index = answer ? parseInt(answer, 10) - 1 : defaultIndex;
|
|
50
|
+
return choices[index] ?? choices[defaultIndex];
|
|
51
|
+
}
|
|
52
|
+
async function askPassword(question, defaultValue) {
|
|
53
|
+
if (!process.stdin.isTTY) {
|
|
54
|
+
return defaultValue;
|
|
55
|
+
}
|
|
56
|
+
process.stdout.write(`? ${question} (${defaultValue}): `);
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
let value = "";
|
|
59
|
+
process.stdin.setRawMode(true);
|
|
60
|
+
process.stdin.resume();
|
|
61
|
+
process.stdin.setEncoding("utf8");
|
|
62
|
+
const onData = (char) => {
|
|
63
|
+
if (char === "\r" || char === "\n") {
|
|
64
|
+
process.stdin.setRawMode(false);
|
|
65
|
+
process.stdin.pause();
|
|
66
|
+
process.stdin.removeListener("data", onData);
|
|
67
|
+
process.stdout.write("\n");
|
|
68
|
+
resolve(value || defaultValue);
|
|
69
|
+
} else if (char === CTRL_C) {
|
|
70
|
+
process.stdin.setRawMode(false);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
} else if (char === BACKSPACE_DEL || char === BACKSPACE_BS) {
|
|
73
|
+
if (value.length > 0) {
|
|
74
|
+
value = value.slice(0, -1);
|
|
75
|
+
process.stdout.write("\b \b");
|
|
76
|
+
}
|
|
77
|
+
} else if (char.charCodeAt(0) >= 32) {
|
|
78
|
+
value += char;
|
|
79
|
+
process.stdout.write("*");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
process.stdin.on("data", onData);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function toPascalCase(value) {
|
|
86
|
+
return value.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join("");
|
|
87
|
+
}
|
|
88
|
+
async function collectAnswers() {
|
|
89
|
+
const projectName = await ask("Project name", "my-app");
|
|
90
|
+
const author = await ask("Author (composer vendor)", "my-name");
|
|
91
|
+
const cssTool = (await askChoice("CSS tooling", ["Tailwind", "SCSS"], 0)).toLowerCase().startsWith("tailwind") ? "tailwind" : "scss";
|
|
92
|
+
const npmPackagesRaw = await ask(
|
|
93
|
+
"npm packages (comma-separated, optional)"
|
|
94
|
+
);
|
|
95
|
+
const composerPackagesRaw = await ask(
|
|
96
|
+
"composer packages (comma-separated, optional)"
|
|
97
|
+
);
|
|
98
|
+
const adminUser = await ask("Admin username", "admin");
|
|
99
|
+
const adminPassword = await askPassword("Admin password", "password");
|
|
100
|
+
return {
|
|
101
|
+
projectName,
|
|
102
|
+
author,
|
|
103
|
+
cssTool,
|
|
104
|
+
npmPackages: splitList(npmPackagesRaw),
|
|
105
|
+
composerPackages: splitList(composerPackagesRaw),
|
|
106
|
+
adminUser,
|
|
107
|
+
adminPassword
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function splitList(value) {
|
|
111
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
112
|
+
}
|
|
113
|
+
function printPreview(answers) {
|
|
114
|
+
console.log("\nReview:");
|
|
115
|
+
console.log(` 1) Project name: ${answers.projectName}`);
|
|
116
|
+
console.log(` 2) Author: ${answers.author}`);
|
|
117
|
+
console.log(` 3) CSS tooling: ${answers.cssTool}`);
|
|
118
|
+
console.log(
|
|
119
|
+
` 4) npm packages: ${answers.npmPackages.join(", ") || "(none)"}`
|
|
120
|
+
);
|
|
121
|
+
console.log(
|
|
122
|
+
` 5) composer packages: ${answers.composerPackages.join(", ") || "(none)"}`
|
|
123
|
+
);
|
|
124
|
+
console.log(` 6) Admin username: ${answers.adminUser}`);
|
|
125
|
+
console.log(` 7) Admin password: ${"*".repeat(answers.adminPassword.length)}`);
|
|
126
|
+
}
|
|
127
|
+
async function editAnswers(answers) {
|
|
128
|
+
for (; ; ) {
|
|
129
|
+
printPreview(answers);
|
|
130
|
+
const choice = await ask(
|
|
131
|
+
"Edit a field (number), or press enter to confirm"
|
|
132
|
+
);
|
|
133
|
+
if (!choice) {
|
|
134
|
+
return answers;
|
|
135
|
+
}
|
|
136
|
+
switch (choice) {
|
|
137
|
+
case "1":
|
|
138
|
+
answers.projectName = await ask("Project name", answers.projectName);
|
|
139
|
+
break;
|
|
140
|
+
case "2":
|
|
141
|
+
answers.author = await ask("Author (composer vendor)", answers.author);
|
|
142
|
+
break;
|
|
143
|
+
case "3":
|
|
144
|
+
answers.cssTool = (await askChoice(
|
|
145
|
+
"CSS tooling",
|
|
146
|
+
["Tailwind", "SCSS"],
|
|
147
|
+
answers.cssTool === "tailwind" ? 0 : 1
|
|
148
|
+
)).toLowerCase().startsWith("tailwind") ? "tailwind" : "scss";
|
|
149
|
+
break;
|
|
150
|
+
case "4":
|
|
151
|
+
answers.npmPackages = splitList(
|
|
152
|
+
await ask(
|
|
153
|
+
"npm packages (comma-separated, optional)",
|
|
154
|
+
answers.npmPackages.join(", ")
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
break;
|
|
158
|
+
case "5":
|
|
159
|
+
answers.composerPackages = splitList(
|
|
160
|
+
await ask(
|
|
161
|
+
"composer packages (comma-separated, optional)",
|
|
162
|
+
answers.composerPackages.join(", ")
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
break;
|
|
166
|
+
case "6":
|
|
167
|
+
answers.adminUser = await ask("Admin username", answers.adminUser);
|
|
168
|
+
break;
|
|
169
|
+
case "7":
|
|
170
|
+
answers.adminPassword = await askPassword(
|
|
171
|
+
"Admin password",
|
|
172
|
+
answers.adminPassword
|
|
173
|
+
);
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
console.log("Not a valid field number.");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function scaffoldFiles(dir, answers) {
|
|
181
|
+
const namespace = toPascalCase(answers.author) + "\\" + toPascalCase(answers.projectName);
|
|
182
|
+
fs14.mkdirSync(path12.join(dir, "src", "js"), { recursive: true });
|
|
183
|
+
fs14.mkdirSync(path12.join(dir, "assets", "css"), { recursive: true });
|
|
184
|
+
fs14.mkdirSync(path12.join(dir, "assets", "js"), { recursive: true });
|
|
185
|
+
writeJson(path12.join(dir, "composer.json"), {
|
|
186
|
+
name: `${answers.author}/${answers.projectName}`,
|
|
187
|
+
type: "wp-app",
|
|
188
|
+
license: "proprietary",
|
|
189
|
+
require: { php: ">=8.0" },
|
|
190
|
+
autoload: { "psr-4": { [`${namespace}\\`]: "src/php/" } }
|
|
191
|
+
});
|
|
192
|
+
const cssDeps = answers.cssTool === "tailwind" ? { tailwindcss: "^4.3.0", "@tailwindcss/cli": "^4.3.0" } : { sass: "^1.103.0" };
|
|
193
|
+
const cssBuild = answers.cssTool === "tailwind" ? "tailwindcss -i src/css/main.css -o assets/css/main.css --minify" : "sass src/css/main.scss assets/css/main.css --style=compressed";
|
|
194
|
+
const cssWatch = answers.cssTool === "tailwind" ? "tailwindcss -i src/css/main.css -o assets/css/main.css --watch" : "sass src/css/main.scss assets/css/main.css --watch";
|
|
195
|
+
writeJson(path12.join(dir, "package.json"), {
|
|
196
|
+
name: answers.projectName,
|
|
197
|
+
private: true,
|
|
198
|
+
scripts: {
|
|
199
|
+
"build:css": cssBuild,
|
|
200
|
+
"watch:css": cssWatch,
|
|
201
|
+
"build:js": "esbuild src/js/main.js --bundle --minify --outfile=assets/js/main.js",
|
|
202
|
+
"watch:js": "esbuild src/js/main.js --bundle --outfile=assets/js/main.js --watch",
|
|
203
|
+
build: "npm run build:css && npm run build:js"
|
|
204
|
+
},
|
|
205
|
+
devDependencies: { esbuild: "^0.28.0", ...cssDeps }
|
|
206
|
+
});
|
|
207
|
+
fs14.writeFileSync(
|
|
208
|
+
path12.join(dir, ".env"),
|
|
209
|
+
`WP_ADMIN_USER=${answers.adminUser}
|
|
210
|
+
WP_ADMIN_PASSWORD=${answers.adminPassword}
|
|
211
|
+
`
|
|
212
|
+
);
|
|
213
|
+
fs14.writeFileSync(
|
|
214
|
+
path12.join(dir, "style.css"),
|
|
215
|
+
`/*
|
|
216
|
+
Theme Name: ${answers.projectName}
|
|
217
|
+
Author: ${answers.author}
|
|
218
|
+
Version: 0.1.0
|
|
219
|
+
*/
|
|
220
|
+
`
|
|
221
|
+
);
|
|
222
|
+
fs14.writeFileSync(
|
|
223
|
+
path12.join(dir, "functions.php"),
|
|
224
|
+
`<?php
|
|
225
|
+
declare(strict_types=1);
|
|
226
|
+
|
|
227
|
+
$autoload = __DIR__ . '/vendor/autoload.php';
|
|
228
|
+
if (file_exists($autoload)) {
|
|
229
|
+
require_once $autoload;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
add_action('wp_enqueue_scripts', function (): void {
|
|
233
|
+
wp_enqueue_style('${answers.projectName}', get_stylesheet_directory_uri() . '/assets/css/main.css');
|
|
234
|
+
wp_enqueue_script('${answers.projectName}', get_stylesheet_directory_uri() . '/assets/js/main.js', [], false, true);
|
|
235
|
+
});
|
|
236
|
+
`
|
|
237
|
+
);
|
|
238
|
+
fs14.writeFileSync(
|
|
239
|
+
path12.join(dir, "index.php"),
|
|
240
|
+
`<?php
|
|
241
|
+
declare(strict_types=1);
|
|
242
|
+
get_header();
|
|
243
|
+
while (have_posts()) : the_post();
|
|
244
|
+
the_content();
|
|
245
|
+
endwhile;
|
|
246
|
+
get_footer();
|
|
247
|
+
`
|
|
248
|
+
);
|
|
249
|
+
const cssEntry = answers.cssTool === "tailwind" ? '@import "tailwindcss";\n' : "// entry stylesheet\n";
|
|
250
|
+
fs14.mkdirSync(path12.join(dir, "src", "css"), { recursive: true });
|
|
251
|
+
fs14.writeFileSync(
|
|
252
|
+
path12.join(
|
|
253
|
+
dir,
|
|
254
|
+
"src",
|
|
255
|
+
"css",
|
|
256
|
+
answers.cssTool === "tailwind" ? "main.css" : "main.scss"
|
|
257
|
+
),
|
|
258
|
+
cssEntry
|
|
259
|
+
);
|
|
260
|
+
fs14.writeFileSync(path12.join(dir, "src", "js", "main.js"), "// entry script\n");
|
|
261
|
+
fs14.mkdirSync(path12.join(dir, "src", "php"), { recursive: true });
|
|
262
|
+
}
|
|
263
|
+
function writeJson(filePath, data) {
|
|
264
|
+
fs14.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
265
|
+
}
|
|
266
|
+
async function runCreateCommand(targetPath) {
|
|
267
|
+
const dir = path12.resolve(targetPath);
|
|
268
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
269
|
+
let answers = await collectAnswers();
|
|
270
|
+
answers = await editAnswers(answers);
|
|
271
|
+
rl.close();
|
|
272
|
+
scaffoldFiles(dir, answers);
|
|
273
|
+
const run = (cmd, args) => runCommand(cmd, args, { cwd: dir, stdio: "inherit" });
|
|
274
|
+
if (answers.npmPackages.length) {
|
|
275
|
+
console.log("Installing additional npm packages...");
|
|
276
|
+
run("npm", ["install", "--save", ...answers.npmPackages]);
|
|
277
|
+
} else {
|
|
278
|
+
console.log("Installing npm dependencies...");
|
|
279
|
+
run("npm", ["install"]);
|
|
280
|
+
}
|
|
281
|
+
if (answers.composerPackages.length) {
|
|
282
|
+
console.log("Requiring additional composer packages...");
|
|
283
|
+
run("composer", ["require", ...answers.composerPackages]);
|
|
284
|
+
} else {
|
|
285
|
+
console.log("Installing composer dependencies...");
|
|
286
|
+
try {
|
|
287
|
+
run("composer", ["install"]);
|
|
288
|
+
} catch {
|
|
289
|
+
console.log("(composer not available or nothing to install yet \u2014 skipped)");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
console.log(`
|
|
293
|
+
Done. cd ${targetPath} && wp-app start`);
|
|
294
|
+
}
|
|
295
|
+
var rl, CTRL_C, BACKSPACE_DEL, BACKSPACE_BS;
|
|
296
|
+
var init_create = __esm({
|
|
297
|
+
"src/create.ts"() {
|
|
298
|
+
init_exec_command();
|
|
299
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
300
|
+
CTRL_C = "";
|
|
301
|
+
BACKSPACE_DEL = "\x7F";
|
|
302
|
+
BACKSPACE_BS = "\b";
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
1
306
|
// src/run-cli.ts
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
307
|
+
init_exec_command();
|
|
308
|
+
import fs15 from "fs";
|
|
309
|
+
import path13 from "path";
|
|
310
|
+
import { parseArgs } from "util";
|
|
6
311
|
import { spawn, execSync } from "child_process";
|
|
7
312
|
|
|
8
313
|
// src/start-server.ts
|
|
9
|
-
import
|
|
314
|
+
import fs12 from "fs";
|
|
10
315
|
import { Readable } from "stream";
|
|
11
316
|
import { pipeline } from "stream/promises";
|
|
12
317
|
import express from "express";
|
|
13
318
|
|
|
14
319
|
// src/engine.ts
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import { createNodeFsMountHandler
|
|
18
|
-
|
|
320
|
+
import fs11 from "fs";
|
|
321
|
+
import path10 from "path";
|
|
322
|
+
import { createNodeFsMountHandler } from "@php-wasm/node";
|
|
323
|
+
|
|
324
|
+
// src/boot.ts
|
|
325
|
+
import { rootCertificates } from "tls";
|
|
326
|
+
import { loadNodeRuntime } from "@php-wasm/node";
|
|
327
|
+
import { PHP, PHPRequestHandler, setPhpIniEntries } from "@php-wasm/universal";
|
|
328
|
+
var CA_BUNDLE_PATH = "/internal/ca-bundle.crt";
|
|
329
|
+
async function createPhp(phpVersion) {
|
|
330
|
+
const php = new PHP(
|
|
331
|
+
await loadNodeRuntime(phpVersion, {
|
|
332
|
+
emscriptenOptions: { processId: process.pid }
|
|
333
|
+
})
|
|
334
|
+
);
|
|
335
|
+
php.setSapiName("cli");
|
|
336
|
+
php.mkdir("/internal");
|
|
337
|
+
php.writeFile(CA_BUNDLE_PATH, rootCertificates.join("\n"));
|
|
338
|
+
await setPhpIniEntries(php, {
|
|
339
|
+
"openssl.cafile": CA_BUNDLE_PATH,
|
|
340
|
+
"curl.cainfo": CA_BUNDLE_PATH,
|
|
341
|
+
allow_url_fopen: "1",
|
|
342
|
+
disable_functions: ""
|
|
343
|
+
});
|
|
344
|
+
return php;
|
|
345
|
+
}
|
|
346
|
+
function createRequestHandler(php, documentRoot, absoluteUrl) {
|
|
347
|
+
return new PHPRequestHandler({
|
|
348
|
+
php,
|
|
349
|
+
documentRoot,
|
|
350
|
+
absoluteUrl,
|
|
351
|
+
// Route unmatched URLs (permalinks, admin-ajax) to index.php, not 404.
|
|
352
|
+
getFileNotFoundAction: () => ({
|
|
353
|
+
type: "internal-redirect",
|
|
354
|
+
uri: "/index.php"
|
|
355
|
+
}),
|
|
356
|
+
// Let Set-Cookie pass through to the real HTTP server (express).
|
|
357
|
+
cookieStore: false
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async function runPhp(php, code) {
|
|
361
|
+
const response = await php.run({ code: `<?php ${code}` });
|
|
362
|
+
if (response.exitCode !== 0) {
|
|
363
|
+
throw new Error(response.errors || `PHP exited ${response.exitCode}`);
|
|
364
|
+
}
|
|
365
|
+
return response;
|
|
366
|
+
}
|
|
367
|
+
async function unzipTo(php, zip, destination) {
|
|
368
|
+
const tmpZip = `/internal/upload-${Date.now()}.zip`;
|
|
369
|
+
php.writeFile(tmpZip, new Uint8Array(await zip.arrayBuffer()));
|
|
370
|
+
await runPhp(
|
|
371
|
+
php,
|
|
372
|
+
`
|
|
373
|
+
$zip = new ZipArchive();
|
|
374
|
+
if ($zip->open(${str(tmpZip)}) !== true) exit(1);
|
|
375
|
+
if (!is_dir(${str(destination)})) mkdir(${str(destination)}, 0777, true);
|
|
376
|
+
if (!$zip->extractTo(${str(destination)})) exit(1);
|
|
377
|
+
$zip->close();
|
|
378
|
+
unlink(${str(tmpZip)});
|
|
379
|
+
`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
async function installCore(php, coreZip, docroot) {
|
|
383
|
+
const staging = `${docroot}/.wpapp-staging`;
|
|
384
|
+
await unzipTo(php, coreZip, staging);
|
|
385
|
+
await runPhp(
|
|
386
|
+
php,
|
|
387
|
+
`
|
|
388
|
+
function wpapp_core_root($dir) {
|
|
389
|
+
if (file_exists("$dir/wp-config-sample.php")) return $dir;
|
|
390
|
+
foreach (scandir($dir) as $entry) {
|
|
391
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
392
|
+
$candidate = "$dir/$entry";
|
|
393
|
+
if (is_dir($candidate) && file_exists("$candidate/wp-config-sample.php")) {
|
|
394
|
+
return $candidate;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
exit(1);
|
|
398
|
+
}
|
|
399
|
+
function wpapp_rrmdir($dir) {
|
|
400
|
+
foreach (scandir($dir) as $entry) {
|
|
401
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
402
|
+
$path = "$dir/$entry";
|
|
403
|
+
is_dir($path) ? wpapp_rrmdir($path) : unlink($path);
|
|
404
|
+
}
|
|
405
|
+
rmdir($dir);
|
|
406
|
+
}
|
|
407
|
+
$root = wpapp_core_root(${str(staging)});
|
|
408
|
+
foreach (scandir($root) as $entry) {
|
|
409
|
+
if ($entry === '.' || $entry === '..') continue;
|
|
410
|
+
rename("$root/$entry", ${str(docroot)} . "/$entry");
|
|
411
|
+
}
|
|
412
|
+
wpapp_rrmdir(${str(staging)});
|
|
413
|
+
if (!file_exists(${str(docroot)} . '/wp-config.php')) {
|
|
414
|
+
copy(
|
|
415
|
+
${str(docroot)} . '/wp-config-sample.php',
|
|
416
|
+
${str(docroot)} . '/wp-config.php'
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
async function installSqliteIntegration(php, pluginZip, docroot) {
|
|
423
|
+
const pluginDir = `${docroot}/wp-content/plugins/sqlite-database-integration`;
|
|
424
|
+
const staging = `${docroot}/wp-content/.wpapp-sqlite-staging`;
|
|
425
|
+
await unzipTo(php, pluginZip, staging);
|
|
426
|
+
await runPhp(
|
|
427
|
+
php,
|
|
428
|
+
`
|
|
429
|
+
$root = ${str(staging)};
|
|
430
|
+
$entries = array_values(array_diff(scandir($root), ['.', '..']));
|
|
431
|
+
if (count($entries) === 1 && is_dir("$root/$entries[0]")) {
|
|
432
|
+
$root = "$root/$entries[0]";
|
|
433
|
+
}
|
|
434
|
+
if (!is_dir(${str(pluginDir)})) {
|
|
435
|
+
rename($root, ${str(pluginDir)});
|
|
436
|
+
}
|
|
437
|
+
if (is_dir(${str(staging)})) {
|
|
438
|
+
@rmdir(${str(staging)});
|
|
439
|
+
}
|
|
440
|
+
$dropIn = file_get_contents(${str(pluginDir)} . '/db.copy');
|
|
441
|
+
$dropIn = str_replace(
|
|
442
|
+
'{SQLITE_IMPLEMENTATION_FOLDER_PATH}',
|
|
443
|
+
${str(pluginDir)},
|
|
444
|
+
$dropIn
|
|
445
|
+
);
|
|
446
|
+
$dropIn = str_replace(
|
|
447
|
+
'{SQLITE_PLUGIN}',
|
|
448
|
+
'sqlite-database-integration/load.php',
|
|
449
|
+
$dropIn
|
|
450
|
+
);
|
|
451
|
+
file_put_contents(${str(docroot)} . '/wp-content/db.php', $dropIn);
|
|
452
|
+
`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
function defineSiteConstants(php, siteUrl, databaseDir) {
|
|
456
|
+
php.defineConstant("WP_HOME", siteUrl);
|
|
457
|
+
php.defineConstant("WP_SITEURL", siteUrl);
|
|
458
|
+
php.defineConstant("DB_DIR", databaseDir);
|
|
459
|
+
php.defineConstant("DB_FILE", ".ht.sqlite");
|
|
460
|
+
php.defineConstant("WP_SQLITE_AST_DRIVER", true);
|
|
461
|
+
php.defineConstant("DISABLE_WP_CRON", true);
|
|
462
|
+
}
|
|
463
|
+
async function runInstaller(requestHandler) {
|
|
464
|
+
const adminUser = process.env.WP_ADMIN_USER || "admin";
|
|
465
|
+
const adminPassword = process.env.WP_ADMIN_PASSWORD || "password";
|
|
466
|
+
const adminEmail = process.env.WP_ADMIN_EMAIL || "admin@localhost.com";
|
|
467
|
+
const fields = new URLSearchParams({
|
|
468
|
+
language: "en",
|
|
469
|
+
prefix: "wp_",
|
|
470
|
+
weblog_title: "My WordPress Website",
|
|
471
|
+
user_name: adminUser,
|
|
472
|
+
admin_password: adminPassword,
|
|
473
|
+
admin_password2: adminPassword,
|
|
474
|
+
Submit: "Install WordPress",
|
|
475
|
+
pw_weak: "1",
|
|
476
|
+
admin_email: adminEmail
|
|
477
|
+
});
|
|
478
|
+
return requestHandler.request({
|
|
479
|
+
url: "/wp-admin/install.php?step=2",
|
|
480
|
+
method: "POST",
|
|
481
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
482
|
+
body: new TextEncoder().encode(fields.toString())
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function str(value) {
|
|
486
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
487
|
+
}
|
|
19
488
|
|
|
20
489
|
// src/constants.ts
|
|
21
|
-
import { RecommendedPHPVersion } from "@wp-playground/common";
|
|
22
490
|
var WP_APP_HIDDEN_FOLDER = ".wp-app";
|
|
23
491
|
var WP_APP_HOME_ENV = "WP_APP_HOME";
|
|
24
492
|
var SQLITE_URL = "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/main.zip";
|
|
25
493
|
var CLASSICPRESS_LATEST_URL = "https://www.classicpress.net/latest.zip";
|
|
26
494
|
var DEFAULT_PORT = 8881;
|
|
27
|
-
var DEFAULT_PHP_VERSION =
|
|
495
|
+
var DEFAULT_PHP_VERSION = "8.3";
|
|
28
496
|
var DEFAULT_WORDPRESS_VERSION = "latest";
|
|
29
497
|
var DOCROOT = "/wordpress";
|
|
30
498
|
|
|
31
499
|
// src/download.ts
|
|
32
|
-
import
|
|
33
|
-
import
|
|
500
|
+
import fs10 from "fs";
|
|
501
|
+
import path9 from "path";
|
|
34
502
|
|
|
35
|
-
// src/
|
|
503
|
+
// src/detect/has-index-file.ts
|
|
36
504
|
import fs from "fs";
|
|
37
505
|
import path from "path";
|
|
38
506
|
function hasIndexFile(projectPath) {
|
|
39
507
|
return fs.existsSync(path.join(projectPath, "index.php"));
|
|
40
508
|
}
|
|
41
509
|
|
|
42
|
-
// src/
|
|
510
|
+
// src/detect/is-valid-wordpress-version.ts
|
|
43
511
|
function isValidWordPressVersion(version) {
|
|
44
512
|
const versionPattern = /^latest$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/;
|
|
45
|
-
const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?)?$/;
|
|
513
|
+
const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?(?:-rc(?:\d+)?)?)?$/;
|
|
46
514
|
return versionPattern.test(version) || classicPressPattern.test(version);
|
|
47
515
|
}
|
|
48
516
|
function isClassicPressVersion(version) {
|
|
49
517
|
return version === "classicpress" || version.startsWith("classicpress-");
|
|
50
518
|
}
|
|
51
519
|
|
|
52
|
-
// src/
|
|
520
|
+
// src/detect/get-plugin-file.ts
|
|
53
521
|
import fs3 from "fs";
|
|
54
522
|
import path2, { basename } from "path";
|
|
55
523
|
|
|
56
|
-
// src/
|
|
524
|
+
// src/detect/read-file-head.ts
|
|
57
525
|
import fs2 from "fs";
|
|
58
526
|
function readFileHead(filePath, length = 8192) {
|
|
59
527
|
const buffer = Buffer.alloc(length);
|
|
@@ -64,7 +532,7 @@ function readFileHead(filePath, length = 8192) {
|
|
|
64
532
|
return fileContentBuffer.toString();
|
|
65
533
|
}
|
|
66
534
|
|
|
67
|
-
// src/
|
|
535
|
+
// src/detect/get-plugin-file.ts
|
|
68
536
|
function heuristicSort(files, projectPath) {
|
|
69
537
|
const heuristicsBestGuess = `${basename(projectPath)}.php`;
|
|
70
538
|
const heuristicsBestGuessIndex = files.indexOf(heuristicsBestGuess);
|
|
@@ -88,13 +556,13 @@ function getPluginFile(projectPath) {
|
|
|
88
556
|
return null;
|
|
89
557
|
}
|
|
90
558
|
|
|
91
|
-
// src/
|
|
559
|
+
// src/detect/is-plugin-directory.ts
|
|
92
560
|
function isPluginDirectory(projectPath) {
|
|
93
561
|
const pluginFile = getPluginFile(projectPath);
|
|
94
562
|
return pluginFile !== null;
|
|
95
563
|
}
|
|
96
564
|
|
|
97
|
-
// src/
|
|
565
|
+
// src/detect/is-theme-directory.ts
|
|
98
566
|
import fs4 from "fs";
|
|
99
567
|
import path3 from "path";
|
|
100
568
|
function isThemeDirectory(projectPath) {
|
|
@@ -107,43 +575,54 @@ function isThemeDirectory(projectPath) {
|
|
|
107
575
|
return themeNameRegex.test(styleCSS);
|
|
108
576
|
}
|
|
109
577
|
|
|
110
|
-
// src/
|
|
578
|
+
// src/detect/is-wp-app-project.ts
|
|
111
579
|
import fs5 from "fs";
|
|
112
580
|
import path4 from "path";
|
|
581
|
+
function isWpAppProject(projectPath) {
|
|
582
|
+
const composerJsonPath = path4.join(projectPath, "composer.json");
|
|
583
|
+
if (!fs5.existsSync(composerJsonPath)) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
const composerJson = JSON.parse(fs5.readFileSync(composerJsonPath, "utf8"));
|
|
588
|
+
return composerJson.type === "wp-app";
|
|
589
|
+
} catch {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/detect/is-wp-content-directory.ts
|
|
595
|
+
import fs6 from "fs";
|
|
596
|
+
import path5 from "path";
|
|
113
597
|
function isWpContentDirectory(projectPath) {
|
|
114
|
-
const muPluginsExists =
|
|
115
|
-
const pluginsExists =
|
|
116
|
-
const themesExists =
|
|
598
|
+
const muPluginsExists = fs6.existsSync(path5.join(projectPath, "mu-plugins"));
|
|
599
|
+
const pluginsExists = fs6.existsSync(path5.join(projectPath, "plugins"));
|
|
600
|
+
const themesExists = fs6.existsSync(path5.join(projectPath, "themes"));
|
|
117
601
|
if (muPluginsExists || pluginsExists || themesExists) {
|
|
118
602
|
return true;
|
|
119
603
|
}
|
|
120
604
|
return false;
|
|
121
605
|
}
|
|
122
606
|
|
|
123
|
-
// src/
|
|
124
|
-
import
|
|
125
|
-
import
|
|
607
|
+
// src/detect/is-wordpress-directory.ts
|
|
608
|
+
import fs7 from "fs";
|
|
609
|
+
import path6 from "path";
|
|
126
610
|
function isWordPressDirectory(projectPath) {
|
|
127
|
-
return
|
|
611
|
+
return fs7.existsSync(path6.join(projectPath, "wp-content")) && fs7.existsSync(path6.join(projectPath, "wp-includes")) && fs7.existsSync(path6.join(projectPath, "wp-load.php"));
|
|
128
612
|
}
|
|
129
613
|
|
|
130
|
-
// src/
|
|
131
|
-
import
|
|
132
|
-
import
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
"build/wp-load.php"
|
|
143
|
-
];
|
|
144
|
-
return requiredFiles.every(
|
|
145
|
-
(file) => fs7.existsSync(path6.join(projectPath, file))
|
|
146
|
-
);
|
|
614
|
+
// src/detect/is-classicpress-directory.ts
|
|
615
|
+
import fs8 from "fs";
|
|
616
|
+
import path7 from "path";
|
|
617
|
+
function isClassicPressDirectory(projectPath) {
|
|
618
|
+
if (!isWordPressDirectory(projectPath)) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
const versionFile = path7.join(projectPath, "wp-includes", "version.php");
|
|
622
|
+
if (!fs8.existsSync(versionFile)) {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
return fs8.readFileSync(versionFile, "utf8").includes("classicpress_version");
|
|
147
626
|
}
|
|
148
627
|
|
|
149
628
|
// src/output.ts
|
|
@@ -154,24 +633,54 @@ var output = shouldOutput() ? console : null;
|
|
|
154
633
|
|
|
155
634
|
// src/paths.ts
|
|
156
635
|
import crypto from "crypto";
|
|
157
|
-
import
|
|
636
|
+
import fs9 from "fs";
|
|
158
637
|
import os from "os";
|
|
159
|
-
import
|
|
638
|
+
import path8 from "path";
|
|
160
639
|
function getWpAppHome() {
|
|
161
|
-
|
|
640
|
+
const home = process.env[WP_APP_HOME_ENV] || path8.join(os.homedir(), WP_APP_HIDDEN_FOLDER);
|
|
641
|
+
cleanupLegacyHome(home);
|
|
642
|
+
return home;
|
|
162
643
|
}
|
|
163
644
|
function getCachePath() {
|
|
164
|
-
return ensureDir(
|
|
645
|
+
return ensureDir(path8.join(getWpAppHome(), "cache"));
|
|
646
|
+
}
|
|
647
|
+
function getCorePath(wordPressVersion) {
|
|
648
|
+
const key = wordPressVersion.replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
649
|
+
return path8.join(getWpAppHome(), "core", key);
|
|
165
650
|
}
|
|
166
651
|
function getSitePath(projectPath) {
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
652
|
+
const resolved = path8.resolve(projectPath);
|
|
653
|
+
const identity = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
654
|
+
const projectName = path8.basename(resolved);
|
|
655
|
+
const hash = crypto.createHash("sha1").update(identity).digest("hex");
|
|
656
|
+
return path8.join(getWpAppHome(), "sites", `${projectName}-${hash}`);
|
|
657
|
+
}
|
|
658
|
+
function getProjectLocalPath(projectPath) {
|
|
659
|
+
return path8.join(path8.resolve(projectPath), ".local", "wp-app");
|
|
170
660
|
}
|
|
171
661
|
function ensureDir(dir) {
|
|
172
|
-
|
|
662
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
173
663
|
return dir;
|
|
174
664
|
}
|
|
665
|
+
var LEGACY_TOP_LEVEL_DIRS = [
|
|
666
|
+
"wordpress-versions",
|
|
667
|
+
"wp-content",
|
|
668
|
+
"mu-plugins",
|
|
669
|
+
"sqlite-database-integration-main"
|
|
670
|
+
];
|
|
671
|
+
var legacyCleanupDone = false;
|
|
672
|
+
function cleanupLegacyHome(home) {
|
|
673
|
+
if (legacyCleanupDone) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
legacyCleanupDone = true;
|
|
677
|
+
for (const name of LEGACY_TOP_LEVEL_DIRS) {
|
|
678
|
+
const dir = path8.join(home, name);
|
|
679
|
+
if (fs9.existsSync(dir)) {
|
|
680
|
+
fs9.rmSync(dir, { recursive: true, force: true });
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
175
684
|
|
|
176
685
|
// src/download.ts
|
|
177
686
|
function getWordPressVersionUrl(version) {
|
|
@@ -190,8 +699,8 @@ function getClassicPressVersionUrl(version) {
|
|
|
190
699
|
return `https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${tag}.zip`;
|
|
191
700
|
}
|
|
192
701
|
async function cachedZip(url, cacheKey) {
|
|
193
|
-
const cacheFile =
|
|
194
|
-
if (!
|
|
702
|
+
const cacheFile = path9.join(getCachePath(), cacheKey);
|
|
703
|
+
if (!fs10.existsSync(cacheFile)) {
|
|
195
704
|
output?.log(`Downloading ${cacheKey}...`);
|
|
196
705
|
const response = await fetch(url, { redirect: "follow" });
|
|
197
706
|
if (!response.ok) {
|
|
@@ -200,11 +709,11 @@ async function cachedZip(url, cacheKey) {
|
|
|
200
709
|
);
|
|
201
710
|
}
|
|
202
711
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
203
|
-
|
|
204
|
-
|
|
712
|
+
fs10.writeFileSync(`${cacheFile}.partial`, bytes);
|
|
713
|
+
fs10.renameSync(`${cacheFile}.partial`, cacheFile);
|
|
205
714
|
output?.log(`Cached ${cacheKey} (${bytes.length} bytes).`);
|
|
206
715
|
}
|
|
207
|
-
return new File([
|
|
716
|
+
return new File([fs10.readFileSync(cacheFile)], cacheKey);
|
|
208
717
|
}
|
|
209
718
|
async function getCoreZip(version) {
|
|
210
719
|
if (isClassicPressVersion(version)) {
|
|
@@ -255,11 +764,6 @@ var PortFinder = class _PortFinder {
|
|
|
255
764
|
});
|
|
256
765
|
});
|
|
257
766
|
}
|
|
258
|
-
/**
|
|
259
|
-
* Returns the first available open port, caching and reusing it for subsequent calls.
|
|
260
|
-
*
|
|
261
|
-
* @returns {Promise<number>} A promise that resolves to the open port number.
|
|
262
|
-
*/
|
|
263
767
|
async getOpenPort() {
|
|
264
768
|
if (this.#openPort) {
|
|
265
769
|
return this.#openPort;
|
|
@@ -311,7 +815,11 @@ async function getWpAppConfig(args) {
|
|
|
311
815
|
}
|
|
312
816
|
});
|
|
313
817
|
if (!options.mode || options.mode === "auto") {
|
|
314
|
-
|
|
818
|
+
const inferred = inferMode(options.projectPath);
|
|
819
|
+
if (!inferred) {
|
|
820
|
+
process.exit(1);
|
|
821
|
+
}
|
|
822
|
+
options.mode = inferred;
|
|
315
823
|
}
|
|
316
824
|
if (!options.absoluteUrl) {
|
|
317
825
|
options.absoluteUrl = await getAbsoluteURL();
|
|
@@ -331,43 +839,71 @@ async function getWpAppConfig(args) {
|
|
|
331
839
|
|
|
332
840
|
// src/engine.ts
|
|
333
841
|
async function startWPApp(options) {
|
|
334
|
-
const projectPath =
|
|
842
|
+
const projectPath = path10.resolve(options.projectPath);
|
|
335
843
|
const mode = options.mode;
|
|
336
844
|
output?.log(`directory: ${options.projectPath}`);
|
|
337
845
|
output?.log(`mode: ${mode}`);
|
|
338
846
|
output?.log(`php: ${options.phpVersion}`);
|
|
339
|
-
const
|
|
847
|
+
const sharesCore = mode === "wp-app" /* WP_APP */ || mode === "theme" /* THEME */ || mode === "plugin" /* PLUGIN */ || mode === "wp-content" /* WP_CONTENT */;
|
|
848
|
+
const hostDocroot = mode === "index" /* INDEX */ || mode === "wordpress" /* WORDPRESS */ || mode === "classicpress" /* CLASSICPRESS */ ? projectPath : ensureDir(getCorePath(options.wordPressVersion));
|
|
849
|
+
const projectDataRoot = sharesCore ? ensureDir(
|
|
850
|
+
mode === "wp-app" /* WP_APP */ ? getProjectLocalPath(projectPath) : getSitePath(projectPath)
|
|
851
|
+
) : null;
|
|
340
852
|
const isWordPressBacked = mode !== "index" /* INDEX */;
|
|
341
|
-
const
|
|
853
|
+
const freshCore = isWordPressBacked && !fs11.existsSync(path10.join(hostDocroot, "wp-load.php"));
|
|
854
|
+
const freshSite = sharesCore ? !fs11.existsSync(path10.join(projectDataRoot, "database", ".ht.sqlite")) : freshCore;
|
|
342
855
|
if (isWordPressBacked) {
|
|
343
856
|
output?.log(`wp: ${options.wordPressVersion}`);
|
|
344
|
-
output?.log(`site data: ${hostDocroot}`);
|
|
345
|
-
}
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
857
|
+
output?.log(`site data: ${projectDataRoot ?? hostDocroot}`);
|
|
858
|
+
}
|
|
859
|
+
const php = await createPhp(options.phpVersion);
|
|
860
|
+
php.mkdir(DOCROOT);
|
|
861
|
+
await php.mount(DOCROOT, createNodeFsMountHandler(hostDocroot));
|
|
862
|
+
const requestHandler = createRequestHandler(
|
|
863
|
+
php,
|
|
864
|
+
DOCROOT,
|
|
865
|
+
options.absoluteUrl
|
|
866
|
+
);
|
|
867
|
+
if (freshCore) {
|
|
868
|
+
await installCore(
|
|
869
|
+
php,
|
|
870
|
+
await getCoreZip(options.wordPressVersion),
|
|
871
|
+
DOCROOT
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
if (isWordPressBacked) {
|
|
875
|
+
if (!php.fileExists(`${DOCROOT}/wp-content/db.php`)) {
|
|
876
|
+
await installSqliteIntegration(
|
|
877
|
+
php,
|
|
878
|
+
await getSqliteIntegrationZip(),
|
|
879
|
+
DOCROOT
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (projectDataRoot) {
|
|
884
|
+
const databaseDir = ensureDir(path10.join(projectDataRoot, "database"));
|
|
885
|
+
const uploadsDir = ensureDir(path10.join(projectDataRoot, "uploads"));
|
|
886
|
+
php.mkdir(`${DOCROOT}/wp-content/database`);
|
|
887
|
+
await php.mount(
|
|
888
|
+
`${DOCROOT}/wp-content/database`,
|
|
889
|
+
createNodeFsMountHandler(databaseDir)
|
|
890
|
+
);
|
|
891
|
+
php.mkdir(`${DOCROOT}/wp-content/uploads`);
|
|
892
|
+
await php.mount(
|
|
893
|
+
`${DOCROOT}/wp-content/uploads`,
|
|
894
|
+
createNodeFsMountHandler(uploadsDir)
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
if (isWordPressBacked) {
|
|
898
|
+
defineSiteConstants(
|
|
899
|
+
php,
|
|
900
|
+
options.absoluteUrl,
|
|
901
|
+
`${DOCROOT}/wp-content/database`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
if (freshSite) {
|
|
905
|
+
await runInstaller(requestHandler);
|
|
906
|
+
}
|
|
371
907
|
await applyProjectMounts(php, mode, projectPath);
|
|
372
908
|
if (freshSite) {
|
|
373
909
|
await activateProject(php, mode, projectPath);
|
|
@@ -375,8 +911,9 @@ async function startWPApp(options) {
|
|
|
375
911
|
return { requestHandler, php, options, freshSite };
|
|
376
912
|
}
|
|
377
913
|
async function applyProjectMounts(php, mode, projectPath) {
|
|
378
|
-
const projectName =
|
|
914
|
+
const projectName = path10.basename(projectPath);
|
|
379
915
|
switch (mode) {
|
|
916
|
+
case "wp-app" /* WP_APP */:
|
|
380
917
|
case "theme" /* THEME */:
|
|
381
918
|
await mountAt(
|
|
382
919
|
php,
|
|
@@ -392,13 +929,13 @@ async function applyProjectMounts(php, mode, projectPath) {
|
|
|
392
929
|
);
|
|
393
930
|
break;
|
|
394
931
|
case "wp-content" /* WP_CONTENT */:
|
|
395
|
-
for (const entry of
|
|
932
|
+
for (const entry of fs11.readdirSync(projectPath)) {
|
|
396
933
|
if (entry === "index.php") {
|
|
397
934
|
continue;
|
|
398
935
|
}
|
|
399
936
|
await mountAt(
|
|
400
937
|
php,
|
|
401
|
-
|
|
938
|
+
path10.join(projectPath, entry),
|
|
402
939
|
`${DOCROOT}/wp-content/${entry}`
|
|
403
940
|
);
|
|
404
941
|
}
|
|
@@ -408,14 +945,14 @@ async function applyProjectMounts(php, mode, projectPath) {
|
|
|
408
945
|
}
|
|
409
946
|
}
|
|
410
947
|
async function mountAt(php, hostPath, vfsPath) {
|
|
411
|
-
if (
|
|
948
|
+
if (fs11.statSync(hostPath).isDirectory()) {
|
|
412
949
|
php.mkdir(vfsPath);
|
|
413
950
|
}
|
|
414
951
|
await php.mount(vfsPath, createNodeFsMountHandler(hostPath));
|
|
415
952
|
}
|
|
416
953
|
async function activateProject(php, mode, projectPath) {
|
|
417
|
-
const projectName =
|
|
418
|
-
if (mode === "theme" /* THEME */) {
|
|
954
|
+
const projectName = path10.basename(projectPath);
|
|
955
|
+
if (mode === "wp-app" /* WP_APP */ || mode === "theme" /* THEME */) {
|
|
419
956
|
await runWordPressCode(
|
|
420
957
|
php,
|
|
421
958
|
`switch_theme(${phpString(projectName)});`
|
|
@@ -442,15 +979,17 @@ async function activateProject(php, mode, projectPath) {
|
|
|
442
979
|
}
|
|
443
980
|
}
|
|
444
981
|
async function runWordPressCode(php, code) {
|
|
445
|
-
|
|
446
|
-
|
|
982
|
+
try {
|
|
983
|
+
await runPhp(
|
|
984
|
+
php,
|
|
985
|
+
`
|
|
447
986
|
require ${phpString(`${DOCROOT}/wp-load.php`)};
|
|
448
987
|
require_once ${phpString(`${DOCROOT}/wp-admin/includes/plugin.php`)};
|
|
449
988
|
${code}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
output?.error(`Activation step failed: ${
|
|
989
|
+
`
|
|
990
|
+
);
|
|
991
|
+
} catch (error) {
|
|
992
|
+
output?.error(`Activation step failed: ${error.message}`);
|
|
454
993
|
}
|
|
455
994
|
}
|
|
456
995
|
function phpString(value) {
|
|
@@ -458,11 +997,11 @@ function phpString(value) {
|
|
|
458
997
|
}
|
|
459
998
|
function findPluginFile(projectPath) {
|
|
460
999
|
const pluginNameRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;
|
|
461
|
-
for (const file of
|
|
1000
|
+
for (const file of fs11.readdirSync(projectPath)) {
|
|
462
1001
|
if (!file.endsWith(".php")) {
|
|
463
1002
|
continue;
|
|
464
1003
|
}
|
|
465
|
-
const content =
|
|
1004
|
+
const content = fs11.readFileSync(path10.join(projectPath, file), "utf8");
|
|
466
1005
|
if (pluginNameRegex.test(content)) {
|
|
467
1006
|
return file;
|
|
468
1007
|
}
|
|
@@ -470,8 +1009,10 @@ function findPluginFile(projectPath) {
|
|
|
470
1009
|
return null;
|
|
471
1010
|
}
|
|
472
1011
|
function inferMode(projectPath) {
|
|
473
|
-
if (
|
|
474
|
-
return "
|
|
1012
|
+
if (isWpAppProject(projectPath)) {
|
|
1013
|
+
return "wp-app" /* WP_APP */;
|
|
1014
|
+
} else if (isClassicPressDirectory(projectPath)) {
|
|
1015
|
+
return "classicpress" /* CLASSICPRESS */;
|
|
475
1016
|
} else if (isWordPressDirectory(projectPath)) {
|
|
476
1017
|
return "wordpress" /* WORDPRESS */;
|
|
477
1018
|
} else if (isWpContentDirectory(projectPath)) {
|
|
@@ -483,12 +1024,12 @@ function inferMode(projectPath) {
|
|
|
483
1024
|
} else if (hasIndexFile(projectPath)) {
|
|
484
1025
|
return "index" /* INDEX */;
|
|
485
1026
|
}
|
|
486
|
-
return
|
|
1027
|
+
return null;
|
|
487
1028
|
}
|
|
488
1029
|
|
|
489
1030
|
// src/start-server.ts
|
|
490
1031
|
async function startServer(options = {}) {
|
|
491
|
-
if (!
|
|
1032
|
+
if (!fs12.existsSync(options.projectPath)) {
|
|
492
1033
|
throw new Error(
|
|
493
1034
|
`The given path "${options.projectPath}" does not exist.`
|
|
494
1035
|
);
|
|
@@ -559,23 +1100,23 @@ var parseHeaders = (req) => {
|
|
|
559
1100
|
};
|
|
560
1101
|
|
|
561
1102
|
// src/execute-php.ts
|
|
562
|
-
import
|
|
563
|
-
import
|
|
1103
|
+
import fs13 from "fs";
|
|
1104
|
+
import path11 from "path";
|
|
564
1105
|
import { loadNodeRuntime as loadNodeRuntime2 } from "@php-wasm/node";
|
|
565
|
-
import { PHP } from "@php-wasm/universal";
|
|
1106
|
+
import { PHP as PHP2 } from "@php-wasm/universal";
|
|
566
1107
|
async function executePHP(phpArgs, options) {
|
|
567
1108
|
const args = phpArgs.filter((arg) => arg !== "php" && arg !== "--");
|
|
568
1109
|
let code;
|
|
569
1110
|
if (args[0] === "-r" && typeof args[1] === "string") {
|
|
570
1111
|
code = `<?php ${args[1]}`;
|
|
571
|
-
} else if (args[0] &&
|
|
572
|
-
code =
|
|
1112
|
+
} else if (args[0] && fs13.existsSync(path11.resolve(args[0]))) {
|
|
1113
|
+
code = fs13.readFileSync(path11.resolve(args[0]), "utf8");
|
|
573
1114
|
} else {
|
|
574
1115
|
throw new Error(
|
|
575
1116
|
'Usage: wp-app php -- -r "<code>" | wp-app php -- <script.php>'
|
|
576
1117
|
);
|
|
577
1118
|
}
|
|
578
|
-
const php = new
|
|
1119
|
+
const php = new PHP2(
|
|
579
1120
|
await loadNodeRuntime2(options.phpVersion, {
|
|
580
1121
|
emscriptenOptions: { processId: process.pid }
|
|
581
1122
|
})
|
|
@@ -591,14 +1132,42 @@ async function executePHP(phpArgs, options) {
|
|
|
591
1132
|
// src/run-cli.ts
|
|
592
1133
|
var MODE_CHOICES = [
|
|
593
1134
|
"auto",
|
|
1135
|
+
"wp-app",
|
|
594
1136
|
"plugin",
|
|
595
1137
|
"theme",
|
|
596
1138
|
"wordpress",
|
|
597
|
-
"
|
|
1139
|
+
"classicpress",
|
|
598
1140
|
"wp-content",
|
|
599
|
-
"index"
|
|
600
|
-
"playground"
|
|
1141
|
+
"index"
|
|
601
1142
|
];
|
|
1143
|
+
var SERVER_OPTIONS = {
|
|
1144
|
+
path: { type: "string" },
|
|
1145
|
+
php: { type: "string" },
|
|
1146
|
+
wp: { type: "string" },
|
|
1147
|
+
port: { type: "string" },
|
|
1148
|
+
mode: { type: "string" }
|
|
1149
|
+
};
|
|
1150
|
+
var HELP = `wp-app <command> [args]
|
|
1151
|
+
|
|
1152
|
+
Commands:
|
|
1153
|
+
wp-app start Start the server
|
|
1154
|
+
wp-app php Run the php command passing the arguments to php cli
|
|
1155
|
+
wp-app build Regenerate the composer autoloader (if vendor/ exists) and run the npm build script, when present
|
|
1156
|
+
wp-app dev Start the server; watch CSS/JS source files and rebuild + refresh on change
|
|
1157
|
+
wp-app install Run npm install and composer install
|
|
1158
|
+
wp-app update Run composer update (optionally scoped to specific packages)
|
|
1159
|
+
wp-app create Scaffold a new wp-app project
|
|
1160
|
+
|
|
1161
|
+
Options:
|
|
1162
|
+
--path=<dir> Path to the PHP or WordPress project. Defaults to the current working directory.
|
|
1163
|
+
--php=<ver> PHP version to use.
|
|
1164
|
+
--wp=<ver> WordPress version to use, e.g. '--wp=6.4'. Use '--wp=classicpress' for ClassicPress.
|
|
1165
|
+
--port=<n> Server port
|
|
1166
|
+
--mode=<mode> Project mode (${MODE_CHOICES.join("|")}). Defaults to auto-detection.
|
|
1167
|
+
--reset (start) Create a new site environment, destroying the old one.
|
|
1168
|
+
--no-open (start/dev) Don't open the site in the default browser.
|
|
1169
|
+
-h, --help Show this help
|
|
1170
|
+
`;
|
|
602
1171
|
function startSpinner(message) {
|
|
603
1172
|
process.stdout.write(`${message}...
|
|
604
1173
|
`);
|
|
@@ -611,218 +1180,219 @@ function startSpinner(message) {
|
|
|
611
1180
|
}
|
|
612
1181
|
};
|
|
613
1182
|
}
|
|
614
|
-
function
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
describe: "PHP version to use.",
|
|
620
|
-
type: "string"
|
|
621
|
-
});
|
|
1183
|
+
function fail(message) {
|
|
1184
|
+
console.error(`wp-app: ${message}
|
|
1185
|
+
`);
|
|
1186
|
+
console.error(HELP);
|
|
1187
|
+
process.exit(1);
|
|
622
1188
|
}
|
|
623
|
-
function
|
|
624
|
-
|
|
625
|
-
yargs2.option("wp", {
|
|
626
|
-
describe: "WordPress version to use, e.g. '--wp=6.4'. Use '--wp=classicpress' for ClassicPress.",
|
|
627
|
-
type: "string"
|
|
628
|
-
});
|
|
629
|
-
yargs2.option("port", {
|
|
630
|
-
describe: "Server port",
|
|
631
|
-
type: "number"
|
|
632
|
-
});
|
|
633
|
-
yargs2.option("mode", {
|
|
634
|
-
describe: "Project mode. Defaults to auto-detection from the project directory.",
|
|
635
|
-
type: "string",
|
|
636
|
-
choices: MODE_CHOICES
|
|
637
|
-
});
|
|
1189
|
+
function hasNegatedFlag(args, flag) {
|
|
1190
|
+
return args.includes(`--no-${flag}`);
|
|
638
1191
|
}
|
|
639
1192
|
async function runCli() {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1193
|
+
const rawArgs = process.argv.slice(2);
|
|
1194
|
+
if (rawArgs.includes("-h") || rawArgs.includes("--help")) {
|
|
1195
|
+
console.log(HELP);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
const command = rawArgs[0];
|
|
1199
|
+
if (!command || command.startsWith("-")) {
|
|
1200
|
+
fail("You must provide a valid command");
|
|
1201
|
+
}
|
|
1202
|
+
if (command === "php") {
|
|
1203
|
+
return runPhpCommand(rawArgs);
|
|
1204
|
+
}
|
|
1205
|
+
if (command === "create") {
|
|
1206
|
+
const { runCreateCommand: runCreateCommand2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
1207
|
+
const target = rawArgs[1] || ".";
|
|
1208
|
+
return runCreateCommand2(target);
|
|
1209
|
+
}
|
|
1210
|
+
const { values } = parseArgs({
|
|
1211
|
+
args: rawArgs.slice(1),
|
|
1212
|
+
options: { ...SERVER_OPTIONS, reset: { type: "boolean" } },
|
|
1213
|
+
allowPositionals: true,
|
|
1214
|
+
strict: false
|
|
1215
|
+
});
|
|
1216
|
+
if (command === "install") {
|
|
1217
|
+
return runInstallCommand(values);
|
|
1218
|
+
}
|
|
1219
|
+
if (command === "update") {
|
|
1220
|
+
return runUpdateCommand(values, rawArgs);
|
|
1221
|
+
}
|
|
1222
|
+
if (values.mode && !MODE_CHOICES.includes(values.mode)) {
|
|
1223
|
+
fail(
|
|
1224
|
+
`Invalid mode "${values.mode}". Choices: ${MODE_CHOICES.join(", ")}`
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
const config = {
|
|
1228
|
+
php: values.php,
|
|
1229
|
+
path: values.path
|
|
1230
|
+
};
|
|
1231
|
+
config.wp = values.wp;
|
|
1232
|
+
config.port = values.port ? Number(values.port) : void 0;
|
|
1233
|
+
config.mode = values.mode;
|
|
1234
|
+
try {
|
|
1235
|
+
await getWpAppConfig(config);
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
fail(error.message);
|
|
1238
|
+
}
|
|
1239
|
+
switch (command) {
|
|
1240
|
+
case "start":
|
|
1241
|
+
return runStartCommand(values, rawArgs);
|
|
1242
|
+
case "build":
|
|
1243
|
+
return runBuildCommand(values);
|
|
1244
|
+
case "dev":
|
|
1245
|
+
return runDevCommand(values, rawArgs);
|
|
1246
|
+
default:
|
|
1247
|
+
fail(`Unknown command "${command}"`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async function runStartCommand(values, rawArgs) {
|
|
1251
|
+
const spinner = startSpinner("Starting the server...");
|
|
1252
|
+
try {
|
|
1253
|
+
const options = await getWpAppConfig({
|
|
1254
|
+
path: values.path,
|
|
1255
|
+
php: values.php,
|
|
1256
|
+
wp: values.wp,
|
|
1257
|
+
port: values.port ? Number(values.port) : void 0,
|
|
1258
|
+
mode: values.mode
|
|
1259
|
+
});
|
|
1260
|
+
portFinder.setPort(options.port);
|
|
1261
|
+
if (values.reset) {
|
|
1262
|
+
if (["wordpress", "classicpress", "index"].includes(
|
|
1263
|
+
options.mode
|
|
1264
|
+
)) {
|
|
1265
|
+
output?.log(
|
|
1266
|
+
"--reset only applies to wp-app managed sites; your project files are never touched."
|
|
1267
|
+
);
|
|
1268
|
+
} else {
|
|
1269
|
+
fs15.rmSync(getSitePath(options.projectPath), {
|
|
1270
|
+
recursive: true,
|
|
1271
|
+
force: true,
|
|
1272
|
+
maxRetries: 10,
|
|
1273
|
+
retryDelay: 100
|
|
1274
|
+
});
|
|
1275
|
+
output?.log("Site environment reset.");
|
|
1276
|
+
}
|
|
643
1277
|
}
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
};
|
|
648
|
-
if (argv._[0] !== "php") {
|
|
649
|
-
config.wp = argv.wp;
|
|
650
|
-
config.port = argv.port;
|
|
651
|
-
config.mode = argv.mode;
|
|
1278
|
+
const { url } = await startServer(options);
|
|
1279
|
+
if (!hasNegatedFlag(rawArgs, "open")) {
|
|
1280
|
+
openInDefaultBrowser(url);
|
|
652
1281
|
}
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
output?.error(error);
|
|
1284
|
+
spinner.fail(`Failed to start the server: ${error.message}`);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
async function runPhpCommand(rawArgs) {
|
|
1288
|
+
const args = rawArgs.slice(1);
|
|
1289
|
+
const pathIndex = args.findIndex((arg) => arg.startsWith("--path="));
|
|
1290
|
+
const phpIndex = args.findIndex((arg) => arg.startsWith("--php="));
|
|
1291
|
+
try {
|
|
1292
|
+
const options = await getWpAppConfig({
|
|
1293
|
+
path: pathIndex >= 0 ? args[pathIndex].split("=")[1] : void 0,
|
|
1294
|
+
php: phpIndex >= 0 ? args[phpIndex].split("=")[1] : void 0
|
|
1295
|
+
});
|
|
1296
|
+
const phpArgs = args.filter(
|
|
1297
|
+
(arg, i) => i !== pathIndex && i !== phpIndex
|
|
1298
|
+
);
|
|
1299
|
+
await executePHP(phpArgs, options);
|
|
1300
|
+
process.exit(0);
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
console.error(error);
|
|
1303
|
+
process.exit(error.status || -1);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
async function runBuildCommand(values) {
|
|
1307
|
+
const projectPath = values.path ? path13.resolve(values.path) : process.cwd();
|
|
1308
|
+
if (fs15.existsSync(path13.join(projectPath, "vendor"))) {
|
|
653
1309
|
try {
|
|
654
|
-
|
|
1310
|
+
execSync("composer dump-autoload --optimize --classmap-authoritative", {
|
|
1311
|
+
cwd: projectPath,
|
|
1312
|
+
stdio: "inherit"
|
|
1313
|
+
});
|
|
655
1314
|
} catch (error) {
|
|
656
|
-
|
|
1315
|
+
console.error(
|
|
1316
|
+
`composer dump-autoload failed: ${error.message}`
|
|
1317
|
+
);
|
|
657
1318
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
(
|
|
663
|
-
|
|
664
|
-
yargs2.option("reset", {
|
|
665
|
-
describe: "Create a new site environment, destroying the old one (WordPress files and database).",
|
|
666
|
-
type: "boolean",
|
|
667
|
-
default: false
|
|
668
|
-
});
|
|
669
|
-
yargs2.option("open", {
|
|
670
|
-
describe: "Open the site in the default browser.",
|
|
671
|
-
type: "boolean",
|
|
672
|
-
default: true
|
|
673
|
-
});
|
|
674
|
-
},
|
|
675
|
-
async (argv) => {
|
|
676
|
-
const spinner = startSpinner("Starting the server...");
|
|
677
|
-
try {
|
|
678
|
-
const options = await getWpAppConfig({
|
|
679
|
-
path: argv.path,
|
|
680
|
-
php: argv.php,
|
|
681
|
-
wp: argv.wp,
|
|
682
|
-
port: argv.port,
|
|
683
|
-
mode: argv.mode
|
|
684
|
-
});
|
|
685
|
-
portFinder.setPort(options.port);
|
|
686
|
-
if (argv.reset) {
|
|
687
|
-
if (["wordpress", "wordpress-develop", "index"].includes(
|
|
688
|
-
options.mode
|
|
689
|
-
)) {
|
|
690
|
-
output?.log(
|
|
691
|
-
"--reset only applies to wp-app managed sites; your project files are never touched."
|
|
692
|
-
);
|
|
693
|
-
} else {
|
|
694
|
-
fs13.rmSync(getSitePath(options.projectPath), {
|
|
695
|
-
recursive: true,
|
|
696
|
-
force: true,
|
|
697
|
-
maxRetries: 10,
|
|
698
|
-
retryDelay: 100
|
|
699
|
-
});
|
|
700
|
-
output?.log("Site environment reset.");
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
const { url } = await startServer(options);
|
|
704
|
-
if (argv.open) {
|
|
705
|
-
openInDefaultBrowser(url);
|
|
706
|
-
}
|
|
707
|
-
} catch (error) {
|
|
708
|
-
output?.error(error);
|
|
709
|
-
spinner.fail(
|
|
710
|
-
`Failed to start the server: ${error.message}`
|
|
711
|
-
);
|
|
712
|
-
}
|
|
1319
|
+
}
|
|
1320
|
+
const packageJsonPath = path13.join(projectPath, "package.json");
|
|
1321
|
+
if (fs15.existsSync(packageJsonPath)) {
|
|
1322
|
+
const pkg = JSON.parse(fs15.readFileSync(packageJsonPath, "utf8"));
|
|
1323
|
+
if (pkg.scripts?.build) {
|
|
1324
|
+
execSync("npm run build", { cwd: projectPath, stdio: "inherit" });
|
|
713
1325
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1326
|
+
}
|
|
1327
|
+
console.log("Build complete.");
|
|
1328
|
+
}
|
|
1329
|
+
async function runInstallCommand(values) {
|
|
1330
|
+
const projectPath = values.path ? path13.resolve(values.path) : process.cwd();
|
|
1331
|
+
if (fs15.existsSync(path13.join(projectPath, "package.json"))) {
|
|
1332
|
+
execSync("npm install", { cwd: projectPath, stdio: "inherit" });
|
|
1333
|
+
}
|
|
1334
|
+
if (fs15.existsSync(path13.join(projectPath, "composer.json"))) {
|
|
1335
|
+
execSync("composer install", { cwd: projectPath, stdio: "inherit" });
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
async function runUpdateCommand(values, rawArgs) {
|
|
1339
|
+
const projectPath = values.path ? path13.resolve(values.path) : process.cwd();
|
|
1340
|
+
const packages = rawArgs.slice(1).filter((arg) => !arg.startsWith("--"));
|
|
1341
|
+
const args = ["update", ...packages];
|
|
1342
|
+
runCommand("composer", args, { cwd: projectPath, stdio: "inherit" });
|
|
1343
|
+
}
|
|
1344
|
+
var WATCHED_EXTENSIONS = [".css", ".scss", ".js", ".ts", ".jsx", ".tsx"];
|
|
1345
|
+
var IGNORED_DIRS = ["node_modules", "vendor", ".git", "dist", "assets"];
|
|
1346
|
+
async function runDevCommand(values, rawArgs) {
|
|
1347
|
+
const projectPath = path13.resolve(values.path || process.cwd());
|
|
1348
|
+
const spinner = startSpinner("Starting the server...");
|
|
1349
|
+
try {
|
|
1350
|
+
const options = await getWpAppConfig({
|
|
1351
|
+
path: values.path,
|
|
1352
|
+
php: values.php,
|
|
1353
|
+
wp: values.wp,
|
|
1354
|
+
port: values.port ? Number(values.port) : void 0,
|
|
1355
|
+
mode: values.mode
|
|
1356
|
+
});
|
|
1357
|
+
portFinder.setPort(options.port);
|
|
1358
|
+
const { url } = await startServer(options);
|
|
1359
|
+
if (!hasNegatedFlag(rawArgs, "open")) {
|
|
1360
|
+
openInDefaultBrowser(url);
|
|
735
1361
|
}
|
|
736
|
-
)
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
const packageJsonPath = path11.join(projectPath, "package.json");
|
|
759
|
-
if (fs13.existsSync(packageJsonPath)) {
|
|
760
|
-
const pkg = JSON.parse(
|
|
761
|
-
fs13.readFileSync(packageJsonPath, "utf8")
|
|
762
|
-
);
|
|
763
|
-
if (pkg.scripts?.build) {
|
|
764
|
-
console.log("Running npm build...");
|
|
765
|
-
execSync("npm run build", {
|
|
766
|
-
cwd: projectPath,
|
|
767
|
-
stdio: "inherit"
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
console.log("Build complete.");
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
output?.error(error);
|
|
1364
|
+
spinner.fail(`Failed to start the server: ${error.message}`);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
const packageJsonPath = path13.join(projectPath, "package.json");
|
|
1368
|
+
const hasBuildScript = fs15.existsSync(packageJsonPath) && JSON.parse(fs15.readFileSync(packageJsonPath, "utf8")).scripts?.build;
|
|
1369
|
+
if (!hasBuildScript) {
|
|
1370
|
+
console.log("Dev mode: no npm build script found, nothing to watch.");
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
let rebuildTimer = null;
|
|
1374
|
+
const rebuild = (filename) => {
|
|
1375
|
+
console.log(`Change detected: ${filename}. Rebuilding...`);
|
|
1376
|
+
try {
|
|
1377
|
+
execSync("npm run build", { cwd: projectPath, stdio: "inherit" });
|
|
1378
|
+
console.log("Rebuild complete. Refresh your browser to see changes.");
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
console.error(`Rebuild failed: ${error.message}`);
|
|
772
1381
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
async (argv) => {
|
|
780
|
-
const selfScript = process.argv[1];
|
|
781
|
-
const watchDir = path11.resolve(
|
|
782
|
-
argv.path || process.cwd()
|
|
783
|
-
);
|
|
784
|
-
const IGNORED = ["node_modules", "vendor", ".git", "dist"];
|
|
785
|
-
let child = null;
|
|
786
|
-
let restartTimer = null;
|
|
787
|
-
let firstRun = true;
|
|
788
|
-
const startChild = () => {
|
|
789
|
-
const args = [selfScript, "start"];
|
|
790
|
-
if (argv.path) args.push(`--path=${argv.path}`);
|
|
791
|
-
if (argv.php) args.push(`--php=${argv.php}`);
|
|
792
|
-
if (argv.wp) args.push(`--wp=${argv.wp}`);
|
|
793
|
-
if (argv.port) args.push(`--port=${argv.port}`);
|
|
794
|
-
if (argv.mode) args.push(`--mode=${argv.mode}`);
|
|
795
|
-
if (!firstRun) args.push("--no-open");
|
|
796
|
-
firstRun = false;
|
|
797
|
-
child = spawn(process.execPath, args, {
|
|
798
|
-
stdio: "inherit"
|
|
799
|
-
});
|
|
800
|
-
};
|
|
801
|
-
startChild();
|
|
802
|
-
fs13.watch(
|
|
803
|
-
watchDir,
|
|
804
|
-
{ recursive: true },
|
|
805
|
-
(_eventType, filename) => {
|
|
806
|
-
if (!filename) return;
|
|
807
|
-
const parts = filename.split(/[\\/]/);
|
|
808
|
-
if (parts.some(
|
|
809
|
-
(part) => IGNORED.includes(part) || part.startsWith(".")
|
|
810
|
-
)) {
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
clearTimeout(restartTimer);
|
|
814
|
-
restartTimer = setTimeout(() => {
|
|
815
|
-
console.log(
|
|
816
|
-
`Change detected: ${filename}. Restarting...`
|
|
817
|
-
);
|
|
818
|
-
child?.kill();
|
|
819
|
-
startChild();
|
|
820
|
-
}, 500);
|
|
821
|
-
}
|
|
822
|
-
);
|
|
823
|
-
console.log("Dev mode: watching for file changes...");
|
|
1382
|
+
};
|
|
1383
|
+
fs15.watch(projectPath, { recursive: true }, (_eventType, filename) => {
|
|
1384
|
+
if (!filename) return;
|
|
1385
|
+
const parts = filename.split(/[\\/]/);
|
|
1386
|
+
if (parts.some((part) => IGNORED_DIRS.includes(part) || part.startsWith("."))) {
|
|
1387
|
+
return;
|
|
824
1388
|
}
|
|
825
|
-
|
|
1389
|
+
if (!WATCHED_EXTENSIONS.includes(path13.extname(filename))) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (rebuildTimer) clearTimeout(rebuildTimer);
|
|
1393
|
+
rebuildTimer = setTimeout(() => rebuild(filename), 300);
|
|
1394
|
+
});
|
|
1395
|
+
console.log("Dev mode: watching CSS/JS source files for changes...");
|
|
826
1396
|
}
|
|
827
1397
|
function openInDefaultBrowser(url) {
|
|
828
1398
|
if (isGitHubCodespace) {
|
|
@@ -859,4 +1429,8 @@ if (currentNodeVersion < requiredMajorVersion) {
|
|
|
859
1429
|
`You are running Node.js version ${currentNodeVersion}, but this application requires at least Node.js ${requiredMajorVersion}. Please upgrade your Node.js version.`
|
|
860
1430
|
);
|
|
861
1431
|
}
|
|
1432
|
+
try {
|
|
1433
|
+
process.loadEnvFile();
|
|
1434
|
+
} catch {
|
|
1435
|
+
}
|
|
862
1436
|
runCli();
|