diff --git a/docs/guide/logs-and-debugging.md b/docs/guide/logs-and-debugging.md index 4fd77c37..c9708981 100644 --- a/docs/guide/logs-and-debugging.md +++ b/docs/guide/logs-and-debugging.md @@ -18,7 +18,7 @@ All three panes read in the terminal's own monospace font, so the columns of a P ## The debug.log tab -Anything WordPress or your code writes to the PHP error log — `error_log()` calls, notices, warnings, deprecations, `_doing_it_wrong()`, fatals — appears here while the dev server runs. This works because every site is booted with WordPress's debug constants already set. Two of them, `WP_DEBUG` and `SCRIPT_DEBUG`, can be turned off under **Sites** in [Settings](./settings); the rest are not configurable: +Anything WordPress or your code writes to the PHP error log — `error_log()` calls, notices, warnings, deprecations, `_doing_it_wrong()`, fatals — appears here while the dev server runs. This works because every site is booted with WordPress's debug constants already set, along with the two that say what kind of environment it is. Two of them, `WP_DEBUG` and `SCRIPT_DEBUG`, can be turned off under **Sites** in [Settings](./settings); the rest are not configurable: | Constant | Value | Effect | | --- | --- | --- | @@ -28,6 +28,8 @@ Anything WordPress or your code writes to the PHP error log — `error_log()` ca | `SCRIPT_DEBUG` | `true` | Core serves unminified JS and CSS. Can be turned off in Settings. | | `WP_DISABLE_FATAL_ERROR_HANDLER` | `true` | A fatal shows the actual error instead of WordPress's "critical error" recovery screen. | | `AUTOMATIC_UPDATER_DISABLED` | `true` | Core's automatic updater does not run (and does not fill the log with its own messages). | +| `WP_ENVIRONMENT_TYPE` | `local` | The site runs as a local environment, as Core's own Docker environment does. Application Passwords are available, which on plain HTTP they are only for a `local` site. As on any site that is not `production`, pingbacks and trackbacks are off, and Site Health skips its caching and HTTPS tests. | +| `WP_DEVELOPMENT_MODE` | `core`, or `plugin` on a Gutenberg site | On a Core site, Core looks for its blocks' stylesheets on every page load instead of using a cached list, so a block stylesheet added in `build/` is picked up at once. On a Gutenberg site it changes nothing today; it is set because it describes the site. | Note that `WP_DEBUG_DISPLAY` has a known cost: a notice fired during a REST or AJAX request is printed into the response and can corrupt the JSON it expects. That trade is made deliberately — seeing the error beats a silent blank page for a newcomer. diff --git a/src/playground-plan.cjs b/src/playground-plan.cjs index 64a19aef..8ee98560 100644 --- a/src/playground-plan.cjs +++ b/src/playground-plan.cjs @@ -66,15 +66,45 @@ function planPlaygroundLaunch(config) { }; } -// The wp-config constants a strategy needs on top of the shared debug/SMTP set. +// The wp-config constants that depend on what the site is, on top of the shared +// debug/SMTP set. // -// Only 'plugin-mount' asks for any, and it asks for the two that make the -// mounted directory read-only from inside WordPress. The mount is a read-write -// NODEFS mount of the *source checkout*, not a regenerable build/, so -// Plugins → Delete on the mounted plugin, or the plugin file editor, writes -// straight through to the contributor's working tree, uncommitted work and .git -// included. Core's docroot strategy exposes only build/, which the app rebuilds, -// so it keeps WordPress's defaults. +// Every strategy gets the environment Core's own Docker environment sets up +// (#598): `.env.example` in wordpress-develop defaults WP_ENVIRONMENT_TYPE to +// `local` and WP_DEVELOPMENT_MODE to `core`, and the handbook documents that +// environment. Left undefined, the type falls back to `production`, and Core +// offers Application Passwords only over HTTPS or on a `local` site, so on the +// dev server's plain http://127.0.0.1 a ticket about them could not be tested. +// Any type but `production` also changes what a site does, and a contributor +// can meet it: pingbacks and trackbacks are off, in and out, and Site Health +// skips its page cache and object cache tests, drops the HTTPS test, and rates +// errors shown to visitors as recommended rather than critical. Core's Docker +// environment has all of that too, and a ticket about one of them needs the +// type changed on purpose: Core's own filters, from an mu-plugin in build/, do +// that per ticket better than a setting here would for every site. `local` itself also lets the screen that authorizes an +// application accept a plain-HTTP redirect URL, and would turn WP_DEBUG on by +// default, which the debug set decides anyway. Both values are strings, +// which is what Core compares them with, so they live here rather than in +// wp-debug-constants.js, whose values are all booleans; and the development +// mode follows from the strategy. +// +// The development mode is what the site is: a wordpress-develop checkout is +// Core development, and a Gutenberg checkout mounted into a stock WordPress is +// plugin development. `core` makes Core list its own blocks' stylesheets on +// every load instead of from a cached list kept until the version changes, so a +// block stylesheet added to or removed from build/ is picked up at once. +// `plugin` changes nothing in Core or Gutenberg today; it is set because it is +// the honest answer, and `core` would describe a Core checkout the site does +// not have. Neither value is a setting: there is nothing to choose that the +// site type does not already answer. +// +// 'plugin-mount' also asks for the two constants that make the mounted +// directory read-only from inside WordPress. The mount is a read-write NODEFS +// mount of the *source checkout*, not a regenerable build/, so Plugins → Delete +// on the mounted plugin, or the plugin file editor, writes straight through to +// the contributor's working tree, uncommitted work and .git included. Core's +// docroot strategy exposes only build/, which the app rebuilds, so it keeps +// WordPress's defaults. // // The cost is real and deliberate: DISALLOW_FILE_MODS also blocks installing a // second plugin or theme into the preview. Losing an afternoon of uncommitted @@ -82,9 +112,14 @@ function planPlaygroundLaunch(config) { function planServeConstants(config) { const cfg = config || {}; if (cfg.strategy === 'plugin-mount') { - return { DISALLOW_FILE_MODS: true, DISALLOW_FILE_EDIT: true }; + return { + WP_ENVIRONMENT_TYPE: 'local', + WP_DEVELOPMENT_MODE: 'plugin', + DISALLOW_FILE_MODS: true, + DISALLOW_FILE_EDIT: true + }; } - return {}; + return { WP_ENVIRONMENT_TYPE: 'local', WP_DEVELOPMENT_MODE: 'core' }; } module.exports = { planPlaygroundLaunch, planServeConstants, WORDPRESS_VFS_ROOT, PLUGINS_VFS_BASE }; diff --git a/src/server-runner.js b/src/server-runner.js index 52d8261a..e9c7096e 100644 --- a/src/server-runner.js +++ b/src/server-runner.js @@ -88,8 +88,10 @@ async function main() { 'WP_MAIL_SMTP_SECURE': process.env.WP_MAIL_SMTP_SECURE || '', // '', 'ssl', or 'tls' 'WP_MAIL_SMTP_USER': process.env.WP_MAIL_SMTP_USER || '', 'WP_MAIL_SMTP_PASS': process.env.WP_MAIL_SMTP_PASS || '', - // Last, so a strategy that has to protect the host directory it - // mounted is not overridden by the shared sets above. + // Last: the environment type and development mode every site + // gets, and, for a strategy that has to protect the host + // directory it mounted, the constants that do, which the shared + // sets above must not override. ...serveConstants } } diff --git a/tests/unit/playground-plan.test.cjs b/tests/unit/playground-plan.test.cjs index 2108b11a..590f555e 100644 --- a/tests/unit/playground-plan.test.cjs +++ b/tests/unit/playground-plan.test.cjs @@ -12,7 +12,7 @@ const test = require('node:test'); const assert = require('node:assert/strict'); -const { planPlaygroundLaunch, PLUGINS_VFS_BASE } = require('../../src/playground-plan.cjs'); +const { planPlaygroundLaunch, planServeConstants, PLUGINS_VFS_BASE } = require('../../src/playground-plan.cjs'); const { getProjectType } = require('../../src/project-type.cjs'); test('docroot strategy mounts the build dir as WordPress and skips the download', () => { @@ -73,3 +73,27 @@ test('a plugin-mount slug that is not a plain name is refused before it becomes assert.throws(() => planPlaygroundLaunch({ strategy: 'plugin-mount', pluginDir: '/sites/gb', pluginSlug: bad }), /plain slug/, `expected a refusal for ${JSON.stringify(bad)}`); } }); + +// The environment Core's own Docker environment sets up (#598). Undefined, the +// type falls back to `production`, and Core offers Application Passwords only +// over HTTPS or on a `local` site: on the dev server's plain HTTP the feature +// was gone, and a ticket about it could not be tested. +test('a Core site runs as a local environment in core development mode', () => { + assert.deepEqual(planServeConstants({ strategy: 'docroot', docroot: '/sites/wp/build' }), { + WP_ENVIRONMENT_TYPE: 'local', + WP_DEVELOPMENT_MODE: 'core' + }); + // The default strategy is docroot, so it gets the same. + assert.deepEqual(planServeConstants({}), planServeConstants({ strategy: 'docroot' })); +}); + +// A Gutenberg checkout is a plugin mounted into a stock WordPress: plugin +// development, not Core. The file locks stay beside the environment. +test('a Gutenberg site runs as a local environment in plugin development mode, with file changes locked', () => { + assert.deepEqual(planServeConstants({ strategy: 'plugin-mount', pluginDir: '/sites/gutenberg', pluginSlug: 'gutenberg' }), { + WP_ENVIRONMENT_TYPE: 'local', + WP_DEVELOPMENT_MODE: 'plugin', + DISALLOW_FILE_MODS: true, + DISALLOW_FILE_EDIT: true + }); +}); diff --git a/tests/unit/runner-wiring.test.cjs b/tests/unit/runner-wiring.test.cjs index a3c12a45..e5c8a763 100644 --- a/tests/unit/runner-wiring.test.cjs +++ b/tests/unit/runner-wiring.test.cjs @@ -217,6 +217,10 @@ test('a docroot config reaches runCLI as the options a Core site always got', () assert.equal('additional-blueprint-steps' in cliOptions, false); assert.equal(cliOptions.wordpressInstallMode, 'install-from-existing-files-if-needed'); assert.equal(cliOptions.blueprint.constants.DISALLOW_FILE_MODS, undefined, 'a Core docroot keeps WordPress\'s file defaults'); + // The environment Core's Docker environment has (#598); without `local`, + // Application Passwords are gone on the dev server's plain HTTP. + assert.equal(cliOptions.blueprint.constants.WP_ENVIRONMENT_TYPE, 'local'); + assert.equal(cliOptions.blueprint.constants.WP_DEVELOPMENT_MODE, 'core'); }); test('a plugin-mount config mounts the checkout as a plugin into a stock install, and locks file modifications', () => { @@ -232,6 +236,8 @@ test('a plugin-mount config mounts the checkout as a plugin into a stock install // Plugins > Delete in the served site removes it. assert.equal(cliOptions.blueprint.constants.DISALLOW_FILE_MODS, true); assert.equal(cliOptions.blueprint.constants.DISALLOW_FILE_EDIT, true); + assert.equal(cliOptions.blueprint.constants.WP_ENVIRONMENT_TYPE, 'local'); + assert.equal(cliOptions.blueprint.constants.WP_DEVELOPMENT_MODE, 'plugin'); // And they are added to the shared constants, not in place of them. assert.equal(cliOptions.blueprint.constants.WP_MAIL_SMTP_HOST, '127.0.0.1'); });