@wp-playground/cli 3.1.29 → 3.1.31

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 CHANGED
@@ -94,13 +94,67 @@ The `server` command supports the following optional arguments:
94
94
  - `--wordpress-install-mode <mode>`: Control how Playground prepares WordPress before booting. Defaults to `download-and-install`. Other options: `install-from-existing-files` (install using files you've mounted), `install-from-existing-files-if-needed` (same, but skip setup when an existing site is detected), and `do-not-attempt-installing` (never download or install WordPress).
95
95
  - `--skip-sqlite-setup`: Do not set up the SQLite database integration.
96
96
  - `--verbosity`: Output logs and progress messages (choices: "quiet", "normal", "debug"). Defaults to "normal".
97
-
98
97
  - `--debug`: Print the PHP error log if an error occurs during boot.
99
98
  - `--follow-symlinks`: Allow Playground to follow symlinks by automatically mounting symlinked directories and files encountered in mounted directories. ⚠️ Warning: Following symlinks will expose files outside mounted directories to Playground and could be a security risk.
100
99
  - `--workers=<n|auto>`: Number of request-handling worker threads. Pass a positive integer, or `auto` to use one worker per CPU core (minus one). Defaults to `min(6, cpus-1)`. Useful for multi-client workloads (e.g. parallel e2e suites) that need more than 6 in-flight requests.
101
100
  - `--experimental-multi-worker`: Deprecated. Use `--workers=<n|auto>` instead. The value of this flag is ignored.
102
101
  - `--phpmyadmin[=<path>]`: Install phpMyAdmin for database management. The phpMyAdmin URL will be printed after boot. Optionally specify a custom URL path (default: `/phpmyadmin`).
103
102
  - `--internal-cookie-store`: Enables Playground's internal cookie handling. When active, Playground uses an HttpCookieStore to manage and persist cookies across requests. If disabled, cookies are handled externally, like by a browser in Node.js.
103
+ - `--php-extension=<manifest>`: Load a custom PHP.wasm extension manifest before PHP starts. Accepts local paths, `file:` URLs, and `http(s):` URLs. Can be used multiple times.
104
+
105
+ ### Loading Custom PHP.wasm Extensions
106
+
107
+ Custom extensions built with `@php-wasm/compile-extension` can be loaded with
108
+ `--php-extension`:
109
+
110
+ ```bash
111
+ npx @wp-playground/cli@latest server \
112
+ --php=8.4 \
113
+ --php-extension=./dist/wp_mysql_parser/manifest.json
114
+ ```
115
+
116
+ The manifest selects the `.so` artifact matching the active PHP version and can
117
+ stage sidecar files before PHP starts. External extensions are JSPI-only, so use
118
+ Node.js 23 or newer.
119
+
120
+ Add runtime settings such as `iniEntries` and `env` directly to the manifest:
121
+
122
+ ```json
123
+ {
124
+ "name": "spx",
125
+ "version": "0.1.0",
126
+ "artifacts": [
127
+ {
128
+ "phpVersion": "8.4",
129
+ "sourcePath": "spx-php8.4-jspi.so"
130
+ }
131
+ ],
132
+ "iniEntries": {
133
+ "spx.http_enabled": "1"
134
+ },
135
+ "env": {
136
+ "SPX_DATA_DIR": "/internal/shared/spx/data"
137
+ }
138
+ }
139
+ ```
140
+
141
+ Set `loadWithIniDirective` to `false` when the artifact is a loadable Wasm
142
+ side module that should be staged before PHP starts but should not be registered
143
+ with `extension=` or `zend_extension=` in php.ini:
144
+
145
+ ```json
146
+ {
147
+ "name": "sqlite_markdown",
148
+ "version": "0.1.0",
149
+ "loadWithIniDirective": false,
150
+ "artifacts": [
151
+ {
152
+ "phpVersion": "8.4",
153
+ "sourcePath": "sqlite_markdown-php8.4-jspi.so"
154
+ }
155
+ ]
156
+ }
157
+ ```
104
158
 
105
159
  ## Need some help with the CLI?
106
160
 
package/cli.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const i=require("child_process");function t(){return!("Suspending"in WebAssembly||process.env.PLAYGROUND_NO_JSPI_RESPAWN||process.versions.bun||"Deno"in globalThis||process.execArgv.includes("--experimental-wasm-jspi")||parseInt(process.versions.node.split(".")[0],10)<23)}function o(){const r=process.argv.slice(2);Promise.resolve().then(()=>require("./run-cli-b6r6MAhq.cjs")).then(e=>e.runCli).then(({parseOptionsAndRunCLI:e})=>{e(r)})}if(t()){const r=Date.now(),e=i.spawn(process.execPath,["--experimental-wasm-jspi",...process.execArgv,...process.argv.slice(1)],{stdio:"inherit"});for(const s of["SIGINT","SIGTERM"])process.on(s,()=>e.kill(s));e.on("error",()=>{o()}),e.on("close",(s,n)=>{if(s!==0&&!n&&Date.now()-r<1e3){o();return}n?process.kill(process.pid,n):process.exit(s)})}else o();
1
+ "use strict";const i=require("child_process");function t(){return!("Suspending"in WebAssembly||process.env.PLAYGROUND_NO_JSPI_RESPAWN||process.versions.bun||"Deno"in globalThis||process.execArgv.includes("--experimental-wasm-jspi")||parseInt(process.versions.node.split(".")[0],10)<23)}function o(){const r=process.argv.slice(2);Promise.resolve().then(()=>require("./run-cli-C1cUS9na.cjs")).then(e=>e.runCli).then(({parseOptionsAndRunCLI:e})=>{e(r)})}if(t()){const r=Date.now(),e=i.spawn(process.execPath,["--experimental-wasm-jspi",...process.execArgv,...process.argv.slice(1)],{stdio:"inherit"});for(const s of["SIGINT","SIGTERM"])process.on(s,()=>e.kill(s));e.on("error",()=>{o()}),e.on("close",(s,n)=>{if(s!==0&&!n&&Date.now()-r<1e3){o();return}n?process.kill(process.pid,n):process.exit(s)})}else o();
2
2
  //# sourceMappingURL=cli.cjs.map
package/cli.js CHANGED
@@ -4,7 +4,7 @@ function t() {
4
4
  }
5
5
  function o() {
6
6
  const r = process.argv.slice(2);
7
- import("./run-cli-CC9V0J3D.js").then((e) => e.d).then(({ parseOptionsAndRunCLI: e }) => {
7
+ import("./run-cli-DxD1h5dy.js").then((e) => e.d).then(({ parseOptionsAndRunCLI: e }) => {
8
8
  e(r);
9
9
  });
10
10
  }
package/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./run-cli-b6r6MAhq.cjs");exports.LogVerbosity=e.LogVerbosity;exports.internalsKeyForTesting=e.internalsKeyForTesting;exports.mergeDefinedConstants=e.mergeDefinedConstants;exports.parseOptionsAndRunCLI=e.parseOptionsAndRunCLI;exports.resolveWorkerCount=e.resolveWorkerCount;exports.runCLI=e.runCLI;exports.spawnWorkerThread=e.spawnWorkerThread;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./run-cli-C1cUS9na.cjs");exports.LogVerbosity=e.LogVerbosity;exports.internalsKeyForTesting=e.internalsKeyForTesting;exports.mergeDefinedConstants=e.mergeDefinedConstants;exports.parseOptionsAndRunCLI=e.parseOptionsAndRunCLI;exports.resolveWorkerCount=e.resolveWorkerCount;exports.runCLI=e.runCLI;exports.spawnWorkerThread=e.spawnWorkerThread;
2
2
  //# sourceMappingURL=index.cjs.map
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { L as r, i as a, m as n, p as o, r as t, a as i, s as p } from "./run-cli-CC9V0J3D.js";
1
+ import { L as r, i as a, m as n, p as o, r as t, a as i, s as p } from "./run-cli-DxD1h5dy.js";
2
2
  export {
3
3
  r as LogVerbosity,
4
4
  a as internalsKeyForTesting,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-playground/cli",
3
- "version": "3.1.29",
3
+ "version": "3.1.31",
4
4
  "description": "WordPress Playground CLI",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,25 +34,25 @@
34
34
  "bin": {
35
35
  "wp-playground-cli": "wp-playground.js"
36
36
  },
37
- "gitHead": "39c1c459c1c4dd360ad0da011a15cd2f12514c8a",
37
+ "gitHead": "d84feb0c0a69675bd502b8c5f51b24ee52822c8c",
38
38
  "dependencies": {
39
39
  "express": "4.22.0",
40
40
  "fs-extra": "11.1.1",
41
41
  "tmp-promise": "3.0.3",
42
42
  "wasm-feature-detect": "1.8.0",
43
43
  "yargs": "17.7.2",
44
- "@wp-playground/common": "3.1.29",
45
- "@php-wasm/logger": "3.1.29",
46
- "@php-wasm/progress": "3.1.29",
47
- "@php-wasm/universal": "3.1.29",
48
- "@wp-playground/blueprints": "3.1.29",
49
- "@wp-playground/wordpress": "3.1.29",
50
- "@php-wasm/node": "3.1.29",
51
- "@php-wasm/util": "3.1.29",
52
- "@php-wasm/cli-util": "3.1.29",
53
- "@wp-playground/storage": "3.1.29",
54
- "@php-wasm/xdebug-bridge": "3.1.29",
55
- "@wp-playground/tools": "3.1.29"
44
+ "@wp-playground/common": "3.1.31",
45
+ "@php-wasm/logger": "3.1.31",
46
+ "@php-wasm/progress": "3.1.31",
47
+ "@php-wasm/universal": "3.1.31",
48
+ "@wp-playground/blueprints": "3.1.31",
49
+ "@wp-playground/wordpress": "3.1.31",
50
+ "@php-wasm/node": "3.1.31",
51
+ "@php-wasm/util": "3.1.31",
52
+ "@php-wasm/cli-util": "3.1.31",
53
+ "@wp-playground/storage": "3.1.31",
54
+ "@php-wasm/xdebug-bridge": "3.1.31",
55
+ "@wp-playground/tools": "3.1.31"
56
56
  },
57
57
  "packageManager": "npm@10.9.2",
58
58
  "overrides": {
@@ -1,21 +1,21 @@
1
1
  import type { PHPExtension, XdebugOptions } from '@php-wasm/node';
2
2
  /**
3
- * Converts the legacy Playground CLI extension options object into the runtime
4
- * `extensions` array.
3
+ * Converts Playground CLI extension options into the runtime `extensions`
4
+ * array.
5
5
  *
6
- * The CLI still receives extensions as individual options: `intl`, `redis`,
7
- * `memcached`, and `xdebug`. The PHP runtime no longer has separate `with*`
8
- * entry points for new callers; it expects one array that can contain built-in
9
- * extension names and, elsewhere, external extension sources. This function is
10
- * the CLI boundary between those two shapes.
6
+ * The CLI receives built-in extensions as individual options (`intl`, `redis`,
7
+ * `memcached`, and `xdebug`) and external extensions as manifest paths.
8
+ * The PHP runtime expects one array that can contain built-in names and
9
+ * external extension sources side by side.
11
10
  *
12
11
  * Xdebug is the only CLI extension here with options. A plain `true` becomes
13
12
  * the built-in `xdebug` request, while an object preserves the Xdebug settings
14
13
  * and passes them through to the Node runtime.
15
14
  */
16
- export declare function legacyPHPExtensionsObjectToExtensionsArray(args: {
15
+ export declare function cliExtensionArgsToExtensionsArray(args: {
17
16
  intl?: boolean;
18
17
  redis?: boolean;
19
18
  memcached?: boolean;
20
19
  xdebug?: boolean | XdebugOptions;
20
+ phpExtension?: string[];
21
21
  }): PHPExtension[];
@@ -1,4 +1,4 @@
1
- "use strict";const p=require("@php-wasm/logger"),m=require("@php-wasm/universal"),D=require("@wp-playground/blueprints"),A=require("@wp-playground/common"),c=require("fs"),q=require("worker_threads"),ve=require("@php-wasm/node"),d=require("path"),de=require("child_process"),Se=require("util"),ce=require("express"),xe=require("stream"),Ee=require("stream/promises"),ke=require("yargs"),V=require("@wp-playground/storage"),oe=require("@php-wasm/progress"),Ie=require("@wp-playground/wordpress"),L=require("fs-extra"),$e=require("module"),N=require("os"),Te=require("@php-wasm/xdebug-bridge"),ie=require("tmp-promise"),R=require("@php-wasm/cli-util"),Ce=require("crypto"),O=require("@wp-playground/tools"),se=require("wasm-feature-detect");var M=typeof document<"u"?document.currentScript:null;function Z(e){const t=[];for(const r of e){const o=r.split(":");if(o.length!==2)throw new Error(`Invalid mount format: ${r}.
1
+ "use strict";const p=require("@php-wasm/logger"),h=require("@php-wasm/universal"),D=require("@wp-playground/blueprints"),A=require("@wp-playground/common"),c=require("fs"),q=require("worker_threads"),ve=require("@php-wasm/node"),d=require("path"),de=require("child_process"),Se=require("util"),ce=require("express"),xe=require("stream"),Ee=require("stream/promises"),ke=require("yargs"),V=require("@wp-playground/storage"),oe=require("@php-wasm/progress"),Ie=require("@wp-playground/wordpress"),L=require("fs-extra"),$e=require("module"),N=require("os"),Te=require("@php-wasm/xdebug-bridge"),ie=require("tmp-promise"),R=require("@php-wasm/cli-util"),Ce=require("crypto"),O=require("@wp-playground/tools"),se=require("wasm-feature-detect");var M=typeof document<"u"?document.currentScript:null;function Z(e){const t=[];for(const r of e){const o=r.split(":");if(o.length!==2)throw new Error(`Invalid mount format: ${r}.
2
2
  Expected format: /host/path:/vfs/path.
3
3
  If your path contains a colon, e.g. C:\\myplugin, use the --mount-dir option instead.
4
4
  Example: --mount-dir C:\\my-plugin /wordpress/wp-content/plugins/my-plugin`);const[i,n]=o;if(!c.existsSync(i))throw new Error(`Host path does not exist: ${i}`);t.push({hostPath:i,vfsPath:n})}return t}function ae(e){if(e.length%2!==0)throw new Error("Invalid mount format. Expected: /host/path /vfs/path");const t=[];for(let r=0;r<e.length;r+=2){const o=e[r],i=e[r+1];if(!c.existsSync(o))throw new Error(`Host path does not exist: ${o}`);t.push({hostPath:d.resolve(process.cwd(),o),vfsPath:i})}return t}async function Re(e,t){for(const r of t)await e.mount(r.vfsPath,ve.createNodeFsMountHandler(r.hostPath))}const le={step:"runPHP",code:{filename:"activate-theme.php",content:`<?php
@@ -14,7 +14,7 @@
14
14
  }
15
15
  `}};function pe(e){if(typeof e.autoMount!="string")return e;const t=e.autoMount,r=[...e.mount||[]],o=[...e["mount-before-install"]||[]],i={...e,mount:r,"mount-before-install":o,"additional-blueprint-steps":[...e["additional-blueprint-steps"]||[]]};if(Le(t)){const a=`/wordpress/wp-content/plugins/${d.basename(t)}`;r.push({hostPath:t,vfsPath:a,autoMounted:!0}),i["additional-blueprint-steps"].push({step:"activatePlugin",pluginPath:`/wordpress/wp-content/plugins/${d.basename(t)}`})}else if(De(t)){const n=d.basename(t),a=`/wordpress/wp-content/themes/${n}`;r.push({hostPath:t,vfsPath:a,autoMounted:!0}),i["additional-blueprint-steps"].push(e["experimental-blueprints-v2-runner"]?{step:"activateTheme",themeDirectoryName:n}:{step:"activateTheme",themeFolderName:n})}else if(Ae(t)){const n=c.readdirSync(t);for(const a of n)a!=="index.php"&&r.push({hostPath:d.join(t,a),vfsPath:`/wordpress/wp-content/${a}`,autoMounted:!0});i["additional-blueprint-steps"].push(le)}else Me(t)&&(o.push({hostPath:t,vfsPath:"/wordpress",autoMounted:!0}),i.mode="apply-to-existing-site",i["additional-blueprint-steps"].push(le),i.wordpressInstallMode||(i.wordpressInstallMode="install-from-existing-files-if-needed"));return i}function Me(e){const t=c.readdirSync(e);return t.includes("wp-admin")&&t.includes("wp-includes")&&t.includes("wp-content")}function Ae(e){const t=c.readdirSync(e);return t.includes("themes")||t.includes("plugins")||t.includes("mu-plugins")||t.includes("uploads")}function De(e){if(!c.readdirSync(e).includes("style.css"))return!1;const r=c.readFileSync(d.join(e,"style.css"),"utf8");return!!/^(?:[ \t]*<\?php)?[ \t/*#@]*Theme Name:(.*)$/im.exec(r)}function Le(e){const t=c.readdirSync(e),r=/^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;return!!t.filter(i=>i.endsWith(".php")).find(i=>{const n=c.readFileSync(d.join(e,i),"utf8");return!!r.exec(n)})}function Ue(e){if(e.length%2!==0)throw new Error("Invalid constant definition format. Expected pairs of NAME value");const t={};for(let r=0;r<e.length;r+=2){const o=e[r],i=e[r+1];if(!o||!o.trim())throw new Error("Constant name cannot be empty");t[o.trim()]=i}return t}function _e(e){if(e.length%2!==0)throw new Error("Invalid boolean constant definition format. Expected pairs of NAME value");const t={};for(let r=0;r<e.length;r+=2){const o=e[r],i=e[r+1].trim().toLowerCase();if(!o||!o.trim())throw new Error("Constant name cannot be empty");if(i==="true"||i==="1")t[o.trim()]=!0;else if(i==="false"||i==="0")t[o.trim()]=!1;else throw new Error(`Invalid boolean value for constant "${o}": "${i}". Must be "true", "false", "1", or "0".`)}return t}function He(e){if(e.length%2!==0)throw new Error("Invalid number constant definition format. Expected pairs of NAME value");const t={};for(let r=0;r<e.length;r+=2){const o=e[r],i=e[r+1].trim();if(!o||!o.trim())throw new Error("Constant name cannot be empty");const n=Number(i);if(isNaN(n))throw new Error(`Invalid number value for constant "${o}": "${i}". Must be a valid number.`);t[o.trim()]=n}return t}function We(e={},t={},r={}){const o={},i=new Set,n=(a,l)=>{for(const u in a){if(i.has(u))throw new Error(`Constant "${u}" is defined multiple times across different --define-${l} flags`);i.add(u),o[u]=a[u]}};return n(e,"string"),n(t,"bool"),n(r,"number"),o}function j(e){return We(e.define,e["define-bool"],e["define-number"])}const Be=Se.promisify(de.exec);function Ne(e){return new Promise(t=>{if(e===0)return t(!1);const r=ce().listen(e);r.once("listening",()=>r.close(()=>t(!1))),r.once("error",o=>t(o.code==="EADDRINUSE"))})}async function Fe(e){const t=ce(),r=await new Promise((a,l)=>{const u=t.listen(e.port,()=>{const w=u.address();w===null||typeof w=="string"?l(new Error("Server address is not available")):a(u)}).once("error",l)});t.use("/",async(a,l)=>{try{const u={url:a.url,headers:je(a),method:a.method,body:await Oe(a)},w=await e.handleRequest(u);await Ve(w,l)}catch(u){p.logger.error(u),l.headersSent||(l.statusCode=500,l.end("Internal Server Error"))}});const i=r.address().port,n=process.env.CODESPACE_NAME;return n&&qe(i,n),await e.onBind(r,i)}async function Ve(e,t){const[r,o]=await Promise.all([e.headers,e.httpStatusCode]);t.statusCode=o;for(const n in r)t.setHeader(n,r[n]);const i=xe.Readable.fromWeb(e.stdout);try{await Ee.pipeline(i,t)}catch(n){if(n instanceof Error&&"code"in n&&(n.code==="ERR_STREAM_PREMATURE_CLOSE"||n.code==="ERR_STREAM_UNABLE_TO_PIPE"))return;throw n}}const Oe=async e=>await new Promise(t=>{const r=[];e.on("data",o=>{r.push(o)}),e.on("end",()=>{t(new Uint8Array(Buffer.concat(r)))})});async function qe(e,t){p.logger.log(`Publishing port ${e}...`);const r=`gh codespace ports visibility ${e}:public -c ${t}`;for(let o=0;o<10;o++)try{await Be(r);return}catch{await new Promise(i=>setTimeout(i,2e3))}}const je=e=>{const t={};if(e.rawHeaders&&e.rawHeaders.length)for(let r=0;r<e.rawHeaders.length;r+=2)t[e.rawHeaders[r].toLowerCase()]=e.rawHeaders[r+1];return t};function ze(e){return/^latest$|^beta$|^trunk$|^nightly$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/.test(e)}async function Ge({sourceString:e,blueprintMayReadAdjacentFiles:t}){if(!e)return;if(e.startsWith("http://")||e.startsWith("https://"))return await D.resolveRemoteBlueprint(e);let r=d.resolve(process.cwd(),e);if(!c.existsSync(r))throw new Error(`Blueprint file does not exist: ${r}`);const o=c.statSync(r);if(o.isDirectory()&&(r=d.join(r,"blueprint.json")),!o.isFile()&&o.isSymbolicLink())throw new Error(`Blueprint path is neither a file nor a directory: ${r}`);const i=d.extname(r);switch(i){case".zip":return V.ZipFilesystem.fromArrayBuffer(c.readFileSync(r).buffer);case".json":{const n=c.readFileSync(r,"utf-8");try{JSON.parse(n)}catch{throw new Error(`Blueprint file at ${r} is not a valid JSON file`)}const a=d.dirname(r),l=new V.NodeJsFilesystem(a);return new V.OverlayFilesystem([new V.InMemoryFilesystem({"blueprint.json":n}),{read(u){if(!t)throw new Error(`Error: Blueprint contained tried to read a local file at path "${u}" (via a resource of type "bundled"). Playground restricts access to local resources by default as a security measure.
16
16
 
17
- You can allow this Blueprint to read files from the same parent directory by explicitly adding the --blueprint-may-read-adjacent-files option to your command.`);return l.read(u)}}])}default:throw new Error(`Unsupported blueprint file extension: ${i}. Only .zip and .json files are supported.`)}}function he(e){const t=[];return e.intl&&t.push("intl"),e.redis&&t.push("redis"),e.memcached&&t.push("memcached"),e.xdebug&&t.push(typeof e.xdebug=="object"?{name:"xdebug",options:e.xdebug}:"xdebug"),t}class Ye{constructor(t,r){this.args=t,this.siteUrl=r.siteUrl,this.phpVersion=t.php,this.cliOutput=r.cliOutput}getWorkerType(){return"v2"}async bootWordPress(t,r){const o={command:this.args.command,siteUrl:this.siteUrl,blueprint:this.args.blueprint,workerPostInstallMountsPort:r};return await t.bootWordPress(o,r),t}async bootRequestHandler({worker:t,fileLockManagerPort:r,nativeInternalDirPath:o}){const i=m.consumeAPI(t.phpPort);await i.useFileLockManager(r);const n={...this.args,phpVersion:this.phpVersion,siteUrl:this.siteUrl,processId:t.processId,trace:this.args.verbosity==="debug",extensions:he(this.args),nativeInternalDirPath:o,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],constants:j(this.args)};return await i.bootWorker(n),i}}const J=d.join(N.homedir(),".wordpress-playground");async function Qe(e="trunk"){const t=typeof __dirname<"u"?__dirname:void 0;let r=d.join(t,"sqlite-database-integration.zip");if(!L.existsSync(r)){const o=$e.createRequire(typeof document>"u"?require("url").pathToFileURL(__filename).href:M&&M.tagName.toUpperCase()==="SCRIPT"&&M.src||new URL("run-cli-b6r6MAhq.cjs",document.baseURI).href),i=d.dirname(o.resolve("@wp-playground/wordpress-builds/package.json"));r=d.join(i,"src","sqlite-database-integration",`sqlite-database-integration-${e}.zip`)}return new File([await L.readFile(r)],d.basename(r))}async function Xe(e,t,r){const o=d.join(J,t);return L.existsSync(o)||(L.ensureDirSync(J),await Ze(e,o,r)),me(o)}async function Ze(e,t,r){const i=(await r.monitorFetch(fetch(e))).body.getReader(),n=`${t}.partial`,a=L.createWriteStream(n);for(;;){const{done:l,value:u}=await i.read();if(u&&a.write(u),l)break}a.close(),a.closed||await new Promise((l,u)=>{a.on("finish",()=>{L.renameSync(n,t),l(null)}),a.on("error",w=>{L.removeSync(n),u(w)})})}function me(e,t){return new File([L.readFileSync(e)],d.basename(e))}class Ke{constructor(t,r){this.args=t,this.siteUrl=r.siteUrl,this.cliOutput=r.cliOutput}getWorkerType(){return"v1"}async bootWordPress(t,r){let o,i,n;const a=new oe.EmscriptenDownloadMonitor;if(this.args.wordpressInstallMode==="download-and-install"){let w=!1;a.addEventListener("progress",U=>{if(w)return;const{loaded:k,total:f}=U.detail,I=Math.floor(Math.min(100,100*k/f));w=I===100,this.cliOutput.updateProgress("Downloading WordPress",I)}),o=await Ie.resolveWordPressRelease(this.args.wp),n=d.join(J,`prebuilt-wp-content-for-wp-${o.version}.zip`),i=c.existsSync(n)?me(n):await Xe(o.releaseUrl,`${o.version}.zip`,a),p.logger.debug(`Resolved WordPress release URL: ${o?.releaseUrl}`)}let l;if(this.args.skipSqliteSetup)p.logger.debug("Skipping SQLite integration plugin setup..."),l=void 0;else{this.cliOutput.updateProgress("Preparing SQLite database");const w=this.args.php||A.RecommendedPHPVersion,k=m.isLegacyPHPVersion(w)?"v3.0.0-rc.3-php52":"trunk";l=await Qe(k)}this.cliOutput.updateProgress("Booting WordPress");const u=await D.resolveRuntimeConfiguration(this.getEffectiveBlueprint());return await t.bootWordPress({phpVersion:u.phpVersion,wpVersion:u.wpVersion,siteUrl:this.siteUrl,wordpressInstallMode:this.args.wordpressInstallMode||"download-and-install",wordPressZip:i&&await i.arrayBuffer(),sqliteIntegrationPluginZip:await l?.arrayBuffer(),constants:j(this.args)},r),n&&!this.args["mount-before-install"]&&!c.existsSync(n)&&(this.cliOutput.updateProgress("Caching WordPress for next boot"),c.writeFileSync(n,await A.zipDirectory(t,"/wordpress"))),t}async bootRequestHandler({worker:t,fileLockManagerPort:r,nativeInternalDirPath:o}){const i=m.consumeAPI(t.phpPort);await i.isConnected();const n=await D.resolveRuntimeConfiguration(this.getEffectiveBlueprint());return await i.useFileLockManager(r),await i.bootRequestHandler({phpVersion:n.phpVersion,siteUrl:this.siteUrl,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],processId:t.processId,followSymlinks:this.args.followSymlinks===!0,trace:this.args.experimentalTrace===!0,extensions:he(this.args),nativeInternalDirPath:o,pathAliases:this.args.pathAliases}),await i.isReady(),i}async compileInputBlueprint(t){const r=this.getEffectiveBlueprint(),o=new oe.ProgressTracker;let i="",n=!1;return o.addEventListener("progress",a=>{if(n)return;n=a.detail.progress===100;const l=Math.floor(a.detail.progress);i=a.detail.caption||i||"Running Blueprint",this.cliOutput.updateProgress(i.trim(),l)}),await D.compileBlueprintV1(r,{progress:o,additionalSteps:t})}getEffectiveBlueprint(){const t=this.args.blueprint;return D.isBlueprintBundle(t)?t:{login:this.args.login,...t||{},preferredVersions:{php:this.args.php??t?.preferredVersions?.php??A.RecommendedPHPVersion,wp:this.args.wp??t?.preferredVersions?.wp??"latest",...t?.preferredVersions||{}}}}}async function Je(e,t=!0){const o=`${d.basename(process.argv0)}${e}${process.pid}-`,i=await ie.dir({prefix:o,unsafeCleanup:!0});return t&&ie.setGracefulCleanup(),i}async function et(e,t,r){const i=(await tt(e,t,r)).map(n=>new Promise(a=>{c.rm(n,{recursive:!0},l=>{l?p.logger.warn(`Failed to delete stale Playground temp dir: ${n}`,l):p.logger.info(`Deleted stale Playground temp dir: ${n}`),a()})}));await Promise.all(i)}async function tt(e,t,r){try{const o=c.readdirSync(r).map(n=>d.join(r,n)),i=[];for(const n of o)await rt(e,t,n)&&i.push(n);return i}catch(o){return p.logger.warn(`Failed to find stale Playground temp dirs: ${o}`),[]}}async function rt(e,t,r){if(!c.lstatSync(r).isDirectory())return!1;const i=d.basename(r);if(!i.includes(e))return!1;const n=i.match(new RegExp(`^(.+)${e}(\\d+)-`));if(!n)return!1;const a={executableName:n[1],pid:n[2]};if(nt(a.pid))return!1;const l=Date.now()-t;return c.statSync(r).mtime.getTime()<l}function nt(e,t){try{return process.kill(Number(e),0),!0}catch(r){const o=r,i=o&&typeof o.code=="string"?o.code:void 0;return i==="ESRCH"?!1:i==="EPERM"||i==="EACCES"?(p.logger.debug(`Permission denied while checking if process ${e} exists (code: ${i}).`,r),!0):(p.logger.warn(`Could not determine if process ${e} exists due to unexpected error${i?` (code: ${i})`:""}.`,r),!0)}}function fe(e){return process.env.CI==="true"||process.env.CI==="1"||process.env.GITHUB_ACTIONS==="true"||process.env.GITHUB_ACTIONS==="1"||(process.env.TERM||"").toLowerCase()==="dumb"?!1:e?!!e.isTTY:process.stdout.isTTY}class ot extends R.CLIOutput{constructor(){super(...arguments),this.lastProgressLine="",this.progressActive=!1}get shouldRender(){return fe(this.writeStream)}printBanner(){if(this.isQuiet)return;const t=this.bold("WordPress Playground CLI");this.writeStream.write(`
17
+ You can allow this Blueprint to read files from the same parent directory by explicitly adding the --blueprint-may-read-adjacent-files option to your command.`);return l.read(u)}}])}default:throw new Error(`Unsupported blueprint file extension: ${i}. Only .zip and .json files are supported.`)}}function me(e){const t=[];e.intl&&t.push("intl"),e.redis&&t.push("redis"),e.memcached&&t.push("memcached"),e.xdebug&&t.push(typeof e.xdebug=="object"?{name:"xdebug",options:e.xdebug}:"xdebug");for(const r of e.phpExtension||[])t.push({source:{format:"manifest",manifestUrl:r}});return t}class Ye{constructor(t,r){this.args=t,this.siteUrl=r.siteUrl,this.phpVersion=t.php,this.cliOutput=r.cliOutput}getWorkerType(){return"v2"}async bootWordPress(t,r){const o={command:this.args.command,siteUrl:this.siteUrl,blueprint:this.args.blueprint,workerPostInstallMountsPort:r};return await t.bootWordPress(o,r),t}async bootRequestHandler({worker:t,fileLockManagerPort:r,nativeInternalDirPath:o}){const i=h.consumeAPI(t.phpPort);await i.useFileLockManager(r);const n={...this.args,phpVersion:this.phpVersion,siteUrl:this.siteUrl,processId:t.processId,trace:this.args.verbosity==="debug",extensions:me(this.args),nativeInternalDirPath:o,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],constants:j(this.args)};return await i.bootWorker(n),i}}const J=d.join(N.homedir(),".wordpress-playground");async function Qe(e="trunk"){const t=typeof __dirname<"u"?__dirname:void 0;let r=d.join(t,"sqlite-database-integration.zip");if(!L.existsSync(r)){const o=$e.createRequire(typeof document>"u"?require("url").pathToFileURL(__filename).href:M&&M.tagName.toUpperCase()==="SCRIPT"&&M.src||new URL("run-cli-C1cUS9na.cjs",document.baseURI).href),i=d.dirname(o.resolve("@wp-playground/wordpress-builds/package.json"));r=d.join(i,"src","sqlite-database-integration",`sqlite-database-integration-${e}.zip`)}return new File([await L.readFile(r)],d.basename(r))}async function Xe(e,t,r){const o=d.join(J,t);return L.existsSync(o)||(L.ensureDirSync(J),await Ze(e,o,r)),he(o)}async function Ze(e,t,r){const i=(await r.monitorFetch(fetch(e))).body.getReader(),n=`${t}.partial`,a=L.createWriteStream(n);for(;;){const{done:l,value:u}=await i.read();if(u&&a.write(u),l)break}a.close(),a.closed||await new Promise((l,u)=>{a.on("finish",()=>{L.renameSync(n,t),l(null)}),a.on("error",w=>{L.removeSync(n),u(w)})})}function he(e,t){return new File([L.readFileSync(e)],d.basename(e))}class Ke{constructor(t,r){this.args=t,this.siteUrl=r.siteUrl,this.cliOutput=r.cliOutput}getWorkerType(){return"v1"}async bootWordPress(t,r){let o,i,n;const a=new oe.EmscriptenDownloadMonitor;if(this.args.wordpressInstallMode==="download-and-install"){let w=!1;a.addEventListener("progress",U=>{if(w)return;const{loaded:k,total:f}=U.detail,I=Math.floor(Math.min(100,100*k/f));w=I===100,this.cliOutput.updateProgress("Downloading WordPress",I)}),o=await Ie.resolveWordPressRelease(this.args.wp),n=d.join(J,`prebuilt-wp-content-for-wp-${o.version}.zip`),i=c.existsSync(n)?he(n):await Xe(o.releaseUrl,`${o.version}.zip`,a),p.logger.debug(`Resolved WordPress release URL: ${o?.releaseUrl}`)}let l;if(this.args.skipSqliteSetup)p.logger.debug("Skipping SQLite integration plugin setup..."),l=void 0;else{this.cliOutput.updateProgress("Preparing SQLite database");const w=this.args.php||A.RecommendedPHPVersion,k=h.isLegacyPHPVersion(w)?"v3.0.0-rc.3-php52":"trunk";l=await Qe(k)}this.cliOutput.updateProgress("Booting WordPress");const u=await D.resolveRuntimeConfiguration(this.getEffectiveBlueprint());return await t.bootWordPress({phpVersion:u.phpVersion,wpVersion:u.wpVersion,siteUrl:this.siteUrl,wordpressInstallMode:this.args.wordpressInstallMode||"download-and-install",wordPressZip:i&&await i.arrayBuffer(),sqliteIntegrationPluginZip:await l?.arrayBuffer(),constants:j(this.args)},r),n&&!this.args["mount-before-install"]&&!c.existsSync(n)&&(this.cliOutput.updateProgress("Caching WordPress for next boot"),c.writeFileSync(n,await A.zipDirectory(t,"/wordpress"))),t}async bootRequestHandler({worker:t,fileLockManagerPort:r,nativeInternalDirPath:o}){const i=h.consumeAPI(t.phpPort);await i.isConnected();const n=await D.resolveRuntimeConfiguration(this.getEffectiveBlueprint());return await i.useFileLockManager(r),await i.bootRequestHandler({phpVersion:n.phpVersion,siteUrl:this.siteUrl,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],processId:t.processId,followSymlinks:this.args.followSymlinks===!0,trace:this.args.experimentalTrace===!0,extensions:me(this.args),nativeInternalDirPath:o,pathAliases:this.args.pathAliases}),await i.isReady(),i}async compileInputBlueprint(t){const r=this.getEffectiveBlueprint(),o=new oe.ProgressTracker;let i="",n=!1;return o.addEventListener("progress",a=>{if(n)return;n=a.detail.progress===100;const l=Math.floor(a.detail.progress);i=a.detail.caption||i||"Running Blueprint",this.cliOutput.updateProgress(i.trim(),l)}),await D.compileBlueprintV1(r,{progress:o,additionalSteps:t})}getEffectiveBlueprint(){const t=this.args.blueprint;return D.isBlueprintBundle(t)?t:{login:this.args.login,...t||{},preferredVersions:{php:this.args.php??t?.preferredVersions?.php??A.RecommendedPHPVersion,wp:this.args.wp??t?.preferredVersions?.wp??"latest",...t?.preferredVersions||{}}}}}async function Je(e,t=!0){const o=`${d.basename(process.argv0)}${e}${process.pid}-`,i=await ie.dir({prefix:o,unsafeCleanup:!0});return t&&ie.setGracefulCleanup(),i}async function et(e,t,r){const i=(await tt(e,t,r)).map(n=>new Promise(a=>{c.rm(n,{recursive:!0},l=>{l?p.logger.warn(`Failed to delete stale Playground temp dir: ${n}`,l):p.logger.info(`Deleted stale Playground temp dir: ${n}`),a()})}));await Promise.all(i)}async function tt(e,t,r){try{const o=c.readdirSync(r).map(n=>d.join(r,n)),i=[];for(const n of o)await rt(e,t,n)&&i.push(n);return i}catch(o){return p.logger.warn(`Failed to find stale Playground temp dirs: ${o}`),[]}}async function rt(e,t,r){if(!c.lstatSync(r).isDirectory())return!1;const i=d.basename(r);if(!i.includes(e))return!1;const n=i.match(new RegExp(`^(.+)${e}(\\d+)-`));if(!n)return!1;const a={executableName:n[1],pid:n[2]};if(nt(a.pid))return!1;const l=Date.now()-t;return c.statSync(r).mtime.getTime()<l}function nt(e,t){try{return process.kill(Number(e),0),!0}catch(r){const o=r,i=o&&typeof o.code=="string"?o.code:void 0;return i==="ESRCH"?!1:i==="EPERM"||i==="EACCES"?(p.logger.debug(`Permission denied while checking if process ${e} exists (code: ${i}).`,r),!0):(p.logger.warn(`Could not determine if process ${e} exists due to unexpected error${i?` (code: ${i})`:""}.`,r),!0)}}function fe(e){return process.env.CI==="true"||process.env.CI==="1"||process.env.GITHUB_ACTIONS==="true"||process.env.GITHUB_ACTIONS==="1"||(process.env.TERM||"").toLowerCase()==="dumb"?!1:e?!!e.isTTY:process.stdout.isTTY}class ot extends R.CLIOutput{constructor(){super(...arguments),this.lastProgressLine="",this.progressActive=!1}get shouldRender(){return fe(this.writeStream)}printBanner(){if(this.isQuiet)return;const t=this.bold("WordPress Playground CLI");this.writeStream.write(`
18
18
  ${t}
19
19
 
20
20
  `)}printConfig(t){if(this.isQuiet)return;const r=[];r.push(`${this.dim("PHP")} ${this.cyan(t.phpVersion)} ${this.dim("WordPress")} ${this.cyan(t.wpVersion)}`);const o=[];if(t.intl&&o.push("intl"),t.redis&&o.push("redis"),t.memcached&&o.push("memcached"),t.xdebug&&o.push(this.yellow("xdebug")),o.length>0&&r.push(`${this.dim("Extensions")} ${o.join(", ")}`),t.mounts.length>0)for(const i of t.mounts){const n=i.autoMounted?` ${this.dim("(auto-mount)")}`:"";r.push(`${this.dim("Mount")} ${i.hostPath} ${this.dim("→")} ${i.vfsPath}${n}`)}t.blueprint&&r.push(`${this.dim("Blueprint")} ${t.blueprint}`),this.writeStream.write(r.join(`
@@ -30,8 +30,8 @@ ${this.green("Ready!")} WordPress is running on ${this.bold(t)} ${this.dim(`(${r
30
30
 
31
31
  `)}printPhpMyAdminUrl(t){this.isQuiet||this.writeStream.write(`${this.cyan("phpMyAdmin")} available at ${this.bold(t)}
32
32
 
33
- `)}}const z={Quiet:{name:"quiet",severity:p.LogSeverity.Fatal},Normal:{name:"normal",severity:p.LogSeverity.Info},Debug:{name:"debug",severity:p.LogSeverity.Debug}};async function we(e){try{const t={"site-url":{describe:"Site URL to use for WordPress. Defaults to http://127.0.0.1:{port}",type:"string"},php:{describe:"PHP version to use.",type:"string",default:A.RecommendedPHPVersion,choices:m.AllPHPVersions},wp:{describe:"WordPress version to use.",type:"string",default:"latest"},define:{describe:'Define PHP string constants (can be used multiple times). Format: NAME value. These constants are set via php.defineConstant() and only exist for the current request. Examples: --define API_KEY secret --define CON=ST "va=lu=e"',type:"string",nargs:2,array:!0,coerce:Ue},"define-bool":{describe:'Define PHP boolean constants (can be used multiple times). Format: NAME value. Value must be "true", "false", "1", or "0". Examples: --define-bool WP_DEBUG true --define-bool MY_FEATURE false',type:"string",nargs:2,array:!0,coerce:_e},"define-number":{describe:"Define PHP number constants (can be used multiple times). Format: NAME value. Examples: --define-number LIMIT 100 --define-number RATE 45.67",type:"string",nargs:2,array:!0,coerce:He},mount:{describe:"Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path",type:"array",string:!0,nargs:1,coerce:Z},"mount-before-install":{describe:"Mount a directory to the PHP runtime before WordPress installation (can be used multiple times). Format: /host/path:/vfs/path",type:"array",string:!0,nargs:1,coerce:Z},"mount-dir":{describe:'Mount a directory to the PHP runtime (can be used multiple times). Format: "/host/path" "/vfs/path"',type:"array",nargs:2,array:!0,coerce:ae},"mount-dir-before-install":{describe:'Mount a directory before WordPress installation (can be used multiple times). Format: "/host/path" "/vfs/path"',type:"string",nargs:2,array:!0,coerce:ae},login:{describe:"Should log the user in",type:"boolean",default:!1},blueprint:{describe:"Blueprint to execute.",type:"string"},"blueprint-may-read-adjacent-files":{describe:'Consent flag: Allow "bundled" resources in a local blueprint to read files in the same directory as the blueprint file.',type:"boolean",default:!1},"wordpress-install-mode":{describe:"Control how Playground prepares WordPress before booting.",type:"string",default:"download-and-install",choices:["download-and-install","install-from-existing-files","install-from-existing-files-if-needed","do-not-attempt-installing"]},"skip-wordpress-install":{describe:"[Deprecated] Use --wordpress-install-mode instead.",type:"boolean",hidden:!0},"skip-sqlite-setup":{describe:"Skip the SQLite integration plugin setup to allow the WordPress site to use MySQL.",type:"boolean",default:!1},quiet:{describe:"Do not output logs and progress messages.",type:"boolean",default:!1,hidden:!0},verbosity:{describe:"Output logs and progress messages.",type:"string",choices:Object.values(z).map(s=>s.name),default:"normal"},debug:{describe:"Print PHP error log content if an error occurs during Playground boot.",type:"boolean",default:!1,hidden:!0},"auto-mount":{describe:"Automatically mount the specified directory. If no path is provided, mount the current working directory. You can mount a WordPress directory, a plugin directory, a theme directory, a wp-content directory, or any directory containing PHP and HTML files.",type:"string"},"follow-symlinks":{describe:`Allow Playground to follow symlinks by automatically mounting symlinked directories and files encountered in mounted directories.
34
- Warning: Following symlinks will expose files outside mounted directories to Playground and could be a security risk.`,type:"boolean",default:!1},"experimental-trace":{describe:"Print detailed messages about system behavior to the console. Useful for troubleshooting.",type:"boolean",default:!1,hidden:!0},"internal-cookie-store":{describe:"Enable internal cookie handling. When enabled, Playground will manage cookies internally using an HttpCookieStore that persists cookies across requests. When disabled, cookies are handled externally (e.g., by a browser in Node.js environments).",type:"boolean",default:!1},intl:{describe:"Enable Intl.",type:"boolean",default:!0},redis:{describe:"Enable Redis (requires JSPI support).",type:"boolean"},memcached:{describe:"Enable Memcached.",type:"boolean"},xdebug:{describe:"Enable Xdebug.",type:"boolean",default:!1},"experimental-unsafe-ide-integration":{describe:"Enable experimental IDE development tools. This option edits IDE config files to set Xdebug path mappings and web server details. CAUTION: If there are bugs, this feature may break your IDE config files. Please consider backing up your IDE configs before using this feature.",type:"string",choices:["","vscode","phpstorm"],coerce:s=>s===""?["vscode","phpstorm"]:[s]},"experimental-blueprints-v2-runner":{describe:"Use the experimental Blueprint V2 runner.",type:"boolean",default:!1,hidden:!0},mode:{describe:"Blueprints v2 runner mode to use. This option is required when using the --experimental-blueprints-v2-runner flag with a blueprint.",type:"string",choices:["create-new-site","apply-to-existing-site"],hidden:!0},phpmyadmin:{describe:"Install phpMyAdmin for database management. The phpMyAdmin URL will be printed after boot. Optionally specify a custom URL path (default: /phpmyadmin).",type:"string",coerce:s=>s===""?"/phpmyadmin":s}},r={port:{describe:"Port to listen on when serving. Defaults to 9400 when available.",type:"number"},workers:{describe:'Number of request-handling worker threads. Accepts a positive integer, or "auto" to use one per CPU core (minus one). Defaults to min(6, cpus-1).',type:"string",coerce:s=>{if(s===void 0)return;if(s==="auto")return"auto";const b=Number(s);if(!Number.isInteger(b)||b<1)throw new Error(`Invalid --workers value "${s}": expected a positive integer or "auto".`);return b}},"experimental-multi-worker":{deprecated:"Use --workers=<n|auto> instead. The value of this flag is ignored.",describe:"Deprecated. Use --workers=<n|auto> to control the number of request-handling worker threads.",type:"number"},"experimental-devtools":{describe:"Enable experimental browser development tools.",type:"boolean"}},o={path:{describe:"Path to the project directory. Playground will auto-detect if this is a plugin, theme, wp-content, or WordPress directory.",type:"string",default:process.cwd()},php:{describe:"PHP version to use.",type:"string",default:A.RecommendedPHPVersion,choices:m.AllPHPVersions},wp:{describe:"WordPress version to use.",type:"string",default:"latest"},port:{describe:"Port to listen on. Defaults to 9400 when available.",type:"number"},blueprint:{describe:"Path to a Blueprint JSON file to execute on startup.",type:"string"},login:{describe:"Auto-login as the admin user.",type:"boolean",default:!0},xdebug:{describe:"Enable Xdebug for debugging.",type:"boolean",default:!1},"experimental-unsafe-ide-integration":t["experimental-unsafe-ide-integration"],"skip-browser":{describe:"Do not open the site in your default browser on startup.",type:"boolean",default:!1},quiet:{describe:"Suppress non-essential output.",type:"boolean",default:!1},"site-url":{describe:"Override the site URL. By default, derived from the port (http://127.0.0.1:<port>).",type:"string"},mount:{describe:"Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path. Use this for additional mounts beyond auto-detection.",type:"array",string:!0,coerce:Z},reset:{describe:"Deletes the stored site directory and starts a new site from scratch.",type:"boolean",default:!1},"auto-mount":{describe:"Automatically detect project type (plugin, theme, wp-content, or WordPress) and mount accordingly. Use --no-auto-mount to disable and --mount to manually specify mounts instead.",type:"boolean",default:!0},define:t.define,"define-bool":t["define-bool"],"define-number":t["define-number"],phpmyadmin:t.phpmyadmin},i={outfile:{describe:"When building, write to this output file.",type:"string",default:"wordpress.zip"}},n=ke(e).usage("Usage: wp-playground <command> [options]").command("start","Start a local WordPress server with automatic project detection (recommended)",s=>s.usage(`Usage: wp-playground start [options]
33
+ `)}}const z={Quiet:{name:"quiet",severity:p.LogSeverity.Fatal},Normal:{name:"normal",severity:p.LogSeverity.Info},Debug:{name:"debug",severity:p.LogSeverity.Debug}};async function we(e){try{const t={"site-url":{describe:"Site URL to use for WordPress. Defaults to http://127.0.0.1:{port}",type:"string"},php:{describe:"PHP version to use.",type:"string",default:A.RecommendedPHPVersion,choices:h.AllPHPVersions},wp:{describe:"WordPress version to use.",type:"string",default:"latest"},define:{describe:'Define PHP string constants (can be used multiple times). Format: NAME value. These constants are set via php.defineConstant() and only exist for the current request. Examples: --define API_KEY secret --define CON=ST "va=lu=e"',type:"string",nargs:2,array:!0,coerce:Ue},"define-bool":{describe:'Define PHP boolean constants (can be used multiple times). Format: NAME value. Value must be "true", "false", "1", or "0". Examples: --define-bool WP_DEBUG true --define-bool MY_FEATURE false',type:"string",nargs:2,array:!0,coerce:_e},"define-number":{describe:"Define PHP number constants (can be used multiple times). Format: NAME value. Examples: --define-number LIMIT 100 --define-number RATE 45.67",type:"string",nargs:2,array:!0,coerce:He},mount:{describe:"Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path",type:"array",string:!0,nargs:1,coerce:Z},"mount-before-install":{describe:"Mount a directory to the PHP runtime before WordPress installation (can be used multiple times). Format: /host/path:/vfs/path",type:"array",string:!0,nargs:1,coerce:Z},"mount-dir":{describe:'Mount a directory to the PHP runtime (can be used multiple times). Format: "/host/path" "/vfs/path"',type:"array",nargs:2,array:!0,coerce:ae},"mount-dir-before-install":{describe:'Mount a directory before WordPress installation (can be used multiple times). Format: "/host/path" "/vfs/path"',type:"string",nargs:2,array:!0,coerce:ae},login:{describe:"Should log the user in",type:"boolean",default:!1},blueprint:{describe:"Blueprint to execute.",type:"string"},"blueprint-may-read-adjacent-files":{describe:'Consent flag: Allow "bundled" resources in a local blueprint to read files in the same directory as the blueprint file.',type:"boolean",default:!1},"wordpress-install-mode":{describe:"Control how Playground prepares WordPress before booting.",type:"string",default:"download-and-install",choices:["download-and-install","install-from-existing-files","install-from-existing-files-if-needed","do-not-attempt-installing"]},"skip-wordpress-install":{describe:"[Deprecated] Use --wordpress-install-mode instead.",type:"boolean",hidden:!0},"skip-sqlite-setup":{describe:"Skip the SQLite integration plugin setup to allow the WordPress site to use MySQL.",type:"boolean",default:!1},quiet:{describe:"Do not output logs and progress messages.",type:"boolean",default:!1,hidden:!0},verbosity:{describe:"Output logs and progress messages.",type:"string",choices:Object.values(z).map(s=>s.name),default:"normal"},debug:{describe:"Print PHP error log content if an error occurs during Playground boot.",type:"boolean",default:!1,hidden:!0},"auto-mount":{describe:"Automatically mount the specified directory. If no path is provided, mount the current working directory. You can mount a WordPress directory, a plugin directory, a theme directory, a wp-content directory, or any directory containing PHP and HTML files.",type:"string"},"follow-symlinks":{describe:`Allow Playground to follow symlinks by automatically mounting symlinked directories and files encountered in mounted directories.
34
+ Warning: Following symlinks will expose files outside mounted directories to Playground and could be a security risk.`,type:"boolean",default:!1},"experimental-trace":{describe:"Print detailed messages about system behavior to the console. Useful for troubleshooting.",type:"boolean",default:!1,hidden:!0},"internal-cookie-store":{describe:"Enable internal cookie handling. When enabled, Playground will manage cookies internally using an HttpCookieStore that persists cookies across requests. When disabled, cookies are handled externally (e.g., by a browser in Node.js environments).",type:"boolean",default:!1},intl:{describe:"Enable Intl.",type:"boolean",default:!0},redis:{describe:"Enable Redis (requires JSPI support).",type:"boolean"},memcached:{describe:"Enable Memcached.",type:"boolean"},xdebug:{describe:"Enable Xdebug.",type:"boolean",default:!1},"php-extension":{describe:"Load a custom PHP.wasm extension manifest before PHP starts. Can be a local path, file: URL, or http(s) URL. Can be used multiple times.",type:"array",string:!0,nargs:1},"experimental-unsafe-ide-integration":{describe:"Enable experimental IDE development tools. This option edits IDE config files to set Xdebug path mappings and web server details. CAUTION: If there are bugs, this feature may break your IDE config files. Please consider backing up your IDE configs before using this feature.",type:"string",choices:["","vscode","phpstorm"],coerce:s=>s===""?["vscode","phpstorm"]:[s]},"experimental-blueprints-v2-runner":{describe:"Use the experimental Blueprint V2 runner.",type:"boolean",default:!1,hidden:!0},mode:{describe:"Blueprints v2 runner mode to use. This option is required when using the --experimental-blueprints-v2-runner flag with a blueprint.",type:"string",choices:["create-new-site","apply-to-existing-site"],hidden:!0},phpmyadmin:{describe:"Install phpMyAdmin for database management. The phpMyAdmin URL will be printed after boot. Optionally specify a custom URL path (default: /phpmyadmin).",type:"string",coerce:s=>s===""?"/phpmyadmin":s}},r={port:{describe:"Port to listen on when serving. Defaults to 9400 when available.",type:"number"},workers:{describe:'Number of request-handling worker threads. Accepts a positive integer, or "auto" to use one per CPU core (minus one). Defaults to min(6, cpus-1).',type:"string",coerce:s=>{if(s===void 0)return;if(s==="auto")return"auto";const b=Number(s);if(!Number.isInteger(b)||b<1)throw new Error(`Invalid --workers value "${s}": expected a positive integer or "auto".`);return b}},"experimental-multi-worker":{deprecated:"Use --workers=<n|auto> instead. The value of this flag is ignored.",describe:"Deprecated. Use --workers=<n|auto> to control the number of request-handling worker threads.",type:"number"},"experimental-devtools":{describe:"Enable experimental browser development tools.",type:"boolean"}},o={path:{describe:"Path to the project directory. Playground will auto-detect if this is a plugin, theme, wp-content, or WordPress directory.",type:"string",default:process.cwd()},php:{describe:"PHP version to use.",type:"string",default:A.RecommendedPHPVersion,choices:h.AllPHPVersions},wp:{describe:"WordPress version to use.",type:"string",default:"latest"},port:{describe:"Port to listen on. Defaults to 9400 when available.",type:"number"},blueprint:{describe:"Path to a Blueprint JSON file to execute on startup.",type:"string"},login:{describe:"Auto-login as the admin user.",type:"boolean",default:!0},xdebug:{describe:"Enable Xdebug for debugging.",type:"boolean",default:!1},"php-extension":t["php-extension"],"experimental-unsafe-ide-integration":t["experimental-unsafe-ide-integration"],"skip-browser":{describe:"Do not open the site in your default browser on startup.",type:"boolean",default:!1},quiet:{describe:"Suppress non-essential output.",type:"boolean",default:!1},"site-url":{describe:"Override the site URL. By default, derived from the port (http://127.0.0.1:<port>).",type:"string"},mount:{describe:"Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path. Use this for additional mounts beyond auto-detection.",type:"array",string:!0,coerce:Z},reset:{describe:"Deletes the stored site directory and starts a new site from scratch.",type:"boolean",default:!1},"auto-mount":{describe:"Automatically detect project type (plugin, theme, wp-content, or WordPress) and mount accordingly. Use --no-auto-mount to disable and --mount to manually specify mounts instead.",type:"boolean",default:!0},define:t.define,"define-bool":t["define-bool"],"define-number":t["define-number"],phpmyadmin:t.phpmyadmin},i={outfile:{describe:"When building, write to this output file.",type:"string",default:"wordpress.zip"}},n=ke(e).usage("Usage: wp-playground <command> [options]").command("start","Start a local WordPress server with automatic project detection (recommended)",s=>s.usage(`Usage: wp-playground start [options]
35
35
 
36
36
  The easiest way to run WordPress locally. Automatically detects
37
37
  if your directory contains a plugin, theme, wp-content, or
@@ -43,8 +43,8 @@ Examples:
43
43
  wp-playground start --wp=6.7 --php=8.3 # Use specific versions
44
44
  wp-playground start --skip-browser # Skip opening browser
45
45
  wp-playground start --no-auto-mount # Disable auto-detection`).options(o)).command("server","Start a local WordPress server (advanced, low-level)",s=>s.options({...t,...r})).command("run-blueprint","Execute a Blueprint without starting a server",s=>s.options({...t})).command("build-snapshot","Build a ZIP snapshot of a WordPress site based on a Blueprint",s=>s.options({...t,...i})).command("php","Run a PHP script",s=>s.options({...t})).demandCommand(1,"Please specify a command").strictCommands().conflicts("experimental-unsafe-ide-integration","experimental-devtools").showHelpOnFail(!1).fail((s,b,g)=>{if(b)throw b;s&&s.includes("Please specify a command")&&(g.showHelp(),console.error(`
46
- `+s),process.exit(1)),console.error(s),process.exit(1)}).strictOptions().check(async s=>{if(s["skip-wordpress-install"]===!0&&(s["wordpress-install-mode"]="do-not-attempt-installing",s.wordpressInstallMode="do-not-attempt-installing"),s.phpmyadmin&&s["skip-sqlite-setup"])throw new Error("The --phpmyadmin option requires SQLite setup. Remove --skip-sqlite-setup to use phpMyAdmin.");if(s.wp!==void 0&&typeof s.wp=="string"&&!ze(s.wp))try{new URL(s.wp)}catch{throw new Error('Unrecognized WordPress version. Please use "latest", "beta", "trunk", "nightly", a URL, or a numeric version such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1" (see --help for details).')}const b=s["site-url"];if(typeof b=="string"&&b.trim()!=="")try{new URL(b)}catch{throw new Error(`Invalid site-url "${b}". Please provide a valid URL (e.g., http://localhost:8080 or https://example.com)`)}const g=s.autoMount;if(s._[0]!=="start"&&typeof g=="string"&&g){let E=!1;try{E=c.statSync(g).isDirectory()}catch{E=!1}if(!E)throw new Error(`The specified --auto-mount path is not a directory: '${g}'.`)}if(s["experimental-blueprints-v2-runner"]===!0)throw new Error("Blueprints v2 are temporarily disabled while we rework their runtime implementation.");if(s.mode!==void 0)throw new Error("The --mode option requires the --experimentalBlueprintsV2Runner flag.");return!0});n.wrap(n.terminalWidth());const a=await n.argv,l=a._[0];["start","run-blueprint","server","build-snapshot","php"].includes(l)||(n.showHelp(),process.exit(1));const u=a.define||{},w=a["define-bool"]||{},U=a["define-number"]||{},k=s=>s in u||s in w||s in U,f=a.php||A.RecommendedPHPVersion;m.isLegacyPHPVersion(f)||(k("WP_DEBUG")||(u.WP_DEBUG="true"),k("WP_DEBUG_LOG")||(u.WP_DEBUG_LOG="true"),k("WP_DEBUG_DISPLAY")||(u.WP_DEBUG_DISPLAY="false"));const $={...a,define:u,command:l,mount:[...a.mount||[],...a["mount-dir"]||[]],"mount-before-install":[...a["mount-before-install"]||[],...a["mount-dir-before-install"]||[]]},C=await te($);C===void 0&&process.exit(0);const _=(()=>{let s;return async()=>{s===void 0&&(s=C[Symbol.asyncDispose]()),await s,process.exit(0)}})();return process.on("SIGINT",_),process.on("SIGTERM",_),{[Symbol.asyncDispose]:async()=>{process.off("SIGINT",_),process.off("SIGTERM",_),await C[Symbol.asyncDispose]()},[G]:{cliServer:C}}}catch(t){console.error(t);const r=process.argv.includes("--debug");if(t instanceof Error)if(r)m.printDebugDetails(t);else{const o=[];let i=t;do o.push(i.message),i=i.cause;while(i instanceof Error);console.error("\x1B[1m"+o.join(" caused by: ")+"\x1B[0m")}else console.error("\x1B[1m"+ye(t)+"\x1B[0m");process.exit(1)}}function ye(e){if(e instanceof Error)return e.message;if(e&&typeof e=="object"){const t=[],r=e;if(r.name&&t.push(String(r.name)),r.message&&t.push(String(r.message)),r.errno!==void 0&&t.push(`errno: ${r.errno}`),t.length>0)return t.join(" — ");try{return JSON.stringify(e)}catch{return String(e)}}return String(e)}function ue(e,t){return e.find(r=>r.vfsPath.replace(/\/$/,"")===t.replace(/\/$/,""))}function ee(e){const t=Math.max(1,N.cpus().length-1);return e===void 0?Math.min(6,t):e==="auto"?t:e}const G=Symbol("playground-cli-testing");async function te(e){let t;const r=e.internalCookieStore?new m.HttpCookieStore:void 0,o=[],i=new Map,n=new ot({verbosity:e.verbosity||"normal"});if(e.command==="start"&&(e=it(e,n)),e.autoMount!==void 0&&(e.autoMount===""&&(e={...e,autoMount:process.cwd()}),e=pe(e)),e.wordpressInstallMode===void 0&&(e.wordpressInstallMode="download-and-install"),e.quiet&&(e.verbosity="quiet",delete e.quiet),e.debug&&(e.verbosity="debug",delete e.debug),e.verbosity){const f=Object.values(z).find(I=>I.name===e.verbosity).severity;p.logger.setSeverityFilterLevel(f)}e.intl||(e.intl=!0),e.redis===void 0&&(e.redis=await se.jspi()),e.memcached===void 0&&(e.memcached=await se.jspi()),m.isLegacyPHPVersion(e.php||A.RecommendedPHPVersion)&&(e.intl=!1,e.redis=!1,e.memcached=!1,e.xdebug=!1),e.phpmyadmin&&(e.phpmyadmin===!0&&(e.phpmyadmin="/phpmyadmin"),e.pathAliases=[{urlPrefix:e.phpmyadmin,fsPath:O.PHPMYADMIN_INSTALL_PATH}]),e.command==="server"&&(n.printBanner(),n.printConfig({phpVersion:e.php||A.RecommendedPHPVersion,wpVersion:e.wp||"latest",port:e.port??9400,xdebug:!!e.xdebug,intl:!!e.intl,redis:!!e.redis,memcached:!!e.memcached,mounts:[...e.mount||[],...e["mount-before-install"]||[]],blueprint:typeof e.blueprint=="string"?e.blueprint:void 0}));const l=e.command==="server"?e.port??9400:0,u=new m.FileLockManagerInMemory;let w=!1,U=!0;const k=await Fe({port:e.port?e.port:await Ne(l)?0:l,onBind:async(f,I)=>{const $="127.0.0.1",C=`http://${$}:${I}`,_=e["site-url"]||C;e["experimental-multi-worker"]!==void 0&&p.logger.warn("--experimental-multi-worker is deprecated and its value is ignored. Use --workers=<n|auto> instead.");const s=ee(e.workers);if(s<6){const h="Running fewer than 6 workers may increase the likelihood of deadlock due to workers blocking on file locks.";e.workers===void 0?p.logger.warn(`The default worker count has been reduced to ${s} because this machine has only ${N.cpus().length} CPU(s). `+h):p.logger.warn(`Worker count (${s}) is below the recommended threshold (6). `+h)}const b="-playground-cli-site-",g=await Je(b);p.logger.debug(`Native temp dir for VFS root: ${g.path}`);const E="WP Playground CLI - Listen for Xdebug",Y=".playground-xdebug-root",Q=d.join(process.cwd(),Y);if(await R.removeTempDirSymlink(Q),e.xdebug){const h={hostPath:d.join(".",d.sep,Y),vfsPath:"/"},y=e.php||A.RecommendedPHPVersion;if(m.SupportedPHPVersions.includes(y)&&m.SupportedPHPVersions.indexOf(y)<=m.SupportedPHPVersions.indexOf("8.5"))await R.createTempDirSymlink(g.path,Q,process.platform),e.xdebug=R.makeXdebugConfig({cwd:process.cwd(),mounts:[h,...e["mount-before-install"]||[],...e.mount||[]],pathSkippings:[...R.DEFAULT_PATH_SKIPPINGS]}),n.print(n.bold("Xdebug configured successfully")),n.print(n.highlight("Playground source root: ")+".playground-xdebug-root"+n.italic(n.dim(" – you can set breakpoints and preview Playground's VFS structure in there.")));else if(e.experimentalUnsafeIdeIntegration){await R.createTempDirSymlink(g.path,Q,process.platform);try{await R.clearXdebugIDEConfig(E,process.cwd());const v=typeof e.xdebug=="object"?e.xdebug:{},S=await R.addXdebugIDEConfig({name:E,host:$,port:I,ides:e.experimentalUnsafeIdeIntegration,cwd:process.cwd(),mounts:[h,...e["mount-before-install"]||[],...e.mount||[]],pathSkippings:[...R.DEFAULT_PATH_SKIPPINGS],ideKey:v.ideKey||"WPPLAYGROUNDCLI"}),x=e.experimentalUnsafeIdeIntegration,T=x.includes("vscode"),F=x.includes("phpstorm"),W=Object.values(S);n.print(""),W.length>0?(n.print(n.bold("Xdebug configured successfully")),n.print(n.highlight("Updated IDE config: ")+W.join(" ")),n.print(n.highlight("Playground source root: ")+".playground-xdebug-root"+n.italic(n.dim(" – you can set breakpoints and preview Playground's VFS structure in there.")))):(n.print(n.bold("Xdebug configuration failed.")),n.print("No IDE-specific project settings directory was found in the current working directory.")),n.print(""),T&&S.vscode&&(n.print(n.bold("VS Code / Cursor instructions:")),n.print(" 1. Ensure you have installed an IDE extension for PHP Debugging"),n.print(` (The ${n.bold("PHP Debug")} extension by ${n.bold("Xdebug")} has been a solid option)`),n.print(" 2. Open the Run and Debug panel on the left sidebar"),n.print(` 3. Select "${n.italic(E)}" from the dropdown`),n.print(' 4. Click "start debugging"'),n.print(" 5. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"),n.print(" 6. Visit Playground in your browser to hit the breakpoint"),F&&n.print("")),F&&S.phpstorm&&(n.print(n.bold("PhpStorm instructions:")),n.print(` 1. Choose "${n.italic(E)}" debug configuration in the toolbar`),n.print(" 2. Click the debug button (bug icon)"),n.print(" 3. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"),n.print(" 4. Visit Playground in your browser to hit the breakpoint")),n.print("")}catch(v){throw new Error("Could not configure Xdebug",{cause:v})}}}const be=d.dirname(g.path),ge=2*24*60*60*1e3;et(b,ge,be);const ne=d.join(g.path,"internal");c.mkdirSync(ne);const Pe=["wordpress","tools","tmp","home"];for(const h of Pe){const y=v=>v.vfsPath===`/${h}`;if(!(e["mount-before-install"]?.some(y)||e.mount?.some(y))){const v=d.join(g.path,h);c.mkdirSync(v),e["mount-before-install"]===void 0&&(e["mount-before-install"]=[]),e["mount-before-install"].unshift({vfsPath:`/${h}`,hostPath:v})}}if(e["mount-before-install"])for(const h of e["mount-before-install"])p.logger.debug(`Mount before WP install: ${h.vfsPath} -> ${h.hostPath}`);if(e.mount)for(const h of e.mount)p.logger.debug(`Mount after WP install: ${h.vfsPath} -> ${h.hostPath}`);let H;e["experimental-blueprints-v2-runner"]?H=new Ye(e,{siteUrl:_,cliOutput:n}):(H=new Ke(e,{siteUrl:_,cliOutput:n}),typeof e.blueprint=="string"&&(e.blueprint=await Ge({sourceString:e.blueprint,blueprintMayReadAdjacentFiles:e["blueprint-may-read-adjacent-files"]===!0})));let X=!1;const B=async function(){X||(X=!0,await Promise.all(o.map(async y=>{await i.get(y)?.dispose(),await y.worker.terminate()})),f&&await new Promise(y=>{f.close(y),f.closeAllConnections()}),await g.cleanup())};try{const h=[],y=H.getWorkerType();for(let P=0;P<s;P++){const v=re(y,{onExit:S=>{X||S!==0&&p.logger.error(`Worker ${P} exited with code ${S}
47
- `)}}).then(async S=>{o.push(S);const x=await st(u),T=await H.bootRequestHandler({worker:S,fileLockManagerPort:x,nativeInternalDirPath:ne});return i.set(S,T),[S,T]});h.push(v),P===0&&await v}await Promise.all(h),t=m.createObjectPoolProxy(o.map(P=>i.get(P)));{const P=new q.MessageChannel,v=P.port1,S=P.port2;if(await m.exposeAPI({applyPostInstallMountsToAllWorkers:async()=>{for(const x of i.values())await x.mountAfterWordPressInstall(e.mount||[])}},void 0,v),await H.bootWordPress(t,S),v.close(),w=!0,!e["experimental-blueprints-v2-runner"]){const x=await H.compileInputBlueprint(e["additional-blueprint-steps"]||[]);x&&await D.runBlueprintV1Steps(x,t)}if(e.phpmyadmin&&!await t.fileExists(`${O.PHPMYADMIN_INSTALL_PATH}/index.php`)){const x=await O.getPhpMyAdminInstallSteps(),T=await D.compileBlueprintV1({steps:x});await D.runBlueprintV1Steps(T,t)}if(e.command==="build-snapshot"){await lt(t,e.outfile),n.printStatus(`Exported to ${e.outfile}`),await B();return}else if(e.command==="run-blueprint"){n.finishProgress("Done"),await B();return}else if(e.command==="php"){const x=["/internal/shared/bin/php",...(e._||[]).slice(1)],T=await t.cli(x),[F]=await Promise.all([T.exitCode,T.stdout.pipeTo(new WritableStream({write(W){process.stdout.write(W)}})),T.stderr.pipeTo(new WritableStream({write(W){process.stderr.write(W)}}))]);await B(),process.exit(F)}}if(n.finishProgress(),n.printReady(C,s),e.phpmyadmin){const P=d.join(e.phpmyadmin,O.PHPMYADMIN_ENTRY_PATH);n.printPhpMyAdminUrl(new URL(P,C).toString())}return e.xdebug&&e.experimentalDevtools&&(await Te.startBridge({phpInstance:t,phpRoot:"/wordpress"})).start(),{playground:t,server:f,serverUrl:C,[Symbol.asyncDispose]:B,[G]:{workerThreadCount:s}}}catch(h){if(e.verbosity!=="debug")throw h;let y="";throw await t?.fileExists(p.errorLogPath)&&(y=await t.readFileAsText(p.errorLogPath)),await B(),new Error(y,{cause:h})}},async handleRequest(f){if(!w)return m.StreamedPHPResponse.forHttpCode(502,"WordPress is not ready yet");if(U){U=!1;const $={"Content-Type":["text/plain"],"Content-Length":["0"],Location:[f.url]};return f.headers?.cookie?.includes("playground_auto_login_already_happened")&&($["Set-Cookie"]=["playground_auto_login_already_happened=1; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/"]),m.StreamedPHPResponse.fromPHPResponse(new m.PHPResponse(302,$,new Uint8Array))}r&&(f={...f,headers:{...f.headers,cookie:r.getCookieRequestHeader()}});const I=await t.requestStreamed(f);if(r){const $=await I.headers;r.rememberCookiesFromResponseHeaders($),delete $["set-cookie"]}return I}}).catch(f=>{n.printError(ye(f)),process.exit(1)});return k&&e.command==="start"&&!e.skipBrowser&&at(k.serverUrl),k}function it(e,t){let r={...e,command:"server"};const o=e.autoMount!==!1;delete r.autoMount,delete r["auto-mount"],o&&(r.autoMount=d.resolve(process.cwd(),r.path??""),r=pe(r),delete r.autoMount);const i=ue(r["mount-before-install"]||[],"/wordpress")||ue(r.mount||[],"/wordpress");if(i)t.print(`Site files stored at: ${i?.hostPath}`),e.reset&&(t.print(""),t.print(t.red("This site is not managed by Playground CLI and cannot be reset.")),t.print("(It's not stored in the ~/.wordpress-playground/sites/<site-id> directory.)"),t.print(""),t.print("You may still remove the site's directory manually if you wish."),process.exit(1));else{const n=r.autoMount||process.cwd(),a=Ce.createHash("sha256").update(n).digest("hex"),l=N.homedir(),u=d.join(l,".wordpress-playground/sites",a);t.print(`Site files stored at: ${u}`),c.existsSync(u)&&e.reset&&(t.print("Resetting site..."),c.rmdirSync(u,{recursive:!0})),c.mkdirSync(u,{recursive:!0}),r["mount-before-install"]=[...r["mount-before-install"]||[],{vfsPath:"/wordpress",hostPath:u}],r.wordpressInstallMode=c.readdirSync(u).length===0?"download-and-install":"install-from-existing-files-if-needed"}return r}const K=new m.ProcessIdAllocator;function re(e,{onExit:t}={}){let r;return e==="v1"?r=new q.Worker(new URL("./worker-thread-v1.cjs",typeof document>"u"?require("url").pathToFileURL(__filename).href:M&&M.tagName.toUpperCase()==="SCRIPT"&&M.src||new URL("run-cli-b6r6MAhq.cjs",document.baseURI).href)):r=new q.Worker(new URL("./worker-thread-v2.cjs",typeof document>"u"?require("url").pathToFileURL(__filename).href:M&&M.tagName.toUpperCase()==="SCRIPT"&&M.src||new URL("run-cli-b6r6MAhq.cjs",document.baseURI).href)),new Promise((o,i)=>{const n=K.claim();r.once("message",function(l){l.command==="worker-script-initialized"&&o({processId:n,worker:r,phpPort:l.phpPort})}),r.once("error",function(l){K.release(n),console.error(l);const u=l?.message||(l?String(l):"unknown error"),w=new Error(`Worker failed to load. Original error: ${u}`,{cause:l});i(w)});let a=!1;r.once("spawn",()=>{a=!0}),r.once("exit",l=>{K.release(n),a||i(new Error(`Worker exited before spawning: ${l}`)),t?.(l)})})}async function st(e){const{port1:t,port2:r}=new q.MessageChannel;return await m.exposeSyncAPI(e,t),r}function at(e){const t=N.platform();let r;switch(t){case"darwin":r=`open "${e}"`;break;case"win32":r=`start "" "${e}"`;break;default:r=`xdg-open "${e}"`;break}de.exec(r,o=>{o&&p.logger.debug(`Could not open browser: ${o.message}`)})}async function lt(e,t){await e.run({code:`<?php
46
+ `+s),process.exit(1)),console.error(s),process.exit(1)}).strictOptions().check(async s=>{if(s["skip-wordpress-install"]===!0&&(s["wordpress-install-mode"]="do-not-attempt-installing",s.wordpressInstallMode="do-not-attempt-installing"),s.phpmyadmin&&s["skip-sqlite-setup"])throw new Error("The --phpmyadmin option requires SQLite setup. Remove --skip-sqlite-setup to use phpMyAdmin.");if(s.wp!==void 0&&typeof s.wp=="string"&&!ze(s.wp))try{new URL(s.wp)}catch{throw new Error('Unrecognized WordPress version. Please use "latest", "beta", "trunk", "nightly", a URL, or a numeric version such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1" (see --help for details).')}const b=s["site-url"];if(typeof b=="string"&&b.trim()!=="")try{new URL(b)}catch{throw new Error(`Invalid site-url "${b}". Please provide a valid URL (e.g., http://localhost:8080 or https://example.com)`)}const g=s.autoMount;if(s._[0]!=="start"&&typeof g=="string"&&g){let E=!1;try{E=c.statSync(g).isDirectory()}catch{E=!1}if(!E)throw new Error(`The specified --auto-mount path is not a directory: '${g}'.`)}if(s["experimental-blueprints-v2-runner"]===!0)throw new Error("Blueprints v2 are temporarily disabled while we rework their runtime implementation.");if(s.mode!==void 0)throw new Error("The --mode option requires the --experimentalBlueprintsV2Runner flag.");return!0});n.wrap(n.terminalWidth());const a=await n.argv,l=a._[0];["start","run-blueprint","server","build-snapshot","php"].includes(l)||(n.showHelp(),process.exit(1));const u=a.define||{},w=a["define-bool"]||{},U=a["define-number"]||{},k=s=>s in u||s in w||s in U,f=a.php||A.RecommendedPHPVersion;h.isLegacyPHPVersion(f)||(k("WP_DEBUG")||(u.WP_DEBUG="true"),k("WP_DEBUG_LOG")||(u.WP_DEBUG_LOG="true"),k("WP_DEBUG_DISPLAY")||(u.WP_DEBUG_DISPLAY="false"));const $={...a,define:u,command:l,mount:[...a.mount||[],...a["mount-dir"]||[]],"mount-before-install":[...a["mount-before-install"]||[],...a["mount-dir-before-install"]||[]]},C=await te($);C===void 0&&process.exit(0);const _=(()=>{let s;return async()=>{s===void 0&&(s=C[Symbol.asyncDispose]()),await s,process.exit(0)}})();return process.on("SIGINT",_),process.on("SIGTERM",_),{[Symbol.asyncDispose]:async()=>{process.off("SIGINT",_),process.off("SIGTERM",_),await C[Symbol.asyncDispose]()},[G]:{cliServer:C}}}catch(t){console.error(t);const r=process.argv.includes("--debug");if(t instanceof Error)if(r)h.printDebugDetails(t);else{const o=[];let i=t;do o.push(i.message),i=i.cause;while(i instanceof Error);console.error("\x1B[1m"+o.join(" caused by: ")+"\x1B[0m")}else console.error("\x1B[1m"+ye(t)+"\x1B[0m");process.exit(1)}}function ye(e){if(e instanceof Error)return e.message;if(e&&typeof e=="object"){const t=[],r=e;if(r.name&&t.push(String(r.name)),r.message&&t.push(String(r.message)),r.errno!==void 0&&t.push(`errno: ${r.errno}`),t.length>0)return t.join(" — ");try{return JSON.stringify(e)}catch{return String(e)}}return String(e)}function ue(e,t){return e.find(r=>r.vfsPath.replace(/\/$/,"")===t.replace(/\/$/,""))}function ee(e){const t=Math.max(1,N.cpus().length-1);return e===void 0?Math.min(6,t):e==="auto"?t:e}const G=Symbol("playground-cli-testing");async function te(e){let t;const r=e.internalCookieStore?new h.HttpCookieStore:void 0,o=[],i=new Map,n=new ot({verbosity:e.verbosity||"normal"});if(e.command==="start"&&(e=it(e,n)),e.autoMount!==void 0&&(e.autoMount===""&&(e={...e,autoMount:process.cwd()}),e=pe(e)),e.wordpressInstallMode===void 0&&(e.wordpressInstallMode="download-and-install"),e.quiet&&(e.verbosity="quiet",delete e.quiet),e.debug&&(e.verbosity="debug",delete e.debug),e.verbosity){const f=Object.values(z).find(I=>I.name===e.verbosity).severity;p.logger.setSeverityFilterLevel(f)}e.intl||(e.intl=!0),e.redis===void 0&&(e.redis=await se.jspi()),e.memcached===void 0&&(e.memcached=await se.jspi()),h.isLegacyPHPVersion(e.php||A.RecommendedPHPVersion)&&(e.intl=!1,e.redis=!1,e.memcached=!1,e.xdebug=!1),e.phpmyadmin&&(e.phpmyadmin===!0&&(e.phpmyadmin="/phpmyadmin"),e.pathAliases=[{urlPrefix:e.phpmyadmin,fsPath:O.PHPMYADMIN_INSTALL_PATH}]),e.command==="server"&&(n.printBanner(),n.printConfig({phpVersion:e.php||A.RecommendedPHPVersion,wpVersion:e.wp||"latest",port:e.port??9400,xdebug:!!e.xdebug,intl:!!e.intl,redis:!!e.redis,memcached:!!e.memcached,mounts:[...e.mount||[],...e["mount-before-install"]||[]],blueprint:typeof e.blueprint=="string"?e.blueprint:void 0}));const l=e.command==="server"?e.port??9400:0,u=new h.FileLockManagerInMemory;let w=!1,U=!0;const k=await Fe({port:e.port?e.port:await Ne(l)?0:l,onBind:async(f,I)=>{const $="127.0.0.1",C=`http://${$}:${I}`,_=e["site-url"]||C;e["experimental-multi-worker"]!==void 0&&p.logger.warn("--experimental-multi-worker is deprecated and its value is ignored. Use --workers=<n|auto> instead.");const s=ee(e.workers);if(s<6){const m="Running fewer than 6 workers may increase the likelihood of deadlock due to workers blocking on file locks.";e.workers===void 0?p.logger.warn(`The default worker count has been reduced to ${s} because this machine has only ${N.cpus().length} CPU(s). `+m):p.logger.warn(`Worker count (${s}) is below the recommended threshold (6). `+m)}const b="-playground-cli-site-",g=await Je(b);p.logger.debug(`Native temp dir for VFS root: ${g.path}`);const E="WP Playground CLI - Listen for Xdebug",Y=".playground-xdebug-root",Q=d.join(process.cwd(),Y);if(await R.removeTempDirSymlink(Q),e.xdebug){const m={hostPath:d.join(".",d.sep,Y),vfsPath:"/"},y=e.php||A.RecommendedPHPVersion;if(h.SupportedPHPVersions.includes(y)&&h.SupportedPHPVersions.indexOf(y)<=h.SupportedPHPVersions.indexOf("8.5"))await R.createTempDirSymlink(g.path,Q,process.platform),e.xdebug=R.makeXdebugConfig({cwd:process.cwd(),mounts:[m,...e["mount-before-install"]||[],...e.mount||[]],pathSkippings:[...R.DEFAULT_PATH_SKIPPINGS]}),n.print(n.bold("Xdebug configured successfully")),n.print(n.highlight("Playground source root: ")+".playground-xdebug-root"+n.italic(n.dim(" – you can set breakpoints and preview Playground's VFS structure in there.")));else if(e.experimentalUnsafeIdeIntegration){await R.createTempDirSymlink(g.path,Q,process.platform);try{await R.clearXdebugIDEConfig(E,process.cwd());const v=typeof e.xdebug=="object"?e.xdebug:{},S=await R.addXdebugIDEConfig({name:E,host:$,port:I,ides:e.experimentalUnsafeIdeIntegration,cwd:process.cwd(),mounts:[m,...e["mount-before-install"]||[],...e.mount||[]],pathSkippings:[...R.DEFAULT_PATH_SKIPPINGS],ideKey:v.ideKey||"WPPLAYGROUNDCLI"}),x=e.experimentalUnsafeIdeIntegration,T=x.includes("vscode"),F=x.includes("phpstorm"),W=Object.values(S);n.print(""),W.length>0?(n.print(n.bold("Xdebug configured successfully")),n.print(n.highlight("Updated IDE config: ")+W.join(" ")),n.print(n.highlight("Playground source root: ")+".playground-xdebug-root"+n.italic(n.dim(" – you can set breakpoints and preview Playground's VFS structure in there.")))):(n.print(n.bold("Xdebug configuration failed.")),n.print("No IDE-specific project settings directory was found in the current working directory.")),n.print(""),T&&S.vscode&&(n.print(n.bold("VS Code / Cursor instructions:")),n.print(" 1. Ensure you have installed an IDE extension for PHP Debugging"),n.print(` (The ${n.bold("PHP Debug")} extension by ${n.bold("Xdebug")} has been a solid option)`),n.print(" 2. Open the Run and Debug panel on the left sidebar"),n.print(` 3. Select "${n.italic(E)}" from the dropdown`),n.print(' 4. Click "start debugging"'),n.print(" 5. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"),n.print(" 6. Visit Playground in your browser to hit the breakpoint"),F&&n.print("")),F&&S.phpstorm&&(n.print(n.bold("PhpStorm instructions:")),n.print(` 1. Choose "${n.italic(E)}" debug configuration in the toolbar`),n.print(" 2. Click the debug button (bug icon)"),n.print(" 3. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"),n.print(" 4. Visit Playground in your browser to hit the breakpoint")),n.print("")}catch(v){throw new Error("Could not configure Xdebug",{cause:v})}}}const be=d.dirname(g.path),ge=2*24*60*60*1e3;et(b,ge,be);const ne=d.join(g.path,"internal");c.mkdirSync(ne);const Pe=["wordpress","tools","tmp","home"];for(const m of Pe){const y=v=>v.vfsPath===`/${m}`;if(!(e["mount-before-install"]?.some(y)||e.mount?.some(y))){const v=d.join(g.path,m);c.mkdirSync(v),e["mount-before-install"]===void 0&&(e["mount-before-install"]=[]),e["mount-before-install"].unshift({vfsPath:`/${m}`,hostPath:v})}}if(e["mount-before-install"])for(const m of e["mount-before-install"])p.logger.debug(`Mount before WP install: ${m.vfsPath} -> ${m.hostPath}`);if(e.mount)for(const m of e.mount)p.logger.debug(`Mount after WP install: ${m.vfsPath} -> ${m.hostPath}`);let H;e["experimental-blueprints-v2-runner"]?H=new Ye(e,{siteUrl:_,cliOutput:n}):(H=new Ke(e,{siteUrl:_,cliOutput:n}),typeof e.blueprint=="string"&&(e.blueprint=await Ge({sourceString:e.blueprint,blueprintMayReadAdjacentFiles:e["blueprint-may-read-adjacent-files"]===!0})));let X=!1;const B=async function(){X||(X=!0,await Promise.all(o.map(async y=>{await i.get(y)?.dispose(),await y.worker.terminate()})),f&&await new Promise(y=>{f.close(y),f.closeAllConnections()}),await g.cleanup())};try{const m=[],y=H.getWorkerType();for(let P=0;P<s;P++){const v=re(y,{onExit:S=>{X||S!==0&&p.logger.error(`Worker ${P} exited with code ${S}
47
+ `)}}).then(async S=>{o.push(S);const x=await st(u),T=await H.bootRequestHandler({worker:S,fileLockManagerPort:x,nativeInternalDirPath:ne});return i.set(S,T),[S,T]});m.push(v),P===0&&await v}await Promise.all(m),t=h.createObjectPoolProxy(o.map(P=>i.get(P)));{const P=new q.MessageChannel,v=P.port1,S=P.port2;if(await h.exposeAPI({applyPostInstallMountsToAllWorkers:async()=>{for(const x of i.values())await x.mountAfterWordPressInstall(e.mount||[])}},void 0,v),await H.bootWordPress(t,S),v.close(),w=!0,!e["experimental-blueprints-v2-runner"]){const x=await H.compileInputBlueprint(e["additional-blueprint-steps"]||[]);x&&await D.runBlueprintV1Steps(x,t)}if(e.phpmyadmin&&!await t.fileExists(`${O.PHPMYADMIN_INSTALL_PATH}/index.php`)){const x=await O.getPhpMyAdminInstallSteps(),T=await D.compileBlueprintV1({steps:x});await D.runBlueprintV1Steps(T,t)}if(e.command==="build-snapshot"){await lt(t,e.outfile),n.printStatus(`Exported to ${e.outfile}`),await B();return}else if(e.command==="run-blueprint"){n.finishProgress("Done"),await B();return}else if(e.command==="php"){const x=["/internal/shared/bin/php",...(e._||[]).slice(1)],T=await t.cli(x),[F]=await Promise.all([T.exitCode,T.stdout.pipeTo(new WritableStream({write(W){process.stdout.write(W)}})),T.stderr.pipeTo(new WritableStream({write(W){process.stderr.write(W)}}))]);await B(),process.exit(F)}}if(n.finishProgress(),n.printReady(C,s),e.phpmyadmin){const P=d.join(e.phpmyadmin,O.PHPMYADMIN_ENTRY_PATH);n.printPhpMyAdminUrl(new URL(P,C).toString())}return e.xdebug&&e.experimentalDevtools&&(await Te.startBridge({phpInstance:t,phpRoot:"/wordpress"})).start(),{playground:t,server:f,serverUrl:C,[Symbol.asyncDispose]:B,[G]:{workerThreadCount:s}}}catch(m){if(e.verbosity!=="debug")throw m;let y="";throw await t?.fileExists(p.errorLogPath)&&(y=await t.readFileAsText(p.errorLogPath)),await B(),new Error(y,{cause:m})}},async handleRequest(f){if(!w)return h.StreamedPHPResponse.forHttpCode(502,"WordPress is not ready yet");if(U){U=!1;const $={"Content-Type":["text/plain"],"Content-Length":["0"],Location:[f.url]};return f.headers?.cookie?.includes("playground_auto_login_already_happened")&&($["Set-Cookie"]=["playground_auto_login_already_happened=1; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/"]),h.StreamedPHPResponse.fromPHPResponse(new h.PHPResponse(302,$,new Uint8Array))}r&&(f={...f,headers:{...f.headers,cookie:r.getCookieRequestHeader()}});const I=await t.requestStreamed(f);if(r){const $=await I.headers;r.rememberCookiesFromResponseHeaders($),delete $["set-cookie"]}return I}}).catch(f=>{n.printError(ye(f)),process.exit(1)});return k&&e.command==="start"&&!e.skipBrowser&&at(k.serverUrl),k}function it(e,t){let r={...e,command:"server"};const o=e.autoMount!==!1;delete r.autoMount,delete r["auto-mount"],o&&(r.autoMount=d.resolve(process.cwd(),r.path??""),r=pe(r),delete r.autoMount);const i=ue(r["mount-before-install"]||[],"/wordpress")||ue(r.mount||[],"/wordpress");if(i)t.print(`Site files stored at: ${i?.hostPath}`),e.reset&&(t.print(""),t.print(t.red("This site is not managed by Playground CLI and cannot be reset.")),t.print("(It's not stored in the ~/.wordpress-playground/sites/<site-id> directory.)"),t.print(""),t.print("You may still remove the site's directory manually if you wish."),process.exit(1));else{const n=r.autoMount||process.cwd(),a=Ce.createHash("sha256").update(n).digest("hex"),l=N.homedir(),u=d.join(l,".wordpress-playground/sites",a);t.print(`Site files stored at: ${u}`),c.existsSync(u)&&e.reset&&(t.print("Resetting site..."),c.rmdirSync(u,{recursive:!0})),c.mkdirSync(u,{recursive:!0}),r["mount-before-install"]=[...r["mount-before-install"]||[],{vfsPath:"/wordpress",hostPath:u}],r.wordpressInstallMode=c.readdirSync(u).length===0?"download-and-install":"install-from-existing-files-if-needed"}return r}const K=new h.ProcessIdAllocator;function re(e,{onExit:t}={}){let r;return e==="v1"?r=new q.Worker(new URL("./worker-thread-v1.cjs",typeof document>"u"?require("url").pathToFileURL(__filename).href:M&&M.tagName.toUpperCase()==="SCRIPT"&&M.src||new URL("run-cli-C1cUS9na.cjs",document.baseURI).href)):r=new q.Worker(new URL("./worker-thread-v2.cjs",typeof document>"u"?require("url").pathToFileURL(__filename).href:M&&M.tagName.toUpperCase()==="SCRIPT"&&M.src||new URL("run-cli-C1cUS9na.cjs",document.baseURI).href)),new Promise((o,i)=>{const n=K.claim();r.once("message",function(l){l.command==="worker-script-initialized"&&o({processId:n,worker:r,phpPort:l.phpPort})}),r.once("error",function(l){K.release(n),console.error(l);const u=l?.message||(l?String(l):"unknown error"),w=new Error(`Worker failed to load. Original error: ${u}`,{cause:l});i(w)});let a=!1;r.once("spawn",()=>{a=!0}),r.once("exit",l=>{K.release(n),a||i(new Error(`Worker exited before spawning: ${l}`)),t?.(l)})})}async function st(e){const{port1:t,port2:r}=new q.MessageChannel;return await h.exposeSyncAPI(e,t),r}function at(e){const t=N.platform();let r;switch(t){case"darwin":r=`open "${e}"`;break;case"win32":r=`start "" "${e}"`;break;default:r=`xdg-open "${e}"`;break}de.exec(r,o=>{o&&p.logger.debug(`Could not open browser: ${o.message}`)})}async function lt(e,t){await e.run({code:`<?php
48
48
  $zip = new ZipArchive();
49
49
  if(false === $zip->open('/tmp/build.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
50
50
  throw new Exception('Failed to create ZIP');
@@ -62,4 +62,4 @@ Examples:
62
62
  $zip->close();
63
63
 
64
64
  `});const r=await e.readFileAsBuffer("/tmp/build.zip");c.writeFileSync(t,r)}const ut=Object.freeze(Object.defineProperty({__proto__:null,LogVerbosity:z,internalsKeyForTesting:G,mergeDefinedConstants:j,parseOptionsAndRunCLI:we,resolveWorkerCount:ee,runCLI:te,spawnWorkerThread:re},Symbol.toStringTag,{value:"Module"}));exports.LogVerbosity=z;exports.internalsKeyForTesting=G;exports.mergeDefinedConstants=j;exports.mountResources=Re;exports.parseOptionsAndRunCLI=we;exports.resolveWorkerCount=ee;exports.runCLI=te;exports.runCli=ut;exports.shouldRenderProgress=fe;exports.spawnWorkerThread=re;
65
- //# sourceMappingURL=run-cli-b6r6MAhq.cjs.map
65
+ //# sourceMappingURL=run-cli-C1cUS9na.cjs.map