@wp-playground/cli 3.0.44 → 3.0.45

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.
@@ -1,7 +1,7 @@
1
- "use strict";var Se=Object.create;var re=Object.defineProperty;var xe=Object.getOwnPropertyDescriptor;var ke=Object.getOwnPropertyNames;var Ie=Object.getPrototypeOf,$e=Object.prototype.hasOwnProperty;var Te=(e,t,r,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ke(t))!$e.call(e,o)&&o!==r&&re(e,o,{get:()=>t[o],enumerable:!(s=xe(t,o))||s.enumerable});return e};var Ee=(e,t,r)=>(r=e!=null?Se(Ie(e)):{},Te(t||!e||!e.__esModule?re(r,"default",{value:e,enumerable:!0}):r,e));const m=require("@php-wasm/logger"),b=require("@php-wasm/universal"),L=require("@wp-playground/blueprints"),M=require("@wp-playground/common"),d=require("fs"),Q=require("worker_threads"),de=require("@php-wasm/node"),c=require("path"),Be=require("express"),A=require("os"),Ce=require("wasm-feature-detect"),Le=require("yargs"),H=require("@wp-playground/storage"),oe=require("@php-wasm/progress"),Re=require("@wp-playground/wordpress"),R=require("fs-extra"),We=require("@php-wasm/xdebug-bridge"),Me=require("child_process"),se=require("tmp-promise"),De=require("ps-man"),q=require("@php-wasm/cli-util"),Ae=require("crypto");var B=typeof document<"u"?document.currentScript:null;function z(e){const t=[];for(const r of e){const s=r.split(":");if(s.length!==2)throw new Error(`Invalid mount format: ${r}.
1
+ "use strict";var xe=Object.create;var oe=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Ie=Object.getOwnPropertyNames;var $e=Object.getPrototypeOf,Te=Object.prototype.hasOwnProperty;var Ee=(e,t,r,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Ie(t))!Te.call(e,o)&&o!==r&&oe(e,o,{get:()=>t[o],enumerable:!(s=ke(t,o))||s.enumerable});return e};var Be=(e,t,r)=>(r=e!=null?xe($e(e)):{},Ee(t||!e||!e.__esModule?oe(r,"default",{value:e,enumerable:!0}):r,e));const m=require("@php-wasm/logger"),b=require("@php-wasm/universal"),R=require("@wp-playground/blueprints"),W=require("@wp-playground/common"),d=require("fs"),Q=require("worker_threads"),ce=require("@php-wasm/node"),c=require("path"),Ce=require("express"),A=require("os"),X=require("wasm-feature-detect"),Re=require("yargs"),H=require("@wp-playground/storage"),se=require("@php-wasm/progress"),Le=require("@wp-playground/wordpress"),L=require("fs-extra"),Me=require("@php-wasm/xdebug-bridge"),We=require("child_process"),ne=require("tmp-promise"),De=require("ps-man"),q=require("@php-wasm/cli-util"),Ae=require("crypto");var B=typeof document<"u"?document.currentScript:null;function z(e){const t=[];for(const r of e){const s=r.split(":");if(s.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
- Example: --mount-dir C:\\my-plugin /wordpress/wp-content/plugins/my-plugin`);const[o,n]=s;if(!d.existsSync(o))throw new Error(`Host path does not exist: ${o}`);t.push({hostPath:o,vfsPath:n})}return t}function ne(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 s=e[r],o=e[r+1];if(!d.existsSync(s))throw new Error(`Host path does not exist: ${s}`);t.push({hostPath:c.resolve(process.cwd(),s),vfsPath:o})}return t}async function Ue(e,t){for(const r of t)await e.mount(r.vfsPath,de.createNodeFsMountHandler(r.hostPath))}const ie={step:"runPHP",code:{filename:"activate-theme.php",content:`<?php
4
+ Example: --mount-dir C:\\my-plugin /wordpress/wp-content/plugins/my-plugin`);const[o,n]=s;if(!d.existsSync(o))throw new Error(`Host path does not exist: ${o}`);t.push({hostPath:o,vfsPath:n})}return t}function ie(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 s=e[r],o=e[r+1];if(!d.existsSync(s))throw new Error(`Host path does not exist: ${s}`);t.push({hostPath:c.resolve(process.cwd(),s),vfsPath:o})}return t}async function Ue(e,t){for(const r of t)await e.mount(r.vfsPath,ce.createNodeFsMountHandler(r.hostPath))}const ae={step:"runPHP",code:{filename:"activate-theme.php",content:`<?php
5
5
  $docroot = getenv('DOCROOT') ? getenv('DOCROOT') : '/wordpress';
6
6
  require_once "$docroot/wp-load.php";
7
7
  $theme = wp_get_theme();
@@ -12,12 +12,12 @@
12
12
  switch_theme($themeName);
13
13
  }
14
14
  }
15
- `}};function ce(e){const t=e.autoMount,r=[...e.mount||[]],s=[...e["mount-before-install"]||[]],o={...e,mount:r,"mount-before-install":s,"additional-blueprint-steps":[...e["additional-blueprint-steps"]||[]]};if(Oe(t)){const i=`/wordpress/wp-content/plugins/${c.basename(t)}`;r.push({hostPath:t,vfsPath:i,autoMounted:!0}),o["additional-blueprint-steps"].push({step:"activatePlugin",pluginPath:`/wordpress/wp-content/plugins/${c.basename(t)}`})}else if(Ne(t)){const n=c.basename(t),i=`/wordpress/wp-content/themes/${n}`;r.push({hostPath:t,vfsPath:i,autoMounted:!0}),o["additional-blueprint-steps"].push(e["experimental-blueprints-v2-runner"]?{step:"activateTheme",themeDirectoryName:n}:{step:"activateTheme",themeFolderName:n})}else if(_e(t)){const n=d.readdirSync(t);for(const i of n)i!=="index.php"&&r.push({hostPath:`${t}/${i}`,vfsPath:`/wordpress/wp-content/${i}`,autoMounted:!0});o["additional-blueprint-steps"].push(ie)}else Fe(t)&&(s.push({hostPath:t,vfsPath:"/wordpress",autoMounted:!0}),o.mode="apply-to-existing-site",o["additional-blueprint-steps"].push(ie),o.wordpressInstallMode||(o.wordpressInstallMode="install-from-existing-files-if-needed"));return o}function Fe(e){const t=d.readdirSync(e);return t.includes("wp-admin")&&t.includes("wp-includes")&&t.includes("wp-content")}function _e(e){const t=d.readdirSync(e);return t.includes("themes")||t.includes("plugins")||t.includes("mu-plugins")||t.includes("uploads")}function Ne(e){if(!d.readdirSync(e).includes("style.css"))return!1;const r=d.readFileSync(c.join(e,"style.css"),"utf8");return!!/^(?:[ \t]*<\?php)?[ \t/*#@]*Theme Name:(.*)$/im.exec(r)}function Oe(e){const t=d.readdirSync(e),r=/^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;return!!t.filter(o=>o.endsWith(".php")).find(o=>{const n=d.readFileSync(c.join(e,o),"utf8");return!!r.exec(n)})}function He(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 s=e[r],o=e[r+1];if(!s||!s.trim())throw new Error("Constant name cannot be empty");t[s.trim()]=o}return t}function qe(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 s=e[r],o=e[r+1].trim().toLowerCase();if(!s||!s.trim())throw new Error("Constant name cannot be empty");if(o==="true"||o==="1")t[s.trim()]=!0;else if(o==="false"||o==="0")t[s.trim()]=!1;else throw new Error(`Invalid boolean value for constant "${s}": "${o}". Must be "true", "false", "1", or "0".`)}return t}function Ve(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 s=e[r],o=e[r+1].trim();if(!s||!s.trim())throw new Error("Constant name cannot be empty");const n=Number(o);if(isNaN(n))throw new Error(`Invalid number value for constant "${s}": "${o}". Must be a valid number.`);t[s.trim()]=n}return t}function je(e={},t={},r={}){const s={},o=new Set,n=(i,l)=>{for(const u in i){if(o.has(u))throw new Error(`Constant "${u}" is defined multiple times across different --define-${l} flags`);o.add(u),s[u]=i[u]}};return n(e,"string"),n(t,"bool"),n(r,"number"),s}function D(e){return je(e.define,e["define-bool"],e["define-number"])}async function Ye(e){const t=Be(),r=await new Promise((n,i)=>{const l=t.listen(e.port,()=>{const u=l.address();u===null||typeof u=="string"?i(new Error("Server address is not available")):n(l)})});t.use("/",async(n,i)=>{let l;try{l=await e.handleRequest({url:n.url,headers:Ge(n),method:n.method,body:await ze(n)})}catch(u){m.logger.error(u),l=b.PHPResponse.forHttpCode(500)}i.statusCode=l.httpStatusCode;for(const u in l.headers)i.setHeader(u,l.headers[u]);i.end(l.bytes)});const o=r.address().port;return await e.onBind(r,o)}const ze=async e=>await new Promise(t=>{const r=[];e.on("data",s=>{r.push(s)}),e.on("end",()=>{t(new Uint8Array(Buffer.concat(r)))})}),Ge=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};class Qe{constructor(t){this.workerLoads=[],this.addWorker(t)}addWorker(t){this.workerLoads.push({worker:t,activeRequests:new Set})}async removeWorker(t){const r=this.workerLoads.findIndex(o=>o.worker===t);if(r===-1)return;const[s]=this.workerLoads.splice(r,1);await Promise.allSettled(s.activeRequests)}async handleRequest(t){let r=this.workerLoads[0];for(let o=1;o<this.workerLoads.length;o++){const n=this.workerLoads[o];n.activeRequests.size<r.activeRequests.size&&(r=n)}const s=r.worker.request(t);return r.activeRequests.add(s),s.url=t.url,s.finally(()=>{r.activeRequests.delete(s)})}}function Xe(e){return/^latest$|^trunk$|^nightly$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/.test(e)}async function Ze({sourceString:e,blueprintMayReadAdjacentFiles:t}){if(!e)return;if(e.startsWith("http://")||e.startsWith("https://"))return await L.resolveRemoteBlueprint(e);let r=c.resolve(process.cwd(),e);if(!d.existsSync(r))throw new Error(`Blueprint file does not exist: ${r}`);const s=d.statSync(r);if(s.isDirectory()&&(r=c.join(r,"blueprint.json")),!s.isFile()&&s.isSymbolicLink())throw new Error(`Blueprint path is neither a file nor a directory: ${r}`);const o=c.extname(r);switch(o){case".zip":return H.ZipFilesystem.fromArrayBuffer(d.readFileSync(r).buffer);case".json":{const n=d.readFileSync(r,"utf-8");try{JSON.parse(n)}catch{throw new Error(`Blueprint file at ${r} is not a valid JSON file`)}const i=c.dirname(r),l=new H.NodeJsFilesystem(i);return new H.OverlayFilesystem([new H.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.
15
+ `}};function pe(e){const t=e.autoMount,r=[...e.mount||[]],s=[...e["mount-before-install"]||[]],o={...e,mount:r,"mount-before-install":s,"additional-blueprint-steps":[...e["additional-blueprint-steps"]||[]]};if(Oe(t)){const i=`/wordpress/wp-content/plugins/${c.basename(t)}`;r.push({hostPath:t,vfsPath:i,autoMounted:!0}),o["additional-blueprint-steps"].push({step:"activatePlugin",pluginPath:`/wordpress/wp-content/plugins/${c.basename(t)}`})}else if(Ne(t)){const n=c.basename(t),i=`/wordpress/wp-content/themes/${n}`;r.push({hostPath:t,vfsPath:i,autoMounted:!0}),o["additional-blueprint-steps"].push(e["experimental-blueprints-v2-runner"]?{step:"activateTheme",themeDirectoryName:n}:{step:"activateTheme",themeFolderName:n})}else if(_e(t)){const n=d.readdirSync(t);for(const i of n)i!=="index.php"&&r.push({hostPath:`${t}/${i}`,vfsPath:`/wordpress/wp-content/${i}`,autoMounted:!0});o["additional-blueprint-steps"].push(ae)}else Fe(t)&&(s.push({hostPath:t,vfsPath:"/wordpress",autoMounted:!0}),o.mode="apply-to-existing-site",o["additional-blueprint-steps"].push(ae),o.wordpressInstallMode||(o.wordpressInstallMode="install-from-existing-files-if-needed"));return o}function Fe(e){const t=d.readdirSync(e);return t.includes("wp-admin")&&t.includes("wp-includes")&&t.includes("wp-content")}function _e(e){const t=d.readdirSync(e);return t.includes("themes")||t.includes("plugins")||t.includes("mu-plugins")||t.includes("uploads")}function Ne(e){if(!d.readdirSync(e).includes("style.css"))return!1;const r=d.readFileSync(c.join(e,"style.css"),"utf8");return!!/^(?:[ \t]*<\?php)?[ \t/*#@]*Theme Name:(.*)$/im.exec(r)}function Oe(e){const t=d.readdirSync(e),r=/^(?:[ \t]*<\?php)?[ \t/*#@]*Plugin Name:(.*)$/im;return!!t.filter(o=>o.endsWith(".php")).find(o=>{const n=d.readFileSync(c.join(e,o),"utf8");return!!r.exec(n)})}function He(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 s=e[r],o=e[r+1];if(!s||!s.trim())throw new Error("Constant name cannot be empty");t[s.trim()]=o}return t}function qe(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 s=e[r],o=e[r+1].trim().toLowerCase();if(!s||!s.trim())throw new Error("Constant name cannot be empty");if(o==="true"||o==="1")t[s.trim()]=!0;else if(o==="false"||o==="0")t[s.trim()]=!1;else throw new Error(`Invalid boolean value for constant "${s}": "${o}". Must be "true", "false", "1", or "0".`)}return t}function Ve(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 s=e[r],o=e[r+1].trim();if(!s||!s.trim())throw new Error("Constant name cannot be empty");const n=Number(o);if(isNaN(n))throw new Error(`Invalid number value for constant "${s}": "${o}". Must be a valid number.`);t[s.trim()]=n}return t}function je(e={},t={},r={}){const s={},o=new Set,n=(i,l)=>{for(const u in i){if(o.has(u))throw new Error(`Constant "${u}" is defined multiple times across different --define-${l} flags`);o.add(u),s[u]=i[u]}};return n(e,"string"),n(t,"bool"),n(r,"number"),s}function D(e){return je(e.define,e["define-bool"],e["define-number"])}async function Ye(e){const t=Ce(),r=await new Promise((n,i)=>{const l=t.listen(e.port,()=>{const u=l.address();u===null||typeof u=="string"?i(new Error("Server address is not available")):n(l)})});t.use("/",async(n,i)=>{let l;try{l=await e.handleRequest({url:n.url,headers:Ge(n),method:n.method,body:await ze(n)})}catch(u){m.logger.error(u),l=b.PHPResponse.forHttpCode(500)}i.statusCode=l.httpStatusCode;for(const u in l.headers)i.setHeader(u,l.headers[u]);i.end(l.bytes)});const o=r.address().port;return await e.onBind(r,o)}const ze=async e=>await new Promise(t=>{const r=[];e.on("data",s=>{r.push(s)}),e.on("end",()=>{t(new Uint8Array(Buffer.concat(r)))})}),Ge=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};class Qe{constructor(t){this.workerLoads=[],this.addWorker(t)}addWorker(t){this.workerLoads.push({worker:t,activeRequests:new Set})}async removeWorker(t){const r=this.workerLoads.findIndex(o=>o.worker===t);if(r===-1)return;const[s]=this.workerLoads.splice(r,1);await Promise.allSettled(s.activeRequests)}async handleRequest(t){let r=this.workerLoads[0];for(let o=1;o<this.workerLoads.length;o++){const n=this.workerLoads[o];n.activeRequests.size<r.activeRequests.size&&(r=n)}const s=r.worker.request(t);return r.activeRequests.add(s),s.url=t.url,s.finally(()=>{r.activeRequests.delete(s)})}}function Xe(e){return/^latest$|^trunk$|^nightly$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/.test(e)}async function Ze({sourceString:e,blueprintMayReadAdjacentFiles:t}){if(!e)return;if(e.startsWith("http://")||e.startsWith("https://"))return await R.resolveRemoteBlueprint(e);let r=c.resolve(process.cwd(),e);if(!d.existsSync(r))throw new Error(`Blueprint file does not exist: ${r}`);const s=d.statSync(r);if(s.isDirectory()&&(r=c.join(r,"blueprint.json")),!s.isFile()&&s.isSymbolicLink())throw new Error(`Blueprint path is neither a file nor a directory: ${r}`);const o=c.extname(r);switch(o){case".zip":return H.ZipFilesystem.fromArrayBuffer(d.readFileSync(r).buffer);case".json":{const n=d.readFileSync(r,"utf-8");try{JSON.parse(n)}catch{throw new Error(`Blueprint file at ${r} is not a valid JSON file`)}const i=c.dirname(r),l=new H.NodeJsFilesystem(i);return new H.OverlayFilesystem([new H.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: ${o}. Only .zip and .json files are supported.`)}}class Ke{constructor(t,r){this.args=t,this.siteUrl=r.siteUrl,this.processIdSpaceLength=r.processIdSpaceLength,this.phpVersion=t.php,this.cliOutput=r.cliOutput}getWorkerType(){return"v2"}async bootAndSetUpInitialPlayground(t,r,s){const o=b.consumeAPI(t);await o.useFileLockManager(r);const n={...this.args,phpVersion:this.phpVersion,siteUrl:this.siteUrl,firstProcessId:1,processIdSpaceLength:this.processIdSpaceLength,trace:this.args.verbosity==="debug",blueprint:this.args.blueprint,withIntl:this.args.intl,withXdebug:!1,xdebug:void 0,nativeInternalDirPath:s,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],constants:D(this.args)};return await o.bootAndSetUpInitialWorker(n),o}async bootPlayground({worker:t,fileLockManagerPort:r,firstProcessId:s,nativeInternalDirPath:o}){const n=b.consumeAPI(t.phpPort);await n.useFileLockManager(r);const i={...this.args,phpVersion:this.phpVersion,siteUrl:this.siteUrl,firstProcessId:s,processIdSpaceLength:this.processIdSpaceLength,trace:this.args.verbosity==="debug",withIntl:this.args.intl,withXdebug:!!this.args.xdebug,nativeInternalDirPath:o,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],constants:D(this.args)};return await n.bootWorker(i),n}}const X=c.join(A.homedir(),".wordpress-playground");async function Je(e){return await pe("https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/develop.zip","sqlite.zip",e)}async function pe(e,t,r){const s=c.join(X,t);return R.existsSync(s)||(R.ensureDirSync(X),await et(e,s,r)),he(s)}async function et(e,t,r){const o=(await r.monitorFetch(fetch(e))).body.getReader(),n=`${t}.partial`,i=R.createWriteStream(n);for(;;){const{done:l,value:u}=await o.read();if(u&&i.write(u),l)break}i.close(),i.closed||await new Promise((l,u)=>{i.on("finish",()=>{R.renameSync(n,t),l(null)}),i.on("error",v=>{R.removeSync(n),u(v)})})}function he(e,t){return new File([R.readFileSync(e)],c.basename(e))}class tt{constructor(t,r){this.args=t,this.siteUrl=r.siteUrl,this.processIdSpaceLength=r.processIdSpaceLength,this.cliOutput=r.cliOutput}getWorkerType(){return"v1"}async bootAndSetUpInitialPlayground(t,r,s){let o,n,i;const l=new oe.EmscriptenDownloadMonitor;if(this.args.wordpressInstallMode==="download-and-install"){let I=!1;l.addEventListener("progress",$=>{if(I)return;const{loaded:U,total:K}=$.detail,F=Math.floor(Math.min(100,100*U/K));I=F===100,this.cliOutput.updateProgress("Downloading WordPress",F)}),o=await Re.resolveWordPressRelease(this.args.wp),i=c.join(X,`prebuilt-wp-content-for-wp-${o.version}.zip`),n=d.existsSync(i)?he(i):await pe(o.releaseUrl,`${o.version}.zip`,l),m.logger.debug(`Resolved WordPress release URL: ${o?.releaseUrl}`)}let u;this.args.skipSqliteSetup?(m.logger.debug("Skipping SQLite integration plugin setup..."),u=void 0):(this.cliOutput.updateProgress("Preparing SQLite database"),u=await Je(l));const v=this.args.followSymlinks===!0,S=this.args.experimentalTrace===!0,g=this.args["mount-before-install"]||[],a=this.args.mount||[],y=b.consumeAPI(t);await y.isConnected(),this.cliOutput.updateProgress("Booting WordPress");const f=await L.resolveRuntimeConfiguration(this.getEffectiveBlueprint());return await y.useFileLockManager(r),await y.bootAndSetUpInitialWorker({phpVersion:f.phpVersion,wpVersion:f.wpVersion,siteUrl:this.siteUrl,mountsBeforeWpInstall:g,mountsAfterWpInstall:a,wordpressInstallMode:this.args.wordpressInstallMode||"download-and-install",wordPressZip:n&&await n.arrayBuffer(),sqliteIntegrationPluginZip:await u?.arrayBuffer(),firstProcessId:0,processIdSpaceLength:this.processIdSpaceLength,followSymlinks:v,trace:S,internalCookieStore:this.args.internalCookieStore,withIntl:this.args.intl,withXdebug:!1,nativeInternalDirPath:s,constants:D(this.args)}),i&&!this.args["mount-before-install"]&&!d.existsSync(i)&&(this.cliOutput.updateProgress("Caching WordPress for next boot"),d.writeFileSync(i,await M.zipDirectory(y,"/wordpress"))),y}async bootPlayground({worker:t,fileLockManagerPort:r,firstProcessId:s,nativeInternalDirPath:o}){const n=b.consumeAPI(t.phpPort);await n.isConnected();const i=await L.resolveRuntimeConfiguration(this.getEffectiveBlueprint());return await n.useFileLockManager(r),await n.bootWorker({phpVersion:i.phpVersion,siteUrl:this.siteUrl,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],firstProcessId:s,processIdSpaceLength:this.processIdSpaceLength,followSymlinks:this.args.followSymlinks===!0,trace:this.args.experimentalTrace===!0,internalCookieStore:this.args.internalCookieStore,withIntl:this.args.intl,withXdebug:!!this.args.xdebug,nativeInternalDirPath:o,constants:D(this.args)}),await n.isReady(),n}async compileInputBlueprint(t){const r=this.getEffectiveBlueprint(),s=new oe.ProgressTracker;let o="",n=!1;return s.addEventListener("progress",i=>{if(n)return;n=i.detail.progress===100;const l=Math.floor(i.detail.progress);o=i.detail.caption||o||"Running Blueprint",this.cliOutput.updateProgress(o.trim(),l)}),await L.compileBlueprintV1(r,{progress:s,additionalSteps:t})}getEffectiveBlueprint(){const t=this.args.blueprint;return L.isBlueprintBundle(t)?t:{login:this.args.login,...t||{},preferredVersions:{php:this.args.php??t?.preferredVersions?.php??M.RecommendedPHPVersion,wp:this.args.wp??t?.preferredVersions?.wp??"latest",...t?.preferredVersions||{}}}}}async function rt(e,t=!0){const s=`${c.basename(process.argv0)}${e}${process.pid}-`,o=await se.dir({prefix:s,unsafeCleanup:!0});return t&&se.setGracefulCleanup(),o}async function ot(e,t,r){const o=(await st(e,t,r)).map(n=>new Promise(i=>{d.rm(n,{recursive:!0},l=>{l?m.logger.warn(`Failed to delete stale Playground temp dir: ${n}`,l):m.logger.info(`Deleted stale Playground temp dir: ${n}`),i()})}));await Promise.all(o)}async function st(e,t,r){try{const s=d.readdirSync(r).map(n=>c.join(r,n)),o=[];for(const n of s)await nt(e,t,n)&&o.push(n);return o}catch(s){return m.logger.warn(`Failed to find stale Playground temp dirs: ${s}`),[]}}async function nt(e,t,r){if(!d.lstatSync(r).isDirectory())return!1;const o=c.basename(r);if(!o.includes(e))return!1;const n=o.match(new RegExp(`^(.+)${e}(\\d+)-`));if(!n)return!1;const i={executableName:n[1],pid:n[2]};if(await it(i.pid,i.executableName))return!1;const l=Date.now()-t;return d.statSync(r).mtime.getTime()<l}async function it(e,t){const[r]=await new Promise((s,o)=>{De.list({pid:e,name:t,clean:!0},(n,i)=>{n?o(n):s(i)})});return!!r&&r.pid===e&&r.command===t}function me(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 at{constructor(t){this.lastProgressLine="",this.progressActive=!1,this.verbosity=t.verbosity,this.writeStream=t.writeStream||process.stdout}get isTTY(){return!!this.writeStream.isTTY}get shouldRender(){return me(this.writeStream)}get isQuiet(){return this.verbosity==="quiet"}bold(t){return this.isTTY?`\x1B[1m${t}\x1B[0m`:t}dim(t){return this.isTTY?`\x1B[2m${t}\x1B[0m`:t}green(t){return this.isTTY?`\x1B[32m${t}\x1B[0m`:t}cyan(t){return this.isTTY?`\x1B[36m${t}\x1B[0m`:t}yellow(t){return this.isTTY?`\x1B[33m${t}\x1B[0m`:t}red(t){return this.isTTY?`\x1B[31m${t}\x1B[0m`:t}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: ${o}. Only .zip and .json files are supported.`)}}class Je{constructor(t,r){this.args=t,this.siteUrl=r.siteUrl,this.processIdSpaceLength=r.processIdSpaceLength,this.phpVersion=t.php,this.cliOutput=r.cliOutput}getWorkerType(){return"v2"}async bootAndSetUpInitialPlayground(t,r,s){const o=b.consumeAPI(t);await o.useFileLockManager(r);const n={...this.args,phpVersion:this.phpVersion,siteUrl:this.siteUrl,firstProcessId:1,processIdSpaceLength:this.processIdSpaceLength,trace:this.args.verbosity==="debug",blueprint:this.args.blueprint,withIntl:this.args.intl,withRedis:this.args.redis,withMemcached:this.args.memcached,withXdebug:!1,xdebug:void 0,nativeInternalDirPath:s,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],constants:D(this.args)};return await o.bootAndSetUpInitialWorker(n),o}async bootPlayground({worker:t,fileLockManagerPort:r,firstProcessId:s,nativeInternalDirPath:o}){const n=b.consumeAPI(t.phpPort);await n.useFileLockManager(r);const i={...this.args,phpVersion:this.phpVersion,siteUrl:this.siteUrl,firstProcessId:s,processIdSpaceLength:this.processIdSpaceLength,trace:this.args.verbosity==="debug",withIntl:this.args.intl,withRedis:this.args.redis,withMemcached:this.args.memcached,withXdebug:!!this.args.xdebug,nativeInternalDirPath:o,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],constants:D(this.args)};return await n.bootWorker(i),n}}const Z=c.join(A.homedir(),".wordpress-playground");async function Ke(e){return await he("https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/develop.zip","sqlite.zip",e)}async function he(e,t,r){const s=c.join(Z,t);return L.existsSync(s)||(L.ensureDirSync(Z),await et(e,s,r)),me(s)}async function et(e,t,r){const o=(await r.monitorFetch(fetch(e))).body.getReader(),n=`${t}.partial`,i=L.createWriteStream(n);for(;;){const{done:l,value:u}=await o.read();if(u&&i.write(u),l)break}i.close(),i.closed||await new Promise((l,u)=>{i.on("finish",()=>{L.renameSync(n,t),l(null)}),i.on("error",v=>{L.removeSync(n),u(v)})})}function me(e,t){return new File([L.readFileSync(e)],c.basename(e))}class tt{constructor(t,r){this.args=t,this.siteUrl=r.siteUrl,this.processIdSpaceLength=r.processIdSpaceLength,this.cliOutput=r.cliOutput}getWorkerType(){return"v1"}async bootAndSetUpInitialPlayground(t,r,s){let o,n,i;const l=new se.EmscriptenDownloadMonitor;if(this.args.wordpressInstallMode==="download-and-install"){let I=!1;l.addEventListener("progress",$=>{if(I)return;const{loaded:U,total:K}=$.detail,F=Math.floor(Math.min(100,100*U/K));I=F===100,this.cliOutput.updateProgress("Downloading WordPress",F)}),o=await Le.resolveWordPressRelease(this.args.wp),i=c.join(Z,`prebuilt-wp-content-for-wp-${o.version}.zip`),n=d.existsSync(i)?me(i):await he(o.releaseUrl,`${o.version}.zip`,l),m.logger.debug(`Resolved WordPress release URL: ${o?.releaseUrl}`)}let u;this.args.skipSqliteSetup?(m.logger.debug("Skipping SQLite integration plugin setup..."),u=void 0):(this.cliOutput.updateProgress("Preparing SQLite database"),u=await Ke(l));const v=this.args.followSymlinks===!0,S=this.args.experimentalTrace===!0,g=this.args["mount-before-install"]||[],a=this.args.mount||[],y=b.consumeAPI(t);await y.isConnected(),this.cliOutput.updateProgress("Booting WordPress");const f=await R.resolveRuntimeConfiguration(this.getEffectiveBlueprint());return await y.useFileLockManager(r),await y.bootAndSetUpInitialWorker({phpVersion:f.phpVersion,wpVersion:f.wpVersion,siteUrl:this.siteUrl,mountsBeforeWpInstall:g,mountsAfterWpInstall:a,wordpressInstallMode:this.args.wordpressInstallMode||"download-and-install",wordPressZip:n&&await n.arrayBuffer(),sqliteIntegrationPluginZip:await u?.arrayBuffer(),firstProcessId:0,processIdSpaceLength:this.processIdSpaceLength,followSymlinks:v,trace:S,internalCookieStore:this.args.internalCookieStore,withIntl:this.args.intl,withRedis:this.args.redis,withMemcached:this.args.memcached,withXdebug:!1,nativeInternalDirPath:s,constants:D(this.args)}),i&&!this.args["mount-before-install"]&&!d.existsSync(i)&&(this.cliOutput.updateProgress("Caching WordPress for next boot"),d.writeFileSync(i,await W.zipDirectory(y,"/wordpress"))),y}async bootPlayground({worker:t,fileLockManagerPort:r,firstProcessId:s,nativeInternalDirPath:o}){const n=b.consumeAPI(t.phpPort);await n.isConnected();const i=await R.resolveRuntimeConfiguration(this.getEffectiveBlueprint());return await n.useFileLockManager(r),await n.bootWorker({phpVersion:i.phpVersion,siteUrl:this.siteUrl,mountsBeforeWpInstall:this.args["mount-before-install"]||[],mountsAfterWpInstall:this.args.mount||[],firstProcessId:s,processIdSpaceLength:this.processIdSpaceLength,followSymlinks:this.args.followSymlinks===!0,trace:this.args.experimentalTrace===!0,internalCookieStore:this.args.internalCookieStore,withIntl:this.args.intl,withRedis:this.args.redis,withMemcached:this.args.memcached,withXdebug:!!this.args.xdebug,nativeInternalDirPath:o,constants:D(this.args)}),await n.isReady(),n}async compileInputBlueprint(t){const r=this.getEffectiveBlueprint(),s=new se.ProgressTracker;let o="",n=!1;return s.addEventListener("progress",i=>{if(n)return;n=i.detail.progress===100;const l=Math.floor(i.detail.progress);o=i.detail.caption||o||"Running Blueprint",this.cliOutput.updateProgress(o.trim(),l)}),await R.compileBlueprintV1(r,{progress:s,additionalSteps:t})}getEffectiveBlueprint(){const t=this.args.blueprint;return R.isBlueprintBundle(t)?t:{login:this.args.login,...t||{},preferredVersions:{php:this.args.php??t?.preferredVersions?.php??W.RecommendedPHPVersion,wp:this.args.wp??t?.preferredVersions?.wp??"latest",...t?.preferredVersions||{}}}}}async function rt(e,t=!0){const s=`${c.basename(process.argv0)}${e}${process.pid}-`,o=await ne.dir({prefix:s,unsafeCleanup:!0});return t&&ne.setGracefulCleanup(),o}async function ot(e,t,r){const o=(await st(e,t,r)).map(n=>new Promise(i=>{d.rm(n,{recursive:!0},l=>{l?m.logger.warn(`Failed to delete stale Playground temp dir: ${n}`,l):m.logger.info(`Deleted stale Playground temp dir: ${n}`),i()})}));await Promise.all(o)}async function st(e,t,r){try{const s=d.readdirSync(r).map(n=>c.join(r,n)),o=[];for(const n of s)await nt(e,t,n)&&o.push(n);return o}catch(s){return m.logger.warn(`Failed to find stale Playground temp dirs: ${s}`),[]}}async function nt(e,t,r){if(!d.lstatSync(r).isDirectory())return!1;const o=c.basename(r);if(!o.includes(e))return!1;const n=o.match(new RegExp(`^(.+)${e}(\\d+)-`));if(!n)return!1;const i={executableName:n[1],pid:n[2]};if(await it(i.pid,i.executableName))return!1;const l=Date.now()-t;return d.statSync(r).mtime.getTime()<l}async function it(e,t){const[r]=await new Promise((s,o)=>{De.list({pid:e,name:t,clean:!0},(n,i)=>{n?o(n):s(i)})});return!!r&&r.pid===e&&r.command===t}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 at{constructor(t){this.lastProgressLine="",this.progressActive=!1,this.verbosity=t.verbosity,this.writeStream=t.writeStream||process.stdout}get isTTY(){return!!this.writeStream.isTTY}get shouldRender(){return fe(this.writeStream)}get isQuiet(){return this.verbosity==="quiet"}bold(t){return this.isTTY?`\x1B[1m${t}\x1B[0m`:t}dim(t){return this.isTTY?`\x1B[2m${t}\x1B[0m`:t}green(t){return this.isTTY?`\x1B[32m${t}\x1B[0m`:t}cyan(t){return this.isTTY?`\x1B[36m${t}\x1B[0m`:t}yellow(t){return this.isTTY?`\x1B[33m${t}\x1B[0m`:t}red(t){return this.isTTY?`\x1B[31m${t}\x1B[0m`:t}printBanner(){if(this.isQuiet)return;const t=this.bold("WordPress Playground CLI");this.writeStream.write(`
18
18
  ${t}
19
19
 
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 s=[];if(t.intl&&s.push("intl"),t.xdebug&&s.push(this.yellow("xdebug")),s.length>0&&r.push(`${this.dim("Extensions")} ${s.join(", ")}`),t.mounts.length>0)for(const o of t.mounts){const n=o.autoMounted?` ${this.dim("(auto-mount)")}`:"";r.push(`${this.dim("Mount")} ${o.hostPath} ${this.dim("→")} ${o.vfsPath}${n}`)}t.blueprint&&r.push(`${this.dim("Blueprint")} ${t.blueprint}`),this.writeStream.write(r.join(`
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 s=[];if(t.intl&&s.push("intl"),t.redis&&s.push("redis"),t.memcached&&s.push("memcached"),t.xdebug&&s.push(this.yellow("xdebug")),s.length>0&&r.push(`${this.dim("Extensions")} ${s.join(", ")}`),t.mounts.length>0)for(const o of t.mounts){const n=o.autoMounted?` ${this.dim("(auto-mount)")}`:"";r.push(`${this.dim("Mount")} ${o.hostPath} ${this.dim("→")} ${o.vfsPath}${n}`)}t.blueprint&&r.push(`${this.dim("Blueprint")} ${t.blueprint}`),this.writeStream.write(r.join(`
21
21
  `)+`
22
22
 
23
23
  `)}startProgress(t){this.isQuiet||this.shouldRender&&(this.progressActive=!0,this.updateProgress(t))}updateProgress(t,r){if(this.isQuiet||!this.shouldRender)return;this.progressActive||(this.progressActive=!0);let s=`${t}`;r!==void 0&&(s=`${t} ${this.dim(`${r}%`)}`),s!==this.lastProgressLine&&(this.lastProgressLine=s,this.isTTY?(this.writeStream.cursorTo(0),this.writeStream.write(s),this.writeStream.clearLine(1)):this.writeStream.write(`${s}
@@ -29,8 +29,8 @@ ${t}
29
29
  ${this.green("Ready!")} WordPress is running on ${this.bold(t)} ${this.dim(`(${r} ${s})`)}
30
30
 
31
31
  `)}printWarning(t){this.isQuiet||this.writeStream.write(`${this.yellow("Warning:")} ${t}
32
- `)}}const Z={Quiet:{name:"quiet",severity:m.LogSeverity.Fatal},Normal:{name:"normal",severity:m.LogSeverity.Info},Debug:{name:"debug",severity:m.LogSeverity.Debug}};async function lt(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:M.RecommendedPHPVersion,choices:b.SupportedPHPVersions},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:He},"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:qe},"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:Ve},mount:{describe:"Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path",type:"array",string:!0,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,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:ne},"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:ne},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(a=>a.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.
33
- 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},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:a=>a===""?["vscode","phpstorm"]:[a]},"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}},r={port:{describe:"Port to listen on when serving.",type:"number",default:9400},"experimental-multi-worker":{describe:"Enable experimental multi-worker support which requires a /wordpress directory backed by a real filesystem. Pass a positive number to specify the number of workers to use. Otherwise, default to the number of CPUs minus 1.",type:"number",coerce:a=>a??A.cpus().length-1},"experimental-devtools":{describe:"Enable experimental browser development tools.",type:"boolean"}},s={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:M.RecommendedPHPVersion,choices:b.SupportedPHPVersions},wp:{describe:"WordPress version to use.",type:"string",default:"latest"},port:{describe:"Port to listen on.",type:"number",default:9400},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},"no-auto-mount":{describe:"Disable automatic project type detection. Use --mount to manually specify mounts instead.",type:"boolean",default:!1},define:t.define,"define-bool":t["define-bool"],"define-number":t["define-number"]},o={outfile:{describe:"When building, write to this output file.",type:"string",default:"wordpress.zip"}},n=Le(e).usage("Usage: wp-playground <command> [options]").command("start","Start a local WordPress server with automatic project detection (recommended)",a=>a.usage(`Usage: wp-playground start [options]
32
+ `)}}const J={Quiet:{name:"quiet",severity:m.LogSeverity.Fatal},Normal:{name:"normal",severity:m.LogSeverity.Info},Debug:{name:"debug",severity:m.LogSeverity.Debug}};async function lt(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:W.RecommendedPHPVersion,choices:b.SupportedPHPVersions},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:He},"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:qe},"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:Ve},mount:{describe:"Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path",type:"array",string:!0,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,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:ie},"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:ie},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(J).map(a=>a.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.
33
+ 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:a=>a===""?["vscode","phpstorm"]:[a]},"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}},r={port:{describe:"Port to listen on when serving.",type:"number",default:9400},"experimental-multi-worker":{describe:"Enable experimental multi-worker support which requires a /wordpress directory backed by a real filesystem. Pass a positive number to specify the number of workers to use. Otherwise, default to the number of CPUs minus 1.",type:"number",coerce:a=>a??A.cpus().length-1},"experimental-devtools":{describe:"Enable experimental browser development tools.",type:"boolean"}},s={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:W.RecommendedPHPVersion,choices:b.SupportedPHPVersions},wp:{describe:"WordPress version to use.",type:"string",default:"latest"},port:{describe:"Port to listen on.",type:"number",default:9400},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},"no-auto-mount":{describe:"Disable automatic project type detection. Use --mount to manually specify mounts instead.",type:"boolean",default:!1},define:t.define,"define-bool":t["define-bool"],"define-number":t["define-number"]},o={outfile:{describe:"When building, write to this output file.",type:"string",default:"wordpress.zip"}},n=Re(e).usage("Usage: wp-playground <command> [options]").command("start","Start a local WordPress server with automatic project detection (recommended)",a=>a.usage(`Usage: wp-playground start [options]
34
34
 
35
35
  The easiest way to run WordPress locally. Automatically detects
36
36
  if your directory contains a plugin, theme, wp-content, or
@@ -42,8 +42,8 @@ Examples:
42
42
  wp-playground start --wp=6.7 --php=8.3 # Use specific versions
43
43
  wp-playground start --skip-browser # Skip opening browser
44
44
  wp-playground start --no-auto-mount # Disable auto-detection`).options(s)).command("server","Start a local WordPress server (advanced, low-level)",a=>a.options({...t,...r})).command("run-blueprint","Execute a Blueprint without starting a server",a=>a.options({...t})).command("build-snapshot","Build a ZIP snapshot of a WordPress site based on a Blueprint",a=>a.options({...t,...o})).demandCommand(1,"Please specify a command").strictCommands().conflicts("experimental-unsafe-ide-integration","experimental-devtools").showHelpOnFail(!1).fail((a,y,f)=>{if(y)throw y;a&&a.includes("Please specify a command")&&(f.showHelp(),console.error(`
45
- `+a),process.exit(1)),console.error(a),process.exit(1)}).strictOptions().check(async a=>{if(a["skip-wordpress-install"]===!0&&(a["wordpress-install-mode"]="do-not-attempt-installing",a.wordpressInstallMode="do-not-attempt-installing"),a.wp!==void 0&&typeof a.wp=="string"&&!Xe(a.wp))try{new URL(a.wp)}catch{throw new Error('Unrecognized WordPress version. Please use "latest", a URL, or a numeric version such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1"')}const y=a["site-url"];if(typeof y=="string"&&y.trim()!=="")try{new URL(y)}catch{throw new Error(`Invalid site-url "${y}". Please provide a valid URL (e.g., http://localhost:8080 or https://example.com)`)}if(a["auto-mount"]){let f=!1;try{f=d.statSync(a["auto-mount"]).isDirectory()}catch{f=!1}if(!f)throw new Error(`The specified --auto-mount path is not a directory: '${a["auto-mount"]}'.`)}if(a["experimental-multi-worker"]!==void 0){if(a._[0]!=="server")throw new Error("The --experimental-multi-worker flag is only supported when running the server command.");if(a["experimental-multi-worker"]!==void 0&&typeof a["experimental-multi-worker"]=="number"&&a["experimental-multi-worker"]<=1)throw new Error("The --experimental-multi-worker flag must be a positive integer greater than 1.")}if(a["experimental-blueprints-v2-runner"]===!0){if(a.mode!==void 0){if(a["wordpress-install-mode"]!==void 0)throw new Error("The --wordpress-install-mode option cannot be used with the --mode option. Use one or the other.");if("skip-sqlite-setup"in a)throw new Error("The --skipSqliteSetup option is not supported in Blueprint V2 mode.");if(a["auto-mount"]!==void 0)throw new Error("The --mode option cannot be used with --auto-mount because --auto-mount automatically sets the mode.")}else a["wordpress-install-mode"]==="do-not-attempt-installing"?a.mode="apply-to-existing-site":a.mode="create-new-site";const f=a.allow||[];a.followSymlinks===!0&&f.push("follow-symlinks"),a["blueprint-may-read-adjacent-files"]===!0&&f.push("read-local-fs"),a.allow=f}else if(a.mode!==void 0)throw new Error("The --mode option requires the --experimentalBlueprintsV2Runner flag.");return!0});n.wrap(n.terminalWidth());const i=await n.argv,l=i._[0];["start","run-blueprint","server","build-snapshot"].includes(l)||(n.showHelp(),process.exit(1));const u=i.define||{};!("WP_DEBUG"in u)&&!("WP_DEBUG_LOG"in u)&&!("WP_DEBUG_DISPLAY"in u)&&(u.WP_DEBUG="true",u.WP_DEBUG_LOG="true",u.WP_DEBUG_DISPLAY="true");const v={...i,define:u,command:l,mount:[...i.mount||[],...i["mount-dir"]||[]],"mount-before-install":[...i["mount-before-install"]||[],...i["mount-dir-before-install"]||[]]},S=await we(v);S===void 0&&process.exit(0);const g=(()=>{let a;return async()=>{a!==void 0&&(a=S[Symbol.asyncDispose]()),await a,process.exit(0)}})();process.on("SIGINT",g),process.on("SIGTERM",g)}catch(t){if(console.error(t),!(t instanceof Error))throw t;if(process.argv.includes("--debug"))b.printDebugDetails(t);else{const s=[];let o=t;do s.push(o.message),o=o.cause;while(o instanceof Error);console.error("\x1B[1m"+s.join(" caused by: ")+"\x1B[0m")}process.exit(1)}}function ae(e,t){return e.find(r=>r.vfsPath.replace(/\/$/,"")===t.replace(/\/$/,""))}const fe=Symbol("playground-cli-testing"),C=e=>process.stdout.isTTY?"\x1B[1m"+e+"\x1B[0m":e,ut=e=>process.stdout.isTTY?"\x1B[31m"+e+"\x1B[0m":e,dt=e=>process.stdout.isTTY?`\x1B[2m${e}\x1B[0m`:e,G=e=>process.stdout.isTTY?`\x1B[3m${e}\x1B[0m`:e,le=e=>process.stdout.isTTY?`\x1B[33m${e}\x1B[0m`:e;async function we(e){let t,r;const s=new Map;if(e.command==="start"&&(e=ct(e)),e.autoMount!==void 0&&(e.autoMount===""&&(e={...e,autoMount:process.cwd()}),e=ce(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 g=Object.values(Z).find(a=>a.name===e.verbosity).severity;m.logger.setSeverityFilterLevel(g)}e.intl||(e.intl=!0);const o=new at({verbosity:e.verbosity||"normal"});e.command==="server"&&(o.printBanner(),o.printConfig({phpVersion:e.php||M.RecommendedPHPVersion,wpVersion:e.wp||"latest",port:e.port||9400,xdebug:!!e.xdebug,intl:!!e.intl,mounts:[...e.mount||[],...e["mount-before-install"]||[]],blueprint:typeof e.blueprint=="string"?e.blueprint:void 0}));const n=e.command==="server"?e.port??9400:0,i=A.platform()==="win32"?void 0:await import("fs-ext").then(g=>g.flockSync).catch(()=>{m.logger.debug("The fs-ext package is not installed. Internal file locking will not be integrated with host OS file locking.")}),l=new de.FileLockManagerForNode(i);let u=!1,v=!0;const S=await Ye({port:n,onBind:async(g,a)=>{const y="127.0.0.1",f=`http://${y}:${a}`,I=e["site-url"]||f,$=e.command==="server"?e.experimentalMultiWorker??1:1,U=e.command==="server"?$+1:$,F=2**31-1,W=Math.floor(F/U),J="-playground-cli-site-",T=await rt(J);m.logger.debug(`Native temp dir for VFS root: ${T.path}`);const _="WP Playground CLI - Listen for Xdebug",ee=".playground-xdebug-root",te=c.join(process.cwd(),ee);if(await q.removeTempDirSymlink(te),e.xdebug&&e.experimentalUnsafeIdeIntegration){await q.createTempDirSymlink(T.path,te,process.platform);const p={hostPath:c.join(".",c.sep,ee),vfsPath:"/"};try{await q.clearXdebugIDEConfig(_,process.cwd());const w=typeof e.xdebug=="object"?e.xdebug:void 0,x=await q.addXdebugIDEConfig({name:_,host:y,port:a,ides:e.experimentalUnsafeIdeIntegration,cwd:process.cwd(),mounts:[p,...e["mount-before-install"]||[],...e.mount||[]],ideKey:w?.ideKey}),h=e.experimentalUnsafeIdeIntegration,P=h.includes("vscode"),k=h.includes("phpstorm"),O=Object.values(x);console.log(""),O.length>0?(console.log(C("Xdebug configured successfully")),console.log(le("Updated IDE config: ")+O.join(" ")),console.log(le("Playground source root: ")+".playground-xdebug-root"+G(dt(" – you can set breakpoints and preview Playground's VFS structure in there.")))):(console.log(C("Xdebug configuration failed.")),console.log("No IDE-specific project settings directory was found in the current working directory.")),console.log(""),P&&x.vscode&&(console.log(C("VS Code / Cursor instructions:")),console.log(" 1. Ensure you have installed an IDE extension for PHP Debugging"),console.log(` (The ${C("PHP Debug")} extension by ${C("Xdebug")} has been a solid option)`),console.log(" 2. Open the Run and Debug panel on the left sidebar"),console.log(` 3. Select "${G(_)}" from the dropdown`),console.log(' 3. Click "start debugging"'),console.log(" 5. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"),console.log(" 6. Visit Playground in your browser to hit the breakpoint"),k&&console.log("")),k&&x.phpstorm&&(console.log(C("PhpStorm instructions:")),console.log(` 1. Choose "${G(_)}" debug configuration in the toolbar`),console.log(" 2. Click the debug button (bug icon)`"),console.log(" 3. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"),console.log(" 4. Visit Playground in your browser to hit the breakpoint")),console.log("")}catch(w){throw new Error("Could not configure Xdebug",{cause:w})}}const ye=c.dirname(T.path),be=2*24*60*60*1e3;ot(J,be,ye);const V=c.join(T.path,"internal");d.mkdirSync(V);const Pe=["wordpress","tmp","home"];for(const p of Pe){const w=h=>h.vfsPath===`/${p}`;if(!(e["mount-before-install"]?.some(w)||e.mount?.some(w))){const h=c.join(T.path,p);d.mkdirSync(h),e["mount-before-install"]===void 0&&(e["mount-before-install"]=[]),e["mount-before-install"].unshift({vfsPath:`/${p}`,hostPath:h})}}if(e["mount-before-install"])for(const p of e["mount-before-install"])m.logger.debug(`Mount before WP install: ${p.vfsPath} -> ${p.hostPath}`);if(e.mount)for(const p of e.mount)m.logger.debug(`Mount after WP install: ${p.vfsPath} -> ${p.hostPath}`);let E;e["experimental-blueprints-v2-runner"]?E=new Ke(e,{siteUrl:I,processIdSpaceLength:W,cliOutput:o}):(E=new tt(e,{siteUrl:I,processIdSpaceLength:W,cliOutput:o}),typeof e.blueprint=="string"&&(e.blueprint=await Ze({sourceString:e.blueprint,blueprintMayReadAdjacentFiles:e["blueprint-may-read-adjacent-files"]===!0})));let j=!1;const N=async function(){j||(j=!0,await Promise.all([...s].map(async([w,x])=>{await x.dispose(),await w.terminate()})),g&&await new Promise(w=>g.close(w)),await T.cleanup())},ve=pt(U,E.getWorkerType(),({exitCode:p,workerIndex:w})=>{j||p===0&&m.logger.error(`Worker ${w} exited with code ${p}
46
- `)});o.startProgress("Starting...");try{const p=await ve,w=await ue(l);{const h=p.shift(),P=await E.bootAndSetUpInitialPlayground(h.phpPort,w,V);if(s.set(h.worker,P),await P.isReady(),u=!0,t=new Qe(P),!e["experimental-blueprints-v2-runner"]){const k=await E.compileInputBlueprint(e["additional-blueprint-steps"]||[]);k&&await L.runBlueprintV1Steps(k,P)}if(e.command==="build-snapshot"){await mt(r,e.outfile),o.printStatus(`Exported to ${e.outfile}`),await N();return}else if(e.command==="run-blueprint"){o.finishProgress("Done"),await N();return}await t.removeWorker(P),await P.dispose(),await h.worker.terminate(),s.delete(h.worker)}const x=W;return[r]=await Promise.all(p.map(async(h,P)=>{const k=x+P*W,O=await ue(l),Y=await E.bootPlayground({worker:h,fileLockManagerPort:O,firstProcessId:k,nativeInternalDirPath:V});return s.set(h.worker,Y),t.addWorker(Y),Y})),o.finishProgress(),o.printReady(f,$),e.xdebug&&e.experimentalDevtools&&(await We.startBridge({phpInstance:r,phpRoot:"/wordpress"})).start(),{playground:r,server:g,serverUrl:f,[Symbol.asyncDispose]:N,[fe]:{workerThreadCount:$,getWorkerNumberFromProcessId:h=>Math.floor(h/W)}}}catch(p){if(e.verbosity!=="debug")throw p;let w="";throw await r?.fileExists(m.errorLogPath)&&(w=await r.readFileAsText(m.errorLogPath)),await N(),new Error(w,{cause:p})}},async handleRequest(g){if(!u)return b.PHPResponse.forHttpCode(502,"WordPress is not ready yet");if(v){v=!1;const a={"Content-Type":["text/plain"],"Content-Length":["0"],Location:[g.url]};return g.headers?.cookie?.includes("playground_auto_login_already_happened")&&(a["Set-Cookie"]=["playground_auto_login_already_happened=1; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/"]),new b.PHPResponse(302,a,new Uint8Array)}return await t.handleRequest(g)}});return S&&e.command==="start"&&!e.skipBrowser&&ht(S.serverUrl),S}function ct(e){let t={...e,command:"server"};e.noAutoMount||(t.autoMount=c.resolve(process.cwd(),t.path??""),t=ce(t),delete t.autoMount);const r=ae(t["mount-before-install"]||[],"/wordpress")||ae(t.mount||[],"/wordpress");if(r)console.log("Site files stored at:",r?.hostPath),e.reset&&(console.log(""),console.log(ut("This site is not managed by Playground CLI and cannot be reset.")),console.log("(It's not stored in the ~/.wordpress-playground/sites/<site-id> directory.)"),console.log(""),console.log("You may still remove the site's directory manually if you wish."),process.exit(1));else{const s=t.autoMount||process.cwd(),o=Ae.createHash("sha256").update(s).digest("hex"),n=A.homedir(),i=c.join(n,".wordpress-playground/sites",o);console.log("Site files stored at:",i),d.existsSync(i)&&e.reset&&(console.log("Resetting site..."),d.rmdirSync(i,{recursive:!0})),d.mkdirSync(i,{recursive:!0}),t["mount-before-install"]=[...t["mount-before-install"]||[],{vfsPath:"/wordpress",hostPath:i}],t.wordpressInstallMode=d.readdirSync(i).length===0?"download-and-install":"install-from-existing-files-if-needed"}return t}async function pt(e,t,r){const s=[];for(let o=0;o<e;o++){const i=ge(t,{onExit:l=>{r({exitCode:l,workerIndex:o})}});s.push(i)}return Promise.all(s)}function ge(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:B&&B.tagName.toUpperCase()==="SCRIPT"&&B.src||new URL("run-cli-le2xCSBZ.cjs",document.baseURI).href)):r=new Q.Worker(new URL("./worker-thread-v2.cjs",typeof document>"u"?require("url").pathToFileURL(__filename).href:B&&B.tagName.toUpperCase()==="SCRIPT"&&B.src||new URL("run-cli-le2xCSBZ.cjs",document.baseURI).href)),new Promise((s,o)=>{r.once("message",function(i){i.command==="worker-script-initialized"&&s({worker:r,phpPort:i.phpPort})}),r.once("error",function(i){console.error(i);const l=new Error(`Worker failed to load worker. ${i.message?`Original error: ${i.message}`:""}`);o(l)});let n=!1;r.once("spawn",()=>{n=!0}),r.once("exit",i=>{n||o(new Error(`Worker exited before spawning: ${i}`)),t?.(i)})})}async function ue(e){const{port1:t,port2:r}=new Q.MessageChannel;return await Ce.jspi()?b.exposeAPI(e,null,t):await b.exposeSyncAPI(e,t),r}function ht(e){const t=A.platform();let r;switch(t){case"darwin":r=`open "${e}"`;break;case"win32":r=`start "" "${e}"`;break;default:r=`xdg-open "${e}"`;break}Me.exec(r,s=>{s&&m.logger.debug(`Could not open browser: ${s.message}`)})}async function mt(e,t){await e.run({code:`<?php
45
+ `+a),process.exit(1)),console.error(a),process.exit(1)}).strictOptions().check(async a=>{if(a["skip-wordpress-install"]===!0&&(a["wordpress-install-mode"]="do-not-attempt-installing",a.wordpressInstallMode="do-not-attempt-installing"),a.wp!==void 0&&typeof a.wp=="string"&&!Xe(a.wp))try{new URL(a.wp)}catch{throw new Error('Unrecognized WordPress version. Please use "latest", a URL, or a numeric version such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1"')}const y=a["site-url"];if(typeof y=="string"&&y.trim()!=="")try{new URL(y)}catch{throw new Error(`Invalid site-url "${y}". Please provide a valid URL (e.g., http://localhost:8080 or https://example.com)`)}if(a["auto-mount"]){let f=!1;try{f=d.statSync(a["auto-mount"]).isDirectory()}catch{f=!1}if(!f)throw new Error(`The specified --auto-mount path is not a directory: '${a["auto-mount"]}'.`)}if(a["experimental-multi-worker"]!==void 0){if(a._[0]!=="server")throw new Error("The --experimental-multi-worker flag is only supported when running the server command.");if(a["experimental-multi-worker"]!==void 0&&typeof a["experimental-multi-worker"]=="number"&&a["experimental-multi-worker"]<=1)throw new Error("The --experimental-multi-worker flag must be a positive integer greater than 1.")}if(a["experimental-blueprints-v2-runner"]===!0){if(a.mode!==void 0){if(a["wordpress-install-mode"]!==void 0)throw new Error("The --wordpress-install-mode option cannot be used with the --mode option. Use one or the other.");if("skip-sqlite-setup"in a)throw new Error("The --skipSqliteSetup option is not supported in Blueprint V2 mode.");if(a["auto-mount"]!==void 0)throw new Error("The --mode option cannot be used with --auto-mount because --auto-mount automatically sets the mode.")}else a["wordpress-install-mode"]==="do-not-attempt-installing"?a.mode="apply-to-existing-site":a.mode="create-new-site";const f=a.allow||[];a.followSymlinks===!0&&f.push("follow-symlinks"),a["blueprint-may-read-adjacent-files"]===!0&&f.push("read-local-fs"),a.allow=f}else if(a.mode!==void 0)throw new Error("The --mode option requires the --experimentalBlueprintsV2Runner flag.");return!0});n.wrap(n.terminalWidth());const i=await n.argv,l=i._[0];["start","run-blueprint","server","build-snapshot"].includes(l)||(n.showHelp(),process.exit(1));const u=i.define||{};!("WP_DEBUG"in u)&&!("WP_DEBUG_LOG"in u)&&!("WP_DEBUG_DISPLAY"in u)&&(u.WP_DEBUG="true",u.WP_DEBUG_LOG="true",u.WP_DEBUG_DISPLAY="true");const v={...i,define:u,command:l,mount:[...i.mount||[],...i["mount-dir"]||[]],"mount-before-install":[...i["mount-before-install"]||[],...i["mount-dir-before-install"]||[]]},S=await ge(v);S===void 0&&process.exit(0);const g=(()=>{let a;return async()=>{a!==void 0&&(a=S[Symbol.asyncDispose]()),await a,process.exit(0)}})();process.on("SIGINT",g),process.on("SIGTERM",g)}catch(t){if(console.error(t),!(t instanceof Error))throw t;if(process.argv.includes("--debug"))b.printDebugDetails(t);else{const s=[];let o=t;do s.push(o.message),o=o.cause;while(o instanceof Error);console.error("\x1B[1m"+s.join(" caused by: ")+"\x1B[0m")}process.exit(1)}}function le(e,t){return e.find(r=>r.vfsPath.replace(/\/$/,"")===t.replace(/\/$/,""))}const we=Symbol("playground-cli-testing"),C=e=>process.stdout.isTTY?"\x1B[1m"+e+"\x1B[0m":e,ut=e=>process.stdout.isTTY?"\x1B[31m"+e+"\x1B[0m":e,dt=e=>process.stdout.isTTY?`\x1B[2m${e}\x1B[0m`:e,G=e=>process.stdout.isTTY?`\x1B[3m${e}\x1B[0m`:e,ue=e=>process.stdout.isTTY?`\x1B[33m${e}\x1B[0m`:e;async function ge(e){let t,r;const s=new Map;if(e.command==="start"&&(e=ct(e)),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 g=Object.values(J).find(a=>a.name===e.verbosity).severity;m.logger.setSeverityFilterLevel(g)}e.intl||(e.intl=!0),e.redis===void 0&&(e.redis=await X.jspi()),e.memcached===void 0&&(e.memcached=await X.jspi());const o=new at({verbosity:e.verbosity||"normal"});e.command==="server"&&(o.printBanner(),o.printConfig({phpVersion:e.php||W.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 n=e.command==="server"?e.port??9400:0,i=A.platform()==="win32"?void 0:await import("fs-ext").then(g=>g.flockSync).catch(()=>{m.logger.debug("The fs-ext package is not installed. Internal file locking will not be integrated with host OS file locking.")}),l=new ce.FileLockManagerForNode(i);let u=!1,v=!0;const S=await Ye({port:n,onBind:async(g,a)=>{const y="127.0.0.1",f=`http://${y}:${a}`,I=e["site-url"]||f,$=e.command==="server"?e.experimentalMultiWorker??1:1,U=e.command==="server"?$+1:$,F=2**31-1,M=Math.floor(F/U),ee="-playground-cli-site-",T=await rt(ee);m.logger.debug(`Native temp dir for VFS root: ${T.path}`);const _="WP Playground CLI - Listen for Xdebug",te=".playground-xdebug-root",re=c.join(process.cwd(),te);if(await q.removeTempDirSymlink(re),e.xdebug&&e.experimentalUnsafeIdeIntegration){await q.createTempDirSymlink(T.path,re,process.platform);const p={hostPath:c.join(".",c.sep,te),vfsPath:"/"};try{await q.clearXdebugIDEConfig(_,process.cwd());const w=typeof e.xdebug=="object"?e.xdebug:void 0,x=await q.addXdebugIDEConfig({name:_,host:y,port:a,ides:e.experimentalUnsafeIdeIntegration,cwd:process.cwd(),mounts:[p,...e["mount-before-install"]||[],...e.mount||[]],ideKey:w?.ideKey}),h=e.experimentalUnsafeIdeIntegration,P=h.includes("vscode"),k=h.includes("phpstorm"),O=Object.values(x);console.log(""),O.length>0?(console.log(C("Xdebug configured successfully")),console.log(ue("Updated IDE config: ")+O.join(" ")),console.log(ue("Playground source root: ")+".playground-xdebug-root"+G(dt(" – you can set breakpoints and preview Playground's VFS structure in there.")))):(console.log(C("Xdebug configuration failed.")),console.log("No IDE-specific project settings directory was found in the current working directory.")),console.log(""),P&&x.vscode&&(console.log(C("VS Code / Cursor instructions:")),console.log(" 1. Ensure you have installed an IDE extension for PHP Debugging"),console.log(` (The ${C("PHP Debug")} extension by ${C("Xdebug")} has been a solid option)`),console.log(" 2. Open the Run and Debug panel on the left sidebar"),console.log(` 3. Select "${G(_)}" from the dropdown`),console.log(' 3. Click "start debugging"'),console.log(" 5. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"),console.log(" 6. Visit Playground in your browser to hit the breakpoint"),k&&console.log("")),k&&x.phpstorm&&(console.log(C("PhpStorm instructions:")),console.log(` 1. Choose "${G(_)}" debug configuration in the toolbar`),console.log(" 2. Click the debug button (bug icon)`"),console.log(" 3. Set a breakpoint. For example, in .playground-xdebug-root/wordpress/index.php"),console.log(" 4. Visit Playground in your browser to hit the breakpoint")),console.log("")}catch(w){throw new Error("Could not configure Xdebug",{cause:w})}}const be=c.dirname(T.path),Pe=2*24*60*60*1e3;ot(ee,Pe,be);const V=c.join(T.path,"internal");d.mkdirSync(V);const ve=["wordpress","tmp","home"];for(const p of ve){const w=h=>h.vfsPath===`/${p}`;if(!(e["mount-before-install"]?.some(w)||e.mount?.some(w))){const h=c.join(T.path,p);d.mkdirSync(h),e["mount-before-install"]===void 0&&(e["mount-before-install"]=[]),e["mount-before-install"].unshift({vfsPath:`/${p}`,hostPath:h})}}if(e["mount-before-install"])for(const p of e["mount-before-install"])m.logger.debug(`Mount before WP install: ${p.vfsPath} -> ${p.hostPath}`);if(e.mount)for(const p of e.mount)m.logger.debug(`Mount after WP install: ${p.vfsPath} -> ${p.hostPath}`);let E;e["experimental-blueprints-v2-runner"]?E=new Je(e,{siteUrl:I,processIdSpaceLength:M,cliOutput:o}):(E=new tt(e,{siteUrl:I,processIdSpaceLength:M,cliOutput:o}),typeof e.blueprint=="string"&&(e.blueprint=await Ze({sourceString:e.blueprint,blueprintMayReadAdjacentFiles:e["blueprint-may-read-adjacent-files"]===!0})));let j=!1;const N=async function(){j||(j=!0,await Promise.all([...s].map(async([w,x])=>{await x.dispose(),await w.terminate()})),g&&await new Promise(w=>g.close(w)),await T.cleanup())},Se=pt(U,E.getWorkerType(),({exitCode:p,workerIndex:w})=>{j||p===0&&m.logger.error(`Worker ${w} exited with code ${p}
46
+ `)});o.startProgress("Starting...");try{const p=await Se,w=await de(l);{const h=p.shift(),P=await E.bootAndSetUpInitialPlayground(h.phpPort,w,V);if(s.set(h.worker,P),await P.isReady(),u=!0,t=new Qe(P),!e["experimental-blueprints-v2-runner"]){const k=await E.compileInputBlueprint(e["additional-blueprint-steps"]||[]);k&&await R.runBlueprintV1Steps(k,P)}if(e.command==="build-snapshot"){await mt(r,e.outfile),o.printStatus(`Exported to ${e.outfile}`),await N();return}else if(e.command==="run-blueprint"){o.finishProgress("Done"),await N();return}await t.removeWorker(P),await P.dispose(),await h.worker.terminate(),s.delete(h.worker)}const x=M;return[r]=await Promise.all(p.map(async(h,P)=>{const k=x+P*M,O=await de(l),Y=await E.bootPlayground({worker:h,fileLockManagerPort:O,firstProcessId:k,nativeInternalDirPath:V});return s.set(h.worker,Y),t.addWorker(Y),Y})),o.finishProgress(),o.printReady(f,$),e.xdebug&&e.experimentalDevtools&&(await Me.startBridge({phpInstance:r,phpRoot:"/wordpress"})).start(),{playground:r,server:g,serverUrl:f,[Symbol.asyncDispose]:N,[we]:{workerThreadCount:$,getWorkerNumberFromProcessId:h=>Math.floor(h/M)}}}catch(p){if(e.verbosity!=="debug")throw p;let w="";throw await r?.fileExists(m.errorLogPath)&&(w=await r.readFileAsText(m.errorLogPath)),await N(),new Error(w,{cause:p})}},async handleRequest(g){if(!u)return b.PHPResponse.forHttpCode(502,"WordPress is not ready yet");if(v){v=!1;const a={"Content-Type":["text/plain"],"Content-Length":["0"],Location:[g.url]};return g.headers?.cookie?.includes("playground_auto_login_already_happened")&&(a["Set-Cookie"]=["playground_auto_login_already_happened=1; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/"]),new b.PHPResponse(302,a,new Uint8Array)}return await t.handleRequest(g)}});return S&&e.command==="start"&&!e.skipBrowser&&ht(S.serverUrl),S}function ct(e){let t={...e,command:"server"};e.noAutoMount||(t.autoMount=c.resolve(process.cwd(),t.path??""),t=pe(t),delete t.autoMount);const r=le(t["mount-before-install"]||[],"/wordpress")||le(t.mount||[],"/wordpress");if(r)console.log("Site files stored at:",r?.hostPath),e.reset&&(console.log(""),console.log(ut("This site is not managed by Playground CLI and cannot be reset.")),console.log("(It's not stored in the ~/.wordpress-playground/sites/<site-id> directory.)"),console.log(""),console.log("You may still remove the site's directory manually if you wish."),process.exit(1));else{const s=t.autoMount||process.cwd(),o=Ae.createHash("sha256").update(s).digest("hex"),n=A.homedir(),i=c.join(n,".wordpress-playground/sites",o);console.log("Site files stored at:",i),d.existsSync(i)&&e.reset&&(console.log("Resetting site..."),d.rmdirSync(i,{recursive:!0})),d.mkdirSync(i,{recursive:!0}),t["mount-before-install"]=[...t["mount-before-install"]||[],{vfsPath:"/wordpress",hostPath:i}],t.wordpressInstallMode=d.readdirSync(i).length===0?"download-and-install":"install-from-existing-files-if-needed"}return t}async function pt(e,t,r){const s=[];for(let o=0;o<e;o++){const i=ye(t,{onExit:l=>{r({exitCode:l,workerIndex:o})}});s.push(i)}return Promise.all(s)}function ye(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:B&&B.tagName.toUpperCase()==="SCRIPT"&&B.src||new URL("run-cli-BUDz-Yr3.cjs",document.baseURI).href)):r=new Q.Worker(new URL("./worker-thread-v2.cjs",typeof document>"u"?require("url").pathToFileURL(__filename).href:B&&B.tagName.toUpperCase()==="SCRIPT"&&B.src||new URL("run-cli-BUDz-Yr3.cjs",document.baseURI).href)),new Promise((s,o)=>{r.once("message",function(i){i.command==="worker-script-initialized"&&s({worker:r,phpPort:i.phpPort})}),r.once("error",function(i){console.error(i);const l=new Error(`Worker failed to load worker. ${i.message?`Original error: ${i.message}`:""}`);o(l)});let n=!1;r.once("spawn",()=>{n=!0}),r.once("exit",i=>{n||o(new Error(`Worker exited before spawning: ${i}`)),t?.(i)})})}async function de(e){const{port1:t,port2:r}=new Q.MessageChannel;return await X.jspi()?b.exposeAPI(e,null,t):await b.exposeSyncAPI(e,t),r}function ht(e){const t=A.platform();let r;switch(t){case"darwin":r=`open "${e}"`;break;case"win32":r=`start "" "${e}"`;break;default:r=`xdg-open "${e}"`;break}We.exec(r,s=>{s&&m.logger.debug(`Could not open browser: ${s.message}`)})}async function mt(e,t){await e.run({code:`<?php
47
47
  $zip = new ZipArchive();
48
48
  if(false === $zip->open('/tmp/build.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
49
49
  throw new Exception('Failed to create ZIP');
@@ -60,5 +60,5 @@ Examples:
60
60
  }
61
61
  $zip->close();
62
62
 
63
- `});const r=await e.readFileAsBuffer("/tmp/build.zip");d.writeFileSync(t,r)}exports.LogVerbosity=Z;exports.internalsKeyForTesting=fe;exports.mergeDefinedConstants=D;exports.mountResources=Ue;exports.parseOptionsAndRunCLI=lt;exports.runCLI=we;exports.shouldRenderProgress=me;exports.spawnWorkerThread=ge;
64
- //# sourceMappingURL=run-cli-le2xCSBZ.cjs.map
63
+ `});const r=await e.readFileAsBuffer("/tmp/build.zip");d.writeFileSync(t,r)}exports.LogVerbosity=J;exports.internalsKeyForTesting=we;exports.mergeDefinedConstants=D;exports.mountResources=Ue;exports.parseOptionsAndRunCLI=lt;exports.runCLI=ge;exports.shouldRenderProgress=fe;exports.spawnWorkerThread=ye;
64
+ //# sourceMappingURL=run-cli-BUDz-Yr3.cjs.map