@wp-operations/wp-app 0.3.0 → 0.4.1

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.
Files changed (4) hide show
  1. package/README.md +20 -33
  2. package/dist/index.js +150 -71
  3. package/dist/main.js +699 -282
  4. package/package.json +5 -6
package/dist/main.js CHANGED
@@ -1,19 +1,324 @@
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
- import fs13 from "fs";
3
- import path11 from "path";
4
- import yargs from "yargs";
5
- import { hideBin } from "yargs/helpers";
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 fs11 from "fs";
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 fs10 from "fs";
16
- import path9 from "path";
320
+ import fs11 from "fs";
321
+ import path10 from "path";
17
322
  import { createNodeFsMountHandler } from "@php-wasm/node";
18
323
 
19
324
  // src/boot.ts
@@ -39,7 +344,18 @@ async function createPhp(phpVersion) {
39
344
  return php;
40
345
  }
41
346
  function createRequestHandler(php, documentRoot, absoluteUrl) {
42
- return new PHPRequestHandler({ 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
+ });
43
359
  }
44
360
  async function runPhp(php, code) {
45
361
  const response = await php.run({ code: `<?php ${code}` });
@@ -142,18 +458,22 @@ function defineSiteConstants(php, siteUrl, databaseDir) {
142
458
  php.defineConstant("DB_DIR", databaseDir);
143
459
  php.defineConstant("DB_FILE", ".ht.sqlite");
144
460
  php.defineConstant("WP_SQLITE_AST_DRIVER", true);
461
+ php.defineConstant("DISABLE_WP_CRON", true);
145
462
  }
146
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";
147
467
  const fields = new URLSearchParams({
148
468
  language: "en",
149
469
  prefix: "wp_",
150
470
  weblog_title: "My WordPress Website",
151
- user_name: "admin",
152
- admin_password: "password",
153
- admin_password2: "password",
471
+ user_name: adminUser,
472
+ admin_password: adminPassword,
473
+ admin_password2: adminPassword,
154
474
  Submit: "Install WordPress",
155
475
  pw_weak: "1",
156
- admin_email: "admin@localhost.com"
476
+ admin_email: adminEmail
157
477
  });
158
478
  return requestHandler.request({
159
479
  url: "/wp-admin/install.php?step=2",
@@ -177,8 +497,8 @@ var DEFAULT_WORDPRESS_VERSION = "latest";
177
497
  var DOCROOT = "/wordpress";
178
498
 
179
499
  // src/download.ts
180
- import fs9 from "fs";
181
- import path8 from "path";
500
+ import fs10 from "fs";
501
+ import path9 from "path";
182
502
 
183
503
  // src/detect/has-index-file.ts
184
504
  import fs from "fs";
@@ -190,7 +510,7 @@ function hasIndexFile(projectPath) {
190
510
  // src/detect/is-valid-wordpress-version.ts
191
511
  function isValidWordPressVersion(version) {
192
512
  const versionPattern = /^latest$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/;
193
- const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?)?$/;
513
+ const classicPressPattern = /^classicpress(?:-(\d+)\.(\d+)(?:\.(\d+))?(?:-rc(?:\d+)?)?)?$/;
194
514
  return versionPattern.test(version) || classicPressPattern.test(version);
195
515
  }
196
516
  function isClassicPressVersion(version) {
@@ -255,13 +575,29 @@ function isThemeDirectory(projectPath) {
255
575
  return themeNameRegex.test(styleCSS);
256
576
  }
257
577
 
258
- // src/detect/is-wp-content-directory.ts
578
+ // src/detect/is-wp-app-project.ts
259
579
  import fs5 from "fs";
260
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";
261
597
  function isWpContentDirectory(projectPath) {
262
- const muPluginsExists = fs5.existsSync(path4.join(projectPath, "mu-plugins"));
263
- const pluginsExists = fs5.existsSync(path4.join(projectPath, "plugins"));
264
- const themesExists = fs5.existsSync(path4.join(projectPath, "themes"));
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"));
265
601
  if (muPluginsExists || pluginsExists || themesExists) {
266
602
  return true;
267
603
  }
@@ -269,29 +605,24 @@ function isWpContentDirectory(projectPath) {
269
605
  }
270
606
 
271
607
  // src/detect/is-wordpress-directory.ts
272
- import fs6 from "fs";
273
- import path5 from "path";
608
+ import fs7 from "fs";
609
+ import path6 from "path";
274
610
  function isWordPressDirectory(projectPath) {
275
- return fs6.existsSync(path5.join(projectPath, "wp-content")) && fs6.existsSync(path5.join(projectPath, "wp-includes")) && fs6.existsSync(path5.join(projectPath, "wp-load.php"));
611
+ return fs7.existsSync(path6.join(projectPath, "wp-content")) && fs7.existsSync(path6.join(projectPath, "wp-includes")) && fs7.existsSync(path6.join(projectPath, "wp-load.php"));
276
612
  }
277
613
 
278
- // src/detect/is-wordpress-develop-directory.ts
279
- import fs7 from "fs";
280
- import path6 from "path";
281
- function isWordPressDevelopDirectory(projectPath) {
282
- const requiredFiles = [
283
- "src",
284
- "src/wp-content",
285
- "src/wp-includes",
286
- "src/wp-load.php",
287
- "build",
288
- "build/wp-content",
289
- "build/wp-includes",
290
- "build/wp-load.php"
291
- ];
292
- return requiredFiles.every(
293
- (file) => fs7.existsSync(path6.join(projectPath, file))
294
- );
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");
295
626
  }
296
627
 
297
628
  // src/output.ts
@@ -302,24 +633,54 @@ var output = shouldOutput() ? console : null;
302
633
 
303
634
  // src/paths.ts
304
635
  import crypto from "crypto";
305
- import fs8 from "fs";
636
+ import fs9 from "fs";
306
637
  import os from "os";
307
- import path7 from "path";
638
+ import path8 from "path";
308
639
  function getWpAppHome() {
309
- return process.env[WP_APP_HOME_ENV] || path7.join(os.homedir(), WP_APP_HIDDEN_FOLDER);
640
+ const home = process.env[WP_APP_HOME_ENV] || path8.join(os.homedir(), WP_APP_HIDDEN_FOLDER);
641
+ cleanupLegacyHome(home);
642
+ return home;
310
643
  }
311
644
  function getCachePath() {
312
- return ensureDir(path7.join(getWpAppHome(), "cache"));
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);
313
650
  }
314
651
  function getSitePath(projectPath) {
315
- const projectName = path7.basename(path7.resolve(projectPath));
316
- const hash = crypto.createHash("sha1").update(path7.resolve(projectPath)).digest("hex");
317
- return path7.join(getWpAppHome(), "sites", `${projectName}-${hash}`);
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");
318
660
  }
319
661
  function ensureDir(dir) {
320
- fs8.mkdirSync(dir, { recursive: true });
662
+ fs9.mkdirSync(dir, { recursive: true });
321
663
  return dir;
322
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
+ }
323
684
 
324
685
  // src/download.ts
325
686
  function getWordPressVersionUrl(version) {
@@ -338,8 +699,8 @@ function getClassicPressVersionUrl(version) {
338
699
  return `https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${tag}.zip`;
339
700
  }
340
701
  async function cachedZip(url, cacheKey) {
341
- const cacheFile = path8.join(getCachePath(), cacheKey);
342
- if (!fs9.existsSync(cacheFile)) {
702
+ const cacheFile = path9.join(getCachePath(), cacheKey);
703
+ if (!fs10.existsSync(cacheFile)) {
343
704
  output?.log(`Downloading ${cacheKey}...`);
344
705
  const response = await fetch(url, { redirect: "follow" });
345
706
  if (!response.ok) {
@@ -348,11 +709,11 @@ async function cachedZip(url, cacheKey) {
348
709
  );
349
710
  }
350
711
  const bytes = Buffer.from(await response.arrayBuffer());
351
- fs9.writeFileSync(`${cacheFile}.partial`, bytes);
352
- fs9.renameSync(`${cacheFile}.partial`, cacheFile);
712
+ fs10.writeFileSync(`${cacheFile}.partial`, bytes);
713
+ fs10.renameSync(`${cacheFile}.partial`, cacheFile);
353
714
  output?.log(`Cached ${cacheKey} (${bytes.length} bytes).`);
354
715
  }
355
- return new File([fs9.readFileSync(cacheFile)], cacheKey);
716
+ return new File([fs10.readFileSync(cacheFile)], cacheKey);
356
717
  }
357
718
  async function getCoreZip(version) {
358
719
  if (isClassicPressVersion(version)) {
@@ -403,11 +764,6 @@ var PortFinder = class _PortFinder {
403
764
  });
404
765
  });
405
766
  }
406
- /**
407
- * Returns the first available open port, caching and reusing it for subsequent calls.
408
- *
409
- * @returns {Promise<number>} A promise that resolves to the open port number.
410
- */
411
767
  async getOpenPort() {
412
768
  if (this.#openPort) {
413
769
  return this.#openPort;
@@ -459,7 +815,11 @@ async function getWpAppConfig(args) {
459
815
  }
460
816
  });
461
817
  if (!options.mode || options.mode === "auto") {
462
- options.mode = inferMode(options.projectPath);
818
+ const inferred = inferMode(options.projectPath);
819
+ if (!inferred) {
820
+ process.exit(1);
821
+ }
822
+ options.mode = inferred;
463
823
  }
464
824
  if (!options.absoluteUrl) {
465
825
  options.absoluteUrl = await getAbsoluteURL();
@@ -479,17 +839,22 @@ async function getWpAppConfig(args) {
479
839
 
480
840
  // src/engine.ts
481
841
  async function startWPApp(options) {
482
- const projectPath = path9.resolve(options.projectPath);
842
+ const projectPath = path10.resolve(options.projectPath);
483
843
  const mode = options.mode;
484
844
  output?.log(`directory: ${options.projectPath}`);
485
845
  output?.log(`mode: ${mode}`);
486
846
  output?.log(`php: ${options.phpVersion}`);
487
- const hostDocroot = mode === "index" /* INDEX */ || mode === "wordpress" /* WORDPRESS */ ? projectPath : mode === "wordpress-develop" /* WORDPRESS_DEVELOP */ ? path9.join(projectPath, "build") : ensureDir(getSitePath(projectPath));
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;
488
852
  const isWordPressBacked = mode !== "index" /* INDEX */;
489
- const freshSite = isWordPressBacked && !fs10.existsSync(path9.join(hostDocroot, "wp-load.php"));
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;
490
855
  if (isWordPressBacked) {
491
856
  output?.log(`wp: ${options.wordPressVersion}`);
492
- output?.log(`site data: ${hostDocroot}`);
857
+ output?.log(`site data: ${projectDataRoot ?? hostDocroot}`);
493
858
  }
494
859
  const php = await createPhp(options.phpVersion);
495
860
  php.mkdir(DOCROOT);
@@ -499,7 +864,7 @@ async function startWPApp(options) {
499
864
  DOCROOT,
500
865
  options.absoluteUrl
501
866
  );
502
- if (freshSite) {
867
+ if (freshCore) {
503
868
  await installCore(
504
869
  php,
505
870
  await getCoreZip(options.wordPressVersion),
@@ -514,6 +879,22 @@ async function startWPApp(options) {
514
879
  DOCROOT
515
880
  );
516
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) {
517
898
  defineSiteConstants(
518
899
  php,
519
900
  options.absoluteUrl,
@@ -530,8 +911,9 @@ async function startWPApp(options) {
530
911
  return { requestHandler, php, options, freshSite };
531
912
  }
532
913
  async function applyProjectMounts(php, mode, projectPath) {
533
- const projectName = path9.basename(projectPath);
914
+ const projectName = path10.basename(projectPath);
534
915
  switch (mode) {
916
+ case "wp-app" /* WP_APP */:
535
917
  case "theme" /* THEME */:
536
918
  await mountAt(
537
919
  php,
@@ -547,13 +929,13 @@ async function applyProjectMounts(php, mode, projectPath) {
547
929
  );
548
930
  break;
549
931
  case "wp-content" /* WP_CONTENT */:
550
- for (const entry of fs10.readdirSync(projectPath)) {
932
+ for (const entry of fs11.readdirSync(projectPath)) {
551
933
  if (entry === "index.php") {
552
934
  continue;
553
935
  }
554
936
  await mountAt(
555
937
  php,
556
- path9.join(projectPath, entry),
938
+ path10.join(projectPath, entry),
557
939
  `${DOCROOT}/wp-content/${entry}`
558
940
  );
559
941
  }
@@ -563,14 +945,14 @@ async function applyProjectMounts(php, mode, projectPath) {
563
945
  }
564
946
  }
565
947
  async function mountAt(php, hostPath, vfsPath) {
566
- if (fs10.statSync(hostPath).isDirectory()) {
948
+ if (fs11.statSync(hostPath).isDirectory()) {
567
949
  php.mkdir(vfsPath);
568
950
  }
569
951
  await php.mount(vfsPath, createNodeFsMountHandler(hostPath));
570
952
  }
571
953
  async function activateProject(php, mode, projectPath) {
572
- const projectName = path9.basename(projectPath);
573
- if (mode === "theme" /* THEME */) {
954
+ const projectName = path10.basename(projectPath);
955
+ if (mode === "wp-app" /* WP_APP */ || mode === "theme" /* THEME */) {
574
956
  await runWordPressCode(
575
957
  php,
576
958
  `switch_theme(${phpString(projectName)});`
@@ -615,11 +997,11 @@ function phpString(value) {
615
997
  }
616
998
  function findPluginFile(projectPath) {
617
999
  const pluginNameRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;
618
- for (const file of fs10.readdirSync(projectPath)) {
1000
+ for (const file of fs11.readdirSync(projectPath)) {
619
1001
  if (!file.endsWith(".php")) {
620
1002
  continue;
621
1003
  }
622
- const content = fs10.readFileSync(path9.join(projectPath, file), "utf8");
1004
+ const content = fs11.readFileSync(path10.join(projectPath, file), "utf8");
623
1005
  if (pluginNameRegex.test(content)) {
624
1006
  return file;
625
1007
  }
@@ -627,8 +1009,10 @@ function findPluginFile(projectPath) {
627
1009
  return null;
628
1010
  }
629
1011
  function inferMode(projectPath) {
630
- if (isWordPressDevelopDirectory(projectPath)) {
631
- return "wordpress-develop" /* WORDPRESS_DEVELOP */;
1012
+ if (isWpAppProject(projectPath)) {
1013
+ return "wp-app" /* WP_APP */;
1014
+ } else if (isClassicPressDirectory(projectPath)) {
1015
+ return "classicpress" /* CLASSICPRESS */;
632
1016
  } else if (isWordPressDirectory(projectPath)) {
633
1017
  return "wordpress" /* WORDPRESS */;
634
1018
  } else if (isWpContentDirectory(projectPath)) {
@@ -640,12 +1024,12 @@ function inferMode(projectPath) {
640
1024
  } else if (hasIndexFile(projectPath)) {
641
1025
  return "index" /* INDEX */;
642
1026
  }
643
- return "playground" /* PLAYGROUND */;
1027
+ return null;
644
1028
  }
645
1029
 
646
1030
  // src/start-server.ts
647
1031
  async function startServer(options = {}) {
648
- if (!fs11.existsSync(options.projectPath)) {
1032
+ if (!fs12.existsSync(options.projectPath)) {
649
1033
  throw new Error(
650
1034
  `The given path "${options.projectPath}" does not exist.`
651
1035
  );
@@ -716,8 +1100,8 @@ var parseHeaders = (req) => {
716
1100
  };
717
1101
 
718
1102
  // src/execute-php.ts
719
- import fs12 from "fs";
720
- import path10 from "path";
1103
+ import fs13 from "fs";
1104
+ import path11 from "path";
721
1105
  import { loadNodeRuntime as loadNodeRuntime2 } from "@php-wasm/node";
722
1106
  import { PHP as PHP2 } from "@php-wasm/universal";
723
1107
  async function executePHP(phpArgs, options) {
@@ -725,8 +1109,8 @@ async function executePHP(phpArgs, options) {
725
1109
  let code;
726
1110
  if (args[0] === "-r" && typeof args[1] === "string") {
727
1111
  code = `<?php ${args[1]}`;
728
- } else if (args[0] && fs12.existsSync(path10.resolve(args[0]))) {
729
- code = fs12.readFileSync(path10.resolve(args[0]), "utf8");
1112
+ } else if (args[0] && fs13.existsSync(path11.resolve(args[0]))) {
1113
+ code = fs13.readFileSync(path11.resolve(args[0]), "utf8");
730
1114
  } else {
731
1115
  throw new Error(
732
1116
  'Usage: wp-app php -- -r "<code>" | wp-app php -- <script.php>'
@@ -748,14 +1132,42 @@ async function executePHP(phpArgs, options) {
748
1132
  // src/run-cli.ts
749
1133
  var MODE_CHOICES = [
750
1134
  "auto",
1135
+ "wp-app",
751
1136
  "plugin",
752
1137
  "theme",
753
1138
  "wordpress",
754
- "wordpress-develop",
1139
+ "classicpress",
755
1140
  "wp-content",
756
- "index",
757
- "playground"
1141
+ "index"
758
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
+ `;
759
1171
  function startSpinner(message) {
760
1172
  process.stdout.write(`${message}...
761
1173
  `);
@@ -768,218 +1180,219 @@ function startSpinner(message) {
768
1180
  }
769
1181
  };
770
1182
  }
771
- function commonParameters(yargs2) {
772
- return yargs2.option("path", {
773
- describe: "Path to the PHP or WordPress project. Defaults to the current working directory.",
774
- type: "string"
775
- }).option("php", {
776
- describe: "PHP version to use.",
777
- type: "string"
778
- });
1183
+ function fail(message) {
1184
+ console.error(`wp-app: ${message}
1185
+ `);
1186
+ console.error(HELP);
1187
+ process.exit(1);
779
1188
  }
780
- function serverParameters(yargs2) {
781
- commonParameters(yargs2);
782
- yargs2.option("wp", {
783
- describe: "WordPress version to use, e.g. '--wp=6.4'. Use '--wp=classicpress' for ClassicPress.",
784
- type: "string"
785
- });
786
- yargs2.option("port", {
787
- describe: "Server port",
788
- type: "number"
789
- });
790
- yargs2.option("mode", {
791
- describe: "Project mode. Defaults to auto-detection from the project directory.",
792
- type: "string",
793
- choices: MODE_CHOICES
794
- });
1189
+ function hasNegatedFlag(args, flag) {
1190
+ return args.includes(`--no-${flag}`);
795
1191
  }
796
1192
  async function runCli() {
797
- return yargs(hideBin(process.argv)).scriptName("wp-app").usage("$0 <cmd> [args]").check(async (argv) => {
798
- if (["build", "dev"].includes(argv._[0])) {
799
- return true;
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
+ }
800
1277
  }
801
- const config = {
802
- php: argv.php,
803
- path: argv.path
804
- };
805
- if (argv._[0] !== "php") {
806
- config.wp = argv.wp;
807
- config.port = argv.port;
808
- config.mode = argv.mode;
1278
+ const { url } = await startServer(options);
1279
+ if (!hasNegatedFlag(rawArgs, "open")) {
1280
+ openInDefaultBrowser(url);
809
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"))) {
810
1309
  try {
811
- await getWpAppConfig(config);
1310
+ execSync("composer dump-autoload --optimize --classmap-authoritative", {
1311
+ cwd: projectPath,
1312
+ stdio: "inherit"
1313
+ });
812
1314
  } catch (error) {
813
- return error.message;
1315
+ console.error(
1316
+ `composer dump-autoload failed: ${error.message}`
1317
+ );
814
1318
  }
815
- return true;
816
- }).command(
817
- "start",
818
- "Start the server",
819
- (yargs2) => {
820
- serverParameters(yargs2);
821
- yargs2.option("reset", {
822
- describe: "Create a new site environment, destroying the old one (WordPress files and database).",
823
- type: "boolean",
824
- default: false
825
- });
826
- yargs2.option("open", {
827
- describe: "Open the site in the default browser.",
828
- type: "boolean",
829
- default: true
830
- });
831
- },
832
- async (argv) => {
833
- const spinner = startSpinner("Starting the server...");
834
- try {
835
- const options = await getWpAppConfig({
836
- path: argv.path,
837
- php: argv.php,
838
- wp: argv.wp,
839
- port: argv.port,
840
- mode: argv.mode
841
- });
842
- portFinder.setPort(options.port);
843
- if (argv.reset) {
844
- if (["wordpress", "wordpress-develop", "index"].includes(
845
- options.mode
846
- )) {
847
- output?.log(
848
- "--reset only applies to wp-app managed sites; your project files are never touched."
849
- );
850
- } else {
851
- fs13.rmSync(getSitePath(options.projectPath), {
852
- recursive: true,
853
- force: true,
854
- maxRetries: 10,
855
- retryDelay: 100
856
- });
857
- output?.log("Site environment reset.");
858
- }
859
- }
860
- const { url } = await startServer(options);
861
- if (argv.open) {
862
- openInDefaultBrowser(url);
863
- }
864
- } catch (error) {
865
- output?.error(error);
866
- spinner.fail(
867
- `Failed to start the server: ${error.message}`
868
- );
869
- }
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" });
870
1325
  }
871
- ).command(
872
- "php [..args]",
873
- "Run the php command passing the arguments to php cli",
874
- (yargs2) => {
875
- commonParameters(yargs2);
876
- yargs2.strict(false);
877
- },
878
- async (argv) => {
879
- try {
880
- const args = process.argv.slice(2);
881
- const options = await getWpAppConfig({
882
- path: argv.path,
883
- php: argv.php
884
- });
885
- const phpArgs = args.includes("--") ? argv._ : args;
886
- await executePHP(phpArgs, options);
887
- process.exit(0);
888
- } catch (error) {
889
- console.error(error);
890
- process.exit(error.status || -1);
891
- }
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);
892
1361
  }
893
- ).command(
894
- "build",
895
- "Build the project: composer install plus the npm build script, when present",
896
- (yargs2) => {
897
- commonParameters(yargs2);
898
- },
899
- async (argv) => {
900
- const projectPath = argv.path ? path11.resolve(argv.path) : process.cwd();
901
- console.log(`Building project at ${projectPath}`);
902
- if (fs13.existsSync(path11.join(projectPath, "composer.json"))) {
903
- console.log("Running composer install...");
904
- try {
905
- execSync(
906
- "composer install --no-dev --optimize-autoloader",
907
- { cwd: projectPath, stdio: "inherit" }
908
- );
909
- } catch (error) {
910
- console.error(
911
- `composer install failed: ${error.message}`
912
- );
913
- }
914
- }
915
- const packageJsonPath = path11.join(projectPath, "package.json");
916
- if (fs13.existsSync(packageJsonPath)) {
917
- const pkg = JSON.parse(
918
- fs13.readFileSync(packageJsonPath, "utf8")
919
- );
920
- if (pkg.scripts?.build) {
921
- console.log("Running npm build...");
922
- execSync("npm run build", {
923
- cwd: projectPath,
924
- stdio: "inherit"
925
- });
926
- }
927
- }
928
- 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}`);
929
1381
  }
930
- ).command(
931
- "dev",
932
- "Start the server and restart it when project files change",
933
- (yargs2) => {
934
- serverParameters(yargs2);
935
- },
936
- async (argv) => {
937
- const selfScript = process.argv[1];
938
- const watchDir = path11.resolve(
939
- argv.path || process.cwd()
940
- );
941
- const IGNORED = ["node_modules", "vendor", ".git", "dist"];
942
- let child = null;
943
- let restartTimer = null;
944
- let firstRun = true;
945
- const startChild = () => {
946
- const args = [selfScript, "start"];
947
- if (argv.path) args.push(`--path=${argv.path}`);
948
- if (argv.php) args.push(`--php=${argv.php}`);
949
- if (argv.wp) args.push(`--wp=${argv.wp}`);
950
- if (argv.port) args.push(`--port=${argv.port}`);
951
- if (argv.mode) args.push(`--mode=${argv.mode}`);
952
- if (!firstRun) args.push("--no-open");
953
- firstRun = false;
954
- child = spawn(process.execPath, args, {
955
- stdio: "inherit"
956
- });
957
- };
958
- startChild();
959
- fs13.watch(
960
- watchDir,
961
- { recursive: true },
962
- (_eventType, filename) => {
963
- if (!filename) return;
964
- const parts = filename.split(/[\\/]/);
965
- if (parts.some(
966
- (part) => IGNORED.includes(part) || part.startsWith(".")
967
- )) {
968
- return;
969
- }
970
- clearTimeout(restartTimer);
971
- restartTimer = setTimeout(() => {
972
- console.log(
973
- `Change detected: ${filename}. Restarting...`
974
- );
975
- child?.kill();
976
- startChild();
977
- }, 500);
978
- }
979
- );
980
- 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;
1388
+ }
1389
+ if (!WATCHED_EXTENSIONS.includes(path13.extname(filename))) {
1390
+ return;
981
1391
  }
982
- ).demandCommand(1, "You must provide a valid command").help().alias("h", "help").strict().argv;
1392
+ if (rebuildTimer) clearTimeout(rebuildTimer);
1393
+ rebuildTimer = setTimeout(() => rebuild(filename), 300);
1394
+ });
1395
+ console.log("Dev mode: watching CSS/JS source files for changes...");
983
1396
  }
984
1397
  function openInDefaultBrowser(url) {
985
1398
  if (isGitHubCodespace) {
@@ -1016,4 +1429,8 @@ if (currentNodeVersion < requiredMajorVersion) {
1016
1429
  `You are running Node.js version ${currentNodeVersion}, but this application requires at least Node.js ${requiredMajorVersion}. Please upgrade your Node.js version.`
1017
1430
  );
1018
1431
  }
1432
+ try {
1433
+ process.loadEnvFile();
1434
+ } catch {
1435
+ }
1019
1436
  runCli();