From 2f07699338292932e0ade27e0742b7383bb2d29b Mon Sep 17 00:00:00 2001 From: bcotrim Date: Wed, 17 Dec 2025 20:09:50 +0000 Subject: [PATCH 1/8] remove php-wasm from Studio --- .gitignore | 3 - common/constants.ts | 5 +- common/lib/cli-args-sanitizer.ts | 6 +- common/types/blueprint.ts | 128 +++ common/types/php-versions.ts | 29 + e2e/sites.test.ts | 5 +- electron.vite.config.ts | 18 +- forge.config.ts | 4 + jest.config.ts | 3 - src/custom-package-definitions.d.ts | 4 - src/hooks/tests/use-add-site.test.tsx | 4 +- src/hooks/use-add-site.ts | 2 +- src/index.ts | 2 - src/ipc-handlers.ts | 25 +- .../download.ts => src/lib/download-utils.ts | 220 +++-- .../import/importers/importer.ts | 12 +- src/lib/server-files-paths.ts | 68 ++ src/lib/sqlite-command-path.ts | 27 - src/lib/sqlite-command-versions.ts | 2 +- src/lib/sqlite-versions.ts | 4 +- src/lib/wordpress-provider/constants.ts | 7 - src/lib/wordpress-provider/index.ts | 86 -- .../playground-cli/index.ts | 2 - .../playground-cli/playground-cli-provider.ts | 280 ------- .../playground-server-process-child.ts | 359 -------- .../playground-server-process.ts | 245 ------ src/lib/wordpress-provider/types.ts | 85 -- src/lib/wordpress-provider/wp-now/index.ts | 2 - .../wp-now/path-utilities.ts | 3 - .../wp-now/site-server-process-child.ts | 106 --- .../wp-now/site-server-process.ts | 163 ---- .../wp-now/wp-now-provider.ts | 350 -------- src/lib/wordpress-server-types.ts | 20 + src/lib/wordpress-setup.ts | 22 + src/lib/wp-versions.ts | 16 +- .../add-site/components/create-site-form.tsx | 2 +- src/modules/cli/lib/cli-server-process.ts | 2 +- src/modules/cli/lib/cli-site-creator.ts | 2 +- .../lib/tests/version-comparison.test.ts | 2 +- .../preview-site/lib/version-comparison.ts | 2 +- .../site-settings/edit-site-details.tsx | 2 +- src/setup-wp-server-files.ts | 67 +- src/site-server.ts | 20 +- src/storage/user-data.ts | 2 +- src/tests/index.test.ts | 1 - src/tests/ipc-handlers.test.ts | 12 +- src/tests/site-server.test.ts | 18 +- vendor/wp-now/.eslintrc.json | 18 - vendor/wp-now/README.md | 279 ------- vendor/wp-now/esbuild.mjs | 13 - vendor/wp-now/package.json | 27 - vendor/wp-now/project.json | 72 -- vendor/wp-now/public/cli.js | 3 - vendor/wp-now/public/with-node-version.js | 54 -- vendor/wp-now/src/add-trailing-slash.ts | 17 - vendor/wp-now/src/assets/.gitkeep | 0 vendor/wp-now/src/config.ts | 202 ----- vendor/wp-now/src/constants.ts | 55 -- vendor/wp-now/src/execute-php.ts | 41 - vendor/wp-now/src/execute-wp-cli.ts | 144 ---- vendor/wp-now/src/get-sqlite-path.ts | 10 - .../wp-now/src/get-wordpress-versions-path.ts | 9 - vendor/wp-now/src/get-wp-cli-path.ts | 20 - vendor/wp-now/src/get-wp-cli-tmp-path.ts | 11 - vendor/wp-now/src/get-wp-now-path.ts | 10 - vendor/wp-now/src/get-wp-now-tmp-path.ts | 11 - vendor/wp-now/src/github-codespaces.ts | 14 - vendor/wp-now/src/index.ts | 6 - vendor/wp-now/src/main.ts | 13 - vendor/wp-now/src/output.ts | 13 - vendor/wp-now/src/port-finder.ts | 59 -- vendor/wp-now/src/run-cli.ts | 178 ---- vendor/wp-now/src/start-server.ts | 120 --- .../src/tests/add-trailing-slash.spec.ts | 34 - .../src/tests/blueprints/wp-config.json | 12 - .../wp-now/src/tests/blueprints/wp-debug.json | 11 - vendor/wp-now/src/tests/execute-php.spec.ts | 73 -- .../src/tests/execute-php/hello-world.php | 2 - vendor/wp-now/src/tests/execute-php/index.php | 2 - .../src/tests/execute-php/php-version.php | 2 - .../src/tests/github-codespaces.spec.ts | 32 - .../child/style.css | 10 - .../mode-examples/child-theme/child/style.css | 10 - .../child-theme/parent/style.css | 9 - .../src/tests/mode-examples/index/index.php | 3 - .../tests/mode-examples/not-plugin/foo.php | 3 - .../tests/mode-examples/not-theme/style.css | 0 .../build/wp-content/.gitkeep | 0 .../build/wp-includes/.gitkeep | 0 .../src/wp-content/.gitkeep | 0 .../src/wp-includes/.gitkeep | 0 .../not-wordpress-develop/src/wp-load.php | 0 .../src/tests/mode-examples/nothing/.gitkeep | 0 .../mode-examples/playground/my-file.txt | 0 .../plugin-case-insensitive/sample-plugin.php | 14 - .../mode-examples/plugin/sample-plugin.php | 14 - .../mode-examples/theme-with-assets/image.jpg | Bin 6604 -> 0 bytes .../mode-examples/theme-with-assets/image.png | Bin 3617 -> 0 bytes .../theme-with-assets/javascript.js | 61 -- .../mode-examples/theme-with-assets/style.css | 67 -- .../src/tests/mode-examples/theme/style.css | 22 - .../build/wp-content/.gitkeep | 0 .../build/wp-includes/.gitkeep | 0 .../wordpress-develop/build/wp-load.php | 1 - .../wordpress-develop/src/wp-content/.gitkeep | 0 .../src/wp-includes/.gitkeep | 0 .../wordpress-develop/src/wp-load.php | 1 - .../wordpress-with-config/index.php | 0 .../wordpress-with-config/wp-config.php | 0 .../wordpress-with-config/wp-content/.gitkeep | 0 .../wp-includes/.gitkeep | 0 .../wordpress-with-config/wp-load.php | 3 - .../wordpress-with-config/wp-login.php | 5 - .../tests/mode-examples/wordpress/index.php | 0 .../wordpress/wp-config-sample.php | 0 .../wordpress/wp-content/.gitkeep | 0 .../wordpress/wp-includes/.gitkeep | 0 .../tests/mode-examples/wordpress/wp-load.php | 3 - .../themes/.gitkeep | 0 .../wp-content-only-themes/themes/.gitkeep | 0 .../mode-examples/wp-content/plugins/.gitkeep | 0 .../mode-examples/wp-content/themes/.gitkeep | 0 vendor/wp-now/src/tests/wp-now.spec.ts | 785 ------------------ vendor/wp-now/src/wp-now.ts | 734 ---------------- .../get-plugin-file.ts | 38 - .../wp-playground-wordpress/has-index-file.ts | 12 - .../src/wp-playground-wordpress/index.ts | 9 - .../is-plugin-directory.ts | 12 - .../is-theme-directory.ts | 19 - .../is-valid-wordpress-version.ts | 19 - .../is-wordpress-develop-directory.ts | 23 - .../is-wordpress-directory.ts | 16 - .../is-wp-content-directory.ts | 18 - .../wp-playground-wordpress/read-file-head.ts | 17 - .../tests/get-plugin-file.spec.ts | 20 - .../tests/is-valid-wordpress-version.test.ts | 17 - vendor/wp-now/tsconfig.json | 17 - vendor/wp-now/tsconfig.lib.json | 9 - vendor/wp-now/tsconfig.spec.json | 7 - vendor/wp-now/vite.config.ts | 30 - 140 files changed, 535 insertions(+), 5509 deletions(-) create mode 100644 common/types/blueprint.ts create mode 100644 common/types/php-versions.ts rename vendor/wp-now/src/download.ts => src/lib/download-utils.ts (63%) create mode 100644 src/lib/server-files-paths.ts delete mode 100644 src/lib/sqlite-command-path.ts delete mode 100644 src/lib/wordpress-provider/constants.ts delete mode 100644 src/lib/wordpress-provider/index.ts delete mode 100644 src/lib/wordpress-provider/playground-cli/index.ts delete mode 100644 src/lib/wordpress-provider/playground-cli/playground-cli-provider.ts delete mode 100644 src/lib/wordpress-provider/playground-cli/playground-server-process-child.ts delete mode 100644 src/lib/wordpress-provider/playground-cli/playground-server-process.ts delete mode 100644 src/lib/wordpress-provider/types.ts delete mode 100644 src/lib/wordpress-provider/wp-now/index.ts delete mode 100644 src/lib/wordpress-provider/wp-now/path-utilities.ts delete mode 100644 src/lib/wordpress-provider/wp-now/site-server-process-child.ts delete mode 100644 src/lib/wordpress-provider/wp-now/site-server-process.ts delete mode 100644 src/lib/wordpress-provider/wp-now/wp-now-provider.ts create mode 100644 src/lib/wordpress-server-types.ts create mode 100644 src/lib/wordpress-setup.ts delete mode 100644 vendor/wp-now/.eslintrc.json delete mode 100644 vendor/wp-now/README.md delete mode 100644 vendor/wp-now/esbuild.mjs delete mode 100644 vendor/wp-now/package.json delete mode 100644 vendor/wp-now/project.json delete mode 100644 vendor/wp-now/public/cli.js delete mode 100644 vendor/wp-now/public/with-node-version.js delete mode 100644 vendor/wp-now/src/add-trailing-slash.ts delete mode 100644 vendor/wp-now/src/assets/.gitkeep delete mode 100644 vendor/wp-now/src/config.ts delete mode 100644 vendor/wp-now/src/constants.ts delete mode 100644 vendor/wp-now/src/execute-php.ts delete mode 100644 vendor/wp-now/src/execute-wp-cli.ts delete mode 100644 vendor/wp-now/src/get-sqlite-path.ts delete mode 100644 vendor/wp-now/src/get-wordpress-versions-path.ts delete mode 100644 vendor/wp-now/src/get-wp-cli-path.ts delete mode 100644 vendor/wp-now/src/get-wp-cli-tmp-path.ts delete mode 100644 vendor/wp-now/src/get-wp-now-path.ts delete mode 100644 vendor/wp-now/src/get-wp-now-tmp-path.ts delete mode 100644 vendor/wp-now/src/github-codespaces.ts delete mode 100644 vendor/wp-now/src/index.ts delete mode 100644 vendor/wp-now/src/main.ts delete mode 100644 vendor/wp-now/src/output.ts delete mode 100644 vendor/wp-now/src/port-finder.ts delete mode 100644 vendor/wp-now/src/run-cli.ts delete mode 100644 vendor/wp-now/src/start-server.ts delete mode 100644 vendor/wp-now/src/tests/add-trailing-slash.spec.ts delete mode 100644 vendor/wp-now/src/tests/blueprints/wp-config.json delete mode 100644 vendor/wp-now/src/tests/blueprints/wp-debug.json delete mode 100644 vendor/wp-now/src/tests/execute-php.spec.ts delete mode 100644 vendor/wp-now/src/tests/execute-php/hello-world.php delete mode 100644 vendor/wp-now/src/tests/execute-php/index.php delete mode 100644 vendor/wp-now/src/tests/execute-php/php-version.php delete mode 100644 vendor/wp-now/src/tests/github-codespaces.spec.ts delete mode 100644 vendor/wp-now/src/tests/mode-examples/child-theme-missing-parent/child/style.css delete mode 100644 vendor/wp-now/src/tests/mode-examples/child-theme/child/style.css delete mode 100644 vendor/wp-now/src/tests/mode-examples/child-theme/parent/style.css delete mode 100644 vendor/wp-now/src/tests/mode-examples/index/index.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/not-plugin/foo.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/not-theme/style.css delete mode 100644 vendor/wp-now/src/tests/mode-examples/not-wordpress-develop/build/wp-content/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/not-wordpress-develop/build/wp-includes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/not-wordpress-develop/src/wp-content/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/not-wordpress-develop/src/wp-includes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/not-wordpress-develop/src/wp-load.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/nothing/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/playground/my-file.txt delete mode 100644 vendor/wp-now/src/tests/mode-examples/plugin-case-insensitive/sample-plugin.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/plugin/sample-plugin.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/theme-with-assets/image.jpg delete mode 100644 vendor/wp-now/src/tests/mode-examples/theme-with-assets/image.png delete mode 100644 vendor/wp-now/src/tests/mode-examples/theme-with-assets/javascript.js delete mode 100644 vendor/wp-now/src/tests/mode-examples/theme-with-assets/style.css delete mode 100644 vendor/wp-now/src/tests/mode-examples/theme/style.css delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-content/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-includes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-load.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-develop/src/wp-content/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-develop/src/wp-includes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-develop/src/wp-load.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-with-config/index.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-with-config/wp-config.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-with-config/wp-content/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-with-config/wp-includes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-with-config/wp-load.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress-with-config/wp-login.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress/index.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress/wp-config-sample.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress/wp-content/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress/wp-includes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wordpress/wp-load.php delete mode 100644 vendor/wp-now/src/tests/mode-examples/wp-content-only-mu-plugins/themes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wp-content-only-themes/themes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wp-content/plugins/.gitkeep delete mode 100644 vendor/wp-now/src/tests/mode-examples/wp-content/themes/.gitkeep delete mode 100644 vendor/wp-now/src/tests/wp-now.spec.ts delete mode 100644 vendor/wp-now/src/wp-now.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/get-plugin-file.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/has-index-file.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/index.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/is-plugin-directory.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/is-theme-directory.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/is-valid-wordpress-version.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/is-wordpress-develop-directory.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/is-wordpress-directory.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/is-wp-content-directory.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/read-file-head.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/tests/get-plugin-file.spec.ts delete mode 100644 vendor/wp-now/src/wp-playground-wordpress/tests/is-valid-wordpress-version.test.ts delete mode 100644 vendor/wp-now/tsconfig.json delete mode 100644 vendor/wp-now/tsconfig.lib.json delete mode 100644 vendor/wp-now/tsconfig.spec.json delete mode 100644 vendor/wp-now/vite.config.ts diff --git a/.gitignore b/.gitignore index b088b6d4bd..4fb7537f15 100644 --- a/.gitignore +++ b/.gitignore @@ -102,9 +102,6 @@ vendor/* /fastlane/report.xml /fastlane/README.md -# Include the vendored copy of wp-now in our repo -!vendor/wp-now - # CLI npm artifacts cli/vendor/ diff --git a/common/constants.ts b/common/constants.ts index 09bc2f79e9..515e708740 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -1,4 +1,4 @@ -import { RecommendedPHPVersion } from '@wp-playground/common'; +import { RecommendedPHPVersion } from './types/php-versions'; // Time constants export const HOUR_MS = 1000 * 60 * 60; @@ -27,7 +27,8 @@ export const PLAYGROUND_CLI_ACTIVITY_CHECK_INTERVAL = 5 * 1000; // Check for ina // Custom domains export const DEFAULT_CUSTOM_DOMAIN_SUFFIX = '.wp.local'; -// WordPress Playground constants +// WordPress constants export const MINIMUM_WORDPRESS_VERSION = '6.2.1' as const; // https://wordpress.github.io/wordpress-playground/blueprints/examples/#load-an-older-wordpress-version export const DEFAULT_WORDPRESS_VERSION = 'latest' as const; export const DEFAULT_PHP_VERSION: typeof RecommendedPHPVersion = RecommendedPHPVersion; +export const SQLITE_FILENAME = 'sqlite-database-integration' as const; diff --git a/common/lib/cli-args-sanitizer.ts b/common/lib/cli-args-sanitizer.ts index 9559a75309..9bff0261a3 100644 --- a/common/lib/cli-args-sanitizer.ts +++ b/common/lib/cli-args-sanitizer.ts @@ -1,5 +1,7 @@ -import { Blueprint } from '@wp-playground/blueprints'; -import { RunCLIArgs } from '@wp-playground/cli'; +import type { Blueprint } from 'common/types/blueprint'; +// RunCLIArgs is only used in child processes (CLI and desktop child process) +// Keep this import for now - it will be used by CLI which retains PHP-WASM dependencies +import type { RunCLIArgs } from '@wp-playground/cli'; /** * Sanitizes a Blueprint step to remove sensitive data while keeping useful debugging info. diff --git a/common/types/blueprint.ts b/common/types/blueprint.ts new file mode 100644 index 0000000000..5661950bc0 --- /dev/null +++ b/common/types/blueprint.ts @@ -0,0 +1,128 @@ +/** + * Local type definitions for WordPress Playground blueprints. + * These replace imports from @wp-playground/blueprints to avoid bundling PHP-WASM in the desktop app. + * + * Note: These are simplified versions of the upstream types, containing only the fields + * actually used by Studio. Keep them in sync with @wp-playground/blueprints if needed. + */ + +import type { SupportedPHPVersion } from './php-versions'; + +/** + * Extra libraries that can be preloaded into the Playground instance. + */ +export type ExtraLibrary = 'wp-cli'; + +/** + * PHP Constants to define on every request. + */ +export type PHPConstants = Record< string, string | boolean | number >; + +/** + * Reference to a file resource. + * Simplified version - the full type supports various resource types. + */ +export type FileReference = + | { resource: 'url'; url: string } + | { resource: 'vfs'; path: string } + | { resource: 'literal'; name: string; contents: string | Uint8Array } + | { resource: 'wordpress.org/themes'; slug: string } + | { resource: 'wordpress.org/plugins'; slug: string }; + +/** + * Step definition for a blueprint. + * This is a simplified union of the most commonly used step types in Studio. + */ +export type StepDefinition = { + step: string; + progress?: { + weight?: number; + caption?: string; + }; +} & Record< string, unknown >; + +/** + * Blueprint V1 declaration. + * Simplified version containing only fields used by Studio. + */ +export type BlueprintV1Declaration = { + /** + * The URL to navigate to after the blueprint has been run. + */ + landingPage?: string; + + /** + * Optional description. + * @deprecated Use meta.description instead. + */ + description?: string; + + /** + * Optional metadata. + */ + meta?: { + title: string; + description?: string; + author: string; + categories?: string[]; + }; + + /** + * The preferred PHP and WordPress versions to use. + */ + preferredVersions?: { + php: SupportedPHPVersion | 'latest'; + wp: string | 'latest'; + }; + + /** + * Feature flags. + */ + features?: { + intl?: boolean; + networking?: boolean; + }; + + /** + * Extra libraries to preload. + */ + extraLibraries?: ExtraLibrary[]; + + /** + * PHP Constants to define on every request. + */ + constants?: PHPConstants; + + /** + * WordPress plugins to install and activate. + */ + plugins?: Array< string | FileReference >; + + /** + * WordPress site options to define. + */ + siteOptions?: Record< string, string > & { + blogname?: string; + }; + + /** + * User to log in as. + */ + login?: + | boolean + | { + username: string; + password: string; + }; + + /** + * The steps to run after every other operation in this Blueprint. + */ + steps?: Array< StepDefinition | string | undefined | false | null >; +}; + +/** + * Blueprint type alias - can be a declaration or a bundle. + * In Studio, we primarily use the declaration form. + */ +export type Blueprint = BlueprintV1Declaration; diff --git a/common/types/php-versions.ts b/common/types/php-versions.ts new file mode 100644 index 0000000000..3f9f968d62 --- /dev/null +++ b/common/types/php-versions.ts @@ -0,0 +1,29 @@ +/** + * Local type definitions for PHP versions. + * These replace imports from @php-wasm/universal to avoid bundling PHP-WASM in the desktop app. + * + * Note: Keep these values in sync with @php-wasm/universal if the upstream package changes. + */ + +export const SupportedPHPVersions = [ + '8.4', + '8.3', + '8.2', + '8.1', + '8.0', + '7.4', + '7.3', + '7.2', +] as const; + +export const LatestSupportedPHPVersion = '8.4' as const; + +export const SupportedPHPVersionsList: string[] = [ ...SupportedPHPVersions ]; + +export type SupportedPHPVersion = ( typeof SupportedPHPVersions )[ number ]; + +/** + * The recommended PHP version for new sites. + * This replaces RecommendedPHPVersion from @wp-playground/common. + */ +export const RecommendedPHPVersion: SupportedPHPVersion = '8.3'; diff --git a/e2e/sites.test.ts b/e2e/sites.test.ts index 1c3e973704..c7e8f781ea 100644 --- a/e2e/sites.test.ts +++ b/e2e/sites.test.ts @@ -2,7 +2,10 @@ import path from 'path'; import { test, expect } from '@playwright/test'; import fs from 'fs-extra'; import { pathExists } from '../common/lib/fs-utils'; -import { DEFAULT_PHP_VERSION, ALLOWED_PHP_VERSIONS } from '../vendor/wp-now/src/constants'; +import { + RecommendedPHPVersion as DEFAULT_PHP_VERSION, + SupportedPHPVersions as ALLOWED_PHP_VERSIONS, +} from '../common/types/php-versions'; import { DEFAULT_SITE_NAME } from './constants'; import { E2ESession } from './e2e-helpers'; import MainSidebar from './page-objects/main-sidebar'; diff --git a/electron.vite.config.ts b/electron.vite.config.ts index e4b541ecee..ba47eac2d7 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -47,25 +47,13 @@ export default defineConfig( { rollupOptions: { input: { index: resolve( __dirname, 'src/index.ts' ), - siteServerProcess: resolve( - __dirname, - 'src/lib/wordpress-provider/wp-now/site-server-process-child.ts' - ), - playgroundServerProcess: resolve( - __dirname, - 'src/lib/wordpress-provider/playground-cli/playground-server-process-child.ts' - ), + // Child process entry points removed - desktop now uses CLI for site operations + // See STU-960: Remove PHP-WASM dependencies from Studio }, output: { entryFileNames: '[name].js', }, - external: [ - '@php-wasm/node', - '@php-wasm/web', - '@php-wasm/logger', - '@php-wasm/universal', - '@php-wasm/scopes', - ], + // PHP-WASM externals removed - no longer needed in desktop build }, }, }, diff --git a/forge.config.ts b/forge.config.ts index c1238a4aff..951af9653e 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -54,6 +54,10 @@ const config: ForgeConfig = { /^\/wp-files/, /^\/dist\/cli/, /^\/dist\/playground-cli/, + // PHP-WASM packages - these are only needed in CLI, not in desktop app + // See STU-960: Remove PHP-WASM dependencies from Studio + /^\/node_modules\/@php-wasm/, + /^\/node_modules\/@wp-playground/, ], }, rebuildConfig: {}, diff --git a/jest.config.ts b/jest.config.ts index b6003e600d..aa1f9ef6ef 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -6,9 +6,6 @@ module.exports = { '^.+\\.(ts|tsx)$': [ 'ts-jest', { - diagnostics: { - exclude: [ '**/vendor/wp-now/**/*' ], - }, useESM: true, tsconfig: { module: 'esnext', diff --git a/src/custom-package-definitions.d.ts b/src/custom-package-definitions.d.ts index f94487acd6..358c394f8e 100644 --- a/src/custom-package-definitions.d.ts +++ b/src/custom-package-definitions.d.ts @@ -39,7 +39,3 @@ declare module '*.wasm' { } declare module 'wpcom-xhr-request'; - -// TODO: Remove this once https://github.com/WordPress/wordpress-playground/pull/3035 has landed -// and a new `@wp-playground/storage` has been published to npm -declare module '@wp-playground/storage'; diff --git a/src/hooks/tests/use-add-site.test.tsx b/src/hooks/tests/use-add-site.test.tsx index 7f3c942aa4..e595efb50b 100644 --- a/src/hooks/tests/use-add-site.test.tsx +++ b/src/hooks/tests/use-add-site.test.tsx @@ -5,8 +5,8 @@ import { Provider } from 'react-redux'; import { useSyncSites } from 'src/hooks/sync-sites'; import { useAddSite } from 'src/hooks/use-add-site'; import { useContentTabs } from 'src/hooks/use-content-tabs'; +import { DEFAULT_WORDPRESS_VERSION } from 'common/constants'; import { useSiteDetails } from 'src/hooks/use-site-details'; -import { getWordPressProvider } from 'src/lib/wordpress-provider'; import { store } from 'src/stores'; import { setProviderConstants } from 'src/stores/provider-constants-slice'; import type { SyncSite } from 'src/modules/sync/types'; @@ -129,7 +129,7 @@ describe( 'useAddSite', () => { it( 'should initialize with default WordPress version', () => { const { result } = renderHookWithProvider( () => useAddSite() ); - expect( result.current.wpVersion ).toBe( getWordPressProvider().DEFAULT_WORDPRESS_VERSION ); + expect( result.current.wpVersion ).toBe( DEFAULT_WORDPRESS_VERSION ); } ); it( 'should initialize with default PHP version', () => { diff --git a/src/hooks/use-add-site.ts b/src/hooks/use-add-site.ts index 53577ad31d..501448f5b3 100644 --- a/src/hooks/use-add-site.ts +++ b/src/hooks/use-add-site.ts @@ -8,7 +8,7 @@ import { useContentTabs } from 'src/hooks/use-content-tabs'; import { useImportExport } from 'src/hooks/use-import-export'; import { useSiteDetails } from 'src/hooks/use-site-details'; import { getIpcApi } from 'src/lib/get-ipc-api'; -import { AllowedPHPVersion } from 'src/lib/wordpress-provider/constants'; +import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; import { useBlueprintDeeplink } from 'src/modules/add-site/hooks/use-blueprint-deeplink'; import { useRootSelector } from 'src/stores'; import { diff --git a/src/index.ts b/src/index.ts index 09892facd7..2ff3ed1048 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,6 @@ import { handleDeeplink } from 'src/lib/deeplink'; import { getUserLocaleWithFallback } from 'src/lib/locale-node'; import { getSentryReleaseInfo } from 'src/lib/sentry-release'; import { startUserDataWatcher, stopUserDataWatcher } from 'src/lib/user-data-watcher'; -import { getWordPressProvider } from 'src/lib/wordpress-provider'; import { setupLogging } from 'src/logging'; import { createMainWindow, getMainWindow } from 'src/main-window'; import { @@ -337,7 +336,6 @@ async function appBoot() { ); await updateWindowsCliVersionedPathIfNeeded(); - getWordPressProvider(); finishedInitialization = true; } ); diff --git a/src/ipc-handlers.ts b/src/ipc-handlers.ts index be4dac8c61..b0f1e9bfdd 100644 --- a/src/ipc-handlers.ts +++ b/src/ipc-handlers.ts @@ -56,10 +56,7 @@ import * as oauthClient from 'src/lib/oauth'; import { shellOpenExternalWrapper } from 'src/lib/shell-open-external-wrapper'; import { keepSqliteIntegrationUpdated } from 'src/lib/sqlite-versions'; import * as windowsHelpers from 'src/lib/windows-helpers'; -import { - getWordPressProvider, - getProviderConstants as getProviderConstantsFromProvider, -} from 'src/lib/wordpress-provider'; +import { setupWordPressFilesOnly } from 'src/lib/wordpress-setup'; import { getLogsFilePath, writeLogToFile, type LogLevel } from 'src/logging'; import { getMainWindow } from 'src/main-window'; import { popupMenu, setupMenu } from 'src/menu'; @@ -204,7 +201,7 @@ export async function importSite( } try { if ( ! isWordPressDirectory( site.details.path ) ) { - await getWordPressProvider().setupWordPressFilesOnly( site.details.path ); + await setupWordPressFilesOnly( site.details.path ); } const onEvent = ( data: ImportExportEventData ) => { @@ -378,7 +375,7 @@ export async function startServer( Sentry.captureException( error, { tags: { - provider: getWordPressProvider().PROVIDER_TYPE, + provider: 'cli', }, contexts, } ); @@ -1330,8 +1327,20 @@ export async function listLocalFileTree( } export async function getProviderConstants( _event: IpcMainInvokeEvent ) { - const provider = getWordPressProvider(); - return getProviderConstantsFromProvider( provider ); + // Import directly to avoid circular dependencies at module load time + const { + DEFAULT_PHP_VERSION, + DEFAULT_WORDPRESS_VERSION, + MINIMUM_WORDPRESS_VERSION, + } = await import( 'common/constants' ); + const { SupportedPHPVersions } = await import( 'common/types/php-versions' ); + + return { + defaultPhpVersion: DEFAULT_PHP_VERSION, + defaultWordPressVersion: DEFAULT_WORDPRESS_VERSION, + allowedPhpVersions: [ ...SupportedPHPVersions ], + minimumWordPressVersion: MINIMUM_WORDPRESS_VERSION, + }; } export async function validateBlueprint( diff --git a/vendor/wp-now/src/download.ts b/src/lib/download-utils.ts similarity index 63% rename from vendor/wp-now/src/download.ts rename to src/lib/download-utils.ts index 2bf52b44e4..b9f314d618 100644 --- a/vendor/wp-now/src/download.ts +++ b/src/lib/download-utils.ts @@ -1,3 +1,10 @@ +/** + * Download utilities for WordPress, WP-CLI, and SQLite command + * + * These replace the functions from vendor/wp-now to remove that dependency. + * See STU-960: Remove PHP-WASM dependencies from Studio + */ + import { IncomingMessage } from 'http'; import os from 'os'; import path from 'path'; @@ -5,10 +12,22 @@ import followRedirects, { FollowResponse } from 'follow-redirects'; import fs from 'fs-extra'; import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; import unzipper from 'unzipper'; -import { DEFAULT_WORDPRESS_VERSION, WP_CLI_URL } from './constants'; -import getWordpressVersionsPath from './get-wordpress-versions-path'; -import getWpCliPath from './get-wp-cli-path'; -import { output } from './output'; +import { getWordPressVersionPath, getWpCliPath } from './server-files-paths'; + +const { https } = followRedirects; + +// Constants +const DEFAULT_WORDPRESS_VERSION = 'latest'; +const WP_CLI_URL = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar'; + +interface DownloadResult { + downloaded: boolean; + statusCode: number; +} + +/** + * Make an HTTPS GET request with proxy support + */ function httpsGet( url: string, callback: ( res: IncomingMessage & FollowResponse ) => void ) { const proxy = process.env.https_proxy || @@ -27,26 +46,25 @@ function httpsGet( url: string, callback: ( res: IncomingMessage & FollowRespons https.get( url, { agent }, callback ); } -interface DownloadFileAndUnzipResult { - downloaded: boolean; - statusCode: number; -} - -const { https } = followRedirects; - -// Local copy of WordPress version utilities to avoid Studio dependencies +/** + * Check if a WordPress version is a dev/nightly build + */ function isWordPressDevVersion( version: string ): boolean { - // Match nightly build patterns that end with a build number - // Examples: 6.8-alpha1-12345, 6.8-beta2-59979, 6.8-dev-12345, 6.8-59979 return /^\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9]+)*-\d+$/.test( version ); } +/** + * Check if a WordPress version string is valid + */ function isValidWordPressVersion( version: string ): boolean { const versionPattern = /^latest$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/; return versionPattern.test( version ); } +/** + * Get the download URL for a WordPress version + */ function getWordPressVersionUrl( version = DEFAULT_WORDPRESS_VERSION ): string { if ( isWordPressDevVersion( version ) ) { return 'https://wordpress.org/nightly-builds/wordpress-latest.zip'; @@ -60,12 +78,20 @@ function getWordPressVersionUrl( version = DEFAULT_WORDPRESS_VERSION ): string { return `https://wordpress.org/wordpress-${ version }.zip`; } +/** + * Download a file to a destination path + */ async function downloadFile( { url, destinationFilePath, itemName, overwrite = false, -} ): Promise< DownloadFileAndUnzipResult > { +}: { + url: string; + destinationFilePath: string; + itemName: string; + overwrite?: boolean; +} ): Promise< DownloadResult > { let statusCode = 0; try { if ( fs.existsSync( destinationFilePath ) && ! overwrite ) { @@ -75,7 +101,7 @@ async function downloadFile( { const response = await new Promise< IncomingMessage >( ( resolve ) => httpsGet( url, ( response ) => resolve( response ) ) ); - statusCode = response.statusCode; + statusCode = response.statusCode ?? 0; if ( response.statusCode !== 200 ) { throw new Error( `Failed to download file (Status code ${ response.statusCode }).` ); } @@ -92,32 +118,32 @@ async function downloadFile( { reject( error ); } ); } ); - output?.log( `Downloaded ${ itemName } to ${ destinationFilePath }` ); + console.log( `Downloaded ${ itemName } to ${ destinationFilePath }` ); return { downloaded: true, statusCode }; } catch ( error ) { - output?.error( `Error downloading file ${ itemName }`, error ); + console.error( `Error downloading file ${ itemName }`, error ); return { downloaded: false, statusCode }; } } -export async function downloadWpCli( overwrite = false ) { - return downloadFile( { - url: WP_CLI_URL, - destinationFilePath: getWpCliPath(), - itemName: 'wp-cli', - overwrite, - } ); -} - +/** + * Download and unzip a file + */ async function downloadFileAndUnzip( { url, destinationFolder, checkFinalPath, itemName, overwrite = false, -} ): Promise< DownloadFileAndUnzipResult > { +}: { + url: string; + destinationFolder: string; + checkFinalPath: string; + itemName: string; + overwrite?: boolean; +} ): Promise< DownloadResult > { if ( ! overwrite && fs.existsSync( checkFinalPath ) ) { - output?.log( `${ itemName } folder already exists. Skipping download.` ); + console.log( `${ itemName } folder already exists. Skipping download.` ); return { downloaded: false, statusCode: 0 }; } @@ -126,11 +152,11 @@ async function downloadFileAndUnzip( { try { await fs.ensureDir( path.dirname( destinationFolder ) ); - output?.log( `Downloading ${ itemName }...` ); + console.log( `Downloading ${ itemName }...` ); const response = await new Promise< IncomingMessage >( ( resolve ) => httpsGet( url, ( response ) => resolve( response ) ) ); - statusCode = response.statusCode; + statusCode = response.statusCode ?? 0; if ( response.statusCode !== 200 ) { throw new Error( `Failed to download file (Status code ${ response.statusCode }).` ); @@ -138,10 +164,6 @@ async function downloadFileAndUnzip( { const entryPromises: Promise< unknown >[] = []; - /** - * Using Parse because Extract is broken: - * https://github.com/WordPress/wordpress-playground/issues/248 - */ await response .pipe( unzipper.Parse() ) .on( 'entry', ( entry ) => { @@ -149,15 +171,11 @@ async function downloadFileAndUnzip( { const filePath = path.join( destinationFolder, normalizedPath ); if ( ! filePath.startsWith( destinationFolder ) ) { - output?.log( `Skipping invalid path: ${ entry.path }` ); + console.log( `Skipping invalid path: ${ entry.path }` ); entryPromises.push( entry.autodrain().promise() ); return; } - /* - * Use the sync version to ensure entry is piped to - * a write stream before moving on to the next entry. - */ fs.ensureDirSync( path.dirname( filePath ) ); if ( entry.type === 'Directory' ) { @@ -174,20 +192,34 @@ async function downloadFileAndUnzip( { } ) .promise(); - // Wait until all entries have been extracted before continuing await Promise.all( entryPromises ); return { downloaded: true, statusCode }; } catch ( err ) { - output?.error( `Error downloading or unzipping ${ itemName }:`, err ); + console.error( `Error downloading or unzipping ${ itemName }:`, err ); } return { downloaded: false, statusCode }; } +/** + * Download WP-CLI + */ +export async function downloadWpCli( overwrite = false ): Promise< DownloadResult > { + return downloadFile( { + url: WP_CLI_URL, + destinationFilePath: getWpCliPath(), + itemName: 'wp-cli', + overwrite, + } ); +} + +/** + * Download a specific WordPress version + */ export async function downloadWordPress( wordPressVersion = DEFAULT_WORDPRESS_VERSION, { overwrite }: { overwrite: boolean } = { overwrite: false } -) { +): Promise< void > { const finalFolder = getWordPressVersionPath( wordPressVersion ); const tempDir = await fs.mkdtemp( path.join( os.tmpdir(), 'wordpress-download-' ) ); @@ -207,7 +239,7 @@ export async function downloadWordPress( overwrite: true, } ); } else if ( 404 === statusCode ) { - output?.log( + console.log( `WordPress ${ wordPressVersion } not found. Check https://wordpress.org/download/releases/ for available versions.` ); } @@ -216,13 +248,81 @@ export async function downloadWordPress( try { await fs.remove( tempDir ); } catch ( cleanupErr ) { - output?.error( 'Error cleaning up temporary directory:', cleanupErr ); + console.error( 'Error cleaning up temporary directory:', cleanupErr ); } } } } -export async function downloadSQLiteCommand( downloadUrl: string, targetPath: string ) { +/** + * Get the latest WP-CLI version from GitHub + */ +let latestWPCliVersionCache: string | null = null; + +async function getLatestWPCliVersion(): Promise< string > { + if ( latestWPCliVersionCache ) { + return latestWPCliVersionCache; + } + + try { + const response = await fetch( + 'https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=1' + ); + const data: Record< string, string >[] = await response.json(); + latestWPCliVersionCache = data?.[ 0 ]?.tag_name || ''; + } catch ( _error ) { + // Discard the failed fetch, return the cache + } + + return latestWPCliVersionCache || ''; +} + +/** + * Check if WP-CLI installation is outdated + */ +async function isWPCliInstallationOutdated( + getVersionFromInstallation: () => Promise< string > +): Promise< boolean > { + const installedVersion = await getVersionFromInstallation(); + const latestVersion = await getLatestWPCliVersion(); + + if ( ! installedVersion ) { + return true; + } + + if ( ! latestVersion ) { + return false; + } + + try { + const { default: semver } = await import( 'semver' ); + return semver.lt( installedVersion, latestVersion ); + } catch ( _error ) { + return false; + } +} + +/** + * Update WP-CLI to the latest version if needed + */ +export async function updateLatestWPCliVersion( + getVersionFromInstallation: () => Promise< string > +): Promise< void > { + let shouldOverwrite = false; + const pathExist = await fs.pathExists( getWpCliPath() ); + if ( pathExist ) { + shouldOverwrite = await isWPCliInstallationOutdated( getVersionFromInstallation ); + } + await downloadWpCli( shouldOverwrite ); +} + +/** + * Download SQLite command + */ +export async function downloadSQLiteCommand( + downloadUrl: string, + targetPath: string +): Promise< void > { const tempFolder = path.join( os.tmpdir(), 'wp-cli-sqlite-command' ); const { downloaded, statusCode } = await downloadFileAndUnzip( { url: downloadUrl, @@ -238,33 +338,7 @@ export async function downloadSQLiteCommand( downloadUrl: string, targetPath: st await fs.ensureDir( path.dirname( targetPath ) ); - await fs.move( path.join( tempFolder ), targetPath, { + await fs.move( tempFolder, targetPath, { overwrite: true, } ); } -export function getWordPressVersionPath( wpVersion: string ) { - return path.join( getWordpressVersionsPath(), wpVersion ); -} - -/** - * This function removes the internal mu-plugins that WP-now used to store. - * - * WP-now used to store some internal mu-plugins in the site's mu-plugins directory. - * This prevented users from using the mu-plugins directory for their own plugins, - * so Studio now mounts the mu-plugins directory to the shared mu-plugins directory. - * - * @param projectPath The path to the project directory. - */ -export async function removeDownloadedMuPlugins( projectPath: string ) { - const wpContentPath = path.join( projectPath, 'wp-content' ); - const muPluginsPath = path.join( wpContentPath, 'mu-plugins' ); - fs.removeSync( path.join( muPluginsPath, '0-32bit-integer-warnings.php' ) ); - fs.removeSync( path.join( muPluginsPath, '0-allowed-redirect-hosts.php' ) ); - fs.removeSync( path.join( muPluginsPath, '0-check-theme-availability.php' ) ); - fs.removeSync( path.join( muPluginsPath, '0-deactivate-jetpack-modules.php' ) ); - fs.removeSync( path.join( muPluginsPath, '0-dns-functions.php' ) ); - fs.removeSync( path.join( muPluginsPath, '0-permalinks.php' ) ); - fs.removeSync( path.join( muPluginsPath, '0-wp-config-constants-polyfill.php' ) ); - fs.removeSync( path.join( muPluginsPath, '0-sqlite.php' ) ); - fs.removeSync( path.join( muPluginsPath, '0-thumbnails.php' ) ); -} diff --git a/src/lib/import-export/import/importers/importer.ts b/src/lib/import-export/import/importers/importer.ts index e7209cef76..d7f5ebac7c 100644 --- a/src/lib/import-export/import/importers/importer.ts +++ b/src/lib/import-export/import/importers/importer.ts @@ -4,8 +4,9 @@ import fs, { createReadStream, createWriteStream } from 'fs'; import fsPromises from 'fs/promises'; import path from 'path'; import { createInterface } from 'readline'; -import { SupportedPHPVersionsList } from '@php-wasm/universal'; import { lstat, move } from 'fs-extra'; +import { DEFAULT_PHP_VERSION } from 'common/constants'; +import { SupportedPHPVersionsList } from 'common/types/php-versions'; import semver from 'semver'; import { getSiteUrl } from 'src/lib/get-site-url'; import { generateBackupFilename } from 'src/lib/import-export/export/generate-backup-filename'; @@ -17,7 +18,6 @@ import { } from 'src/lib/import-export/import/types'; import { serializePlugins } from 'src/lib/serialize-plugins'; import { updateSiteUrl } from 'src/lib/update-site-url'; -import { getWordPressProvider } from 'src/lib/wordpress-provider'; import { SiteServer } from 'src/site-server'; export interface ImporterResult extends Omit< BackupContents, 'metaFile' > { @@ -77,7 +77,7 @@ abstract class BaseImporter extends EventEmitter implements Importer { `sqlite import ${ sqlTempFile } --require=/tmp/sqlite-command/command.php --enable-ast-driver`, // SQLite plugin requires PHP 8+ { - targetPhpVersion: getWordPressProvider().DEFAULT_PHP_VERSION, + targetPhpVersion: DEFAULT_PHP_VERSION, skipPluginsAndThemes: true, } ); @@ -285,18 +285,18 @@ abstract class BaseBackupImporter extends BaseImporter { protected parsePhpVersion( version: string | undefined ): string { if ( ! version ) { - return getWordPressProvider().DEFAULT_PHP_VERSION; + return DEFAULT_PHP_VERSION; } const phpVersion = semver.coerce( version ); if ( ! phpVersion ) { - return getWordPressProvider().DEFAULT_PHP_VERSION; + return DEFAULT_PHP_VERSION; } const parsedVersion = `${ phpVersion.major }.${ phpVersion.minor }`; return SupportedPHPVersionsList.includes( parsedVersion ) ? parsedVersion - : getWordPressProvider().DEFAULT_PHP_VERSION; + : DEFAULT_PHP_VERSION; } } diff --git a/src/lib/server-files-paths.ts b/src/lib/server-files-paths.ts new file mode 100644 index 0000000000..691468d210 --- /dev/null +++ b/src/lib/server-files-paths.ts @@ -0,0 +1,68 @@ +/** + * Path utilities for server files (WordPress versions, WP-CLI, SQLite) + * + * These replace the functions from vendor/wp-now to remove that dependency. + * See STU-960: Remove PHP-WASM dependencies from Studio + */ + +import os from 'os'; +import path from 'path'; +import { getServerFilesPath } from 'src/storage/paths'; + +// SQLite integration folder name +const SQLITE_FILENAME = 'sqlite-database-integration'; + +/** + * Get a temporary path for tests + */ +function getTmpPath( subfolder: string ): string { + return path.join( os.tmpdir(), `studio-tests-${ subfolder }` ); +} + +/** + * Get the base path for server files (WordPress versions, SQLite, etc.) + */ +function getBasePath(): string { + if ( process.env.NODE_ENV === 'test' ) { + return getTmpPath( 'server-files' ); + } + return getServerFilesPath(); +} + +/** + * The path where WordPress zip files will be unzipped and stored. + */ +export function getWordPressVersionsPath(): string { + return path.join( getBasePath(), 'wordpress-versions' ); +} + +/** + * Get the path to a specific WordPress version folder. + */ +export function getWordPressVersionPath( version: string ): string { + return path.join( getWordPressVersionsPath(), version ); +} + +/** + * The full path to the "SQLite database integration" folder. + */ +export function getSqlitePath(): string { + return path.join( getBasePath(), SQLITE_FILENAME ); +} + +/** + * The path to the wp-cli folder. + */ +export function getWpCliFolderPath(): string { + if ( process.env.NODE_ENV === 'test' ) { + return getTmpPath( 'wp-cli' ); + } + return getServerFilesPath(); +} + +/** + * The path for wp-cli.phar file. + */ +export function getWpCliPath(): string { + return path.join( getWpCliFolderPath(), 'wp-cli.phar' ); +} diff --git a/src/lib/sqlite-command-path.ts b/src/lib/sqlite-command-path.ts deleted file mode 100644 index f3e24223f3..0000000000 --- a/src/lib/sqlite-command-path.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Minimal SQLite command path module for vendor/wp-now to import - * This avoids circular dependencies with the provider - */ -import os from 'os'; -import path from 'path'; -import fs from 'fs-extra'; - -/** - * Get the path for SQLite command files - * This is a simplified version that doesn't depend on other modules - */ -export function getSqliteCommandPath() { - if ( process.env.NODE_ENV !== 'test' ) { - // Use a simple path construction instead of importing getServerFilesPath - const appPath = - process.env.APPDATA || - ( process.platform === 'darwin' - ? path.join( os.homedir(), 'Library', 'Application Support' ) - : path.join( os.homedir(), '.local', 'share' ) ); - return path.join( appPath, 'Studio', 'server-files', 'sqlite-command' ); - } - - const tmpPath = path.join( os.tmpdir(), `wp-now-tests-wp-sqlite-command-hidden-folder` ); - fs.ensureDirSync( tmpPath ); - return tmpPath; -} diff --git a/src/lib/sqlite-command-versions.ts b/src/lib/sqlite-command-versions.ts index 15cf00bc8c..3d506bc247 100644 --- a/src/lib/sqlite-command-versions.ts +++ b/src/lib/sqlite-command-versions.ts @@ -1,9 +1,9 @@ import path from 'path'; import fs from 'fs-extra'; import semver, { SemVer } from 'semver'; +import { downloadSQLiteCommand } from 'src/lib/download-utils'; import { getLatestSQLiteCommandRelease } from 'src/lib/sqlite-command-release'; import { getServerFilesPath } from 'src/storage/paths'; -import { downloadSQLiteCommand } from 'vendor/wp-now/src/download'; interface DistributionCheckResult { needsDownload: boolean; diff --git a/src/lib/sqlite-versions.ts b/src/lib/sqlite-versions.ts index 559917f04d..31add32469 100644 --- a/src/lib/sqlite-versions.ts +++ b/src/lib/sqlite-versions.ts @@ -1,6 +1,6 @@ +import { SQLITE_FILENAME } from 'common/constants'; import { sequential } from 'common/lib/sequential'; import { SqliteIntegrationProvider } from 'common/lib/sqlite-integration'; -import { getWordPressProvider } from 'src/lib/wordpress-provider'; import { getServerFilesPath } from 'src/storage/paths'; class ElectronSqliteProvider extends SqliteIntegrationProvider { @@ -9,7 +9,7 @@ class ElectronSqliteProvider extends SqliteIntegrationProvider { } getSqliteDirname(): string { - return getWordPressProvider().SQLITE_FILENAME; + return SQLITE_FILENAME; } } diff --git a/src/lib/wordpress-provider/constants.ts b/src/lib/wordpress-provider/constants.ts deleted file mode 100644 index 92d849bda3..0000000000 --- a/src/lib/wordpress-provider/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { - DEFAULT_PHP_VERSION, - DEFAULT_WORDPRESS_VERSION, - ALLOWED_PHP_VERSIONS, - type AllowedPHPVersion, - SQLITE_FILENAME, -} from 'vendor/wp-now/src/constants'; diff --git a/src/lib/wordpress-provider/index.ts b/src/lib/wordpress-provider/index.ts deleted file mode 100644 index 5255a42a2d..0000000000 --- a/src/lib/wordpress-provider/index.ts +++ /dev/null @@ -1,86 +0,0 @@ -export * from './types'; -export { WpNowProvider } from './wp-now'; -export { PlaygroundCliProvider, PLAYGROUND_CLI_PROVIDER_NAME } from './playground-cli'; - -import { getFeatureFlagFromEnv } from 'src/lib/feature-flags'; -import { PlaygroundCliProvider } from './playground-cli/playground-cli-provider'; -import { WpNowProvider } from './wp-now'; -import type { WordPressProvider } from './types'; - -let provider: WordPressProvider | null = null; - -export function getWordPressProvider(): WordPressProvider { - const blueprintsEnabled = getFeatureFlagFromEnv( 'enableBlueprints' ); - - if ( blueprintsEnabled ) { - if ( provider?.PROVIDER_TYPE !== 'playground-cli' ) { - provider = new PlaygroundCliProvider(); - } - return provider; - } - - // If blueprints are disabled, ensure we use WpNowProvider - if ( provider?.PROVIDER_TYPE !== 'wp-now' ) { - provider = new WpNowProvider(); - } - - return provider; -} - -export function getWordPressProviderType(): string { - const blueprintsEnabled = getFeatureFlagFromEnv( 'enableBlueprints' ); - return blueprintsEnabled ? 'playground-cli' : 'wp-now'; -} - -export const getProviderConstants = ( - provider: WordPressProvider -): { - defaultPhpVersion: string; - defaultWordPressVersion: string; - allowedPhpVersions: string[]; - minimumWordPressVersion: string; -} => { - return { - defaultPhpVersion: provider.DEFAULT_PHP_VERSION, - defaultWordPressVersion: provider.DEFAULT_WORDPRESS_VERSION, - allowedPhpVersions: provider.ALLOWED_PHP_VERSIONS, - minimumWordPressVersion: provider.MINIMUM_WORDPRESS_VERSION, - }; -}; - -export function sendProviderConstantsChanged( provider: WordPressProvider ): void { - // Notify renderer processes about provider constants changes - try { - const { sendIpcEventToRenderer } = require( 'src/ipc-utils' ); - const constants = { - defaultPhpVersion: provider.DEFAULT_PHP_VERSION, - defaultWordPressVersion: provider.DEFAULT_WORDPRESS_VERSION, - allowedPhpVersions: provider.ALLOWED_PHP_VERSIONS, - }; - sendIpcEventToRenderer( 'providerConstantsChanged', constants ); - } catch ( error ) { - console.error( 'Error notifying renderer processes about provider constants changes:', error ); - } -} - -// Methods as proxy functions -export const getSqlitePath = ( ...args: Parameters< WordPressProvider[ 'getSqlitePath' ] > ) => - getWordPressProvider().getSqlitePath( ...args ); - -export const getWpLoadPath = ( ...args: Parameters< WordPressProvider[ 'getWpLoadPath' ] > ) => - getWordPressProvider().getWpLoadPath( ...args ); - -export const setupWordPressSite = ( - ...args: Parameters< WordPressProvider[ 'setupWordPressSite' ] > -) => getWordPressProvider().setupWordPressSite( ...args ); -export const installWordPressWhenNoWpConfig = ( - ...args: Parameters< WordPressProvider[ 'installWordPressWhenNoWpConfig' ] > -) => getWordPressProvider().installWordPressWhenNoWpConfig( ...args ); -export const startServer = ( ...args: Parameters< WordPressProvider[ 'startServer' ] > ) => - getWordPressProvider().startServer( ...args ); -export const isValidWordPressVersion = ( - ...args: Parameters< WordPressProvider[ 'isValidWordPressVersion' ] > -) => getWordPressProvider().isValidWordPressVersion( ...args ); -export const createServerProcess = ( - ...args: Parameters< WordPressProvider[ 'createServerProcess' ] > -) => getWordPressProvider().createServerProcess( ...args ); diff --git a/src/lib/wordpress-provider/playground-cli/index.ts b/src/lib/wordpress-provider/playground-cli/index.ts deleted file mode 100644 index e3b0cfc204..0000000000 --- a/src/lib/wordpress-provider/playground-cli/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { PlaygroundCliProvider, PLAYGROUND_CLI_PROVIDER_NAME } from './playground-cli-provider'; -export type { PlaygroundCliOptions } from './playground-cli-provider'; diff --git a/src/lib/wordpress-provider/playground-cli/playground-cli-provider.ts b/src/lib/wordpress-provider/playground-cli/playground-cli-provider.ts deleted file mode 100644 index 2a8f04ce11..0000000000 --- a/src/lib/wordpress-provider/playground-cli/playground-cli-provider.ts +++ /dev/null @@ -1,280 +0,0 @@ -import nodePath from 'path'; -import { SupportedPHPVersions } from '@php-wasm/universal'; -import { Blueprint, StepDefinition } from '@wp-playground/blueprints'; -import { RecommendedPHPVersion } from '@wp-playground/common'; -import { WordPressInstallMode } from '@wp-playground/wordpress'; -import { recursiveCopyDirectory, pathExists, isWordPressDirectory } from 'common/lib/fs-utils'; -import { DEFAULT_LOCALE } from 'common/lib/locale'; -import { isOnline } from 'common/lib/network-utils'; -import { isValidWordPressVersion } from 'common/lib/wordpress-version-utils'; -import { getBetaFeatures } from 'src/lib/beta-features'; -import { getPreferredSiteLanguage } from 'src/lib/site-language'; -import { keepSqliteIntegrationUpdated } from 'src/lib/sqlite-versions'; -import { SiteServer } from 'src/site-server'; -import { getResourcesPath, getServerFilesPath } from 'src/storage/paths'; -import { - WordPressProvider, - WordPressServerInstance, - WordPressServerOptions, - WordPressServerProcess, -} from '../types'; -import { PlaygroundServerProcess } from './playground-server-process'; - -export interface PlaygroundCliOptions { - port: number; - phpVersion: string; - documentRoot: string; - autoMount: boolean; - wordpressInstallMode: WordPressInstallMode; - blueprint?: Blueprint; - enableMultiWorker?: boolean; -} - -export const PLAYGROUND_CLI_PROVIDER_NAME = 'playground-cli'; - -export class PlaygroundCliProvider implements WordPressProvider { - readonly PROVIDER_TYPE = PLAYGROUND_CLI_PROVIDER_NAME; - - static readonly DEFAULT_PHP_VERSION = RecommendedPHPVersion; - static readonly DEFAULT_WORDPRESS_VERSION = 'latest'; - static readonly ALLOWED_PHP_VERSIONS = [ ...SupportedPHPVersions ]; - static readonly MINIMUM_WORDPRESS_VERSION = '6.2.1'; // https://wordpress.github.io/wordpress-playground/blueprints/examples/#load-an-older-wordpress-version - static readonly SQLITE_FILENAME = 'sqlite-database-integration'; - - // Instance constants for interface compatibility - readonly DEFAULT_PHP_VERSION = PlaygroundCliProvider.DEFAULT_PHP_VERSION; - readonly DEFAULT_WORDPRESS_VERSION = PlaygroundCliProvider.DEFAULT_WORDPRESS_VERSION; - readonly ALLOWED_PHP_VERSIONS = PlaygroundCliProvider.ALLOWED_PHP_VERSIONS; - readonly MINIMUM_WORDPRESS_VERSION = PlaygroundCliProvider.MINIMUM_WORDPRESS_VERSION; - readonly SQLITE_FILENAME = PlaygroundCliProvider.SQLITE_FILENAME; - - // Start/Stop functionality only - async startServer( options: { - path: string; - port: number; - adminPassword: string; - siteTitle: string; - phpVersion?: string; - wpVersion?: string; - isWpAutoUpdating?: boolean; - absoluteUrl?: string; - siteLanguage?: string; - wpCliPharPath?: string; - blueprint?: Blueprint; - } ): Promise< WordPressServerInstance > { - const port = options.port; - const phpVersion = options.phpVersion || '8.3'; - const hasWordPress = isWordPressDirectory( options.path ); - - // Get beta features to check if multi-worker support is enabled - const betaFeatures = await getBetaFeatures(); - - if ( betaFeatures.multiWorkerSupport ) { - console.log( '[PlaygroundCliProvider] Multi-worker support is enabled via beta features' ); - } - - const playgroundOptions: PlaygroundCliOptions = { - port, - phpVersion, - documentRoot: options.path, - autoMount: true, - wordpressInstallMode: hasWordPress - ? 'install-from-existing-files-if-needed' - : 'download-and-install', - blueprint: options.blueprint, - enableMultiWorker: betaFeatures.multiWorkerSupport, - }; - - const serverOptions: WordPressServerOptions = { - documentRoot: options.path, - phpVersion, - port, - absoluteUrl: options.absoluteUrl, - projectPath: options.path, - adminPassword: options.adminPassword, - siteTitle: options.siteTitle, - siteLanguage: options.siteLanguage, - wordPressVersion: options.wpVersion, - isWpAutoUpdating: options.isWpAutoUpdating, - }; - - return { - url: `http://localhost:${ port }`, - options: serverOptions, - _internal: playgroundOptions, - }; - } - - createServerProcess( serverInstance: WordPressServerInstance ): WordPressServerProcess { - const playgroundOptions = serverInstance._internal as PlaygroundCliOptions; - return new PlaygroundServerProcess( - serverInstance.url, - playgroundOptions, - serverInstance.options - ); - } - - getSqlitePath(): string { - return nodePath.join( getServerFilesPath(), this.SQLITE_FILENAME ); - } - - getWpLoadPath( _serverProcess: WordPressServerProcess ): string { - // Playground CLI mounts the WordPress directory at /wordpress in VFS - return '/wordpress/wp-load.php'; - } - - private escapePhpString( str: string ): string { - return str.replace( /\\/g, '\\\\' ).replace( /'/g, "\\'" ); - } - - private createInstallationStep( siteName: string, adminPassword: string ): StepDefinition { - return { - step: 'runPHP', - code: ` 'en_US', - 'prefix' => 'wp_', - 'weblog_title' => '${ this.escapePhpString( siteName ) }', - 'user_name' => 'admin', - 'admin_password' => '${ this.escapePhpString( adminPassword ) }', - 'admin_password2' => '${ this.escapePhpString( adminPassword ) }', - 'Submit' => 'Install WordPress', - 'pw_weak' => '1', - 'admin_email' => 'admin@localhost.com', - ); - $_REQUEST = $_POST; - $_GET['step'] = 2; - - // Include WordPress installation - require_once('/wordpress/wp-admin/install.php'); - `, - }; - } - - async setupWordPressSite( server: SiteServer, wpVersion = 'latest' ): Promise< boolean > { - const { path, name } = server.details; - - try { - const isOnlineStatus = await isOnline(); - const siteLanguage = await getPreferredSiteLanguage( wpVersion ); - - const setupSteps: StepDefinition[] = []; - - if ( isOnlineStatus && siteLanguage && siteLanguage !== DEFAULT_LOCALE ) { - setupSteps.push( - { - step: 'setSiteLanguage', - language: siteLanguage, - }, - { - step: 'setSiteOptions', - options: { - WPLANG: siteLanguage, - }, - } - ); - } - - if ( name ) { - setupSteps.push( { - step: 'setSiteOptions', - options: { - blogname: name, - }, - } ); - } - - if ( ! isOnlineStatus ) { - if ( wpVersion !== 'latest' ) { - throw new Error( - `Cannot set up WordPress version '${ wpVersion }' while offline. ` + - 'Specific WordPress versions require an internet connection to download. ' + - 'Try using "latest" version or ensure internet connectivity.' - ); - } - - const bundledWPPath = nodePath.join( - getResourcesPath(), - 'wp-files', - 'latest', - 'wordpress' - ); - - if ( ! ( await pathExists( bundledWPPath ) ) ) { - throw new Error( - 'Cannot set up WordPress while offline. Bundled WordPress files not found. ' + - 'Please connect to the internet or reinstall WordPress Studio.' - ); - } - - try { - await recursiveCopyDirectory( bundledWPPath, path ); - } catch ( error ) { - throw new Error( - `Failed to copy WordPress files for offline setup: ${ ( error as Error ).message }` - ); - } - } - - if ( ! server.meta.blueprint ) { - server.meta.blueprint = {}; - } - const existingSteps = server.meta.blueprint.steps || []; - server.meta.blueprint.steps = [ ...setupSteps, ...existingSteps ]; - - await keepSqliteIntegrationUpdated( path ); - - return true; - } catch ( error ) { - console.error( 'Failed to setup WordPress site:', error ); - throw error; - } - } - - // Install WordPress if user adds a WordPress folder with no wp-config.php and no database. - async installWordPressWhenNoWpConfig( - server: SiteServer, - siteName: string, - adminPassword: string - ): Promise< void > { - const { path } = server.details; - const databaseExists = await pathExists( - nodePath.join( path, 'wp-content', 'database', '.ht.sqlite' ) - ); - const wpConfigExists = await pathExists( nodePath.join( path, 'wp-config.php' ) ); - - if ( wpConfigExists || databaseExists ) { - return; - } - - // Add installation blueprint step to auto-install WordPress - if ( ! server.meta.blueprint ) { - server.meta.blueprint = {}; - } - - const installationStep = this.createInstallationStep( siteName, adminPassword ); - - const existingSteps = server.meta.blueprint.steps || []; - server.meta.blueprint.steps = [ installationStep, ...existingSteps ]; - } - - isValidWordPressVersion( version: string ): boolean { - return isValidWordPressVersion( version ); - } - - async setupWordPressFilesOnly( path: string ): Promise< void > { - try { - const bundledWPPath = nodePath.join( getResourcesPath(), 'wp-files', 'latest', 'wordpress' ); - - if ( ! ( await pathExists( bundledWPPath ) ) ) { - throw new Error( 'Bundled WordPress files not found. Please reinstall WordPress Studio.' ); - } - - await recursiveCopyDirectory( bundledWPPath, path ); - await keepSqliteIntegrationUpdated( path ); - } catch ( error ) { - console.error( 'Failed to setup WordPress files:', error ); - throw error; - } - } -} diff --git a/src/lib/wordpress-provider/playground-cli/playground-server-process-child.ts b/src/lib/wordpress-provider/playground-cli/playground-server-process-child.ts deleted file mode 100644 index 8e6cae23f6..0000000000 --- a/src/lib/wordpress-provider/playground-cli/playground-server-process-child.ts +++ /dev/null @@ -1,359 +0,0 @@ -import { cpus } from 'os'; -import { SupportedPHPVersion, PHPRunOptions } from '@php-wasm/universal'; -import { __, sprintf } from '@wordpress/i18n'; -import { runCLI, RunCLIArgs, RunCLIServer } from '@wp-playground/cli'; -import { sanitizeRunCLIArgs } from 'common/lib/cli-args-sanitizer'; -import { getMuPlugins } from 'common/lib/mu-plugins'; -import { formatPlaygroundCliMessage } from 'common/lib/playground-cli-messages'; -import { isWordPressDevVersion } from 'common/lib/wordpress-version-utils'; -import { WordPressServerOptions } from '../types'; -import { PlaygroundCliOptions } from './playground-cli-provider'; - -interface BaseMessage { - id: number; - type: string; -} - -interface StartServerMessage extends BaseMessage { - type: 'start-server'; - data: { - options: PlaygroundCliOptions; - serverOptions: WordPressServerOptions; - }; -} - -interface StopServerMessage extends BaseMessage { - type: 'stop-server'; - data: Record< string, never >; -} - -interface RunPhpMessage extends BaseMessage { - type: 'run-php'; - data: { - code: string; - scriptPath?: string; - phpVersion?: string; - }; -} - -interface HttpRequestMessage extends BaseMessage { - type: 'http-request'; - data: { - url: string; - method: string; - body: Record< string, string >; - }; -} - -type Message = StartServerMessage | StopServerMessage | RunPhpMessage | HttpRequestMessage; - -// Intercept and prefix all console output from playground-cli -const originalConsoleLog = console.log; -const originalConsoleError = console.error; -const originalConsoleWarn = console.warn; - -console.log = ( ...args: any[] ) => { - originalConsoleLog( '[playground-cli]', ...args ); - const message = args.join( ' ' ); - process.parentPort.postMessage( { type: 'activity' } ); - const formattedMessage = formatPlaygroundCliMessage( message ); - if ( formattedMessage ) { - process.parentPort.postMessage( { type: 'console-message', message: formattedMessage } ); - } -}; - -console.error = ( ...args: any[] ) => { - originalConsoleError( '[playground-cli]', ...args ); - process.parentPort.postMessage( { type: 'activity' } ); -}; - -console.warn = ( ...args: any[] ) => { - originalConsoleWarn( '[playground-cli]', ...args ); - process.parentPort.postMessage( { type: 'activity' } ); -}; - -const originalStdoutWrite = process.stdout.write.bind( process.stdout ); -const originalStderrWrite = process.stderr.write.bind( process.stderr ); - -process.stdout.write = function ( ...args: Parameters< typeof originalStdoutWrite > ) { - process.parentPort.postMessage( { type: 'activity' } ); - return originalStdoutWrite( ...args ); -} as typeof process.stdout.write; - -process.stderr.write = function ( ...args: Parameters< typeof originalStderrWrite > ) { - process.parentPort.postMessage( { type: 'activity' } ); - return originalStderrWrite( ...args ); -} as typeof process.stderr.write; - -let server: RunCLIServer | null | void = null; -let lastCliArgs: Record< string, unknown > | null = null; - -process.parentPort.on( 'message', async ( event ) => { - const message = event.data as Message; - - try { - let result: unknown; - - switch ( message.type ) { - case 'start-server': - result = await startServer( message.data.options, message.data.serverOptions ); - break; - case 'stop-server': - result = await stopServerFunc(); - break; - case 'run-php': - result = await runPhp( message.data ); - break; - case 'http-request': - result = await httpRequest( message.data ); - break; - default: - throw new Error( `Unknown message type: ${ message }` ); - } - - process.parentPort.postMessage( { id: message.id, result } ); - } catch ( error ) { - process.parentPort.postMessage( { - id: message.id, - error: error instanceof Error ? error.message : String( error ), - errorStack: error instanceof Error ? error.stack : undefined, - cliArgs: lastCliArgs, - } ); - } -} ); - -process.parentPort.postMessage( { type: 'ready' } ); - -function escapePhpString( str: string ): string { - return str.replace( /\\/g, '\\\\' ).replace( /'/g, "\\'" ); -} - -async function setAdminPassword( server: RunCLIServer, adminPassword: string ): Promise< void > { - // Use the persistent mu-plugin API endpoint to avoid loading WordPress again - await server.playground.request( { - url: '/?studio-admin-api', - method: 'POST', - body: { - action: 'set_admin_password', - password: escapePhpString( adminPassword ), - }, - } ); -} - -async function startServer( - options: PlaygroundCliOptions, - serverOptions: WordPressServerOptions -): Promise< void > { - if ( server ) { - return; - } - - try { - const [ studioMuPluginsHostPath, loaderMuPluginHostPath ] = await getMuPlugins( serverOptions ); - - const defaultConstants = { - WP_SQLITE_AST_DRIVER: true, - }; - - const mounts = [ - { - hostPath: options.documentRoot, - vfsPath: '/wordpress', - }, - { - hostPath: studioMuPluginsHostPath, - vfsPath: '/internal/studio/mu-plugins', - }, - { - hostPath: loaderMuPluginHostPath, - vfsPath: '/internal/shared/mu-plugins/99-studio-loader.php', - }, - ]; - - const args: RunCLIArgs & { command: 'server' } = { - command: 'server', - internalCookieStore: false, - login: false, - followSymlinks: true, - skipSqliteSetup: true, - port: options.port, - 'mount-before-install': mounts, - 'site-url': serverOptions.absoluteUrl, - blueprint: options.blueprint || {}, - wordpressInstallMode: options.wordpressInstallMode, - }; - - // Enable multi-worker support if beta feature is enabled - if ( options.enableMultiWorker ) { - const workerCount = Math.max( 1, cpus().length - 1 ); - console.log( - sprintf( - __( 'Enabling experimental multi-worker support with %d workers (CPU cores - 1)' ), - workerCount - ) - ); - args.experimentalMultiWorker = workerCount; - } - - if ( options.phpVersion ) { - args.php = options.phpVersion as SupportedPHPVersion; - } - - if ( serverOptions.wordPressVersion ) { - if ( isWordPressDevVersion( serverOptions.wordPressVersion ) ) { - args.wp = 'nightly'; - } else { - args.wp = serverOptions.wordPressVersion; - } - } - - args.blueprint.constants = { ...args.blueprint.constants, ...defaultConstants }; - - lastCliArgs = sanitizeRunCLIArgs( args ); - - server = await runCLI( args ); - - // Log actual worker count for verification - if ( options.enableMultiWorker ) { - console.log( - sprintf( __( 'Server started with %d worker thread(s)' ), server.workerThreadCount ) - ); - } - - if ( serverOptions.adminPassword ) { - await setAdminPassword( server, serverOptions.adminPassword ); - } - } catch ( error ) { - server = null; - // Re-throw the original error to preserve stack trace - if ( error instanceof Error ) { - error.message = `Could not start server: ${ error.message }`; - throw error; - } - throw new Error( `Could not start server: ${ error }` ); - } -} - -async function stopServerFunc(): Promise< void > { - if ( ! server ) { - return; - } - - const serverToDispose = server; - server = null; - - try { - const disposalTimeout = new Promise( ( _, reject ) => - setTimeout( () => reject( new Error( 'Disposal timeout' ) ), 5000 ) - ); - - await Promise.race( [ serverToDispose[ Symbol.asyncDispose ](), disposalTimeout ] ); - } catch ( error ) { - // Suppress expected disposal errors that occur during site deletion - // These are typically race conditions that don't affect functionality - const errorMessage = error instanceof Error ? error.message : String( error ); - if ( ! errorMessage.includes( 'Cannot read properties of undefined' ) ) { - console.warn( 'Error during server disposal:', error ); - } - } finally { - server = null; - } -} - -async function httpRequest( options: { - url: string; - method: string; - body: Record< string, string >; -} ): Promise< { text: string } > { - if ( ! server ) { - throw new Error( 'Server is not initialized. Make sure the server is started.' ); - } - - try { - const response = await server.playground.request( { - url: options.url, - method: options.method as 'GET' | 'POST', - body: options.body, - } ); - - return { text: response.text }; - } catch ( error ) { - throw new Error( `Failed to make HTTP request: ${ error }` ); - } -} - -async function runPhp( options: { - code: string; - scriptPath?: string; - phpVersion?: string; -} ): Promise< string > { - if ( ! server ) { - throw new Error( 'Server is not initialized. Make sure the server is started.' ); - } - - try { - // Replace host filesystem paths with VFS paths - // The document root is mounted at /wordpress in the VFS - let modifiedCode = options.code; - - modifiedCode = modifiedCode.replace( - /((?:require_once|require|include_once|include)\s*\(\s*['"])([^'"]+)(['"]\s*\))/g, - ( match, prefix, path, suffix ) => { - // Don't modify phar:// paths - if ( path.startsWith( 'phar://' ) ) { - return match; - } - - if ( path.startsWith( '/' ) && ! path.startsWith( '/wordpress' ) ) { - const wpMatch = path.match( /(\/(?:wp-[^/]+\.php|wp-content|wp-includes|wp-admin).*)$/ ); - if ( wpMatch ) { - return `${ prefix }/wordpress${ wpMatch[ 1 ] }${ suffix }`; - } - const fileMatch = path.match( /\/([^/]+\.php)$/ ); - if ( fileMatch ) { - return `${ prefix }/wordpress/${ fileMatch[ 1 ] }${ suffix }`; - } - } - return match; - } - ); - - // Also handle direct file references and string concatenations - modifiedCode = modifiedCode.replace( - /(['"])([^'"]*\/(?:wp-[^/]*|[^/]+\.php|wp-content|wp-includes|wp-admin)[^'"]*)(['"])/g, - ( match, quote1, path, quote2 ) => { - // Don't modify phar:// paths - if ( path.startsWith( 'phar://' ) ) { - return match; - } - - if ( path.startsWith( '/wordpress' ) || ! path.startsWith( '/' ) ) { - return match; - } - const wpMatch = path.match( /(\/(?:wp-[^/]+\.php|wp-content|wp-includes|wp-admin).*)$/ ); - if ( wpMatch ) { - return `${ quote1 }/wordpress${ wpMatch[ 1 ] }${ quote2 }`; - } - const fileMatch = path.match( /\/([^/]+\.php)$/ ); - if ( fileMatch ) { - return `${ quote1 }/wordpress/${ fileMatch[ 1 ] }${ quote2 }`; - } - return match; - } - ); - - const runOptions: PHPRunOptions = { - code: modifiedCode, - }; - - if ( options.scriptPath ) { - runOptions.scriptPath = options.scriptPath; - } - - const response = await server.playground.run( runOptions ); - - return response.text || ''; - } catch ( error ) { - throw new Error( `Failed to run PHP code: ${ error }` ); - } -} diff --git a/src/lib/wordpress-provider/playground-cli/playground-server-process.ts b/src/lib/wordpress-provider/playground-cli/playground-server-process.ts deleted file mode 100644 index 230d55d673..0000000000 --- a/src/lib/wordpress-provider/playground-cli/playground-server-process.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { utilityProcess } from 'electron'; -import path from 'path'; -import { - PLAYGROUND_CLI_INACTIVITY_TIMEOUT, - PLAYGROUND_CLI_MAX_TIMEOUT, - PLAYGROUND_CLI_ACTIVITY_CHECK_INTERVAL, -} from 'src/constants'; -import { sendIpcEventToRendererWithWindow } from 'src/ipc-utils'; -import { getMainWindow } from 'src/main-window'; -import { WordPressServerProcess, WordPressServerOptions } from '../types'; -import { PlaygroundCliOptions } from './playground-cli-provider'; - -export class PlaygroundServerProcess implements WordPressServerProcess { - private process: Electron.UtilityProcess | null = null; - private siteId: string | null = null; - private messageId = 0; - private responseHandlers = new Map< - number, - { resolve: ( value: unknown ) => void; reject: ( reason: Error ) => void } - >(); - private exitPromise: Promise< void > | null = null; - private exitResolve: ( () => void ) | null = null; - private isSetupMode = false; - private lastActivityTimestamp = 0; - private consoleMessageCallback?: ( message: string ) => void; - - constructor( - public url: string, - private options: PlaygroundCliOptions, - private serverOptions: WordPressServerOptions - ) {} - - setConsoleMessageCallback( callback: ( message: string ) => void ): void { - this.consoleMessageCallback = callback; - } - - async start( siteId?: string ): Promise< void > { - if ( this.process ) { - return; - } - - // Create exit promise before starting the process - this.exitPromise = new Promise( ( resolve ) => { - this.exitResolve = resolve; - } ); - - this.process = utilityProcess.fork( path.join( __dirname, 'playgroundServerProcess.js' ) ); - - if ( siteId ) { - this.siteId = siteId; - } - - this.process.on( - 'message', - async ( message: { - type?: string; - id?: number; - error?: string; - errorStack?: string; - result?: unknown; - message?: string; - cliArgs?: Record< string, unknown >; - } ) => { - if ( message.type === 'ready' ) { - return; - } - - if ( message.type === 'console-message' && message.message ) { - if ( this.siteId ) { - try { - const mainWindow = await getMainWindow(); - sendIpcEventToRendererWithWindow( mainWindow, 'on-site-create-progress', { - siteId: this.siteId, - message: message.message, - } ); - } catch ( error ) { - console.error( 'Failed to get main window for site creation progress:', error ); - } - } - if ( this.consoleMessageCallback ) { - this.consoleMessageCallback( message.message ); - } - } - - if ( message.type === 'activity' || message.id !== undefined ) { - this.lastActivityTimestamp = Date.now(); - } - - if ( message.id !== undefined && this.responseHandlers.has( message.id ) ) { - const handler = this.responseHandlers.get( message.id )!; - this.responseHandlers.delete( message.id ); - - if ( message.error ) { - const error = new Error( message.error ) as Error & { - cliArgs?: Record< string, unknown >; - }; - if ( message.errorStack ) { - error.stack = message.errorStack; - } - if ( message.cliArgs ) { - error.cliArgs = message.cliArgs; - } - handler.reject( error ); - } else { - handler.resolve( message.result ); - } - } - } - ); - - this.process.on( 'exit', () => { - this.process = null; - - // In setup mode (run-blueprint), process exit is expected after completion - if ( ! this.isSetupMode ) { - this.responseHandlers.forEach( ( { reject } ) => { - reject( new Error( 'Process exited unexpectedly' ) ); - } ); - } else { - this.responseHandlers.forEach( ( { resolve } ) => { - resolve( undefined ); - } ); - } - this.responseHandlers.clear(); - - if ( this.exitResolve ) { - this.exitResolve(); - this.exitResolve = null; - } - } ); - - // Wait for child process to be ready - await new Promise< void >( ( resolve ) => { - const readyHandler = ( message: { type?: string } ) => { - if ( message.type === 'ready' ) { - this.process!.off( 'message', readyHandler ); - resolve(); - } - }; - this.process!.on( 'message', readyHandler ); - } ); - - await this.sendMessage( 'start-server', { - options: this.options, - serverOptions: this.serverOptions, - } ); - } - - async stop(): Promise< void > { - if ( ! this.process ) { - return; - } - - try { - await this.sendMessage( 'stop-server', {} ); - } catch ( error ) { - // Process might have already exited - } - - // Force kill if still running - if ( this.process ) { - this.process.kill(); - } - - // Wait for the process to exit - if ( this.exitPromise ) { - await this.exitPromise; - } - - this.process = null; - this.exitPromise = null; - this.exitResolve = null; - } - - async runPhp( data: { code: string; [ key: string ]: unknown } ): Promise< string > { - if ( ! this.process ) { - throw new Error( 'Server process is not running' ); - } - - const result = await this.sendMessage( 'run-php', data ); - return result as string; - } - - private async sendMessage( type: string, data: unknown ): Promise< unknown > { - if ( ! this.process ) { - throw new Error( 'Process is not running' ); - } - - const id = this.messageId++; - - return new Promise( ( resolve, reject ) => { - this.responseHandlers.set( id, { resolve, reject } ); - - const messageToSend = { id, type, data }; - this.process!.postMessage( messageToSend ); - - this.lastActivityTimestamp = Date.now(); - const startTime = Date.now(); - - const activityCheckInterval = setInterval( () => { - if ( ! this.responseHandlers.has( id ) ) { - clearInterval( activityCheckInterval ); - return; - } - - const now = Date.now(); - const timeSinceLastActivity = now - this.lastActivityTimestamp; - const totalElapsedTime = now - startTime; - - if ( - timeSinceLastActivity > PLAYGROUND_CLI_INACTIVITY_TIMEOUT || - totalElapsedTime > PLAYGROUND_CLI_MAX_TIMEOUT - ) { - clearInterval( activityCheckInterval ); - if ( this.responseHandlers.has( id ) ) { - this.responseHandlers.delete( id ); - const timeoutReason = - totalElapsedTime > PLAYGROUND_CLI_MAX_TIMEOUT - ? `Maximum timeout of ${ PLAYGROUND_CLI_MAX_TIMEOUT / 1000 }s exceeded` - : `No activity for ${ PLAYGROUND_CLI_INACTIVITY_TIMEOUT / 1000 }s`; - reject( - new Error( `Timeout waiting for response to message ${ id }: ${ timeoutReason }` ) - ); - } - } - }, PLAYGROUND_CLI_ACTIVITY_CHECK_INTERVAL ); - } ); - } - - get php() { - return { - documentRoot: this.options.documentRoot, - run: ( options: { code: string; scriptPath?: string } ) => - this.runPhp( { ...options, phpVersion: this.options.phpVersion } ), - request: async ( options: { - url: string; - method: string; - body: Record< string, string >; - } ) => { - const result = await this.sendMessage( 'http-request', options ); - return result as { text: string }; - }, - }; - } -} diff --git a/src/lib/wordpress-provider/types.ts b/src/lib/wordpress-provider/types.ts deleted file mode 100644 index 8f3d6a5bdc..0000000000 --- a/src/lib/wordpress-provider/types.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { SiteServer } from 'src/site-server'; -import { Blueprint } from 'src/stores/wpcom-api'; - -export type AllowedPHPVersion = string; - -export interface ServerOptions { - path: string; - port: number; - adminPassword: string; - siteTitle: string; - phpVersion?: string; - wpVersion?: string; - isWpAutoUpdating?: boolean; - absoluteUrl?: string; - siteLanguage?: string; - blueprint?: Blueprint[ 'blueprint' ]; -} - -export interface WordPressServerOptions { - port?: number; - phpVersion?: string; - documentRoot?: string; - absoluteUrl?: string; - projectPath?: string; - wpContentPath?: string; - wordPressVersion?: string; - isWpAutoUpdating?: boolean; - adminPassword?: string; - siteTitle?: string; - siteLanguage?: string; - isSetupMode?: boolean; -} - -export interface WordPressServerInstance { - url: string; - options: WordPressServerOptions; - // Internal options for server process implementation - _internal?: unknown; -} - -export interface WordPressServerProcess { - url: string; - php?: { - documentRoot: string; - request?: ( options: { - url: string; - method: string; - body: Record< string, string >; - } ) => Promise< { text: string } >; - }; - start( siteId?: string ): Promise< void >; - stop(): Promise< void >; - runPhp( data: { code: string; [ key: string ]: unknown } ): Promise< string >; -} - -export interface WordPressProvider { - readonly PROVIDER_TYPE: string; - - // Constants - readonly DEFAULT_PHP_VERSION: string; - readonly DEFAULT_WORDPRESS_VERSION: string; - readonly ALLOWED_PHP_VERSIONS: string[]; - readonly MINIMUM_WORDPRESS_VERSION: string; - readonly SQLITE_FILENAME: string; - - // Path utilities - getSqlitePath(): string; - getWpLoadPath( serverProcess: WordPressServerProcess ): string; - - // Core functionality - setupWordPressSite( server: SiteServer, wpVersion?: string ): Promise< boolean >; - setupWordPressFilesOnly( path: string ): Promise< void >; - installWordPressWhenNoWpConfig( - server: SiteServer, - siteName: string, - adminPassword: string - ): Promise< void >; - startServer( options: ServerOptions ): Promise< WordPressServerInstance >; - - // Server process management - createServerProcess( serverInstance: WordPressServerInstance ): WordPressServerProcess; - - // Version utilities - isValidWordPressVersion( version: string ): boolean; -} diff --git a/src/lib/wordpress-provider/wp-now/index.ts b/src/lib/wordpress-provider/wp-now/index.ts deleted file mode 100644 index 19760dfd04..0000000000 --- a/src/lib/wordpress-provider/wp-now/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './path-utilities'; -export { WpNowProvider } from './wp-now-provider'; diff --git a/src/lib/wordpress-provider/wp-now/path-utilities.ts b/src/lib/wordpress-provider/wp-now/path-utilities.ts deleted file mode 100644 index 2e9b0f21cb..0000000000 --- a/src/lib/wordpress-provider/wp-now/path-utilities.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { getWordPressVersionPath } from 'vendor/wp-now/src/download'; -export { default as getSqlitePath } from 'vendor/wp-now/src/get-sqlite-path'; -export { default as getWpCliPath, getWpCliFolderPath } from 'vendor/wp-now/src/get-wp-cli-path'; diff --git a/src/lib/wordpress-provider/wp-now/site-server-process-child.ts b/src/lib/wordpress-provider/wp-now/site-server-process-child.ts deleted file mode 100644 index 8dbe08ec2e..0000000000 --- a/src/lib/wordpress-provider/wp-now/site-server-process-child.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { PHPRunOptions } from '@php-wasm/universal'; -import { setupLogging } from 'src/logging'; -import type { MessageName } from './site-server-process'; -import type { WPNowServer } from 'vendor/wp-now/src'; -import type { WPNowOptions } from 'vendor/wp-now/src/config'; - -type Handler = ( message: string, messageId: number, data: unknown ) => void; -type Handlers = { [ K in MessageName ]: Handler }; - -// Setup logging for the forked process -if ( process.env.STUDIO_APP_LOGS_PATH ) { - setupLogging( { - processId: 'site-server-process', - isForkedProcess: true, - logDir: process.env.STUDIO_APP_LOGS_PATH, - } ); -} - -let options: WPNowOptions; -try { - options = JSON.parse( process.argv[ 2 ] ); - if ( typeof options !== 'object' || options === null ) { - throw new Error( 'Parsed options is not a valid object' ); - } -} catch ( err ) { - console.error( 'Failed to parse process arguments as JSON:', err ); - process.exit( 1 ); -} -let server: WPNowServer; - -const handlers: Handlers = { - 'start-server': createHandler( start ), - 'stop-server': createHandler( stop ), - 'run-php': createHandler( runPhp ), -}; - -async function start() { - // Lazy load the provider based on environment variable - const providerType = process.env.WORDPRESS_PROVIDER_TYPE || 'wp-now'; - - switch ( providerType ) { - case 'wp-now': { - const { startServer } = await import( 'vendor/wp-now/src' ); - server = await startServer( options ); - return { - php: { - documentRoot: server.php.documentRoot, - }, - }; - } - default: - throw new Error( `Unknown WordPress provider type: ${ providerType }` ); - } -} - -async function stop() { - await server?.stopServer(); -} - -async function runPhp( data: unknown ) { - const request = data as PHPRunOptions; - if ( ! request ) { - throw Error( 'PHP request is not valid' ); - } - const response = await server.php.run( request ); - return response.text; -} - -function createHandler< T >( handler: ( data: unknown ) => T ) { - return async ( message: string, messageId: number, data: unknown ) => { - try { - const response = await handler( data ); - process.parentPort.postMessage( { - message, - messageId, - data: response, - } ); - } catch ( error ) { - const errorObj = error as Error; - if ( ! errorObj ) { - process.parentPort.postMessage( { message, messageId, error: Error( 'Unknown Error' ) } ); - return; - } - process.parentPort.postMessage( { - message, - messageId, - error: errorObj, - } ); - } - }; -} - -process.parentPort.on( 'message', async ( { data: messagePayload } ) => { - const { message, messageId, data }: { message: MessageName; messageId: number; data: unknown } = - messagePayload; - const handler = handlers[ message ]; - if ( ! handler ) { - process.parentPort.postMessage( { - message, - messageId, - error: Error( `No handler defined for message '${ message }'` ), - } ); - return; - } - await handler( message, messageId, data ); -} ); diff --git a/src/lib/wordpress-provider/wp-now/site-server-process.ts b/src/lib/wordpress-provider/wp-now/site-server-process.ts deleted file mode 100644 index 00780d55d8..0000000000 --- a/src/lib/wordpress-provider/wp-now/site-server-process.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { app, utilityProcess, UtilityProcess } from 'electron'; -import path from 'path'; -import { kill } from 'process'; -import { PHPRunOptions } from '@php-wasm/universal'; -import * as Sentry from '@sentry/electron/renderer'; -import { getWordPressProviderType } from 'src/lib/wordpress-provider'; -import { WPNowOptions } from 'vendor/wp-now/src/config'; -import type { WordPressServerProcess } from '../types'; - -export type MessageName = 'start-server' | 'stop-server' | 'run-php'; - -const DEFAULT_RESPONSE_TIMEOUT = 120000; - -export default class SiteServerProcess implements WordPressServerProcess { - lastMessageId = 0; - options: WPNowOptions; - process?: UtilityProcess; - php?: { documentRoot: string }; - url: string; - exitCode: number | null = null; - - constructor( options: WPNowOptions ) { - this.options = options; - this.url = options.absoluteUrl ?? ''; - } - - async start(): Promise< void > { - return new Promise( ( resolve, reject ) => { - const spawnListener = async () => { - const messageId = this.sendMessage( 'start-server' ); - try { - const { php } = await this.waitForResponse< Pick< SiteServerProcess, 'php' > >( - 'start-server', - messageId - ); - this.php = php; - // Removing exit listener as we only need it upon starting - this.process?.off( 'exit', exitListener ); - resolve(); - } catch ( error ) { - reject( error ); - } - }; - const exitListener = ( code: number ) => { - this.exitCode = code; - if ( code !== 0 ) { - reject( new Error( `Site server process exited with code ${ code } upon starting` ) ); - } - }; - - this.process = utilityProcess - .fork( path.join( __dirname, 'siteServerProcess.js' ), [ JSON.stringify( this.options ) ], { - serviceName: 'studio-site-server', - env: { - ...process.env, - STUDIO_IN_CHILD_PROCESS: 'true', - STUDIO_APP_NAME: app.name, - STUDIO_APP_DATA_PATH: app.getPath( 'appData' ), - STUDIO_APP_LOGS_PATH: app.getPath( 'logs' ), - WORDPRESS_PROVIDER_TYPE: getWordPressProviderType(), - }, - } ) - .on( 'spawn', spawnListener ) - .on( 'exit', exitListener ); - } ); - } - - async stop() { - const message = 'stop-server'; - const messageId = this.sendMessage( message ); - try { - await this.waitForResponse( message, messageId, 5_000 ); - } finally { - await this.#killProcess(); - } - } - - async runPhp( data: PHPRunOptions ): Promise< string > { - const message = 'run-php'; - const messageId = this.sendMessage( message, data ); - return await this.waitForResponse( message, messageId ); - } - - sendMessage< T >( message: MessageName, data?: T ) { - const process = this.process; - if ( ! process ) { - throw Error( 'Server process is not running' ); - } - - const messageId = this.lastMessageId++; - process.postMessage( { message, messageId, data } ); - return messageId; - } - - async waitForResponse< T = undefined >( - originalMessage: MessageName, - originalMessageId: number, - timeout = DEFAULT_RESPONSE_TIMEOUT - ): Promise< T > { - const process = this.process; - if ( ! process ) { - throw Error( 'Server process is not running' ); - } - - return new Promise( ( resolve, reject ) => { - const handler = ( { - message, - messageId, - data, - error, - }: { - message: MessageName; - messageId: number; - data: T; - error?: Error; - } ) => { - if ( message !== originalMessage || messageId !== originalMessageId ) { - return; - } - process.removeListener( 'message', handler ); - clearTimeout( timeoutId ); - if ( typeof error !== 'undefined' ) { - reject( error ); - return; - } - resolve( data ); - }; - - const timeoutId = setTimeout( () => { - reject( new Error( `Request for message ${ originalMessage } timed out` ) ); - process.removeListener( 'message', handler ); - }, timeout ); - - process.addListener( 'message', handler ); - } ); - } - - async #killProcess(): Promise< void > { - const process = this.process; - if ( ! process || this.exitCode !== null ) { - throw Error( 'Server process is not running. Exit code: ' + this.exitCode ); - } - - return new Promise< void >( ( resolve, reject ) => { - process.once( 'exit', ( code ) => { - if ( code !== 0 ) { - reject( new Error( `Site server process exited with code ${ code } upon stopping` ) ); - } else { - resolve(); - } - } ); - if ( ! process.kill() ) { - if ( process.pid ) { - kill( process.pid, 'SIGKILL' ); - } else { - resolve(); - } - } - } ).catch( ( error ) => { - Sentry.captureException( error ); - } ); - } -} diff --git a/src/lib/wordpress-provider/wp-now/wp-now-provider.ts b/src/lib/wordpress-provider/wp-now/wp-now-provider.ts deleted file mode 100644 index 88c281e74d..0000000000 --- a/src/lib/wordpress-provider/wp-now/wp-now-provider.ts +++ /dev/null @@ -1,350 +0,0 @@ -import path from 'path'; -import fs from 'fs-extra'; -import semver from 'semver'; -import { pathExists, recursiveCopyDirectory, isEmptyDir } from 'common/lib/fs-utils'; -import { isOnline } from 'common/lib/network-utils'; -import { isValidWordPressVersion } from 'common/lib/wordpress-version-utils'; -import { getPreferredSiteLanguage } from 'src/lib/site-language'; -import { copyBundledLatestWPVersion } from 'src/setup-wp-server-files'; -import { SiteServer } from 'src/site-server'; -import { getWpNowConfig } from 'vendor/wp-now/src'; -import { WPNowMode } from 'vendor/wp-now/src/config'; -import { - downloadWordPress, - downloadWpCli, - downloadSQLiteCommand, -} from 'vendor/wp-now/src/download'; -import { executeWPCli } from 'vendor/wp-now/src/execute-wp-cli'; -import { - DEFAULT_PHP_VERSION, - DEFAULT_WORDPRESS_VERSION, - ALLOWED_PHP_VERSIONS, - SQLITE_FILENAME, -} from '../constants'; -import { - getWordPressVersionPath, - getSqlitePath, - getWpCliPath, - getWpCliFolderPath, -} from './path-utilities'; -import SiteServerProcess from './site-server-process'; -import type { - WordPressProvider, - ServerOptions, - WordPressServerInstance, - WordPressServerOptions, - WordPressServerProcess, -} from '../types'; -import type { WPNowOptions } from 'vendor/wp-now/src/config'; - -export class WpNowProvider implements WordPressProvider { - readonly PROVIDER_TYPE = 'wp-now'; - - // Constants - static readonly DEFAULT_PHP_VERSION = DEFAULT_PHP_VERSION; - static readonly DEFAULT_WORDPRESS_VERSION = DEFAULT_WORDPRESS_VERSION; - static readonly ALLOWED_PHP_VERSIONS = ALLOWED_PHP_VERSIONS; - static readonly MINIMUM_WORDPRESS_VERSION = '6.2.6'; - static readonly SQLITE_FILENAME = SQLITE_FILENAME; - - // Instance constants for interface compatibility - readonly DEFAULT_PHP_VERSION = WpNowProvider.DEFAULT_PHP_VERSION; - readonly DEFAULT_WORDPRESS_VERSION = WpNowProvider.DEFAULT_WORDPRESS_VERSION; - readonly ALLOWED_PHP_VERSIONS = WpNowProvider.ALLOWED_PHP_VERSIONS; - readonly MINIMUM_WORDPRESS_VERSION = WpNowProvider.MINIMUM_WORDPRESS_VERSION; - readonly SQLITE_FILENAME = WpNowProvider.SQLITE_FILENAME; - - // Path utilities (static methods for setup use) - static getWordPressVersionPath( version: string ): string { - return getWordPressVersionPath( version ); - } - - static getWpCliPath(): string { - return getWpCliPath(); - } - - static getWpCliFolderPath(): string { - return getWpCliFolderPath(); - } - - getSqlitePath(): string { - return getSqlitePath(); - } - - getWpLoadPath( serverProcess: WordPressServerProcess ): string { - return `${ serverProcess.php?.documentRoot }/wp-load.php`; - } - - getWpCliPath(): string { - return WpNowProvider.getWpCliPath(); - } - - getWpCliFolderPath(): string { - return WpNowProvider.getWpCliFolderPath(); - } - - static async downloadWordPress( - version?: string, - options?: { overwrite: boolean } - ): Promise< void > { - await downloadWordPress( version, options ); - } - - static async downloadWpCli( - overwrite?: boolean - ): Promise< { downloaded: boolean; statusCode: number } > { - return await downloadWpCli( overwrite ); - } - - async downloadWpCli( - overwrite?: boolean - ): Promise< { downloaded: boolean; statusCode: number } > { - return await WpNowProvider.downloadWpCli( overwrite ); - } - - async downloadSQLiteCommand( downloadUrl: string, targetPath: string ): Promise< void > { - await downloadSQLiteCommand( downloadUrl, targetPath ); - } - - async setupWordPressSite( server: SiteServer, wpVersion = 'latest' ): Promise< boolean > { - try { - const { path } = server.details; - if ( ( await pathExists( path ) ) && ! ( await isEmptyDir( path ) ) ) { - // We can only create into a clean directory - return false; - } - - const wpVersionPath = WpNowProvider.getWordPressVersionPath( wpVersion ); - const wpVersionExists = await pathExists( wpVersionPath ); - - if ( ! wpVersionExists ) { - if ( await isOnline() ) { - try { - await WpNowProvider.downloadWordPress( wpVersion, { overwrite: false } ); - } catch ( error ) { - console.error( `Failed to download WordPress version ${ wpVersion }:`, error ); - throw new Error( - `Failed to download WordPress version ${ wpVersion }. Please try a different version.` - ); - } - } else if ( wpVersion === 'latest' ) { - await copyBundledLatestWPVersion(); - } else { - return false; - } - } - - await this.verifyWordPressChecksums( wpVersion ); - await this.purgeWpConfig( wpVersion ); - await recursiveCopyDirectory( WpNowProvider.getWordPressVersionPath( wpVersion ), path ); - - return true; - } catch ( error ) { - console.error( 'Error in setupWordPressSite:', error ); - throw error; - } - } - - async setupWordPressFilesOnly( _path: string ): Promise< void > { - // No-op for wp-now provider - } - - async installWordPressWhenNoWpConfig( - _server: SiteServer, - _siteName: string, - _adminPassword: string - ): Promise< void > { - // wp-now handles WordPress installation automatically via installationSteps - // in vendor/wp-now/src/wp-now.ts, so no action needed here - } - - async startServer( options: ServerOptions ): Promise< WordPressServerInstance > { - const wpNowOptions = await getWpNowConfig( { - path: options.path, - port: options.port, - adminPassword: options.adminPassword, - siteTitle: options.siteTitle, - php: options.phpVersion, - wp: options.wpVersion, - isWpAutoUpdating: options.isWpAutoUpdating, - } ); - - if ( options.absoluteUrl ) { - wpNowOptions.absoluteUrl = options.absoluteUrl; - } - - if ( options.siteLanguage ) { - wpNowOptions.siteLanguage = options.siteLanguage; - } else { - wpNowOptions.siteLanguage = await getPreferredSiteLanguage( wpNowOptions.wordPressVersion ); - } - - if ( wpNowOptions.mode !== WPNowMode.WORDPRESS ) { - throw new Error( - `Site server started with Playground's '${ wpNowOptions.mode }' mode. Studio only supports 'wordpress' mode.` - ); - } - - // Map wp-now options to provider-agnostic options - const providerOptions: WordPressServerOptions = { - port: wpNowOptions.port, - phpVersion: wpNowOptions.phpVersion, - documentRoot: wpNowOptions.documentRoot, - absoluteUrl: wpNowOptions.absoluteUrl, - projectPath: wpNowOptions.projectPath, - wpContentPath: wpNowOptions.wpContentPath, - wordPressVersion: wpNowOptions.wordPressVersion, - isWpAutoUpdating: wpNowOptions.isWpAutoUpdating, - adminPassword: wpNowOptions.adminPassword, - siteTitle: wpNowOptions.siteTitle, - siteLanguage: wpNowOptions.siteLanguage, - }; - - return { - url: options.absoluteUrl || `http://localhost:${ wpNowOptions.port }`, - options: providerOptions, - _internal: wpNowOptions, - }; - } - - async executeWPCli( - projectPath: string, - args: string[], - options?: { phpVersion?: string } - ): Promise< { stdout: string; stderr: string; exitCode: number } > { - return await executeWPCli( projectPath, args, options ); - } - - isValidWordPressVersion( version: string ): boolean { - return isValidWordPressVersion( version ); - } - - createServerProcess( serverInstance: WordPressServerInstance ): WordPressServerProcess { - return new SiteServerProcess( serverInstance._internal as WPNowOptions ); - } - - /** - * Deletes the wp-config.php file at the specified path. - * - * Prior to enforcing Playground's WordPress mode, it was possible to run - * alternative modes, which mutated Studio's bundled WordPress installation. - * To rectify the situation, we remove the erroneous wp-config.php file. - * - * If we are confident this situation is not a widespread issue, we can remove - * this function in the future. - * - * @param wpVersion - The WordPress version the wp-config.php file. - */ - private async purgeWpConfig( wpVersion: string ): Promise< void > { - const wpVersionPath = WpNowProvider.getWordPressVersionPath( wpVersion ); - const wpConfigPath = path.join( wpVersionPath, 'wp-config.php' ); - - if ( ! ( await pathExists( wpConfigPath ) ) ) { - return; - } - - await fs.promises.unlink( wpConfigPath ); - } - - /** - * Verifies the checksums of a WordPress installation. - * If checksums don't match, it will retry the download once. - * - * @param wpVersion - The WordPress version to verify - */ - private async verifyWordPressChecksums( wpVersion: string ): Promise< void > { - if ( ! ( await isOnline() ) ) { - console.log( 'Skipping WordPress checksum verification - offline mode' ); - return; - } - - try { - console.log( `Verifying checksums for WordPress version ${ wpVersion }...` ); - const result = await this.executeWPCli( WpNowProvider.getWordPressVersionPath( wpVersion ), [ - 'core', - 'verify-checksums', - '--skip-plugins', - '--skip-themes', - ] ); - - if ( result.exitCode !== 0 ) { - console.log( `Checksums don't match for WordPress ${ wpVersion }, retrying download...` ); - await fs.remove( WpNowProvider.getWordPressVersionPath( wpVersion ) ); - // Retry download one more time - await WpNowProvider.downloadWordPress( wpVersion, { overwrite: true } ); - } - } catch ( error ) { - console.error( `Failed to verify WordPress checksums:`, error ); - throw error; - } - } - - // WP-CLI Version Management - async updateLatestWPCliVersion() { - let shouldOverwrite = false; - const pathExist = await fs.pathExists( WpNowProvider.getWpCliPath() ); - if ( pathExist ) { - shouldOverwrite = await this.isWPCliInstallationOutdated(); - } - await WpNowProvider.downloadWpCli( shouldOverwrite ); - } - - async isWPCliInstallationOutdated(): Promise< boolean > { - const installedVersion = await this.getWPCliVersionFromInstallation(); - const latestVersion = await this.getLatestWPCliVersion(); - - if ( ! installedVersion ) { - return true; - } - - if ( ! latestVersion ) { - return false; - } - - try { - return semver.lt( installedVersion, latestVersion ); - } catch ( _error ) { - return false; - } - } - - private latestWPCliVersionCache: string | null = null; - - private async getLatestWPCliVersion() { - // Only fetch the latest version once per app session - if ( this.latestWPCliVersionCache ) { - return this.latestWPCliVersionCache; - } - - try { - const response = await fetch( - 'https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=1' - ); - const data: Record< string, string >[] = await response.json(); - this.latestWPCliVersionCache = data?.[ 0 ]?.tag_name || ''; - } catch ( _error ) { - // Discard the failed fetch, return the cache - } - - return this.latestWPCliVersionCache || ''; - } - - async getWPCliVersionFromInstallation(): Promise< string > { - /** - * Version checks don't require a project folder to be mounted for WP-cli - * to execute a version check. - */ - const { stdout } = await this.executeWPCli( WpNowProvider.getWpCliFolderPath(), [ - '--version', - ] ); - - if ( stdout?.startsWith( 'WP-CLI ' ) ) { - const version = stdout.split( ' ' )[ 1 ]; - if ( ! version ) { - return ''; - } - return `v${ version }`; - } - return ''; - } -} diff --git a/src/lib/wordpress-server-types.ts b/src/lib/wordpress-server-types.ts new file mode 100644 index 0000000000..197b2edec7 --- /dev/null +++ b/src/lib/wordpress-server-types.ts @@ -0,0 +1,20 @@ +/** + * Types for WordPress server processes. + */ + +export type AllowedPHPVersion = string; + +export interface WordPressServerProcess { + url: string; + php?: { + documentRoot: string; + request?: ( options: { + url: string; + method: string; + body: Record< string, string >; + } ) => Promise< { text: string } >; + }; + start( siteId?: string ): Promise< void >; + stop(): Promise< void >; + runPhp( data: { code: string; [ key: string ]: unknown } ): Promise< string >; +} diff --git a/src/lib/wordpress-setup.ts b/src/lib/wordpress-setup.ts new file mode 100644 index 0000000000..988e81e15a --- /dev/null +++ b/src/lib/wordpress-setup.ts @@ -0,0 +1,22 @@ +/** + * WordPress site setup utilities. + * Handles copying WordPress files for offline site creation. + */ + +import nodePath from 'path'; +import { recursiveCopyDirectory, pathExists } from 'common/lib/fs-utils'; +import { getResourcesPath } from 'src/storage/paths'; + +/** + * Copy bundled WordPress files to a directory. + * Used when setting up WordPress without full site creation. + */ +export async function setupWordPressFilesOnly( path: string ): Promise< void > { + const bundledWPPath = nodePath.join( getResourcesPath(), 'wp-files', 'latest', 'wordpress' ); + + if ( ! ( await pathExists( bundledWPPath ) ) ) { + throw new Error( 'Bundled WordPress files not found. Please reinstall WordPress Studio.' ); + } + + await recursiveCopyDirectory( bundledWPPath, path ); +} diff --git a/src/lib/wp-versions.ts b/src/lib/wp-versions.ts index bc5c431e80..618fa979ae 100644 --- a/src/lib/wp-versions.ts +++ b/src/lib/wp-versions.ts @@ -2,10 +2,14 @@ import path from 'path'; import fs from 'fs-extra'; import semver from 'semver'; import { recursiveCopyDirectory } from 'common/lib/fs-utils'; -import { WpNowProvider } from 'src/lib/wordpress-provider/wp-now'; +import { downloadWordPress } from 'src/lib/download-utils'; +import { getWordPressVersionPath } from 'src/lib/server-files-paths'; export const MINIMUM_SUPPORTED_WP_VERSION = 6; +// Default WordPress version when API call fails +const DEFAULT_WORDPRESS_VERSION = 'latest'; + async function fetchWordPressVersions() { try { const response = await fetch( 'https://api.wordpress.org/core/stable-check/1.0/' ); @@ -30,13 +34,13 @@ async function fetchWordPressVersions() { ); return { versions, latest: latestVersion }; } catch ( exception ) { - return { versions: [], latest: WpNowProvider.DEFAULT_WORDPRESS_VERSION }; + return { versions: [], latest: DEFAULT_WORDPRESS_VERSION }; } } async function getLatestWordPressVersion() { const wordPressVersions = await fetchWordPressVersions(); - return wordPressVersions.latest ?? WpNowProvider.DEFAULT_WORDPRESS_VERSION; + return wordPressVersions.latest ?? DEFAULT_WORDPRESS_VERSION; } export async function getWordPressVersionFromInstallation( installationPath: string ) { @@ -55,7 +59,7 @@ export async function getWordPressVersionFromInstallation( installationPath: str export async function updateLatestWordPressVersion() { let shouldOverwrite = false; - const latestVersionPath = WpNowProvider.getWordPressVersionPath( 'latest' ); + const latestVersionPath = getWordPressVersionPath( 'latest' ); const latestVersionFiles = ( await fs.pathExists( latestVersionPath ) ) ? await fs.readdir( latestVersionPath ) : []; @@ -66,10 +70,10 @@ export async function updateLatestWordPressVersion() { // We keep a copy of the latest installed version instead of removing it. await recursiveCopyDirectory( latestVersionPath, - WpNowProvider.getWordPressVersionPath( installedVersion ) + getWordPressVersionPath( installedVersion ) ); shouldOverwrite = true; } } - await WpNowProvider.downloadWordPress( 'latest', { overwrite: shouldOverwrite } ); + await downloadWordPress( 'latest', { overwrite: shouldOverwrite } ); } diff --git a/src/modules/add-site/components/create-site-form.tsx b/src/modules/add-site/components/create-site-form.tsx index 51d9b08248..a18f4ace1f 100644 --- a/src/modules/add-site/components/create-site-form.tsx +++ b/src/modules/add-site/components/create-site-form.tsx @@ -12,7 +12,7 @@ import { WPVersionSelector } from 'src/components/wp-version-selector'; import { cx } from 'src/lib/cx'; import { getIpcApi } from 'src/lib/get-ipc-api'; import { getLocalizedLink } from 'src/lib/get-localized-link'; -import { AllowedPHPVersion } from 'src/lib/wordpress-provider/constants'; +import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; import { useRootSelector, useI18nLocale } from 'src/stores'; import { useCheckCertificateTrustQuery } from 'src/stores/certificate-trust-api'; import { diff --git a/src/modules/cli/lib/cli-server-process.ts b/src/modules/cli/lib/cli-server-process.ts index 013c94a867..a8eddb281c 100644 --- a/src/modules/cli/lib/cli-server-process.ts +++ b/src/modules/cli/lib/cli-server-process.ts @@ -1,5 +1,5 @@ import { executeCliCommand } from './execute-command'; -import type { WordPressServerProcess } from 'src/lib/wordpress-provider/types'; +import type { WordPressServerProcess } from 'src/lib/wordpress-server-types'; /** * A WordPressServerProcess implementation that delegates to CLI commands. diff --git a/src/modules/cli/lib/cli-site-creator.ts b/src/modules/cli/lib/cli-site-creator.ts index 5b3270be8c..7fd92eb14e 100644 --- a/src/modules/cli/lib/cli-site-creator.ts +++ b/src/modules/cli/lib/cli-site-creator.ts @@ -3,9 +3,9 @@ import os from 'node:os'; import path from 'node:path'; import { z } from 'zod'; import { SiteCommandLoggerAction } from 'common/logger-actions'; +import type { Blueprint } from 'common/types/blueprint'; import { sendIpcEventToRenderer } from 'src/ipc-utils'; import { executeCliCommand } from './execute-command'; -import type { Blueprint } from '@wp-playground/blueprints'; const cliEventSchema = z.discriminatedUnion( 'action', [ z.object( { diff --git a/src/modules/preview-site/lib/tests/version-comparison.test.ts b/src/modules/preview-site/lib/tests/version-comparison.test.ts index 6d9670a88b..d2fbc417f8 100644 --- a/src/modules/preview-site/lib/tests/version-comparison.test.ts +++ b/src/modules/preview-site/lib/tests/version-comparison.test.ts @@ -1,4 +1,4 @@ -import { DEFAULT_PHP_VERSION } from 'src/lib/wordpress-provider/constants'; +import { RecommendedPHPVersion as DEFAULT_PHP_VERSION } from 'common/types/php-versions'; import { hasVersionMismatch } from 'src/modules/preview-site/lib/version-comparison'; describe( 'hasVersionMismatch', () => { diff --git a/src/modules/preview-site/lib/version-comparison.ts b/src/modules/preview-site/lib/version-comparison.ts index ac7eac5daf..0df153d471 100644 --- a/src/modules/preview-site/lib/version-comparison.ts +++ b/src/modules/preview-site/lib/version-comparison.ts @@ -1,5 +1,5 @@ import semver from 'semver'; -import { DEFAULT_PHP_VERSION } from 'src/lib/wordpress-provider/constants'; +import { RecommendedPHPVersion as DEFAULT_PHP_VERSION } from 'common/types/php-versions'; /** * Compares the WordPress and PHP versions of the current site with the versions supported by Jurassic Ninja preview sites. diff --git a/src/modules/site-settings/edit-site-details.tsx b/src/modules/site-settings/edit-site-details.tsx index 5ed3d17985..a3a517498c 100644 --- a/src/modules/site-settings/edit-site-details.tsx +++ b/src/modules/site-settings/edit-site-details.tsx @@ -12,7 +12,7 @@ import { WPVersionSelector } from 'src/components/wp-version-selector'; import { useSiteDetails } from 'src/hooks/use-site-details'; import { cx } from 'src/lib/cx'; import { getIpcApi } from 'src/lib/get-ipc-api'; -import { AllowedPHPVersion } from 'src/lib/wordpress-provider/constants'; +import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; import { useRootSelector } from 'src/stores'; import { useCheckCertificateTrustQuery } from 'src/stores/certificate-trust-api'; import { diff --git a/src/setup-wp-server-files.ts b/src/setup-wp-server-files.ts index aa74d4a7c6..ff3ec17e4e 100644 --- a/src/setup-wp-server-files.ts +++ b/src/setup-wp-server-files.ts @@ -2,21 +2,30 @@ import path from 'path'; import fs from 'fs-extra'; import semver from 'semver'; import { recursiveCopyDirectory } from 'common/lib/fs-utils'; +import { downloadWordPress, updateLatestWPCliVersion } from 'src/lib/download-utils'; +import { + getWordPressVersionPath, + getSqlitePath, + getWpCliPath, + getWpCliFolderPath, +} from 'src/lib/server-files-paths'; import { getSqliteCommandPath, updateLatestSQLiteCommandVersion, getSQLiteCommandVersion, } from 'src/lib/sqlite-command-versions'; import { getSqliteVersionFromInstallation } from 'src/lib/sqlite-versions'; -import { getSqlitePath, getWordPressProvider } from 'src/lib/wordpress-provider'; -import { WpNowProvider } from 'src/lib/wordpress-provider/wp-now'; import { getWordPressVersionFromInstallation, updateLatestWordPressVersion, } from 'src/lib/wp-versions'; +import { executeCliCommand } from 'src/modules/cli/lib/execute-command'; import { getResourcesPath } from 'src/storage/paths'; -// Tries to copy the app's bundled WordPress version to `wp-now` WP versions if needed +// SQLite integration folder name +const SQLITE_FILENAME = 'sqlite-database-integration'; + +// Tries to copy the app's bundled WordPress version to server files if needed export async function copyBundledLatestWPVersion() { const bundledWPVersionPath = path.join( getResourcesPath(), 'wp-files', 'latest', 'wordpress' ); const bundledWPVersion = semver.coerce( @@ -25,7 +34,7 @@ export async function copyBundledLatestWPVersion() { if ( ! bundledWPVersion ) { return; } - const latestWPVersionPath = WpNowProvider.getWordPressVersionPath( 'latest' ); + const latestWPVersionPath = getWordPressVersionPath( 'latest' ); const latestWPVersion = await getWordPressVersionFromInstallation( latestWPVersionPath ); const latestWPSemVersion = semver.coerce( latestWPVersion ); const isBundledVersionNewer = @@ -33,13 +42,9 @@ export async function copyBundledLatestWPVersion() { if ( ! latestWPVersion || isBundledVersionNewer ) { if ( isBundledVersionNewer ) { // We keep a copy of the latest installed version instead of removing it. - await fs.move( - latestWPVersionPath, - WpNowProvider.getWordPressVersionPath( latestWPVersion ), - { - overwrite: true, - } - ); + await fs.move( latestWPVersionPath, getWordPressVersionPath( latestWPVersion ), { + overwrite: true, + } ); } console.log( `Copying bundled WP version ${ bundledWPVersion } as 'latest' version…` ); await recursiveCopyDirectory( bundledWPVersionPath, latestWPVersionPath ); @@ -47,11 +52,7 @@ export async function copyBundledLatestWPVersion() { } async function copyBundledSqlite() { - const bundledSqlitePath = path.join( - getResourcesPath(), - 'wp-files', - getWordPressProvider().SQLITE_FILENAME - ); + const bundledSqlitePath = path.join( getResourcesPath(), 'wp-files', SQLITE_FILENAME ); const bundledSqliteVersion = semver.coerce( await getSqliteVersionFromInstallation( bundledSqlitePath ), { @@ -78,12 +79,12 @@ async function copyBundledSqlite() { } async function copyBundledWPCLI() { - const bundledWPCLIInstalled = await fs.pathExists( WpNowProvider.getWpCliPath() ); + const bundledWPCLIInstalled = await fs.pathExists( getWpCliPath() ); if ( bundledWPCLIInstalled ) { return; } const bundledWPCLIPath = path.join( getResourcesPath(), 'wp-files', 'wp-cli', 'wp-cli.phar' ); - await fs.copyFile( bundledWPCLIPath, WpNowProvider.getWpCliPath() ); + await fs.copyFile( bundledWPCLIPath, getWpCliPath() ); } async function copyBundledSQLiteCommand() { @@ -116,7 +117,7 @@ async function copyBundledTranslations() { return; } const installedTranslationsPath = path.join( - WpNowProvider.getWordPressVersionPath( 'latest' ), + getWordPressVersionPath( 'latest' ), 'available-site-translations.json' ); // Always copy the bundled translations file to ensure CLI has access to it @@ -131,10 +132,32 @@ export async function setupWPServerFiles() { await copyBundledTranslations(); } +/** + * Get WP-CLI version from installation by running wp-cli --version + */ +async function getWPCliVersionFromInstallation(): Promise< string > { + return new Promise( ( resolve ) => { + const [ emitter ] = executeCliCommand( [ 'wp', '--path', getWpCliFolderPath(), '--version' ], { + output: 'capture', + } ); + + emitter.on( 'success', ( { result } ) => { + const stdout = result?.stdout || ''; + if ( stdout.startsWith( 'WP-CLI ' ) ) { + const version = stdout.split( ' ' )[ 1 ]; + resolve( version ? `v${ version }` : '' ); + } else { + resolve( '' ); + } + } ); + + emitter.on( 'failure', () => resolve( '' ) ); + emitter.on( 'error', () => resolve( '' ) ); + } ); +} + export async function updateWPServerFiles() { await updateLatestWordPressVersion(); - const provider = new WpNowProvider(); - await provider.updateLatestWPCliVersion(); - + await updateLatestWPCliVersion( getWPCliVersionFromInstallation ); await updateLatestSQLiteCommandVersion(); } diff --git a/src/site-server.ts b/src/site-server.ts index 18795213d4..078d139a9d 100644 --- a/src/site-server.ts +++ b/src/site-server.ts @@ -1,10 +1,10 @@ import fs from 'fs'; import nodePath from 'path'; import * as Sentry from '@sentry/electron/main'; -import { BlueprintV1Declaration } from '@wp-playground/blueprints'; import fsExtra from 'fs-extra'; import { parse } from 'shell-quote'; import { z } from 'zod'; +import { SQLITE_FILENAME } from 'common/constants'; import { portFinder } from 'common/lib/port-finder'; import { WP_CLI_DEFAULT_RESPONSE_TIMEOUT, @@ -14,27 +14,20 @@ import { deleteSiteCertificate, generateSiteCertificate } from 'src/lib/certific import { getSiteUrl } from 'src/lib/get-site-url'; import { removeDomainFromHosts, updateDomainInHosts } from 'src/lib/hosts-file'; import { updateSiteUrl } from 'src/lib/update-site-url'; -import { setupWordPressSite, getWordPressProvider } from 'src/lib/wordpress-provider'; import { CliServerProcess } from 'src/modules/cli/lib/cli-server-process'; import { createSiteViaCli, type CreateSiteOptions } from 'src/modules/cli/lib/cli-site-creator'; import { executeCliCommand } from 'src/modules/cli/lib/execute-command'; import { createScreenshotWindow } from 'src/screenshot-window'; import { getSiteThumbnailPath } from 'src/storage/paths'; import { loadUserData } from 'src/storage/user-data'; -import type { WordPressServerProcess } from 'src/lib/wordpress-provider/types'; +import type { BlueprintV1Declaration } from 'common/types/blueprint'; +import type { WordPressServerProcess } from 'src/lib/wordpress-server-types'; export type WpCliResult = { stdout: string; stderr: string; exitCode: number }; const servers = new Map< string, SiteServer >(); const deletedServers: string[] = []; -export async function createSiteWorkingDirectory( - server: SiteServer, - wpVersion = 'latest' -): Promise< boolean > { - return setupWordPressSite( server, wpVersion ); -} - export async function stopAllServersOnQuit() { // We're quitting so this doesn't have to be tidy, just stop the // servers as directly as possible. @@ -447,12 +440,11 @@ export class SiteServer { async hasSQLitePlugin(): Promise< boolean > { const wpContentPath = nodePath.join( this.details.path, 'wp-content' ); - const sqliteFilename = getWordPressProvider().SQLITE_FILENAME; const sqliteIntegrationPaths = { - muPlugin: nodePath.join( wpContentPath, 'mu-plugins', sqliteFilename ), - muPluginLegacy: nodePath.join( wpContentPath, 'mu-plugins', `${ sqliteFilename }-main` ), - regularPlugin: nodePath.join( wpContentPath, 'plugins', sqliteFilename ), + muPlugin: nodePath.join( wpContentPath, 'mu-plugins', SQLITE_FILENAME ), + muPluginLegacy: nodePath.join( wpContentPath, 'mu-plugins', `${ SQLITE_FILENAME }-main` ), + regularPlugin: nodePath.join( wpContentPath, 'plugins', SQLITE_FILENAME ), }; const requiredConfigPaths = { diff --git a/src/storage/user-data.ts b/src/storage/user-data.ts index a173dc43a0..e67775650f 100644 --- a/src/storage/user-data.ts +++ b/src/storage/user-data.ts @@ -1,8 +1,8 @@ import { app } from 'electron'; import fs from 'fs'; import nodePath from 'node:path'; -import { SupportedPHPVersion, SupportedPHPVersions } from '@php-wasm/universal'; import * as Sentry from '@sentry/electron/main'; +import { SupportedPHPVersion, SupportedPHPVersions } from 'common/types/php-versions'; import { readFile, writeFile } from 'atomically'; import { LOCKFILE_STALE_TIME, LOCKFILE_WAIT_TIME } from 'common/constants'; import { isErrnoException } from 'common/lib/is-errno-exception'; diff --git a/src/tests/index.test.ts b/src/tests/index.test.ts index 09d049ee9b..3e4e0fda7d 100644 --- a/src/tests/index.test.ts +++ b/src/tests/index.test.ts @@ -21,7 +21,6 @@ jest.mock( 'atomically', () => ( { readFile: jest.fn(), writeFile: jest.fn(), } ) ); -jest.mock( 'src/lib/wordpress-provider' ); ( readFile as jest.Mock ).mockResolvedValue( JSON.stringify( { sites: [] } ) ); require( 'fs' ).__setFileContents( normalize( '/path/to/app/temp/com.wordpress.studio/' ), '' ); diff --git a/src/tests/ipc-handlers.test.ts b/src/tests/ipc-handlers.test.ts index c7882d55ca..0e88edbbab 100644 --- a/src/tests/ipc-handlers.test.ts +++ b/src/tests/ipc-handlers.test.ts @@ -18,16 +18,8 @@ jest.mock( 'fs' ); jest.mock( 'fs-extra' ); jest.mock( 'common/lib/fs-utils' ); jest.mock( 'src/site-server' ); -jest.mock( 'src/lib/wordpress-provider', () => ( { - downloadWordPress: jest.fn(), - downloadWpCli: jest.fn(), - downloadSQLiteCommand: jest.fn(), - getWordPressProvider: jest.fn().mockReturnValue( { - DEFAULT_PHP_VERSION: '8.3', - DEFAULT_WORDPRESS_VERSION: 'latest', - SQLITE_FILENAME: 'sqlite.php', - setupWordPressFilesOnly: jest.fn().mockResolvedValue( undefined ), - } ), +jest.mock( 'src/lib/wordpress-setup', () => ( { + setupWordPressFilesOnly: jest.fn().mockResolvedValue( undefined ), } ) ); jest.mock( 'src/main-window' ); jest.mock( '@sentry/electron/main' ); diff --git a/src/tests/site-server.test.ts b/src/tests/site-server.test.ts index 43d33e9595..edbaffa9af 100644 --- a/src/tests/site-server.test.ts +++ b/src/tests/site-server.test.ts @@ -10,20 +10,10 @@ jest.mock( 'common/lib/passwords' ); // Mock the CLI server process jest.mock( 'src/modules/cli/lib/cli-server-process' ); -// Mock the WordPress provider -jest.mock( 'src/lib/wordpress-provider', () => { - const mockProvider = { - DEFAULT_PHP_VERSION: '8.0', - DEFAULT_WORDPRESS_VERSION: 'latest', - ALLOWED_PHP_VERSIONS: [ '8.0', '8.1', '8.2', '8.3' ], - SQLITE_FILENAME: 'sqlite-database-integration', - }; - - return { - ...mockProvider, - getWordPressProvider: jest.fn( () => mockProvider ), - }; -} ); +// Mock the WordPress setup +jest.mock( 'src/lib/wordpress-setup', () => ( { + setupWordPressFilesOnly: jest.fn().mockResolvedValue( undefined ), +} ) ); // Mock port finder jest.mock( 'common/lib/port-finder', () => ( { diff --git a/vendor/wp-now/.eslintrc.json b/vendor/wp-now/.eslintrc.json deleted file mode 100644 index dd6da33e2d..0000000000 --- a/vendor/wp-now/.eslintrc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} - }, - { - "files": ["*.ts", "*.tsx"], - "rules": {} - }, - { - "files": ["*.js", "*.jsx"], - "rules": {} - } - ] -} diff --git a/vendor/wp-now/README.md b/vendor/wp-now/README.md deleted file mode 100644 index d4471811b6..0000000000 --- a/vendor/wp-now/README.md +++ /dev/null @@ -1,279 +0,0 @@ -# wp-now - -`wp-now` streamlines the process of setting up a local WordPress environment. - -It uses automatic mode detection to provide a fast setup process, regardless of whether you're working on a plugin or an entire site. You can easily switch between PHP and WordPress versions with just a configuration argument. Under the hood, `wp-now` is powered by WordPress Playground and only requires Node.js. - -![Demo GIF of wp-now](https://github.com/WordPress/wordpress-playground/assets/36432/474ecb85-d789-4db9-b820-a19c25da79ad) - -## Table of Contents - -- [Requirements](#requirements) -- [Usage](#usage) - - [Automatic Modes](#automatic-modes) - - [Arguments](#arguments) -- [Technical Details](#technical-details) -- [Using Blueprints](#using-blueprints) - - [Defining Custom URLs](#defining-custom-urls) - - [Defining Debugging Constants](#defining-debugging-constants) -- [Known Issues](#known-issues) -- [Comparisons](#comparisons) - - [Laravel Valet](#laravel-valet) - - [wp-env](#wp-env) -- [Contributing](#contributing) -- [Testing](#testing) -- [Publishing](#publishing) - -## Requirements - -Node 18 is the minimum supported version. Node 20 is required for Blueprint support. - -## Usage - - - -To run `wp-now` with one command, you can use [npx](https://docs.npmjs.com/cli/v10/commands/npx): - -```bash -npx @wp-now/wp-now start -``` - -You can also install `@wp-now/wp-now` globally to just run `wp-now` from any directory: - -```bash -npm install -g @wp-now/wp-now -``` - -Lastly, you can install `wp-now` via `git clone` if you'd like to hack on it too. See [Contributing](#contributing) for more details. - -Once installed, you can start a new server like so: - -```bash -cd wordpress-plugin-or-theme -wp-now start -``` - -Use the `--php=` and `--wp=` arguments to switch to different versions on the fly: - -```bash -wp-now start --wp=5.9 --php=7.4 -``` - -For the modes that support it, `wp-now` will create a persistent SQLite database and `wp-content` directory in `~/.wp-now`. Use the `--reset` argument to create a fresh project. - -Use `wp-now php ` to execute a specific PHP file: - -```bash -cd wordpress-plugin-or-theme -wp-now php my-file.php -``` - -### Automatic Modes - -`wp-now` automatically operates in a few different modes for both the `start` and the `php` commands. The selected mode depends on the directory in which it is executed: - -- **plugin**, **theme**, or **wp-content**: Loads the project files into a virtual filesytem with WordPress and a SQLite-based database. Everything (including WordPress core files, the database, `wp-config.php`, etc.) is stored in the user's home directory and loaded into the virtual filesystem. The latest version of WordPress will be used, unless the `--wp=` argument is provided. Here are the heuristics for each mode: - - **plugin** mode: Presence of a PHP file with 'Plugin Name:' in its contents. - - **theme** mode: Presence of a `style.css` file with 'Theme Name:' in its contents. - - **wp-content** mode: Presence of `plugins` and `themes` subdirectories. -- **wordpress**: Runs the directory as a WordPress installation when WordPress files are detected. An existing `wp-config.php` file will be used if it exists; if it doesn't exist, it will be created along with a SQLite database. -- **wordpress-develop**: Same as `wordpress` mode, except the `build` directory is served as the web root. -- **index**: When an `index.php` file is present, starts a PHP webserver in the working directory and simply passes requests to the `index.php`. -- **playground**: If no other conditions are matched, launches a completely virtualized WordPress site. - -### Arguments - -`wp-now start` currently supports the following arguments: - -- `--path=`: the path to the PHP file or WordPress project to use. If not provided, it will use the current working directory; -- `--php=`: the version of PHP to use. This is optional and if not provided, it will use a default version which is `8.3`(example usage: `--php=7.4`); -- `--port=`: the port number on which the server will listen. This is optional and if not provided, it will pick an open port number automatically. The default port number is set to `8881`(example of usage: `--port=3000`); -- `--wp=`: the version of WordPress to use. This is optional and if not provided, it will use a default version. The default version is set to the [latest WordPress version](https://wordpress.org/download/releases/)(example usage: `--wp=5.8`) -- `--blueprint=`: the path of a JSON file with the Blueprint steps (requires Node 20). This is optional, if provided will execute the defined Blueprints. See [Using Blueprints](#using-blueprints) for more details. -- `--reset`: create a fresh SQLite database and wp-content directory, for modes that support persistence. - -Of these, `wp-now php` currently supports the `--path=` and `--php=` arguments. - -## Technical Details - -- The `~/.wp-now` home directory is used to store the WP versions and the `wp-content` folders for projects using 'theme', 'plugin', 'wp-content', and 'playground' modes. The path to `wp-content` directory for the 'plugin', 'theme', and 'wp-content' modes is `~/.wp-now/wp-content/${projectName}-${directoryHash}`. 'playground' mode shares the same `~/.wp-now/wp-content/playground` directory, regardless of where it's started. -- For the database setup, `wp-now` is using [SQLite database integration plugin](https://wordpress.org/plugins/sqlite-database-integration/). The path to SQLite database is ` ~/.wp-now/wp-content/${projectName}-${directoryHash}/database/.ht.sqlite` - -## Using Blueprints - -Blueprints are JSON files with a list of steps to execute after starting wp-now. They can be used to automate the setup of a WordPress site, including defining wp-config constants, installing plugins, themes, and content. [Learn more about Blueprints.](https://wordpress.github.io/wordpress-playground/blueprints-api/index) - -### Defining Custom URLs - -Here is an example of a Blueprint that defines custom URL constant. `wp-now` will automatically detect the blueprint and execute it after starting the server. In consequence, the site will be available at `http://myurl.wpnow`. Make sure myurl.wpnow is added to your hosts file. - -To execute this Blueprint, create a file named `blueprint-example.json` and run `wp-now start --blueprint=path/to/blueprint-example.json`. Note that the `virtualize` is set to `true` to avoid modifying the `wp-config.php` file that is shared between all the projects. - -```json -{ - "steps": [ - { - "step": "defineWpConfigConsts", - "consts": { - "WP_HOME": "http://myurl.wpnow:8881", - "WP_SITEURL": "http://myurl.wpnow:8881" - }, - "method": "define-before-run" - } - ] -} -``` - -This step can be also used along with `ngrok`, in this case you can run `ngrok http 8881`, copy the ngrok URL and replace `WP_HOME` and `WP_SITEURL` in the blueprint file. - -If you prefer to use a different port, you can use the `--port` argument when starting the server. -`wp-now start --blueprint=path/to/blueprint-example.json --port=80` - -The Blueprint to listen on port `80` will look like this: - -```json -{ - "steps": [ - { - "step": "defineWpConfigConsts", - "consts": { - "WP_HOME": "http://myurl.wpnow", - "WP_SITEURL": "http://myurl.wpnow" - }, - "method": "define-before-run" - } - ] -} -``` - -### Defining Debugging Constants - -In the similar way we can define `WP_DEBUG` constants and read the debug logs. - -Run `wp-now start --blueprint=path/to/blueprint-example.json` where `blueprint-example.json` is: - -```json -{ - "steps": [ - { - "step": "defineWpConfigConsts", - "consts": { - "WP_DEBUG": true, - "WP_DEBUG_LOG": true - }, - "method": "define-before-run" - } - ] -} -``` - -This will enable the debug logs and will create a `debug.log` file in the `~/.wp-now/wp-content/${project}/debug.log` directory. - -If you prefer to set a custom path for the debug log file, you can set `WP_DEBUG_LOG` to be a directory. Remember that the `php-wasm` server runs udner a VFS (virtual file system) where the default documentRoot is always `/var/www/html`. - -For example, if you run `wp-now start --blueprint=path/to/blueprint-example.json` from a theme named `atlas` you could use this directory: `/var/www/html/wp-content/themes/atlas/example.log` and you will find the `example.log` file in your project directory. - -```json -{ - "steps": [ - { - "step": "defineWpConfigConsts", - "consts": { - "WP_DEBUG_LOG": "/var/www/html/wp-content/themes/atlas/example.log" - }, - "method": "define-before-run" - } - ] -} -``` - -## Known Issues - -- Running `wp-now start` in 'wp-content' or 'wordpress' mode will produce some empty directories: [WordPress/wordpress-playground#328](https://github.com/WordPress/wordpress-playground/issues/328) -- Inline images are broken when site starts on a different port: [WordPress/wordpress-playground#356](https://github.com/WordPress/wordpress-playground/issues/356) -- `wp-now` doesn't build or start on Windows: [WordPress/playground-tools#66](https://github.com/WordPress/playground-tools/issues/66), [WordPress/playground-tools#11](https://github.com/WordPress/playground-tools/issues/11) -- Pretty permalinks aren't yet supported: [WordPress/playground-tools#53](https://github.com/WordPress/playground-tools/issues/53) -- It's not possible to set `WP_DEBUG` and other `wp-config.php` constants: [WordPress/playground-tools#17](https://github.com/WordPress/playground-tools/issues/17) -- In 'wordpress' mode, `wp-now` can connect to a MySQL database with `define( 'DB_HOST', '127.0.0.1' );`, but `define( 'DB_HOST', 'localhost' );` does not work: [WordPress/wordpress-playground#369](https://github.com/WordPress/wordpress-playground/issues/369) -- `wp-now` published versions can appear random: [WordPress/wordpress-playground#357](https://github.com/WordPress/wordpress-playground/issues/357) - -## Comparisons - -### Laravel Valet - -If you are migrating from Laravel Valet, you should be aware of the differences it has with `wp-now`: - -- `wp-now` does not require you to install WordPress separately, create a database, connect WordPress to that database or create a user account. All of these steps are handled by the `wp-now start` command and are running under the hood; -- `wp-now` works across all platforms (Mac, Linux, Windows); -- `wp-now` does not support custom domains or SSL (yet!); -- `wp-now` works with WordPress themes and plugins even if you don't have WordPress installed; -- `wp-now` allows to easily switch the WordPress version with `wp-now start --wp=version.number`(make sure to replace the `version.number` with the actual WordPress version); -- `wp-now` does not support Xdebug PHP extension (yet!) - -Some similarities between Laravel Valet and `wp-now` to be aware of: - -- could be used for non-WordPress projects; -- deployments are not possible with neither Laravel Valet, nor `wp-now`; -- possible to switch easily the PHP version; -- possibility to work on multiple WordPress sites simultaneously - -### wp-env - -If you are migrating from `wp-env`, you should be aware of the differences it has with `wp-now`: - -- `wp-now` supports non-WordPress projects; -- `wp-now` does not need Docker; -- `wp-now` does not support Xdebug PHP extension (yet!); -- `wp-now` does not include Jest for automatic browser testing - -Some similarities between `wp-env` and `wp-now` to be aware of: - -- no support for custom domains or SSL; -- `plugin`, `themes` and index modes are available on `wp-env` and `wp-now`; -- deployments are not possible with neither `wp-env`, nor `wp-now`; -- possible to switch easily the PHP version - -## Contributing - -We welcome contributions from the community! - -In order to contribute to `wp-now`, you'll need to first install a few global dependencies: - -- Make sure you have `nvm` installed. If you need to install it first, - [follow these installation instructions](https://github.com/nvm-sh/nvm#installation). -- Install `nx` by running `npm install -g nx`. - -Once the global dependencies are installed, you can start using the repo: - -```bash -git clone git@github.com:WordPress/playground-tools.git -cd playground-tools -nvm use -npm install -npm run build -nx preview wp-now start --path=/path/to/wordpress-plugin-or-theme -``` - -If you'd like to make the `wp-now` executable globally available when using this installation method, run `npm link`. It's not particularly reliable, however. - -## Testing - -To run the unit tests, use the following command: - -```bash -nx test wp-now -``` - -## Publishing - -The `wp-now` package is part of a larger monorepo, sharing its space with other sibling packages. To publish the `wp-now` package to npm, you must first understand the automated release process facilitated by lerna. This process includes automatically incrementing the version number, creating a new tag, and publishing all modified packages to npm simultaneously. Notably, all published packages share the same version number. - -Each package identifies a distinct organization in its `package.json` file. To publish the `wp-now` package, you need access to the `@wp-now` npm organization. - -To initiate the publishing process for `wp-now`, execute the following commands: - -```bash -npm login # this is required only once and it will store the credentials in ~/.npmrc file. -npm run build -npm run release:wp-now -``` diff --git a/vendor/wp-now/esbuild.mjs b/vendor/wp-now/esbuild.mjs deleted file mode 100644 index b15c5d25b7..0000000000 --- a/vendor/wp-now/esbuild.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import * as esbuild from 'esbuild'; - -await esbuild.build({ - entryPoints: [ - 'packages/wp-now/src/index.ts', - 'packages/wp-now/src/main.ts', - ], - outdir: 'dist/packages/wp-now', - bundle: true, - packages: 'external', - format: 'esm', - platform: 'node', -}); diff --git a/vendor/wp-now/package.json b/vendor/wp-now/package.json deleted file mode 100644 index 91ca4b624d..0000000000 --- a/vendor/wp-now/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "@wp-now/wp-now", - "version": "0.1.65", - "description": "WordPress Playground CLI", - "repository": { - "type": "git", - "url": "https://github.com/WordPress/playground-tools" - }, - "homepage": "https://developer.wordpress.org/playground", - "author": "The WordPress contributors", - "contributors": [ - { - "name": "Antonio Sejas", - "email": "antonio@sejas.es", - "url": "https://github.com/sejas" - } - ], - "publishConfig": { - "access": "public", - "directory": "../../dist/packages/wp-now" - }, - "license": "GPL-2.0-or-later", - "type": "module", - "main": "index.js", - "bin": "with-node-version.js", - "gitHead": "dc3e3bd36d631cdc7b653f634439ad6c30384f41" -} diff --git a/vendor/wp-now/project.json b/vendor/wp-now/project.json deleted file mode 100644 index 53d29c1fd1..0000000000 --- a/vendor/wp-now/project.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "name": "wp-now", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "packages/wp-now/src", - "projectType": "library", - "targets": { - "build": { - "executor": "nx:noop", - "dependsOn": ["build:package-json", "build:bundle"] - }, - "build:package-json": { - "executor": "nx-extensions:package-json", - "options": { - "tsConfig": "packages/wp-now/tsconfig.lib.json", - "outputPath": "dist/packages/wp-now", - "buildTarget": "wp-now:build:bundle" - }, - "dependsOn": ["build:bundle"] - }, - "build:bundle": { - "executor": "nx:run-commands", - "options": { - "commands": [ - "node packages/wp-now/esbuild.mjs", - "cp packages/wp-now/public/* dist/packages/wp-now", - "cp packages/wp-now/package.json dist/packages/wp-now", - "cp packages/wp-now/README.md dist/packages/wp-now" - ], - "parallel": false - } - }, - "preview": { - "executor": "nx-extensions:built-script", - "options": { - "scriptPath": "dist/packages/wp-now/main.js" - }, - "dependsOn": ["build", "^build"] - }, - "serve": { - "executor": "@nx/js:node", - "defaultConfiguration": "development", - "options": { - "buildTarget": "wp-now:build" - }, - "configurations": { - "development": { - "buildTarget": "wp-now:build:development" - }, - "production": { - "buildTarget": "wp-now:build:production" - } - } - }, - "lint": { - "executor": "@nx/linter:eslint", - "outputs": ["{options.outputFile}"], - "options": { - "lintFilePatterns": ["packages/wp-now/**/*.ts"] - } - }, - "test": { - "executor": "@nx/vite:test", - "outputs": ["coverage/packages/wp-now"], - "options": { - "passWithNoTests": true, - "reportsDirectory": "../../coverage/packages/wp-now/node", - "testTimeout": 20000 - } - } - }, - "tags": [] -} diff --git a/vendor/wp-now/public/cli.js b/vendor/wp-now/public/cli.js deleted file mode 100644 index a6b4f8c835..0000000000 --- a/vendor/wp-now/public/cli.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -import './main.js'; diff --git a/vendor/wp-now/public/with-node-version.js b/vendor/wp-now/public/with-node-version.js deleted file mode 100644 index 1022c813b4..0000000000 --- a/vendor/wp-now/public/with-node-version.js +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env node -import child_process from 'child_process'; -import path from 'path'; -import fs from 'fs'; - -// Set the minimum required/supported version of node here. - -// Check if `--blueprint=` is passed in proccess.argv -const hasBlueprint = process.argv.some( ( arg ) => arg.startsWith( '--blueprint=' ) ); - -const minimum = { - // `--blueprint=` requires node v20 - major: hasBlueprint ? 20 : 18, - minor: 0, - patch: 0, -}; - -// Matches "v18.14.2", as an example. -const versionPattern = /^v(\d+)\.(\d+)\.(\d+)$/; -const [ major, minor, patch ] = versionPattern.exec( process.version ).slice( 1, 4 ); - -function meetsMinimumVersion( minimum, [ major, minor, patch ] ) { - if ( major > minimum.major ) { - return true; - } - - if ( major < minimum.major ) { - return false; - } - - if ( minor > minimum.minor ) { - return true; - } - - if ( minor < minimum.minor ) { - return false; - } - - return patch >= minimum.patch; -} - -if ( ! meetsMinimumVersion( minimum, [ major, minor, patch ] ) ) { - const extra = hasBlueprint ? ' when --blueprint= is used' : ''; - console.error( - `This script is requires node version v${ minimum.major }.${ minimum.minor }.${ minimum.patch } or above${ extra }; found ${ process.version }` - ); - process.exit( 1 ); -} - -// Launch the wp-now process and pipe output through this wrappers streams. -const dir = path.dirname( fs.realpathSync( process.argv[ 1 ] ) ); -child_process.spawn( 'node', [ dir + '/cli.js', ...process.argv.slice( 2 ) ], { - stdio: [ 'inherit', 'inherit', 'inherit' ], -} ); diff --git a/vendor/wp-now/src/add-trailing-slash.ts b/vendor/wp-now/src/add-trailing-slash.ts deleted file mode 100644 index ac983dbceb..0000000000 --- a/vendor/wp-now/src/add-trailing-slash.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Adds redirection adding a trailing slash, when a request matches a given path. - * @param path - The path to add a trailing slash to. E.g. '/wp-admin' - * @returns - Returns a middleware function that may redirect adding a trailing slash to the given path. E.g. '/wp-admin/' - */ -export function addTrailingSlash( path ) { - return ( req, res, next ) => { - const urlParts = req.url.split( '?' ); - const url = urlParts[ 0 ]; - const queryString = req.url.substr( url.length ); - if ( url === path ) { - res.redirect( 301, `${ path }/${ queryString }` ); - } else { - next(); - } - }; -} diff --git a/vendor/wp-now/src/assets/.gitkeep b/vendor/wp-now/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/vendor/wp-now/src/config.ts b/vendor/wp-now/src/config.ts deleted file mode 100644 index 3c98daa1b5..0000000000 --- a/vendor/wp-now/src/config.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { SupportedPHPVersion, SupportedPHPVersionsList } from '@php-wasm/universal'; -import crypto from 'crypto'; -import fs from 'fs-extra'; -import path from 'path'; -import { Blueprint } from '@wp-playground/blueprints'; -import { getCodeSpaceURL, isGitHubCodespace } from './github-codespaces'; -import { inferMode } from './wp-now'; -import { portFinder } from './port-finder'; -import { isValidWordPressVersion } from './wp-playground-wordpress'; -import getWpNowPath from './get-wp-now-path'; -import { DEFAULT_PHP_VERSION, DEFAULT_WORDPRESS_VERSION } from './constants'; -// Local copy of isWordPressDevVersion to avoid Studio dependencies -function isWordPressDevVersion( version: string ): boolean { - // Match nightly build patterns that end with a build number - // Examples: 6.8-alpha1-12345, 6.8-beta2-59979, 6.8-dev-12345, 6.8-59979 - return /^\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9]+)*-\d+$/.test( version ); -} - -export interface CliOptions { - php?: string; - path?: string; - wp?: string; - port?: number; - blueprint?: string; - reset?: boolean; - adminPassword?: string; - siteTitle?: string; - mode?: WPNowMode; - isWpAutoUpdating?: boolean; -} - -export const enum WPNowMode { - PLUGIN = 'plugin', - THEME = 'theme', - WORDPRESS = 'wordpress', - WORDPRESS_DEVELOP = 'wordpress-develop', - INDEX = 'index', - WP_CONTENT = 'wp-content', - PLAYGROUND = 'playground', - AUTO = 'auto', - CLI = 'cli', -} - -export interface WPNowOptions { - phpVersion?: SupportedPHPVersion; - documentRoot?: string; - absoluteUrl?: string; - mode?: WPNowMode; - port?: number; - projectPath?: string; - wpContentPath?: string; - wordPressVersion?: string; - isWpAutoUpdating?: boolean; - numberOfPhpInstances?: number; - blueprintObject?: Blueprint; - reset?: boolean; - adminPassword?: string; - siteTitle?: string; - siteLanguage?: string; -} - -export const DEFAULT_OPTIONS: WPNowOptions = { - phpVersion: DEFAULT_PHP_VERSION, - wordPressVersion: DEFAULT_WORDPRESS_VERSION, - isWpAutoUpdating: true, - documentRoot: '/var/www/html', - projectPath: process.cwd(), - mode: WPNowMode.AUTO, - numberOfPhpInstances: 1, - reset: false, - adminPassword: 'password', - siteTitle: 'My WordPress Website', - siteLanguage: 'en', -}; - -export interface WPEnvOptions { - core: string | null; - phpVersion: SupportedPHPVersion | null; - plugins: string[]; - themes: string[]; - port: number; - testsPort: number; - config: Object; - mappings: Object; -} - -let absoluteUrlFromBlueprint = ''; - -async function getAbsoluteURL() { - const port = await portFinder.getOpenPort(); - if ( isGitHubCodespace ) { - return getCodeSpaceURL( port ); - } - - if ( absoluteUrlFromBlueprint ) { - return absoluteUrlFromBlueprint; - } - - const url = 'http://localhost'; - if ( port === 80 ) { - return url; - } - return `${ url }:${ port }`; -} - -function getWpContentHomePath( projectPath: string, mode: string ) { - const basename = path.basename( projectPath ); - const directoryHash = crypto.createHash( 'sha1' ).update( projectPath ).digest( 'hex' ); - const projectDirectory = - mode === WPNowMode.PLAYGROUND ? 'playground' : `${ basename }-${ directoryHash }`; - return path.join( getWpNowPath(), 'wp-content', projectDirectory ); -} - -export default async function getWpNowConfig( args: CliOptions ): Promise< WPNowOptions > { - if ( args.port ) { - portFinder.setPort( args.port ); - } - const port = await portFinder.getOpenPort(); - const optionsFromCli: WPNowOptions = { - phpVersion: args.php as SupportedPHPVersion, - projectPath: args.path as string, - wordPressVersion: args.wp as string, - port, - reset: args.reset as boolean, - mode: args.mode as WPNowMode, - }; - - const options: WPNowOptions = {} as WPNowOptions; - - [ optionsFromCli, DEFAULT_OPTIONS ].forEach( ( config ) => { - for ( const key in config ) { - if ( ! options[ key ] ) { - options[ key ] = config[ key ]; - } - } - } ); - - if ( ! options.mode || options.mode === 'auto' ) { - options.mode = inferMode( options.projectPath ); - } - if ( ! options.wpContentPath ) { - options.wpContentPath = getWpContentHomePath( options.projectPath, options.mode ); - } - if ( ! options.absoluteUrl ) { - options.absoluteUrl = await getAbsoluteURL(); - } - if ( - ! isValidWordPressVersion( options.wordPressVersion ) && - ! isWordPressDevVersion( options.wordPressVersion ) - ) { - throw new Error( - 'Unrecognized WordPress version. Please use "latest" or numeric versions such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1"' - ); - } - if ( options.phpVersion && ! SupportedPHPVersionsList.includes( options.phpVersion ) ) { - throw new Error( - `Unsupported PHP version: ${ - options.phpVersion - }. Supported versions: ${ SupportedPHPVersionsList.join( ', ' ) }` - ); - } - if ( args.blueprint ) { - const blueprintPath = path.resolve( args.blueprint ); - if ( ! fs.existsSync( blueprintPath ) ) { - throw new Error( `Blueprint file not found: ${ blueprintPath }` ); - } - const blueprintObject = JSON.parse( fs.readFileSync( blueprintPath, 'utf8' ) ); - - options.blueprintObject = blueprintObject; - const siteUrl = extractSiteUrlFromBlueprint( blueprintObject ); - if ( siteUrl ) { - options.absoluteUrl = siteUrl; - absoluteUrlFromBlueprint = siteUrl; - } - } - if ( args.adminPassword ) { - options.adminPassword = args.adminPassword; - } - if ( args.siteTitle ) { - options.siteTitle = args.siteTitle; - } - - options.isWpAutoUpdating = - typeof args.isWpAutoUpdating === 'boolean' ? args.isWpAutoUpdating : true; - - return options; -} - -function extractSiteUrlFromBlueprint( blueprintObject: Blueprint ): string | false { - for ( const step of blueprintObject.steps ) { - if ( typeof step !== 'object' ) { - return false; - } - - if ( step.step === 'defineSiteUrl' ) { - return `${ step.siteUrl }`; - } else if ( step.step === 'defineWpConfigConsts' && step.consts.WP_SITEURL ) { - return `${ step.consts.WP_SITEURL }`; - } - } - return false; -} diff --git a/vendor/wp-now/src/constants.ts b/vendor/wp-now/src/constants.ts deleted file mode 100644 index f6b145a0c7..0000000000 --- a/vendor/wp-now/src/constants.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Playground internal shared folder. - */ -export const PLAYGROUND_INTERNAL_SHARED_FOLDER = '/internal/shared'; - -/** - * Playground internal mu-plugins folder. - */ -export const PLAYGROUND_INTERNAL_MU_PLUGINS_FOLDER = '/internal/shared/mu-plugins'; - -/** - * Playground internal preload path. - */ -export const PLAYGROUND_INTERNAL_PRELOAD_PATH = '/internal/shared/preload'; - -/** - * The file name for the SQLite plugin name. - */ -export const SQLITE_FILENAME = 'sqlite-database-integration'; - -/** - * The folder for the SQLite plugin. - */ -export const SQLITE_PLUGIN_FOLDER = '/internal/shared/mu-plugins/sqlite-database-integration'; - -/** - * The default starting port for running the WP Now server. - */ -export const DEFAULT_PORT = 8881; - -/** - * The default PHP version to use when running the WP Now server. - */ -export const DEFAULT_PHP_VERSION = '8.3'; - -/** - * The allowed PHP versions that can be used - */ -export const ALLOWED_PHP_VERSIONS = [ '8.4', '8.3', '8.2', '8.1', '8.0', '7.4' ]; - -/** - * The type for the allowed PHP versions. - */ -export type AllowedPHPVersion = ( typeof ALLOWED_PHP_VERSIONS )[ number ]; - -/** - * The default WordPress version to use when running the WP Now server. - */ -export const DEFAULT_WORDPRESS_VERSION = 'latest'; - -/** - * The URL for downloading the "wp-cli" WordPress cli. - */ -export const WP_CLI_URL = - 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar'; diff --git a/vendor/wp-now/src/execute-php.ts b/vendor/wp-now/src/execute-php.ts deleted file mode 100644 index 70ca5b64e0..0000000000 --- a/vendor/wp-now/src/execute-php.ts +++ /dev/null @@ -1,41 +0,0 @@ -import startWPNow from './wp-now'; -import { WPNowOptions } from './config'; -import { disableOutput } from './output'; -import * as path from 'path'; -import fs from 'fs-extra'; - -/** - * Execute a PHP cli given its parameters. - * - * @param phpArgs - Arguments to pass to the PHP cli. The first argument should be the string 'php'. - * @param options - Optional configuration object for WPNow. Defaults to an empty object. - * @returns - Returns a Promise that resolves to an object containing - * the exit name and status (0 for success). - * @throws - Throws an error if the first element in phpArgs is not the string 'php'. - */ -export async function executePHP( phpArgs: string[], options: WPNowOptions = {} ) { - if ( phpArgs[ 0 ] !== 'php' ) { - throw new Error( 'The first argument to executePHP must be the string "php".' ); - } - disableOutput(); - const { phpInstances, options: wpNowOptions } = await startWPNow( { - ...options, - numberOfPhpInstances: 2, - } ); - const [ , php ] = phpInstances; - - try { - if ( ! path.isAbsolute( phpArgs[ 1 ] ) ) { - const maybePhpFile = path.join( wpNowOptions.projectPath, phpArgs[ 1 ] ); - if ( fs.existsSync( maybePhpFile ) ) { - phpArgs[ 1 ] = maybePhpFile; - } - } - await php.cli( phpArgs ); - } catch ( resultOrError ) { - const success = resultOrError.name === 'ExitStatus' && resultOrError.status === 0; - if ( ! success ) { - throw resultOrError; - } - } -} diff --git a/vendor/wp-now/src/execute-wp-cli.ts b/vendor/wp-now/src/execute-wp-cli.ts deleted file mode 100644 index 7a01c05d15..0000000000 --- a/vendor/wp-now/src/execute-wp-cli.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { readFileSync } from 'fs'; -import path from 'path'; -import { rootCertificates } from 'tls'; -import { createNodeFsMountHandler, loadNodeRuntime } from '@php-wasm/node'; -import { PHP, MountHandler, writeFiles, setPhpIniEntries } from '@php-wasm/universal'; -import { phpVar } from '@php-wasm/util'; -import { getSqliteCommandPath } from 'src/lib/sqlite-command-path'; -import getWpNowConfig, { WPNowMode } from './config'; -import { - DEFAULT_PHP_VERSION, - DEFAULT_WORDPRESS_VERSION, - PLAYGROUND_INTERNAL_SHARED_FOLDER, -} from './constants'; -import { downloadWpCli } from './download'; -import getWpCliPath from './get-wp-cli-path'; -import { prepareWordPress } from './wp-now'; - -const isWindows = process.platform === 'win32'; - -/** - * This is an unstable API. Multiple wp-cli commands may not work due to a current limitation on php-wasm and pthreads. - * - * @param projectPath - The path to the project. - * @param args - The arguments to pass to wp-cli. - * @param phpVersion - The PHP version to use. - * @returns The result of the wp-cli command. - */ -export async function executeWPCli( - projectPath: string, - args: string[], - { phpVersion }: { phpVersion?: string } = {} -): Promise< { stdout: string; stderr: string; exitCode: number } > { - await downloadWpCli(); - const options = await getWpNowConfig( { - php: phpVersion || DEFAULT_PHP_VERSION, - wp: DEFAULT_WORDPRESS_VERSION, - path: projectPath, - mode: WPNowMode.CLI, - } ); - - const id = await loadNodeRuntime( options.phpVersion, { - followSymlinks: true, - } ); - const php = new PHP( id ); - php.mkdir( options.documentRoot ); - await php.mount( - options.documentRoot, - createNodeFsMountHandler( projectPath ) as unknown as MountHandler - ); - - //Set the SAPI name to cli before running the script - await php.setSapiName( 'cli' ); - - await prepareWordPress( php, options ); - - php.mkdir( '/tmp' ); - - const wpCliPath = '/tmp/wp-cli.phar'; - const stderrPath = '/tmp/stderr'; - const sqliteCommandPath = '/tmp/sqlite-command'; - const runCliPath = '/tmp/run-cli.php'; - const createFiles = { - [ wpCliPath ]: readFileSync( getWpCliPath() ), - [ stderrPath ]: '', - [ runCliPath ]: ` { - return new Promise( ( resolve ) => { - const server = http.createServer(); - - server - .listen( this.#searchPort, () => { - server.close(); - resolve( true ); - } ) - .on( 'error', () => { - resolve( false ); - } ); - } ); - } - - /** - * Returns the first available open port, caching and reusing it for subsequent calls. - * - * @returns {Promise} A promise that resolves to the open port number. - */ - public async getOpenPort(): Promise< number > { - if ( this.#openPort ) { - return this.#openPort; - } - while ( ! ( await this.#isPortFree() ) ) { - this.#incrementPort(); - } - - this.#openPort = this.#searchPort; - return this.#openPort; - } - - public setPort( port: number ): void { - this.#openPort = port; - } -} - -export const portFinder = PortFinder.getInstance(); diff --git a/vendor/wp-now/src/run-cli.ts b/vendor/wp-now/src/run-cli.ts deleted file mode 100644 index dd44ac2481..0000000000 --- a/vendor/wp-now/src/run-cli.ts +++ /dev/null @@ -1,178 +0,0 @@ -import yargs from 'yargs'; -import { hideBin } from 'yargs/helpers'; -import { startServer } from './start-server'; -import { portFinder } from './port-finder'; -import { SupportedPHPVersion } from '@php-wasm/universal'; -import getWpNowConfig, { CliOptions } from './config'; -import { spawn, SpawnOptionsWithoutStdio } from 'child_process'; -import { executePHP } from './execute-php'; -import { output } from './output'; -import { isGitHubCodespace } from './github-codespaces'; - -function startSpinner( message: string ) { - process.stdout.write( `${ message }...\n` ); - return { - succeed: ( text: string ) => { - output?.log( `${ text }` ); - }, - fail: ( text: string ) => { - output?.error( `${ text }` ); - }, - }; -} - -function commonParameters( yargs ) { - return yargs - .option( 'path', { - describe: 'Path to the PHP or WordPress project. Defaults to the current working directory.', - type: 'string', - } ) - .option( 'php', { - describe: 'PHP version to use.', - type: 'string', - } ); -} - -export async function runCli() { - return yargs( hideBin( process.argv ) ) - .scriptName( 'wp-now' ) - .usage( '$0 [args]' ) - .check( async ( argv ) => { - const config: CliOptions = { - php: argv.php as SupportedPHPVersion, - path: argv.path as string, - }; - if ( argv._[ 0 ] !== 'php' ) { - config.wp = argv.wp as string; - config.port = argv.port as number; - } - try { - await getWpNowConfig( config ); - } catch ( error ) { - return error.message; - } - return true; - } ) - .command( - 'start', - 'Start the server', - ( yargs ) => { - commonParameters( yargs ); - yargs.option( 'wp', { - describe: "WordPress version to use: e.g. '--wp=6.2'", - type: 'string', - } ); - yargs.option( 'port', { - describe: 'Server port', - type: 'number', - } ); - yargs.option( 'blueprint', { - describe: 'Path to a blueprint file to be executed', - type: 'string', - } ); - yargs.option( 'reset', { - describe: 'Create a new project environment, destroying the old project environment.', - type: 'boolean', - } ); - yargs.option( 'inspect', { - describe: 'Use Node debugging client.', - type: 'number', - } ); - yargs.option( 'inspect-brk', { - describe: 'Use Node debugging client. Break immediately on script execution start.', - type: 'number', - } ); - yargs.option( 'trace-exit', { - describe: - 'Prints a stack trace whenever an environment is exited proactively, i.e. invoking process.exit().', - type: 'number', - } ); - yargs.option( 'trace-uncaught', { - describe: - 'Print stack traces for uncaught exceptions; usually, the stack trace associated with the creation of an Error is printed, whereas this makes Node.js also print the stack trace associated with throwing the value (which does not need to be an Error instance).', - type: 'number', - } ); - yargs.option( 'trace-warnings', { - describe: 'Print stack traces for process warnings (including deprecations).', - type: 'number', - } ); - }, - async ( argv ) => { - const spinner = startSpinner( 'Starting the server...' ); - try { - const options = await getWpNowConfig( { - path: argv.path as string, - php: argv.php as SupportedPHPVersion, - wp: argv.wp as string, - port: argv.port as number, - blueprint: argv.blueprint as string, - reset: argv.reset as boolean, - adminPassword: argv.adminPassword as string, - siteTitle: argv.siteTitle as string, - } ); - portFinder.setPort( options.port as number ); - const { url } = await startServer( options ); - openInDefaultBrowser( url ); - } catch ( error ) { - output?.error( error ); - spinner.fail( `Failed to start the server: ${ ( error as Error ).message }` ); - } - } - ) - .command( - 'php [..args]', - 'Run the php command passing the arguments to php cli', - ( yargs ) => { - commonParameters( yargs ); - yargs.strict( false ); - }, - async ( argv ) => { - try { - // 0: node, 1: wp-now, 2: php, ...args - const args = process.argv.slice( 2 ); - const options = await getWpNowConfig( { - path: argv.path as string, - php: argv.php as SupportedPHPVersion, - } ); - const phpArgs = args.includes( '--' ) ? ( argv._ as string[] ) : args; - // 0: php, ...args - await executePHP( phpArgs, options ); - process.exit( 0 ); - } catch ( error ) { - console.error( error ); - process.exit( error.status || -1 ); - } - } - ) - .demandCommand( 1, 'You must provide a valid command' ) - .help() - .alias( 'h', 'help' ) - .strict().argv; -} - -function openInDefaultBrowser( url: string ) { - if ( isGitHubCodespace ) { - return; - } - let cmd: string, args: string[] | SpawnOptionsWithoutStdio; - switch ( process.platform ) { - case 'darwin': - cmd = 'open'; - args = [ url ]; - break; - case 'linux': - cmd = 'xdg-open'; - args = [ url ]; - break; - case 'win32': - cmd = 'cmd'; - args = [ '/c', `start ${ url }` ]; - break; - default: - output?.log( `Platform '${ process.platform }' not supported` ); - return; - } - spawn( cmd, args ).on( 'error', function ( err ) { - console.error( err.message ); - } ); -} diff --git a/vendor/wp-now/src/start-server.ts b/vendor/wp-now/src/start-server.ts deleted file mode 100644 index e93c7b0de2..0000000000 --- a/vendor/wp-now/src/start-server.ts +++ /dev/null @@ -1,120 +0,0 @@ -import fs from 'fs'; -import { WPNowOptions } from './config'; -import { HTTPMethod } from '@php-wasm/universal'; -import express from 'express'; -import compression from 'compression'; -import compressible from 'compressible'; -import { portFinder } from './port-finder'; -import { PHP } from '@php-wasm/universal'; -import startWPNow from './wp-now'; -import { output } from './output'; -import { addTrailingSlash } from './add-trailing-slash'; - -const requestBodyToBytes = async ( req ): Promise< Uint8Array > => - await new Promise( ( resolve ) => { - const body = []; - req.on( 'data', ( chunk ) => { - body.push( chunk ); - } ); - req.on( 'end', () => { - resolve( Buffer.concat( body ) ); - } ); - } ); - -export interface WPNowServer { - url: string; - php: PHP; - options: WPNowOptions; - stopServer: () => Promise< void >; -} - -function shouldCompress( _, res ) { - const types = res.getHeader( 'content-type' ); - const type = Array.isArray( types ) ? types[ 0 ] : types; - return type && compressible( type ); -} - -export async function startServer( options: WPNowOptions = {} ): Promise< WPNowServer > { - if ( ! fs.existsSync( options.projectPath ) ) { - throw new Error( `The given path "${ options.projectPath }" does not exist.` ); - } - - const app = express(); - app.use( compression( { filter: shouldCompress } ) ); - app.use( addTrailingSlash( '/wp-admin' ) ); - const port = options.port ?? ( await portFinder.getOpenPort() ); - const { php, options: wpNowOptions } = await startWPNow( options ); - - // Middleware to check if auto-login should be executed - app.use( async ( req, res, next ) => { - if ( req.query[ 'playground-auto-login' ] === 'true' ) { - await php.requestHandler.request( { url: '/wp-login.php' } ); - const response = await php.requestHandler.request( { - url: '/wp-login.php', - method: 'POST', - body: { - log: 'admin', - pwd: options.adminPassword, - rememberme: 'forever', - }, - } ); - const cookies = response.headers[ 'set-cookie' ]; - if ( cookies ) { - res.setHeader( 'set-cookie', cookies ); - } - // Remove query parameter to avoid infinite loop - let redirectUrl = req.url.replace( /&?playground-auto-login=true/, '' ); - // If no more query parameters, remove ? from URL - if ( Object.keys( req.query ).length === 1 ) { - redirectUrl = redirectUrl.substring( 0, redirectUrl.length - 1 ); - } - return res.redirect( redirectUrl ); - } - next(); - } ); - - // Handle requests - app.use( '/', async ( req, res ) => { - try { - const requestHeaders = {}; - if ( req.rawHeaders && req.rawHeaders.length ) { - for ( let i = 0; i < req.rawHeaders.length; i += 2 ) { - requestHeaders[ req.rawHeaders[ i ].toLowerCase() ] = req.rawHeaders[ i + 1 ]; - } - } - - const data = { - url: req.url, - headers: requestHeaders, - method: req.method as HTTPMethod, - body: await requestBodyToBytes( req ), - }; - const resp = await php.requestHandler.request( data ); - res.statusCode = resp.httpStatusCode; - Object.keys( resp.headers ).forEach( ( key ) => { - res.setHeader( key, resp.headers[ key ] ); - } ); - res.end( resp.bytes ); - } catch ( e ) { - output?.trace( e ); - } - } ); - const url = options.absoluteUrl; - const server = app.listen( port, () => { - output?.log( `Server running at ${ url }` ); - } ); - - return { - url, - php, - options: wpNowOptions, - stopServer: () => - new Promise( ( res ) => { - server.close( () => { - output?.log( `Server stopped` ); - php.exit(); - res(); - } ); - } ), - }; -} diff --git a/vendor/wp-now/src/tests/add-trailing-slash.spec.ts b/vendor/wp-now/src/tests/add-trailing-slash.spec.ts deleted file mode 100644 index df2d9a83c5..0000000000 --- a/vendor/wp-now/src/tests/add-trailing-slash.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { addTrailingSlash } from '../add-trailing-slash'; - -describe( 'add trailing slash middleware', () => { - const middlewareTrailingSlash = addTrailingSlash( '/wp-admin' ); - let res, next; - - beforeEach( () => { - res = { - redirect: vi.fn(), - }; - next = vi.fn(); - } ); - - test( 'adds a trailing slash to the given path', () => { - const req = { url: '/wp-admin' }; - middlewareTrailingSlash( req, res, next ); - expect( res.redirect ).toHaveBeenCalledWith( 301, '/wp-admin/' ); - expect( next ).not.toHaveBeenCalled(); - } ); - - test( 'adds a trailing slash to the given path with parameters', () => { - const req = { url: '/wp-admin?foo=bar' }; - middlewareTrailingSlash( req, res, next ); - expect( res.redirect ).toHaveBeenCalledWith( 301, '/wp-admin/?foo=bar' ); - expect( next ).not.toHaveBeenCalled(); - } ); - - test( 'does not add a trailing slash to the given path', () => { - const req = { url: '/wp-admin/' }; - middlewareTrailingSlash( req, res, next ); - expect( res.redirect ).not.toHaveBeenCalled(); - expect( next ).toHaveBeenCalled(); - } ); -} ); diff --git a/vendor/wp-now/src/tests/blueprints/wp-config.json b/vendor/wp-now/src/tests/blueprints/wp-config.json deleted file mode 100644 index b2d511d429..0000000000 --- a/vendor/wp-now/src/tests/blueprints/wp-config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "steps": [ - { - "step": "defineWpConfigConsts", - "consts": { - "WP_HOME": "http://127.0.0.1", - "WP_SITEURL": "http://127.0.0.1" - }, - "method": "define-before-run" - } - ] -} diff --git a/vendor/wp-now/src/tests/blueprints/wp-debug.json b/vendor/wp-now/src/tests/blueprints/wp-debug.json deleted file mode 100644 index d3a49540a6..0000000000 --- a/vendor/wp-now/src/tests/blueprints/wp-debug.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "steps": [ - { - "step": "defineWpConfigConsts", - "consts": { - "WP_DEBUG_LOG": "/var/www/html/wp-content/themes/fake/example.log" - }, - "method": "define-before-run" - } - ] -} diff --git a/vendor/wp-now/src/tests/execute-php.spec.ts b/vendor/wp-now/src/tests/execute-php.spec.ts deleted file mode 100644 index b1c75d313f..0000000000 --- a/vendor/wp-now/src/tests/execute-php.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import path from 'path'; -import { executePHP } from '../execute-php'; -import getWpNowConfig from '../config'; - -const exampleDir = path.join( __dirname, 'execute-php' ); - -describe( 'validate php execution', () => { - let output = ''; - beforeEach( () => { - vi.spyOn( console, 'log' ).mockImplementation( ( newLine: string ) => { - output += `${ newLine }`; - } ); - } ); - - afterEach( () => { - output = ''; - vi.restoreAllMocks(); - } ); - test( 'php file execution in index mode', async () => { - const options = await getWpNowConfig( { - path: exampleDir, - } ); - await executePHP( [ 'php', path.join( exampleDir, 'hello-world.php' ) ], options ); - - expect( output ).toMatch( /Hello World!/ ); - } ); - test( 'php file execution for each PHP Version', async () => { - const options = await getWpNowConfig( { - path: exampleDir, - } ); - await executePHP( [ 'php', path.join( exampleDir, 'php-version.php' ) ], { - ...options, - phpVersion: '7.4', - } ); - expect( output.substring( 0, 16 ) ).toBe( 'PHP Version: 7.4' ); - - output = ''; - await executePHP( [ 'php', path.join( exampleDir, 'php-version.php' ) ], { - ...options, - phpVersion: '8.3', - } ); - expect( output.substring( 0, 16 ) ).toBe( 'PHP Version: 8.3' ); - - output = ''; - await executePHP( [ 'php', path.join( exampleDir, 'php-version.php' ) ], { - ...options, - phpVersion: '8.2', - } ); - expect( output.substring( 0, 16 ) ).toBe( 'PHP Version: 8.2' ); - } ); - test( 'php file execution with non absolute path', async () => { - const options = await getWpNowConfig( { - path: exampleDir, - } ); - await executePHP( [ 'php', 'hello-world.php' ], options ); - - expect( output ).toMatch( /Hello World!/ ); - } ); - - test( 'php throws an error if the first element is not the string "php"', async () => { - const options = await getWpNowConfig( { - path: exampleDir, - } ); - try { - await executePHP( [ 'word-different-to-php', path.join( exampleDir, 'php-version.php' ) ], { - ...options, - phpVersion: '7.4', - } ); - } catch ( error ) { - expect( error.message ).toBe( 'The first argument to executePHP must be the string "php".' ); - } - } ); -} ); diff --git a/vendor/wp-now/src/tests/execute-php/hello-world.php b/vendor/wp-now/src/tests/execute-php/hello-world.php deleted file mode 100644 index f34c0a1111..0000000000 --- a/vendor/wp-now/src/tests/execute-php/hello-world.php +++ /dev/null @@ -1,2 +0,0 @@ - { - let processEnv; - beforeAll( () => { - processEnv = process.env; - process.env = {}; - } ); - - afterAll( () => { - process.env = processEnv; - } ); - - test( 'getAbsoluteURL returns the localhost URL', async () => { - vi.spyOn( codespaces, 'isGitHubCodespace', 'get' ).mockReturnValue( false ); - const options = await getWpNowConfig( { port: 7777 } ); - - expect( options.absoluteUrl ).toBe( 'http://localhost:7777' ); - vi.resetAllMocks(); - } ); - - test( 'getAbsoluteURL returns the codespace URL', async () => { - vi.spyOn( codespaces, 'isGitHubCodespace', 'get' ).mockReturnValue( true ); - process.env.CODESPACE_NAME = 'my-codespace-name'; - process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = 'preview.app.github.dev'; - const options = await getWpNowConfig( { port: 7777 } ); - - expect( options.absoluteUrl ).toBe( 'https://my-codespace-name-7777.preview.app.github.dev' ); - vi.resetAllMocks(); - } ); -} ); diff --git a/vendor/wp-now/src/tests/mode-examples/child-theme-missing-parent/child/style.css b/vendor/wp-now/src/tests/mode-examples/child-theme-missing-parent/child/style.css deleted file mode 100644 index fb6fa58340..0000000000 --- a/vendor/wp-now/src/tests/mode-examples/child-theme-missing-parent/child/style.css +++ /dev/null @@ -1,10 +0,0 @@ -/* -Theme Name: Child Theme -Description: Child Theme -Author URI: http://example.com -Template: parent -Version: 1.0.0 -License: GNU General Public License v2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html -Text Domain: parent-child -*/ diff --git a/vendor/wp-now/src/tests/mode-examples/child-theme/child/style.css b/vendor/wp-now/src/tests/mode-examples/child-theme/child/style.css deleted file mode 100644 index fb6fa58340..0000000000 --- a/vendor/wp-now/src/tests/mode-examples/child-theme/child/style.css +++ /dev/null @@ -1,10 +0,0 @@ -/* -Theme Name: Child Theme -Description: Child Theme -Author URI: http://example.com -Template: parent -Version: 1.0.0 -License: GNU General Public License v2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html -Text Domain: parent-child -*/ diff --git a/vendor/wp-now/src/tests/mode-examples/child-theme/parent/style.css b/vendor/wp-now/src/tests/mode-examples/child-theme/parent/style.css deleted file mode 100644 index c5970cda64..0000000000 --- a/vendor/wp-now/src/tests/mode-examples/child-theme/parent/style.css +++ /dev/null @@ -1,9 +0,0 @@ -/* -Theme Name: Parent Theme -Description: Parent Theme -Author URI: http://example.com -Version: 1.0.0 -License: GNU General Public License v2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html -Text Domain: parent -*/ diff --git a/vendor/wp-now/src/tests/mode-examples/index/index.php b/vendor/wp-now/src/tests/mode-examples/index/index.php deleted file mode 100644 index e29ec676bb..0000000000 --- a/vendor/wp-now/src/tests/mode-examples/index/index.php +++ /dev/null @@ -1,3 +0,0 @@ -1B$Nd@B7Yq&&=d`XOy>;-9R@wf)fD{ z1c0S*1Iiw-F1%Ef4M1!xZ~y?103Oi;7(@u%0Ad30Y79U$GE##R5xb!n6oC06fE$X* zfUU}4WmWc2i&a+wBJAnGyBqtMN#EaK~nknWL|k5&zH{%3S^Eg^(!rq6bR&8W@$mbSmsw6z)~0YgIMKuVKLPq z^1J|6dTczCT__bWy_{X0U0E<&%Fp&o42u}bg}ngQ&?HMrN}NkPoC~EnE^fZQzAmor zF7ED5Fv3YzCYEzcoy0QB;R;~_8BZ#b$VG)>rm7-0t56{iV6j}_2rffIsGw%9fz0pr zeFeU+!1opSz5?G@;QI>v|E<6`wIdKiiBkf#3{d_KeBz;0DujAT!t`)<1wNsiSe#0d zK~rOEHPpRkw&?Z}up8Gun_T<+edtW(Lz*3j&7K;c7#G1A7p_)?G$XD=l8@>FP#~5| z6C*>J=@T-T#6BqZP__K?;PPaWkofp$qmLx%laDMUlLio6t!Unt;c>X!?X(}rV;MXBS5u`Dl1GoNxHTjQ515;gM|h$BOX zJ{&I)r)c;}C!RLtRslAF1)nijy?Ca%r;0FOkP<)-abFt-9~kxgZJ1fdcS>2owS)2!pK*NI*W| zf-^PERB-Cf;108l-*su%!X8>*j2#PHlV-@Il3 z0`@DF$^+kg(U$?(l?ec)^_$ObG61@L0Gv68MqU;>x|ct8jWfE1K~>0l111PkFS;Yv^k)`N|p3H$_?6vXOS-CHqwLiBd?H;D27r|ebfxKL0wQEGz5)7lhMg&4qAkk zqZR04v=(hdx1(+7G4woo9qmS+p@SH}v@k==0&~KAFg6y4WnkHu6q|`vVKrDI)`E3l zr?IP8H})L+fFs}-I3~^s=Z}lRrQmqDBHT>eB3vD=3D<@@fxC+9!42TP;AwbMyd&NZ z&%vkTbMPhjN_-7|Grkpn5`P`vi+@WX5DW>n1aCqVVFDqSFpaQ?u$HicaFozRctjW^ z5{bq{M`8dmj>sj-iIv3F#O=f*#4E&J;(L-7$(-a)iXdf>#H6{T8q!wMVbT>+AL$cW zmuyY;Bgc{XsT|kMdeeON*uDtCgT7 z(wePRtF=?>j8>1<2dW;`fy$;%qAIA1savQ=skf=GX*8M*Etr-;lhYQ{nrX*r4`}bT z8QRX;9Bsb#EbV&jHtj3gFLcN{);b|NQ*_F8YIOGKT+(^2OV+i~W$SWvXX@7L?$^Dk zJ4k2H-RN=j0{SBQR{B}`6Fs7yjb6AOU#~)Mquz17M+}T%$zU^hjCqVrjFXIBeS*HN zK1V-Sf1&;_`WN*F4D<}f7^E7M8q^ycGUztM3~dZKhWUm|40jveF#I^eY(&Ti!H5MT zT1H$M@y=+ZQLvG~sLE)E(RHJb#!TaI<2>VK#`}!#o8U|wOcG2=OxBs4G1}j#@G5@Th)shIyd5 z$b5zQ5%Z@^17-*_pIOU1!5pwKwTQA%STtB%wD`obWu>s@vUafUTT(22EVC_FSaw+WMW1txcNEe4BkXPi&2BIkx4t+idUI(d>fkitHNg zuGtgpeeCn>*VE~j0-xO%$gyKZv5>!#-x?Kan~!|j#3gFDZ?-u;>f%_G8N zmPfnC%Q22)1Y_2Xx#h|5jP;!F+3ESGm$#SHtHrC|+sZrBdyV%^AAO&ApG7`reDS`a zzO#G}`hM{9@{{}R_WQ%%$v@A3tN)X+wqpfjH;sK1zzX07Gz4@9G6ORMHw1PESp;!| z8iO7NTL$xkHwX8H*oWkXY!7)6>KZByZ4G_L_G8ar9}UC8BEzb}E{5xcr-avqKZsyO zM+YoECXB3Lg~{wJho;$Be_{G;;=`y`yJDpNOHxB*oOlJRIjRPCBk57LARL zT^@UHy!CkT_$?tZ*&d{O*?1VTc5LVd#HM7PB0iKmkoNmG-yCcRA#O6eB+p5e zlWt75om@Kk>=ctJ`BM%})tSnjx+fE#nU=XV^D{S&+sGZv;$*GK8sM>ct9Z}&!Tc5c zenEhsTF{prkX@bqBqtzec}~ADNLV9$E@F%7L@#rra@Xg+%Ztm~lBdj1%imEzD#$F@ zFV++1iBA-cDl9GRk~m5#CEZ1SMJtO2q%qRXGDJ36)-KnF0_lRnPVs}Hr#P^c)9sad~@vP!Y4yLMb{`)aGzOILrc6V_d?53S#^#&pesHSgB)*Iro{ zysl-v+4_a+KW-3hxYZEZ(AH?vShJC^v3TR-O{tqs|LpT~^JbIH3pf9{MZBfEDY5B9 zvv+gzR@1FZwxQb;+xoXp+J5<$uwU9+oLU-o817iO6YNy%e7Y-h*NxpVyF2&z>}lC+ zy?0G3qqV9HX)9|R*eBfA-9DkcYk&0q&JO>M)&tH5wj8uLSbxai(2~QH!xe{>N6L>3 z9+ez@ek|wMqt2vwklIiGX;&U>A2 zyWn}D^;gedTQ7QDY`f%rsr|C=<&LhgU5Bp(T{(6&?CPm&oNMQ=kH6k^Bjv`eo0D#K z-{RkTay$R_%R9w)KHZ&t4}Y)fzV7{+2c{1i|7HKL9o=5thkC+#E<8+nc=r+S(X-!V zzkTklcuad-(`Vk-{KWmq!TyN;%TFgh?R_SB_UU=$@ATi-ys&$*_mAK|E)1j(^uCn7 zRK8mD+W7UBH)Gy(4#p2Ycw6xH)4Qto#_yXx_@K^1vKIa8M z3j`{8#6AkZPPp6520$km0F^XK4hE=)hh~Q!m3`IgDn!tQ`F`u4+LS*ST{oce{MEi{ zdFUCQ_upH{23A+uf3(V@fKCD|V1pqnfYK3+jwnw6bI1b^MG7=^3OpbvhQkwxBr-({ z2DIt|6u~eQhvD(67cyi9JP&Ymyq<+y2!WBnC9;b2-RCT8AX$d)I%bgAHDKkDC7nyA z7>+P9HnFy`wX=5^o;!R zx_zhT;iKPrANM`!fBN#(>ouyb!cRMVyYq zTeuPQLK2AFA_mKS4oN?BS;MYlWJ{040fQ`Q7sb$OOpo>IDU zfDPYpRffVJhNAFNVbGv@QyGB>1sF6awA4mY8(C#g7Y&&b7C}^*7>0*GEfRq=eCnUK l%KK13W6C>#iXrf3!ss9feEDM6SOtc6<>kNiyZ+X{{cpSIYs~-v diff --git a/vendor/wp-now/src/tests/mode-examples/theme-with-assets/image.png b/vendor/wp-now/src/tests/mode-examples/theme-with-assets/image.png deleted file mode 100644 index e12add012a4ece911009efa06b9bfbbe5980f231..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3617 zcmb7GXIN8Pv))wcRRO6n0#ZUk`{+HPgd-iKA8H_x5=;U_nxIG%q#Zg)5tJ?n0#c=l z3PC}XCQXWz0|-hHfiHNx9-n)E-Ss@PX4cHS@9Z_Rf2>$jV|``@UIqXFn2`nubFvmC z3z&|UyvtQqGywoZ4F(Q3MZ)1AQykU<=6 z;>g#OVg_gUF3__X(uvcJCHJ0lV3YutR(_I*xtRbCfY{hjulwJEfLEJsY6`cZ(vcrR zccya!J&Z|;mzFgbNk^U}n((4t_E=eGD?s+P`87lVOdYBbaaRbS3#Z@$W_#ug5d&LW zrhj!7AGL%uQ5DDpj|4#8cbuF!=_WkS@S_OtGBch>Hf4zk>3>zX7EY%Oe_Ifi+H3xX zC$OOMf*v2jR-;dx^Lto)YTtP{PhxDGb7AEfAYx!H#sUD@7lj3}DAEkVS#-w#?B}F2 zBk7Jy%E{e{#xcH3N5|7G)_#vK?pQz`*pq~Hjae?~c5>FmGj^JQ*inAl$3xFQcZkTr z6>Pitt#IW^Vw6wNPOsa(RI<@9rY=PqJq}kC)FhQXF8G98GPI#j9r(C^Ta4r}K%ckZ zt_|2H>%Y94?cUy_iA(9VWskFa;mUT++**|1uO&Kpf9o=>V)idr(k4;ElM3}bP1!*5GLz3aJU#|Jt~md423B1OTOp&JsS zcI>c}ZxUByWxLLO{vNM&+bM;!Oi5DLslTjlF3vNvkDevposGU&io`szv)O6u2u)sdY7X+ZGCF`2*pS51VAKks?{;c5l%L7f{NFD@X&P&p5^? z^=B#7!dbV@bb^==5yTocKW&MAYS$Vb2u+0+yBGyu=M__m@@ImkR4ftq3(n_KE_b5* z1PBq6vlo(Xg>7*#MX)+k-vp^4B1)4K-08Bk6_PIYgtvljh@FiKpD#p+(QxR?0#;lB9(xka|7+xMCi0czJSA@i~mX zFBrFQZu|VLZc`aTyi^|*XJl-jpUimXVy1s4!)4|N^mukW=g6J&T_MlSYl3X}Dpk`H z61pEabGzNCL5+ou>8*>b`*>^aQn5tepM&^SH-Q|~pv;ZbjVz6v@55E2*ZRKBMSUi= zDooRE#!&Hw!$B_0jGl?e%Q!oz)KRrbx_01$xEwPLIpxg^z*l6i4BCLwe zzscj#TVUUb;fo25t?J?H37e-wCb(;F3+pBi8&nu976%QB3||~p6%i3}6HzRAYDig< zX1Hh=omMUORU%ZhBDLxTWIN<>eCxf|{E)jL`QPvA6lPk4h|!-FN2!;j<07^rw~<-p z-VX<^EQvodt+w2yfkyHwkr$L`C7eN z!)rCDEp=JS!Y6Nn;z`BTq0!H)ORLALYF~A}s;_dtJuIagjeJ>zE2}6AF3TuA;#Fd1 zWNy&Q){E`CkYJo}!N`9wD_bV}X?9xn!~ZPGM|v2$jmV<;(E6A_w9|tX6F1|JCibdT zVRl3HiFVC)8g?O7u2r;ElXgz^D-*{PD-${OIZuSvoC%fwORJD~<0W|Ha$0p%bu`qv z(Ppo7gG$|I$PT81jhz;3V`*~1k+*>^8-v9PB zp<%^*L}lcR2SP5&wtQTnE}$-PiEF8L$!j2IFy9_+TO+kCwF(imHHWlG)k#%IVQuFs zd9JKnQLSpObn)_Bvs^P=<5Q?fxRdbA|Am3uz zLfPWc@?P$;r|`g-{Qc0rz+lfqro*;fR)MzyHw75@?F6P(>{M|Ak^)#|vs}vDD@sq4 z+OjZmsharr1HmNA<#Cc%?qqJq)28Ma$5lsKM8ec3z^#Gz?Yr%+GkPOxUJRkBb~$bAKbdVE8C zx^81{Y0p4nS;7I&8n->KWMU3`BV;3})wmVZE<%ydmRbXIA>KP_t=jpvtU3hCzTp|s zdvl7$ntK&8U_%T_ehDTOxy6q4jP+2(Iw`y4UM2Z)sABYo*)n=R^o73Ine&-r-c~;- z`5xguv@pD|EPhWs$AZ?P&El206TQt%hR&S&g8K6M#1)s7!x4QsS29J+Q2j@2JP_tPdL5f3lBj zY@KOFuZ=#=LRn23OuA1lEEX~r;eY1pUel8U{T#HZ313_AyY3V$Ln5sT8E~E zSs_O=@@X_@CIy;W$|hQyy;*%tNcTL0-F6#Jy7o%~@=5I}p)d5{KR&eV`exMypOQ2(D(X?Y)L&dIiFXqR&_n7`}}ZLm)w2NogtQd&b(lM zjl~eB$)bj6?s@1`v(ldHa!Jx3*3dH!?XOARn!&w0FUu-6tW6x;9K6~$aBXGXPw#)W zUUuSBbyUgmZJI`};MT7GeQz}M!Sj~o??xRt8Q5B^V~6^3z+T=b)x8Z;hh)d|mc8#` zeMgF{IKg-|hXcC9(*4@ikB=_}nUvqC`&OmB0oMGm_I~oS{Ps$>hVRU)8Ig_~f5Srb zu3u4bRx&-D`g~B<8QxjVz4PanjWHjXhUAv#U`!IVbe&}x}*AAw5$Z_0>-SOb6{n}pAW}=>)zP>KM zjAzI>Ez{ensT9N(A;3@#up~r-26M(Lp3`LSbMai-$|t(+3377-t7dWyg0&$pwQgkETkA0?v?69AqIDQ2xpmEui{kgUK;K+X9Y6lC^~!4u$f?qp<`RjjyNA zYkEHeTRZ@8iJS@r(p-3p%>Nc+X+y9vx~$@c^?|y$V_i{DqL1Gx51>X=A)7uZf(wY~ z-xgbH~-(md({~r7a zRD+$~`?o&))8rpsa+ft2)L_3Rromtrc*}-7M1Bmy#FDJZzu1q(PTs}Ha;i_oo4yxx znx}+BXj|IQuMgLfy>wk#V>i(Lw+UQ@54QeP$xyqj&J?CAL&w#7_C96(X5YVi2N`;D lvMIX&B^&=a<@o#m%V0GXoufKnj3yHRNL^z@wT|=6{{nMxOvV5J diff --git a/vendor/wp-now/src/tests/mode-examples/theme-with-assets/javascript.js b/vendor/wp-now/src/tests/mode-examples/theme-with-assets/javascript.js deleted file mode 100644 index 44572fd451..0000000000 --- a/vendor/wp-now/src/tests/mode-examples/theme-with-assets/javascript.js +++ /dev/null @@ -1,61 +0,0 @@ -// This is a JavaScript file. - -// Don't remove the lorem ipsum. it's required to have a mininum file size to -// test that the server is correctly compressing this file. - -// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin at mi ligula. -// Duis lacinia feugiat tellus, quis pretium nulla. Cras urna ligula, gravida ut -// risus finibus, porta accumsan sem. Phasellus id ante a urna tristique rutrum. -// Donec sollicitudin fermentum enim sit amet viverra. Proin nec nunc libero. -// Phasellus non felis nibh. Duis sit amet ipsum ultrices, aliquet turpis non, -// dictum metus. Cras aliquam, augue a placerat tincidunt, ligula ex placerat -// lectus, a commodo nisi elit sit amet odio. Praesent lectus lacus, eleifend et -// eros sed, ornare aliquam massa. Nullam et elit faucibus, tristique justo -// eget, vulputate leo. Ut sagittis lectus diam. Mauris porttitor porta urna. -// -// Aliquam erat volutpat. Proin efficitur sem nec mi viverra suscipit. -// Vestibulum egestas tortor nec cursus pellentesque. In rutrum vehicula nibh -// accumsan ornare. Vivamus laoreet ultrices felis, quis vestibulum odio -// tincidunt in. Aliquam pulvinar vehicula nisi, id pharetra mauris malesuada -// in. Etiam tristique ligula id egestas condimentum. Morbi non iaculis massa, -// sit amet posuere magna. Suspendisse lacus metus, placerat facilisis porttitor -// nec, tincidunt at sapien. Ut a felis lorem. Proin pellentesque quam dui, eu -// maximus libero molestie nec. Fusce eleifend nunc magna, nec condimentum dolor -// fermentum non. Quisque eget eros mattis, aliquet tortor id, ullamcorper enim. -// Sed at mi porta, consectetur sem et, sollicitudin nunc. Nullam eu dolor id -// metus vestibulum gravida in at mauris. Integer a leo tempus, mattis augue at, -// placerat ipsum. -// -// Cras ex leo, iaculis sit amet metus non, cursus maximus massa. Proin ac urna -// eu nunc fermentum vulputate id sit amet eros. Cras dictum dui sit amet sem -// rhoncus sollicitudin. Integer risus libero, viverra tincidunt nisi fringilla, -// posuere mollis urna. Mauris commodo, tellus vitae bibendum condimentum, nisl -// neque porta massa, ornare imperdiet nunc lacus et purus. Suspendisse nec nisi -// vehicula, pretium tortor sit amet, vulputate arcu. Curabitur quis aliquam -// urna. Sed ornare velit nec erat bibendum, sed dapibus mauris rutrum. Cras -// feugiat lacus pellentesque metus mattis, pretium posuere ante tempus. Donec -// id sapien nec dui pharetra gravida. Mauris consequat diam sed sem eleifend -// sagittis. Duis vitae fringilla nunc, a commodo velit. Etiam facilisis, libero -// vehicula finibus tristique, arcu lorem pharetra ex, vitae facilisis neque -// ipsum a leo. Maecenas et augue eget est condimentum dapibus vel id metus. -// Nullam sodales lobortis placerat. -// -// Aenean enim nibh, mollis nec nulla porttitor, ullamcorper consequat turpis. -// Donec hendrerit quam sodales, condimentum elit et, feugiat leo. Mauris mauris -// odio, convallis nec ante at, ultricies blandit ante. Curabitur eu dolor ac -// nisl scelerisque molestie vulputate et elit. Suspendisse non blandit risus. -// Nulla feugiat metus ut leo luctus, vitae lacinia nisl mollis. Morbi tristique -// justo eu dui varius fermentum. Nunc iaculis ut tellus sed varius. Suspendisse -// non semper dui. Quisque eget laoreet metus. In hac habitasse platea dictumst. -// -// Etiam dignissim libero non mauris placerat, suscipit consequat sapien -// fringilla. Mauris consectetur blandit justo, vitae cursus turpis laoreet -// eget. Nulla hendrerit ante in ante maximus eleifend. Nulla non convallis dui, -// ut auctor nibh. Fusce mattis leo et mauris mattis congue. Sed vitae eleifend -// tortor, non pulvinar lorem. Mauris facilisis lorem dui, id auctor neque -// dictum id. Nam laoreet suscipit metus, tristique porta ex tristique id. Donec -// lacinia vitae odio sed blandit. Ut finibus auctor auctor. Curabitur odio -// tortor, dignissim eget consectetur nec, vehicula sit amet metus. Vestibulum -// massa quam, pharetra mollis ante nec, tristique mollis erat. Morbi nisl -// lacus, imperdiet in porttitor vel, bibendum non sem. Phasellus nibh libero, -// condimentum sit amet consequat ac, eleifend a tellus. diff --git a/vendor/wp-now/src/tests/mode-examples/theme-with-assets/style.css b/vendor/wp-now/src/tests/mode-examples/theme-with-assets/style.css deleted file mode 100644 index a9b3b265b7..0000000000 --- a/vendor/wp-now/src/tests/mode-examples/theme-with-assets/style.css +++ /dev/null @@ -1,67 +0,0 @@ -/* -Theme Name: Test Theme -Text Domain: test-theme -Version: 1.0 -*/ - -/* Don't remove the lorem ipsum. it's required to have a mininum file size to - * test that the server is correctly compressing this file. */ - -/* -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin at mi ligula. -Duis lacinia feugiat tellus, quis pretium nulla. Cras urna ligula, gravida ut -risus finibus, porta accumsan sem. Phasellus id ante a urna tristique rutrum. -Donec sollicitudin fermentum enim sit amet viverra. Proin nec nunc libero. -Phasellus non felis nibh. Duis sit amet ipsum ultrices, aliquet turpis non, -dictum metus. Cras aliquam, augue a placerat tincidunt, ligula ex placerat -lectus, a commodo nisi elit sit amet odio. Praesent lectus lacus, eleifend et -eros sed, ornare aliquam massa. Nullam et elit faucibus, tristique justo eget, -vulputate leo. Ut sagittis lectus diam. Mauris porttitor porta urna. - -Aliquam erat volutpat. Proin efficitur sem nec mi viverra suscipit. Vestibulum -egestas tortor nec cursus pellentesque. In rutrum vehicula nibh accumsan ornare. -Vivamus laoreet ultrices felis, quis vestibulum odio tincidunt in. Aliquam -pulvinar vehicula nisi, id pharetra mauris malesuada in. Etiam tristique ligula -id egestas condimentum. Morbi non iaculis massa, sit amet posuere magna. -Suspendisse lacus metus, placerat facilisis porttitor nec, tincidunt at sapien. -Ut a felis lorem. Proin pellentesque quam dui, eu maximus libero molestie nec. -Fusce eleifend nunc magna, nec condimentum dolor fermentum non. Quisque eget -eros mattis, aliquet tortor id, ullamcorper enim. Sed at mi porta, consectetur -sem et, sollicitudin nunc. Nullam eu dolor id metus vestibulum gravida in at -mauris. Integer a leo tempus, mattis augue at, placerat ipsum. - -Cras ex leo, iaculis sit amet metus non, cursus maximus massa. Proin ac urna eu -nunc fermentum vulputate id sit amet eros. Cras dictum dui sit amet sem rhoncus -sollicitudin. Integer risus libero, viverra tincidunt nisi fringilla, posuere -mollis urna. Mauris commodo, tellus vitae bibendum condimentum, nisl neque porta -massa, ornare imperdiet nunc lacus et purus. Suspendisse nec nisi vehicula, -pretium tortor sit amet, vulputate arcu. Curabitur quis aliquam urna. Sed ornare -velit nec erat bibendum, sed dapibus mauris rutrum. Cras feugiat lacus -pellentesque metus mattis, pretium posuere ante tempus. Donec id sapien nec dui -pharetra gravida. Mauris consequat diam sed sem eleifend sagittis. Duis vitae -fringilla nunc, a commodo velit. Etiam facilisis, libero vehicula finibus -tristique, arcu lorem pharetra ex, vitae facilisis neque ipsum a leo. Maecenas -et augue eget est condimentum dapibus vel id metus. Nullam sodales lobortis -placerat. - -Aenean enim nibh, mollis nec nulla porttitor, ullamcorper consequat turpis. -Donec hendrerit quam sodales, condimentum elit et, feugiat leo. Mauris mauris -odio, convallis nec ante at, ultricies blandit ante. Curabitur eu dolor ac nisl -scelerisque molestie vulputate et elit. Suspendisse non blandit risus. Nulla -feugiat metus ut leo luctus, vitae lacinia nisl mollis. Morbi tristique justo eu -dui varius fermentum. Nunc iaculis ut tellus sed varius. Suspendisse non semper -dui. Quisque eget laoreet metus. In hac habitasse platea dictumst. - -Etiam dignissim libero non mauris placerat, suscipit consequat sapien fringilla. -Mauris consectetur blandit justo, vitae cursus turpis laoreet eget. Nulla -hendrerit ante in ante maximus eleifend. Nulla non convallis dui, ut auctor -nibh. Fusce mattis leo et mauris mattis congue. Sed vitae eleifend tortor, non -pulvinar lorem. Mauris facilisis lorem dui, id auctor neque dictum id. Nam -laoreet suscipit metus, tristique porta ex tristique id. Donec lacinia vitae -odio sed blandit. Ut finibus auctor auctor. Curabitur odio tortor, dignissim -eget consectetur nec, vehicula sit amet metus. Vestibulum massa quam, pharetra -mollis ante nec, tristique mollis erat. Morbi nisl lacus, imperdiet in porttitor -vel, bibendum non sem. Phasellus nibh libero, condimentum sit amet consequat ac, -eleifend a tellus. - -*/ diff --git a/vendor/wp-now/src/tests/mode-examples/theme/style.css b/vendor/wp-now/src/tests/mode-examples/theme/style.css deleted file mode 100644 index 97d1186a3c..0000000000 --- a/vendor/wp-now/src/tests/mode-examples/theme/style.css +++ /dev/null @@ -1,22 +0,0 @@ -/* -Theme Name: Yolo Theme -Text Domain: twentytwenty -Version: 2.2 -Tested up to: 6.2 -Requires at least: 4.7 -Requires PHP: 5.2.4 -Description: Our default theme for 2020 is designed to take full advantage of the flexibility of the block editor. Organizations and businesses have the ability to create dynamic landing pages with endless layouts using the group and column blocks. The centered content column and fine-tuned typography also makes it perfect for traditional blogs. Complete editor styles give you a good idea of what your content will look like, even before you publish. You can give your site a personal touch by changing the background colors and the accent color in the Customizer. The colors of all elements on your site are automatically calculated based on the colors you pick, ensuring a high, accessible color contrast for your visitors. -Tags: blog, one-column, custom-background, custom-colors, custom-logo, custom-menu, editor-style, featured-images, footer-widgets, full-width-template, rtl-language-support, sticky-post, theme-options, threaded-comments, translation-ready, block-patterns, block-styles, wide-blocks, accessibility-ready -Author: the WordPress team -Author URI: https://wordpress.org/ -Theme URI: https://wordpress.org/themes/twentytwenty/ -License: GNU General Public License v2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html - -All files, unless otherwise stated, are released under the GNU General Public -License version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) - -This theme, like WordPress, is licensed under the GPL. -Use it to make something cool, have fun, and share what you've learned -with others. -*/ diff --git a/vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-content/.gitkeep b/vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-content/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-includes/.gitkeep b/vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-includes/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-load.php b/vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-load.php deleted file mode 100644 index b3d9bbc7f3..0000000000 --- a/vendor/wp-now/src/tests/mode-examples/wordpress-develop/build/wp-load.php +++ /dev/null @@ -1 +0,0 @@ - { - const projectDirectory = exampleDir + '/index'; - const rawOptions: CliOptions = { - path: projectDirectory, - }; - const options = await getWpNowConfig( rawOptions ); - - expect( options.phpVersion ).toBe( '8.3' ); - expect( options.wordPressVersion ).toBe( 'latest' ); - expect( options.documentRoot ).toBe( '/var/www/html' ); - expect( options.mode ).toBe( WPNowMode.INDEX ); - expect( options.projectPath ).toBe( projectDirectory ); -} ); - -//TODO: Add it back when all options are supported as cli arguments -// test('parseOptions with custom options', async () => { -// const rawOptions: Partial = { -// phpVersion: '7.3', -// wordPressVersion: '5.7', -// documentRoot: '/var/www/my-site', -// mode: WPNowMode.WORDPRESS, -// projectPath: '/path/to/my-site', -// wpContentPath: '/path/to/my-site/wp-content', -// absoluteUrl: 'http://localhost:8080', -// }; -// const options = await parseOptions(rawOptions); -// expect(options.phpVersion).toBe('7.3'); -// expect(options.wordPressVersion).toBe('5.7'); -// expect(options.documentRoot).toBe('/var/www/my-site'); -// expect(options.mode).toBe(WPNowMode.WORDPRESS); -// expect(options.projectPath).toBe('/path/to/my-site'); -// expect(options.wpContentPath).toBe('/path/to/my-site/wp-content'); -// expect(options.absoluteUrl).toBe('http://localhost:8080'); -// }); - -test( 'getWpNowConfig with unsupported PHP version', async () => { - const rawOptions: CliOptions = { - php: '5.4' as any, - }; - await expect( getWpNowConfig( rawOptions ) ).rejects.toThrowError( - 'Unsupported PHP version: 5.4.' - ); -} ); - -// Plugin mode -test( 'isPluginDirectory detects a WordPress plugin and infer PLUGIN mode.', () => { - const projectPath = exampleDir + '/plugin'; - expect( isPluginDirectory( projectPath ) ).toBe( true ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.PLUGIN ); -} ); - -test( 'isPluginDirectory detects a WordPress plugin in case-insensitive way and infer PLUGIN mode.', () => { - const projectPath = exampleDir + '/plugin-case-insensitive'; - expect( isPluginDirectory( projectPath ) ).toBe( true ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.PLUGIN ); -} ); - -test( 'isPluginDirectory returns false for non-plugin directory', () => { - const projectPath = exampleDir + '/not-plugin'; - expect( isPluginDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.PLAYGROUND ); -} ); - -// Theme mode -test( 'isThemeDirectory detects a WordPress theme and infer THEME mode', () => { - const projectPath = exampleDir + '/theme'; - expect( isThemeDirectory( projectPath ) ).toBe( true ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.THEME ); -} ); - -test( 'getThemeTemplate detects if a WordPress theme has a parent template.', () => { - const projectPath = exampleDir + '/child-theme/child'; - expect( getThemeTemplate( projectPath ) ).toBe( 'parent' ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.THEME ); -} ); - -test( 'getThemeTemplate returns falsy value for themes without parent', () => { - const projectPath = exampleDir + '/theme'; - expect( getThemeTemplate( projectPath ) ).toBe( undefined ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.THEME ); -} ); - -test( 'isThemeDirectory returns false for non-theme directory', () => { - const projectPath = exampleDir + '/not-theme'; - expect( isThemeDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.PLAYGROUND ); -} ); - -test( 'isThemeDirectory returns false for a directory with style.css but without Theme Name', () => { - const projectPath = exampleDir + '/not-theme'; - - expect( isThemeDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.PLAYGROUND ); -} ); - -// WP_CONTENT mode -test( 'isWpContentDirectory detects a WordPress wp-content directory and infer WP_CONTENT mode', () => { - const projectPath = exampleDir + '/wp-content'; - expect( isWpContentDirectory( projectPath ) ).toBe( true ); - expect( isWordPressDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.WP_CONTENT ); -} ); - -test( 'isWpContentDirectory returns false for wp-content parent directory', () => { - const projectPath = exampleDir + '/index'; - expect( isWpContentDirectory( projectPath ) ).toBe( false ); - expect( isWordPressDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.INDEX ); -} ); - -test( 'isWpContentDirectory returns true for a directory with only themes directory', () => { - const projectPath = exampleDir + '/wp-content-only-themes'; - expect( isWpContentDirectory( projectPath ) ).toBe( true ); - expect( isWordPressDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.WP_CONTENT ); -} ); - -test( 'isWpContentDirectory returns true for a directory with only mu-plugins directory', () => { - const projectPath = exampleDir + '/wp-content-only-mu-plugins'; - expect( isWpContentDirectory( projectPath ) ).toBe( true ); - expect( isWordPressDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.WP_CONTENT ); -} ); - -// WordPress mode -test( 'isWordPressDirectory detects a WordPress directory and WORDPRESS mode', () => { - const projectPath = exampleDir + '/wordpress'; - - expect( isWordPressDirectory( projectPath ) ).toBe( true ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.WORDPRESS ); -} ); - -test( 'isWordPressDirectory returns false for non-WordPress directory', () => { - const projectPath = exampleDir + '/nothing'; - - expect( isWordPressDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.PLAYGROUND ); -} ); - -// WordPress developer mode -test( 'isWordPressDevelopDirectory detects a WordPress-develop directory and WORDPRESS_DEVELOP mode', () => { - const projectPath = exampleDir + '/wordpress-develop'; - - expect( isWordPressDevelopDirectory( projectPath ) ).toBe( true ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.WORDPRESS_DEVELOP ); -} ); - -test( 'isWordPressDevelopDirectory returns false for non-WordPress-develop directory', () => { - const projectPath = exampleDir + '/nothing'; - - expect( isWordPressDevelopDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.PLAYGROUND ); -} ); - -test( 'isWordPressDevelopDirectory returns false for incomplete WordPress-develop directory', () => { - const projectPath = exampleDir + '/not-wordpress-develop'; - - expect( isWordPressDevelopDirectory( projectPath ) ).toBe( false ); - expect( inferMode( projectPath ) ).toBe( WPNowMode.PLAYGROUND ); -} ); - -describe( 'Test starting different modes', () => { - let tmpExampleDirectory; - - /** - * Download an initial copy of WordPress - */ - beforeAll( async () => { - fs.rmSync( getWpNowTmpPath(), { recursive: true, force: true } ); - await downloadWithTimer( 'wordpress', downloadWordPress ); - } ); - - /** - * Copy example directory to a temporary directory - */ - beforeEach( () => { - const tmpDirectory = os.tmpdir(); - const directoryHash = crypto.randomBytes( 20 ).toString( 'hex' ); - - tmpExampleDirectory = path.join( tmpDirectory, `wp-now-tests-examples-${ directoryHash }` ); - fs.ensureDirSync( tmpExampleDirectory ); - fs.copySync( exampleDir, tmpExampleDirectory ); - } ); - - /** - * Remove temporary directory - */ - afterEach( () => { - fs.rmSync( tmpExampleDirectory, { recursive: true, force: true } ); - } ); - - /** - * Remove wp-now hidden directory from temporary directory. - */ - afterAll( () => { - fs.rmSync( getWpNowTmpPath(), { recursive: true, force: true } ); - } ); - - /** - * Expect that all provided mount point paths are empty directories which are result of file system mounts. - * - * @param mountPaths List of mount point paths that should exist on file system. - * @param projectPath Project path. - */ - const expectEmptyMountPoints = ( mountPaths, projectPath ) => { - mountPaths.map( ( relativePath ) => { - const fullPath = path.join( projectPath, relativePath ); - - expect( { - path: fullPath, - exists: fs.existsSync( fullPath ), - } ).toStrictEqual( { path: fullPath, exists: true } ); - - expect( { - path: fullPath, - content: fs.readdirSync( fullPath ), - } ).toStrictEqual( { path: fullPath, content: [] } ); - - expect( { - path: fullPath, - isDirectory: fs.lstatSync( fullPath ).isDirectory(), - } ).toStrictEqual( { path: fullPath, isDirectory: true } ); - } ); - }; - - /** - * Expect that all listed files do not exist in project directory - * - * @param forbiddenFiles List of files that should not exist on file system. - * @param projectPath Project path. - */ - const expectForbiddenProjectFiles = ( forbiddenFiles, projectPath ) => { - forbiddenFiles.map( ( relativePath ) => { - const fullPath = path.join( projectPath, relativePath ); - expect( { - path: fullPath, - exists: fs.existsSync( fullPath ), - } ).toStrictEqual( { path: fullPath, exists: false } ); - } ); - }; - - /** - * Expect that all required files exist for PHP. - * - * @param requiredFiles List of files that should be accessible by PHP. - * @param documentRoot Document root of the PHP server. - * @param php NodePHP instance. - */ - const expectRequiredRootFiles = ( requiredFiles, documentRoot, php ) => { - requiredFiles.map( ( relativePath ) => { - const fullPath = path.join( documentRoot, relativePath ); - expect( { - path: fullPath, - exists: php.fileExists( fullPath ), - } ).toStrictEqual( { path: fullPath, exists: true } ); - } ); - }; - - /** - * Test that startWPNow in "index", "plugin", "theme", and "playground" modes doesn't change anything in the project directory. - */ - test.each( [ - [ 'index', [ 'index.php' ] ], - [ 'plugin', [ 'sample-plugin.php' ] ], - [ 'theme', [ 'style.css' ] ], - [ 'playground', [ 'my-file.txt' ] ], - ] )( 'startWPNow starts %s mode', async ( mode, expectedDirectories ) => { - const projectPath = path.join( tmpExampleDirectory, mode ); - - const rawOptions: CliOptions = { - path: projectPath, - }; - - const options = await getWpNowConfig( rawOptions ); - - await startWPNow( options ); - - const forbiddenPaths = [ 'wp-config.php' ]; - - expectForbiddenProjectFiles( forbiddenPaths, projectPath ); - - expect( fs.readdirSync( projectPath ) ).toEqual( expectedDirectories ); - } ); - - /** - * Test that startWPNow in "wp-content" mode mounts required files and directories, and - * that required files exist for PHP. - */ - test( 'startWPNow starts wp-content mode', async () => { - const projectPath = path.join( tmpExampleDirectory, 'wp-content' ); - - const rawOptions: CliOptions = { - path: projectPath, - }; - - const options = await getWpNowConfig( rawOptions ); - - const { php, options: wpNowOptions } = await startWPNow( options ); - - const mountPointPaths = [ - 'database', - 'db.php', - 'mu-plugins', - 'plugins/sqlite-database-integration', - ]; - - expectEmptyMountPoints( mountPointPaths, projectPath ); - - const forbiddenPaths = [ 'wp-config.php' ]; - - expectForbiddenProjectFiles( forbiddenPaths, projectPath ); - - const requiredFiles = [ 'wp-content/db.php', 'wp-content/mu-plugins/0-allow-wp-org.php' ]; - - expectRequiredRootFiles( requiredFiles, wpNowOptions.documentRoot, php ); - } ); - - /** - * Test that startWPNow in "wordpress" mode without existing wp-config.php file mounts - * required files and directories, and that required files exist for PHP. - */ - test( 'startWPNow starts wordpress mode', async () => { - const projectPath = path.join( tmpExampleDirectory, 'wordpress' ); - - const rawOptions: CliOptions = { - path: projectPath, - }; - const options = await getWpNowConfig( rawOptions ); - - const { php, options: wpNowOptions } = await startWPNow( options ); - - const mountPointPaths = [ - 'wp-content/database', - 'wp-content/db.php', - 'wp-content/mu-plugins', - 'wp-content/plugins/sqlite-database-integration', - ]; - - expectEmptyMountPoints( mountPointPaths, projectPath ); - - const requiredFiles = [ - 'wp-content/db.php', - 'wp-content/mu-plugins/0-allow-wp-org.php', - 'wp-config.php', - ]; - - expectRequiredRootFiles( requiredFiles, wpNowOptions.documentRoot, php ); - } ); - - /** - * Test that startWPNow in "wordpress" mode with existing wp-config.php file mounts - * required files and directories, and that required files exist for PHP. - */ - test( 'startWPNow starts wordpress mode with existing wp-config', async () => { - const projectPath = path.join( tmpExampleDirectory, 'wordpress-with-config' ); - - const rawOptions: CliOptions = { - path: projectPath, - }; - const options = await getWpNowConfig( rawOptions ); - - const { php, options: wpNowOptions } = await startWPNow( options ); - - const mountPointPaths = [ 'wp-content/mu-plugins' ]; - - expectEmptyMountPoints( mountPointPaths, projectPath ); - - const forbiddenPaths = [ - 'wp-content/database', - 'wp-content/db.php', - 'wp-content/plugins/sqlite-database-integration', - ]; - - expectForbiddenProjectFiles( forbiddenPaths, projectPath ); - - const requiredFiles = [ 'wp-content/mu-plugins/0-allow-wp-org.php', 'wp-config.php' ]; - - expectRequiredRootFiles( requiredFiles, wpNowOptions.documentRoot, php ); - } ); - - /** - * Test that startWPNow in "plugin" mode auto installs the plugin. - */ - test( 'startWPNow auto installs the plugin', async () => { - const projectPath = path.join( tmpExampleDirectory, 'plugin' ); - const options = await getWpNowConfig( { path: projectPath } ); - const { php } = await startWPNow( options ); - const codeIsPluginActivePhp = ` { - const projectPath = path.join( tmpExampleDirectory, 'plugin' ); - const options = await getWpNowConfig( { path: projectPath } ); - const { php } = await startWPNow( options ); - const deactivatePluginPhp = ` { - const projectPath = path.join( tmpExampleDirectory, 'theme' ); - const options = await getWpNowConfig( { path: projectPath } ); - const { php } = await startWPNow( options ); - const codeActiveThemeNamePhp = `get('Name'); - `; - const themeName = await php.run( { - code: codeActiveThemeNamePhp, - } ); - - expect( themeName.text ).toContain( 'Yolo Theme' ); - } ); - - /** - * Test that startWPNow in "theme" mode auto activates the theme. - */ - test( 'startWPNow auto installs mounts child and parent theme.', async () => { - const projectPath = path.join( tmpExampleDirectory, 'child-theme/child' ); - const options = await getWpNowConfig( { path: projectPath } ); - const { php } = await startWPNow( options ); - - const childThemePhp = `get('Name'); - `; - - const parentThemePhp = `exists() ) { - echo $theme->get('Name'); - } else { - echo 'Not found'; - } - `; - - const childThemeName = await php.run( { - code: childThemePhp, - } ); - const parentThemeName = await php.run( { - code: parentThemePhp, - } ); - - expect( childThemeName.text ).toContain( 'Child Theme' ); - expect( parentThemeName.text ).toContain( 'Parent Theme' ); - } ); - - /** - * Test that startWPNow in "theme" mode auto activates the theme. - */ - test( 'startWPNow auto handles child theme with missing parent.', async () => { - const projectPath = path.join( tmpExampleDirectory, 'child-theme-missing-parent/child' ); - const options = await getWpNowConfig( { path: projectPath } ); - const { php } = await startWPNow( options ); - - const childThemePhp = `get('Name'); - `; - - const childThemeName = await php.run( { - code: childThemePhp, - } ); - - expect( childThemeName.text ).toContain( 'Child Theme' ); - } ); - - /** - * Test that startWPNow in "theme" mode does not auto activate the theme the second time. - */ - test( 'startWPNow auto installs the theme', async () => { - const projectPath = path.join( tmpExampleDirectory, 'theme' ); - const options = await getWpNowConfig( { path: projectPath } ); - const { php } = await startWPNow( options ); - const switchThemePhp = `get('Name'); - `; - const themeName = await phpSecondTime.run( { - code: codeActiveThemeNamePhp, - } ); - - expect( themeName.text ).toContain( 'Twenty Twenty-Three' ); - } ); - - /* - * Test that startWPNow doesn't leave dirty files. - * - * @see https://github.com/WordPress/playground-tools/issues/32 - */ - test.skip( 'startWPNow does not leave dirty folders and files', async () => { - const projectPath = path.join( tmpExampleDirectory, 'wordpress' ); - const options = await getWpNowConfig( { path: projectPath } ); - await startWPNow( options ); - expect( - fs.existsSync( path.join( projectPath, 'wp-content', 'mu-plugins', '0-allow-wp-org.php' ) ) - ).toBe( false ); - expect( fs.existsSync( path.join( projectPath, 'wp-content', 'database' ) ) ).toBe( false ); - expect( - fs.existsSync( - path.join( projectPath, 'wp-content', 'plugins', 'sqlite-database-integration' ) - ) - ).toBe( false ); - expect( fs.existsSync( path.join( projectPath, 'wp-content', 'db.php' ) ) ).toBe( false ); - } ); - - /** - * Test PHP integration test executing runCli. - */ - describe( 'validate php comand arguments passed through yargs', () => { - const phpExampleDir = path.join( __dirname, 'execute-php' ); - const argv = process.argv; - let output = ''; - let processExitMock; - beforeEach( () => { - vi.spyOn( console, 'log' ).mockImplementation( ( newLine: string ) => { - output += `${ newLine }\n`; - } ); - processExitMock = vi.spyOn( process, 'exit' ).mockImplementation( () => null ); - } ); - - afterEach( () => { - output = ''; - process.argv = argv; - vi.resetAllMocks(); - } ); - - test( 'php should receive the correct yargs arguments', async () => { - process.argv = [ 'node', 'wp-now', 'php', '--', '--version' ]; - await runCli(); - expect( output ).toMatch( /PHP 8\.3(.*)\(cli\)/i ); - expect( processExitMock ).toHaveBeenCalledWith( 0 ); - } ); - - test( 'wp-now should change the php version', async () => { - process.argv = [ 'node', 'wp-now', 'php', '--php=7.4', '--', '--version' ]; - await runCli(); - expect( output ).toMatch( /PHP 7\.4(.*)\(cli\)/i ); - expect( processExitMock ).toHaveBeenCalledWith( 0 ); - } ); - - test( 'php should execute a file', async () => { - const filePath = path.join( phpExampleDir, 'php-version.php' ); - process.argv = [ 'node', 'wp-now', 'php', filePath ]; - await runCli(); - expect( output ).toMatch( /8\.3/i ); - expect( processExitMock ).toHaveBeenCalledWith( 0 ); - } ); - - test( 'php should execute a file and change php version', async () => { - const filePath = path.join( phpExampleDir, 'php-version.php' ); - process.argv = [ 'node', 'wp-now', 'php', '--php=7.4', '--', filePath ]; - await runCli(); - expect( output ).toMatch( /7\.4/i ); - expect( processExitMock ).toHaveBeenCalledWith( 0 ); - } ); - } ); - - /** - * Test startServer. - */ - describe( 'startServer', () => { - let stopServer, options; - - beforeEach( async () => { - const projectPath = path.join( tmpExampleDirectory, 'theme-with-assets' ); - const config = await getWpNowConfig( { path: projectPath } ); - const server = await startServer( config ); - stopServer = server.stopServer; - options = server.options; - } ); - - afterEach( async () => { - options = null; - await stopServer(); - } ); - - /** - * Test that startServer compresses the text files correctly. - */ - test.each( [ - [ 'html', '' ], - [ 'css', '/wp-content/themes/theme-with-assets/style.css' ], - [ 'javascript', '/wp-content/themes/theme-with-assets/javascript.js' ], - ] )( 'startServer compresses the %s file', async ( _, file ) => { - const req = await fetch( `${ options.absoluteUrl }${ file }` ); - expect( req.headers.get( 'content-encoding' ) ).toBe( 'gzip' ); - } ); - - /** - * Test that startServer doesn't compress non text files. - */ - test.each( [ - [ 'png', '/wp-content/themes/theme-with-assets/image.png' ], - [ 'jpg', '/wp-content/themes/theme-with-assets/image.jpg' ], - ] )( "startServer doesn't compress the %s file", async ( _, file ) => { - const req = await fetch( `${ options.absoluteUrl }${ file }` ); - expect( req.headers.get( 'content-encoding' ) ).toBe( null ); - } ); - } ); - - /** - * Test blueprints execution. - */ - describe( 'blueprints', () => { - const blueprintExamplesPath = path.join( __dirname, 'blueprints' ); - - afterEach( () => { - // Clean the custom url from the SQLite database - fs.rmSync( path.join( getWpNowTmpPath(), 'wp-content', 'playground' ), { recursive: true } ); - } ); - - test( 'setting wp-config variable WP_DEBUG_LOG through blueprint', async () => { - const options = await getWpNowConfig( { - blueprint: path.join( blueprintExamplesPath, 'wp-debug.json' ), - } ); - const { php, stopServer } = await startServer( options ); - php.writeFile( `${ php.documentRoot }/print-constants.php`, ` { - const options = await getWpNowConfig( { - blueprint: path.join( blueprintExamplesPath, 'wp-config.json' ), - } ); - const { php, stopServer } = await startServer( options ); - expect( options.absoluteUrl ).toBe( 'http://127.0.0.1' ); - - php.writeFile( `${ php.documentRoot }/print-constants.php`, ` { - let removeSyncSpy; - beforeAll( () => { - removeSyncSpy = vi.spyOn( fs, 'removeSync' ).mockImplementation( () => {} ); - } ); - - afterAll( () => { - removeSyncSpy.mockRestore(); - } ); - - test( 'creates a new environment, destroying the old environment', async () => { - const mode = 'theme'; - const projectPath = path.join( tmpExampleDirectory, mode ); - const rawOptions: CliOptions = { - path: projectPath, - reset: true, - }; - const options = await getWpNowConfig( rawOptions ); - await startWPNow( options ); - const wpContentPathRegExp = new RegExp( - `${ getWpNowTmpPath() }\\/wp-content\\/${ mode }-[\\w\\d]+` - ); - - expect( removeSyncSpy ).toHaveBeenCalledWith( expect.stringMatching( wpContentPathRegExp ) ); - } ); - } ); -} ); - -/** - * Test wp-cli command. - */ -describe( 'wp-cli command', () => { - let consoleSpy; - let output = ''; - - beforeEach( () => { - function onStdout( outputLine: string ) { - output += outputLine; - } - consoleSpy = vi.spyOn( console, 'log' ); - consoleSpy.mockImplementation( onStdout ); - } ); - - afterEach( () => { - output = ''; - consoleSpy.mockRestore(); - } ); - - beforeAll( async () => { - await downloadWithTimer( 'wp-cli', downloadWpCli ); - } ); - - afterAll( () => { - fs.removeSync( getWpCliTmpPath() ); - } ); - - /** - * Test wp-cli displays the version. - * We don't need the WordPress context for this test. - */ - test( 'wp-cli displays the version', async () => { - await executeWPCli( [ 'cli', 'version' ] ); - expect( output ).toMatch( /WP-CLI (\d\.?)+/i ); - } ); -} ); diff --git a/vendor/wp-now/src/wp-now.ts b/vendor/wp-now/src/wp-now.ts deleted file mode 100644 index 3a8e5da72e..0000000000 --- a/vendor/wp-now/src/wp-now.ts +++ /dev/null @@ -1,734 +0,0 @@ -import path from 'path'; -import { rootCertificates } from 'tls'; -import { createNodeFsMountHandler, loadNodeRuntime } from '@php-wasm/node'; -import { - MountHandler, - PHP, - PHPRequestHandler, - proxyFileSystem, - rotatePHPRuntime, - setPhpIniEntries, -} from '@php-wasm/universal'; -import { phpVar } from '@php-wasm/util'; -import { - StepDefinition, - activatePlugin, - activateTheme, - compileBlueprint, - defineWpConfigConsts, - runBlueprintSteps, -} from '@wp-playground/blueprints'; -import { - wordPressRewriteRules, - getFileNotFoundActionForWordPress, - setupPlatformLevelMuPlugins, -} from '@wp-playground/wordpress'; -import fs from 'fs-extra'; -import { WPNowOptions, WPNowMode } from './config'; -import { - PLAYGROUND_INTERNAL_MU_PLUGINS_FOLDER, - PLAYGROUND_INTERNAL_PRELOAD_PATH, - PLAYGROUND_INTERNAL_SHARED_FOLDER, - SQLITE_FILENAME, - SQLITE_PLUGIN_FOLDER, -} from './constants'; -import { removeDownloadedMuPlugins } from './download'; -import getSqlitePath from './get-sqlite-path'; -import getWordpressVersionsPath from './get-wordpress-versions-path'; -import { output } from './output'; -import { - hasIndexFile, - isPluginDirectory, - isThemeDirectory, - isWpContentDirectory, - isWordPressDirectory, - isWordPressDevelopDirectory, - getPluginFile, - readFileHead, -} from './wp-playground-wordpress'; - -export default async function startWPNow( - options: Partial< WPNowOptions > = {} -): Promise< { php: PHP; options: WPNowOptions } > { - const { documentRoot } = options; - const requestHandler = new PHPRequestHandler( { - phpFactory: async ( { isPrimary, requestHandler: reqHandler } ) => { - const { php } = await getPHPInstance( options, isPrimary, reqHandler ); - if ( ! isPrimary ) { - proxyFileSystem( await requestHandler.getPrimaryPhp(), php, [ - '/tmp', - requestHandler.documentRoot, - '/internal/shared', - ] ); - } - - if ( reqHandler ) { - php.requestHandler = reqHandler; - } - - return php; - }, - documentRoot: documentRoot || '/wordpress', - absoluteUrl: options.absoluteUrl, - rewriteRules: wordPressRewriteRules, - getFileNotFoundAction: getFileNotFoundActionForWordPress, - cookieStore: false, - } ); - - const php = await requestHandler.getPrimaryPhp(); - - applyOverrideUmaskWorkaround( php ); - await prepareDocumentRoot( php, options ); - - output?.log( `directory: ${ options.projectPath }` ); - output?.log( `mode: ${ options.mode }` ); - output?.log( `php: ${ options.phpVersion }` ); - - if ( options.mode === WPNowMode.INDEX ) { - await runIndexMode( php, options ); - return { php, options }; - } - output?.log( `wp: ${ options.wordPressVersion }` ); - - if ( options.reset ) { - fs.removeSync( options.wpContentPath ); - output?.log( 'Created a fresh SQLite database and wp-content directory.' ); - } - - const isFirstTimeProject = ! fs.existsSync( options.wpContentPath ); - - await prepareWordPress( php, options ); - - await installationSteps( php, options ); - - if ( options.blueprintObject ) { - output?.log( `blueprint steps: ${ options.blueprintObject.steps.length }` ); - const compiled = await compileBlueprint( options.blueprintObject, { - onStepCompleted: ( result, step: StepDefinition ) => { - output?.log( `Blueprint step completed: ${ step.step }` ); - }, - } ); - await runBlueprintSteps( compiled, php ); - } - - await login( php, options ); - - if ( isFirstTimeProject && [ WPNowMode.PLUGIN, WPNowMode.THEME ].includes( options.mode ) ) { - await activatePluginOrTheme( php, options ); - } - - rotatePHPRuntime( { - php, - cwd: requestHandler.documentRoot, - recreateRuntime: async () => { - output?.log( 'Recreating and rotating PHP runtime' ); - const { runtimeId } = await getPHPInstance( options, true, requestHandler ); - return runtimeId; - }, - maxRequests: 400, - } ); - - return { - php, - options, - }; -} - -async function getPHPInstance( - options: WPNowOptions, - isPrimary: boolean, - requestHandler: PHPRequestHandler -): Promise< { php: PHP; runtimeId: number } > { - const id = await loadNodeRuntime( options.phpVersion, { - followSymlinks: true, - } ); - const php = new PHP( id ); - php.requestHandler = requestHandler; - - await setPhpIniEntries( php, { - memory_limit: '256M', - disable_functions: '', - allow_url_fopen: '1', - 'openssl.cafile': path.posix.join( PLAYGROUND_INTERNAL_SHARED_FOLDER, 'ca-bundle.crt' ), - } ); - - return { php, runtimeId: id }; -} - -async function prepareDocumentRoot( php: PHP, options: WPNowOptions ) { - php.mkdir( options.documentRoot ); - php.chdir( options.documentRoot ); - php.writeFile( - path.posix.join( PLAYGROUND_INTERNAL_SHARED_FOLDER, 'ca-bundle.crt' ), - rootCertificates.join( '\n' ) - ); -} - -export async function prepareWordPress( php: PHP, options: WPNowOptions ) { - /** - * Studio used to store internal mu-plugins in the site's mu-plugins folder. - * Internal mu-plugins are now mounted into Playground's internal mu-plugins folder, - * so we need to remove the mu-plugins from the site's mu-plugins folder. - */ - await removeDownloadedMuPlugins( options.projectPath ); - - switch ( options.mode ) { - case WPNowMode.WP_CONTENT: - await runWpContentMode( php, options ); - break; - case WPNowMode.WORDPRESS_DEVELOP: - await runWordPressDevelopMode( php, options ); - break; - case WPNowMode.WORDPRESS: - await runWordPressMode( php, options ); - break; - case WPNowMode.PLUGIN: - await runPluginOrThemeMode( php, options ); - break; - case WPNowMode.THEME: - await runPluginOrThemeMode( php, options ); - break; - case WPNowMode.PLAYGROUND: - await runWpPlaygroundMode( php, options ); - break; - } - - await mountInternalMuPlugins( php, options ); - await setupPlatformLevelMuPlugins( php ); -} - -async function runIndexMode( php: PHP, { documentRoot, projectPath }: WPNowOptions ) { - await php.mount( - projectPath, - createNodeFsMountHandler( documentRoot ) as unknown as MountHandler - ); -} - -async function runWpContentMode( - php: PHP, - { documentRoot, wordPressVersion, wpContentPath, projectPath }: WPNowOptions -) { - const wordPressPath = path.join( getWordpressVersionsPath(), wordPressVersion ); - await php.mount( - wordPressPath, - createNodeFsMountHandler( documentRoot ) as unknown as MountHandler - ); - await initWordPress( php, wordPressVersion, documentRoot ); - fs.ensureDirSync( wpContentPath ); - - await php.mount( - projectPath, - createNodeFsMountHandler( `${ documentRoot }/wp-content` ) as unknown as MountHandler - ); - - await mountSqlitePlugin( php, documentRoot ); - await mountSqliteDatabaseDirectory( php, documentRoot, wpContentPath ); -} - -async function runWordPressDevelopMode( - php: PHP, - { documentRoot, projectPath, absoluteUrl }: WPNowOptions -) { - await runWordPressMode( php, { - documentRoot, - projectPath: projectPath + '/build', - absoluteUrl, - } ); -} - -async function runWordPressMode( php: PHP, { documentRoot, projectPath }: WPNowOptions ) { - php.mkdir( documentRoot ); - await php.mount( - documentRoot, - createNodeFsMountHandler( projectPath ) as unknown as MountHandler - ); - await initWordPress( php, 'user-provided', documentRoot ); -} - -async function runPluginOrThemeMode( - php: PHP, - { wordPressVersion, documentRoot, projectPath, wpContentPath, mode }: WPNowOptions -) { - const wordPressPath = path.join( getWordpressVersionsPath(), wordPressVersion ); - await php.mount( - wordPressPath, - createNodeFsMountHandler( documentRoot ) as unknown as MountHandler - ); - await initWordPress( php, wordPressVersion, documentRoot ); - - fs.ensureDirSync( wpContentPath ); - fs.copySync( - path.join( getWordpressVersionsPath(), wordPressVersion, 'wp-content' ), - wpContentPath - ); - await php.mount( - wpContentPath, - createNodeFsMountHandler( `${ documentRoot }/wp-content` ) as unknown as MountHandler - ); - - const pluginName = path.basename( projectPath ); - const directoryName = mode === WPNowMode.PLUGIN ? 'plugins' : 'themes'; - await php.mount( - projectPath, - createNodeFsMountHandler( - `${ documentRoot }/wp-content/${ directoryName }/${ pluginName }` - ) as unknown as MountHandler - ); - if ( mode === WPNowMode.THEME ) { - const templateName = getThemeTemplate( projectPath ); - if ( templateName ) { - // We assume that the theme template is in the parent directory - const templatePath = path.join( projectPath, '..', templateName ); - if ( fs.existsSync( templatePath ) ) { - await php.mount( - templatePath, - createNodeFsMountHandler( - `${ documentRoot }/wp-content/${ directoryName }/${ templateName }` - ) as unknown as MountHandler - ); - } else { - output?.error( `Parent for child theme not found: ${ templateName }` ); - } - } - } - await mountSqlitePlugin( php, documentRoot ); -} - -async function runWpPlaygroundMode( - php: PHP, - { documentRoot, wordPressVersion, wpContentPath }: WPNowOptions -) { - const wordPressPath = path.join( getWordpressVersionsPath(), wordPressVersion ); - await php.mount( - wordPressPath, - createNodeFsMountHandler( documentRoot ) as unknown as MountHandler - ); - await initWordPress( php, wordPressVersion, documentRoot ); - - fs.ensureDirSync( wpContentPath ); - fs.copySync( - path.join( getWordpressVersionsPath(), wordPressVersion, 'wp-content' ), - wpContentPath - ); - await php.mount( - wpContentPath, - createNodeFsMountHandler( `${ documentRoot }/wp-content` ) as unknown as MountHandler - ); - - await mountSqlitePlugin( php, documentRoot ); -} - -async function login( php: PHP, options: WPNowOptions = {} ) { - const { documentRoot } = options; - - await php.writeFile( - `${ documentRoot }/playground-login.php`, - `ID ); - } else { - $user_data = array( - 'user_login' => 'admin', - 'user_pass' => '${ options.adminPassword }', - 'user_email' => 'admin@localhost.com', - 'role' => 'administrator', - ); - $user_id = wp_insert_user( $user_data ); - $user = get_user_by( 'id', $user_id ); - } - - wp_set_current_user( $user->ID, $user->user_login ); - wp_set_auth_cookie( $user->ID ); - do_action( 'wp_login', $user->user_login, $user );` - ); - - await php.requestHandler.request( { - url: '/playground-login.php', - } ); - - await php.unlink( `${ documentRoot }/playground-login.php` ); -} - -/** - * Initialize WordPress - * - * Initializes WordPress by copying sample config file to wp-config.php if it doesn't exist, - * and sets up additional constants for PHP. - * - * It also returns information about whether the default database should be initialized. - * - * @param php - * @param wordPressVersion - * @param vfsDocumentRoot - * @param siteUrl - */ -async function initWordPress( php: PHP, wordPressVersion: string, vfsDocumentRoot: string ) { - let initializeDefaultDatabase = false; - if ( ! php.fileExists( `${ vfsDocumentRoot }/wp-config.php` ) ) { - php.writeFile( - `${ vfsDocumentRoot }/wp-config.php`, - php.readFileAsText( `${ vfsDocumentRoot }/wp-config-sample.php` ) - ); - initializeDefaultDatabase = true; - } - - const wpConfigConsts = { - WP_SQLITE_AST_DRIVER: true, - }; - - if ( wordPressVersion !== 'user-provided' ) { - wpConfigConsts[ 'WP_AUTO_UPDATE_CORE' ] = wordPressVersion === 'latest'; - } - await defineWpConfigConsts( php, { - consts: wpConfigConsts, - method: 'define-before-run', - } ); - - return { initializeDefaultDatabase }; -} - -async function activatePluginOrTheme( php: PHP, { projectPath, mode }: WPNowOptions ) { - if ( mode === WPNowMode.PLUGIN ) { - const pluginFile = getPluginFile( projectPath ); - await activatePlugin( php, { pluginPath: pluginFile } ); - } else if ( mode === WPNowMode.THEME ) { - const themeFolderName = path.basename( projectPath ); - await activateTheme( php, { themeFolderName } ); - } -} - -export function getThemeTemplate( projectPath: string ) { - const themeTemplateRegex = /^(?:[ \t]*<\?php)?[ \t/*#@]*Template:(.*)$/im; - const styleCSS = readFileHead( path.join( projectPath, 'style.css' ) ); - if ( themeTemplateRegex.test( styleCSS ) ) { - const themeName = themeTemplateRegex.exec( styleCSS )[ 1 ].trim(); - return themeName; - } -} - -async function mountInternalMuPlugins( php: PHP, options: WPNowOptions ) { - php.mkdir( PLAYGROUND_INTERNAL_MU_PLUGINS_FOLDER ); - - php.writeFile( - path.posix.join( PLAYGROUND_INTERNAL_MU_PLUGINS_FOLDER, '0-https-for-reverse-proxy.php' ), - ` ) returns - // a private network IP address for some reason. - add_filter( 'allowed_redirect_hosts', function( $hosts ) { - $redirect_hosts = array( - 'wordpress.org', - 'api.wordpress.org', - 'downloads.wordpress.org', - 'themes.svn.wordpress.org', - 'fonts.gstatic.com', - ); - return array_merge( $hosts, $redirect_hosts ); - } ); - add_filter('http_request_host_is_external', '__return_true', 20, 3 ); - ` - ); - - php.writeFile( - path.posix.join( PLAYGROUND_INTERNAL_MU_PLUGINS_FOLDER, '0-thumbnails.php' ), - `stylesheet; - - if (!is_dir($theme_dir)) { - $all_themes = wp_get_themes(); - $available_themes = []; - - foreach ($all_themes as $theme_slug => $theme_obj) { - if ($theme_slug != $current_theme->get_stylesheet()) { - $available_themes[$theme_slug] = $theme_obj; - } - } - - if (!empty($available_themes)) { - $new_theme_slug = array_keys($available_themes)[0]; - switch_theme($new_theme_slug); - } - } - } - add_action('after_setup_theme', 'check_current_theme_availability'); - ` - ); - - php.writeFile( - path.posix.join( PLAYGROUND_INTERNAL_MU_PLUGINS_FOLDER, '0-permalinks.php' ), - `id ) && 'plugins' === $current_screen->id ) { - wp_add_inline_style( - 'dashicons', - '.toggle-auto-update .dashicons.hidden { display: none; }' - ); - } - } - ` - ); -} - -async function mountSqlitePlugin( php: PHP, vfsDocumentRoot: string ) { - const sqlitePluginPath = `${ vfsDocumentRoot }/wp-content/plugins/${ SQLITE_FILENAME }`; - if ( php.listFiles( sqlitePluginPath ).length === 0 ) { - await php.mount( - getSqlitePath(), - createNodeFsMountHandler( sqlitePluginPath ) as unknown as MountHandler - ); - await php.mount( - path.join( getSqlitePath(), 'db.copy' ), - createNodeFsMountHandler( - `${ vfsDocumentRoot }/wp-content/db.php` - ) as unknown as MountHandler - ); - } -} - -/** - * Create SQLite database directory in hidden utility directory and mount it to the document root - * - * @param php - * @param vfsDocumentRoot - * @param wpContentPath - */ -async function mountSqliteDatabaseDirectory( - php: PHP, - vfsDocumentRoot: string, - wpContentPath: string -) { - fs.ensureDirSync( path.join( wpContentPath, 'database' ) ); - await php.mount( - path.join( wpContentPath, 'database' ), - createNodeFsMountHandler( - `${ vfsDocumentRoot }/wp-content/database` - ) as unknown as MountHandler - ); -} - -export function inferMode( projectPath: string ): Exclude< WPNowMode, WPNowMode.AUTO > { - if ( isWordPressDevelopDirectory( projectPath ) ) { - return WPNowMode.WORDPRESS_DEVELOP; - } else if ( isWordPressDirectory( projectPath ) ) { - return WPNowMode.WORDPRESS; - } else if ( isWpContentDirectory( projectPath ) ) { - return WPNowMode.WP_CONTENT; - } else if ( isPluginDirectory( projectPath ) ) { - return WPNowMode.PLUGIN; - } else if ( isThemeDirectory( projectPath ) ) { - return WPNowMode.THEME; - } else if ( hasIndexFile( projectPath ) ) { - return WPNowMode.INDEX; - } - return WPNowMode.PLAYGROUND; -} - -async function installationSteps( php: PHP, options: WPNowOptions ) { - const siteLanguage = options.siteLanguage; - - const executeStep = async ( step: 0 | 1 | 2 ) => { - return php.requestHandler.request( { - url: `/wp-admin/install.php?step=${ step }`, - method: 'POST', - body: - step === 2 - ? { - language: siteLanguage, - prefix: 'wp_', - weblog_title: options.siteTitle, - user_name: 'admin', - admin_password: options.adminPassword, - admin_password2: options.adminPassword, - Submit: 'Install WordPress', - pw_weak: '1', - admin_email: 'admin@localhost.com', - } - : { - language: siteLanguage, - }, - } ); - }; - // First two steps are needed to download and set translations - await executeStep( 0 ); - await executeStep( 1 ); - - // Set up site details - await executeStep( 2 ); -} - -/** - * The default `umask` set by Emscripten is 0777 which is too restrictive. This has been updated - * in https://github.com/emscripten-core/emscripten/pull/22589 but is not available in the stable - * version of Emscripten yet. In the meantime, we'll apply a workaround by setting the umask via - * a preload file that will be executed before running any PHP file. - * - * Once the Emscripten update is available, a new version of Playground is released using the - * updated Emscripten, and the Playground dependency is updated in the app, this workaround can be removed. - */ -function applyOverrideUmaskWorkaround( php: PHP ) { - php.writeFile( - path.posix.join( PLAYGROUND_INTERNAL_PRELOAD_PATH, 'override-umask-workaround.php' ), - ' fs.existsSync( path.join( projectPath, file ) ) ); -} diff --git a/vendor/wp-now/src/wp-playground-wordpress/is-wordpress-directory.ts b/vendor/wp-now/src/wp-playground-wordpress/is-wordpress-directory.ts deleted file mode 100644 index b1bf2f528b..0000000000 --- a/vendor/wp-now/src/wp-playground-wordpress/is-wordpress-directory.ts +++ /dev/null @@ -1,16 +0,0 @@ -import fs from 'fs-extra'; -import path from 'path'; - -/** - * Checks if the given path is a WordPress directory. - * - * @param projectPath The path to the project to check. - * @returns Is it a WordPress directory? - */ -export function isWordPressDirectory( projectPath: string ): Boolean { - return ( - fs.existsSync( path.join( projectPath, 'wp-content' ) ) && - fs.existsSync( path.join( projectPath, 'wp-includes' ) ) && - fs.existsSync( path.join( projectPath, 'wp-load.php' ) ) - ); -} diff --git a/vendor/wp-now/src/wp-playground-wordpress/is-wp-content-directory.ts b/vendor/wp-now/src/wp-playground-wordpress/is-wp-content-directory.ts deleted file mode 100644 index e85ee3cb1b..0000000000 --- a/vendor/wp-now/src/wp-playground-wordpress/is-wp-content-directory.ts +++ /dev/null @@ -1,18 +0,0 @@ -import fs from 'fs-extra'; -import path from 'path'; - -/** - * Checks if the given path is a WordPress wp-content directory. - * - * @param projectPath The path to the project to check. - * @returns A boolean value indicating whether the project is a WordPress wp-content directory. - */ -export function isWpContentDirectory( projectPath: string ): Boolean { - const muPluginsExists = fs.existsSync( path.join( projectPath, 'mu-plugins' ) ); - const pluginsExists = fs.existsSync( path.join( projectPath, 'plugins' ) ); - const themesExists = fs.existsSync( path.join( projectPath, 'themes' ) ); - if ( muPluginsExists || pluginsExists || themesExists ) { - return true; - } - return false; -} diff --git a/vendor/wp-now/src/wp-playground-wordpress/read-file-head.ts b/vendor/wp-now/src/wp-playground-wordpress/read-file-head.ts deleted file mode 100644 index da220ebcb7..0000000000 --- a/vendor/wp-now/src/wp-playground-wordpress/read-file-head.ts +++ /dev/null @@ -1,17 +0,0 @@ -import fs from 'fs-extra'; - -/** - * - * @param filePath The path to the file to read. - * @param length The number of bytes to read from the file. By default 8KB. - * @returns The first `length` bytes of the file as string. - * @see https://developer.wordpress.org/reference/functions/get_file_data/ - */ -export function readFileHead( filePath: string, length = 8192 ) { - const buffer = Buffer.alloc( length ); - const fd = fs.openSync( filePath, 'r' ); - fs.readSync( fd, buffer, 0, buffer.length, 0 ); - const fileContentBuffer = buffer.toString( 'utf8' ); - fs.closeSync( fd ); - return fileContentBuffer.toString(); -} diff --git a/vendor/wp-now/src/wp-playground-wordpress/tests/get-plugin-file.spec.ts b/vendor/wp-now/src/wp-playground-wordpress/tests/get-plugin-file.spec.ts deleted file mode 100644 index f3e42c8175..0000000000 --- a/vendor/wp-now/src/wp-playground-wordpress/tests/get-plugin-file.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import path from 'path'; -import { getPluginFile } from '../get-plugin-file'; - -const modesPath = path.resolve( __dirname, '..', '..', 'tests', 'mode-examples' ); - -describe( 'getPluginFile', () => { - it( 'should return the path to the plugin file if it exists', () => { - const pluginFilePath = getPluginFile( path.join( modesPath, 'plugin' ) ); - expect( pluginFilePath ).toEqual( path.join( 'plugin', 'sample-plugin.php' ) ); - } ); - - it( 'should return null if no plugin file is found', () => { - const pluginFilePath = getPluginFile( path.join( modesPath, 'not-plugin' ) ); - expect( pluginFilePath ).toBeNull(); - } ); - - it( 'should handle non-existing directories', () => { - expect( () => getPluginFile( path.resolve( 'non-existing' ) ) ).toThrowError(); - } ); -} ); diff --git a/vendor/wp-now/src/wp-playground-wordpress/tests/is-valid-wordpress-version.test.ts b/vendor/wp-now/src/wp-playground-wordpress/tests/is-valid-wordpress-version.test.ts deleted file mode 100644 index 53b41e6994..0000000000 --- a/vendor/wp-now/src/wp-playground-wordpress/tests/is-valid-wordpress-version.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Test for isValidWordpressVersion - */ -import { isValidWordPressVersion } from '../is-valid-wordpress-version'; - -test( 'isValidWordPressVersion', () => { - // Accepted versions - // Check https://wordpress.org/download/releases/ - expect( isValidWordPressVersion( 'latest' ) ).toBe( true ); - expect( isValidWordPressVersion( '6.2' ) ).toBe( true ); - expect( isValidWordPressVersion( '6.0.1' ) ).toBe( true ); - expect( isValidWordPressVersion( '6.2-beta1' ) ).toBe( true ); - expect( isValidWordPressVersion( '6.2-RC1' ) ).toBe( true ); - // Rejected versions - expect( isValidWordPressVersion( 'v6.2' ) ).toBe( false ); - expect( isValidWordPressVersion( '6.2-rc1' ) ).toBe( false ); -} ); diff --git a/vendor/wp-now/tsconfig.json b/vendor/wp-now/tsconfig.json deleted file mode 100644 index fcac3e14a7..0000000000 --- a/vendor/wp-now/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "files": [], - "include": [], - "references": [ - { - "path": "./tsconfig.lib.json" - }, - { - "path": "./tsconfig.spec.json" - } - ], - "compilerOptions": { - "noImplicitAny": false, - "strict": false - } -} diff --git a/vendor/wp-now/tsconfig.lib.json b/vendor/wp-now/tsconfig.lib.json deleted file mode 100644 index 5fd3ecec5e..0000000000 --- a/vendor/wp-now/tsconfig.lib.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../dist/out-tsc", - "types": [ "node" ] - }, - "exclude": [ "jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts" ], - "include": [ "src/**/*.ts" ] -} diff --git a/vendor/wp-now/tsconfig.spec.json b/vendor/wp-now/tsconfig.spec.json deleted file mode 100644 index fea542b361..0000000000 --- a/vendor/wp-now/tsconfig.spec.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../dist/out-tsc" - }, - "include": [ "jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts" ] -} diff --git a/vendor/wp-now/vite.config.ts b/vendor/wp-now/vite.config.ts deleted file mode 100644 index 4a4c33b25e..0000000000 --- a/vendor/wp-now/vite.config.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * For Vitest only! The module is built with esbuild which is configured - * in project.json. - */ -/// -import { defineConfig } from 'vite'; - -// eslint-disable-next-line @nx/enforce-module-boundaries -import { viteTsConfigPaths } from '../vite-ts-config-paths'; - -export default defineConfig( () => { - return { - cacheDir: '../../node_modules/.vite/wp-now', - - plugins: [ - viteTsConfigPaths( { - root: '../../', - } ), - ], - - test: { - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'jsdom', - include: [ 'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}' ], - }, - }; -} ); From 76ebd94c1531c67dfa12f1715fcde5c2a4051a48 Mon Sep 17 00:00:00 2001 From: bcotrim Date: Thu, 18 Dec 2025 09:31:52 +0000 Subject: [PATCH 2/8] remove php-wasm and wp-playground code from Studio --- cli/wordpress-server-child.ts | 2 +- common/lib/cli-args-sanitizer.ts | 141 ------------------ common/types/blueprint.ts | 129 +--------------- electron.vite.config.ts | 3 - src/components/tests/app.test.tsx | 9 -- .../tests/content-tab-settings.test.tsx | 33 +--- src/components/wp-version-selector/index.tsx | 16 +- src/custom-package-definitions.d.ts | 4 + src/hooks/tests/use-add-site.test.tsx | 13 +- src/hooks/use-add-site.ts | 20 +-- src/ipc-handlers.ts | 17 --- src/ipc-utils.ts | 7 - .../import/importers/importer.ts | 6 +- src/lib/test-utils.tsx | 19 --- .../add-site/components/create-site-form.tsx | 18 +-- src/modules/add-site/index.tsx | 6 +- src/modules/add-site/tests/add-site.test.tsx | 9 +- src/modules/cli/lib/cli-site-creator.ts | 2 +- .../site-settings/edit-site-details.tsx | 29 ++-- .../tests/edit-site-details.test.tsx | 9 +- src/preload.ts | 1 - src/setup-wp-server-files.ts | 2 +- src/storage/user-data.ts | 2 +- src/stores/index.ts | 25 +--- src/stores/provider-constants-slice.ts | 41 ----- 25 files changed, 52 insertions(+), 511 deletions(-) delete mode 100644 common/lib/cli-args-sanitizer.ts delete mode 100644 src/stores/provider-constants-slice.ts diff --git a/cli/wordpress-server-child.ts b/cli/wordpress-server-child.ts index efbdda1332..866a2bc145 100644 --- a/cli/wordpress-server-child.ts +++ b/cli/wordpress-server-child.ts @@ -21,12 +21,12 @@ import { OverlayFilesystem, InMemoryFilesystem, } from '@wp-playground/storage'; -import { sanitizeRunCLIArgs } from 'common/lib/cli-args-sanitizer'; import { isWordPressDirectory } from 'common/lib/fs-utils'; import { getMuPlugins } from 'common/lib/mu-plugins'; import { formatPlaygroundCliMessage } from 'common/lib/playground-cli-messages'; import { isWordPressDevVersion } from 'common/lib/wordpress-version-utils'; import { z } from 'zod'; +import { sanitizeRunCLIArgs } from 'cli/lib/cli-args-sanitizer'; import { getSqliteCommandPath, getWpCliPharPath } from 'cli/lib/server-files'; import { ServerConfig, diff --git a/common/lib/cli-args-sanitizer.ts b/common/lib/cli-args-sanitizer.ts deleted file mode 100644 index 9bff0261a3..0000000000 --- a/common/lib/cli-args-sanitizer.ts +++ /dev/null @@ -1,141 +0,0 @@ -import type { Blueprint } from 'common/types/blueprint'; -// RunCLIArgs is only used in child processes (CLI and desktop child process) -// Keep this import for now - it will be used by CLI which retains PHP-WASM dependencies -import type { RunCLIArgs } from '@wp-playground/cli'; - -/** - * Sanitizes a Blueprint step to remove sensitive data while keeping useful debugging info. - */ -function sanitizeBlueprintStep( step: Blueprint[ 'steps' ][ number ] ): Record< string, unknown > { - const baseStep: Record< string, unknown > = { step: step.step }; - const stepRecord = step as Record< string, unknown >; - - // For steps that might contain secrets, only include safe fields - switch ( step.step ) { - case 'login': - // Omit login details, indicate it exists - return { ...baseStep, hasLogin: true }; - - case 'runPHP': - case 'runPHPWithOptions': - // Omit code (might contain secrets), indicate it exists - return { ...baseStep, hasCode: !! stepRecord.code }; - - case 'defineWpConfigConsts': - // Omit actual constants (might contain DB credentials, auth keys) - return { - ...baseStep, - constCount: - stepRecord.consts && typeof stepRecord.consts === 'object' - ? Object.keys( stepRecord.consts as object ).length - : 0, - }; - - case 'runSql': - // Omit SQL (might contain sensitive data) - return { ...baseStep, hasSql: !! stepRecord.sql }; - - case 'writeFile': - // Keep path for debugging, omit file content - return { - ...baseStep, - path: stepRecord.path, - hasData: !! stepRecord.data, - }; - - case 'request': - // Keep URL and method, omit headers and body (might contain auth tokens) - return { - ...baseStep, - url: - stepRecord.request && typeof stepRecord.request === 'object' - ? ( stepRecord.request as Record< string, unknown > ).url - : undefined, - method: - stepRecord.request && typeof stepRecord.request === 'object' - ? ( stepRecord.request as Record< string, unknown > ).method - : undefined, - }; - - case 'setSiteOptions': - case 'updateUserMeta': { - // Keep option/meta keys but not values (values might be API keys) - let keys: string[] = []; - if ( stepRecord.options && typeof stepRecord.options === 'object' ) { - keys = Object.keys( stepRecord.options as object ); - } else if ( stepRecord.meta && typeof stepRecord.meta === 'object' ) { - keys = Object.keys( stepRecord.meta as object ); - } - return { - ...baseStep, - keys, - }; - } - - case 'installPlugin': - case 'installTheme': - // These are safe - just WordPress.org slugs or URLs - return step as Record< string, unknown >; - - default: - // For other steps, include everything (they're generally safe) - return step as Record< string, unknown >; - } -} - -/** - * Sanitizes a Blueprint object to remove sensitive data (passwords, tokens, code) - * while preserving useful debugging information. - */ -export function sanitizeBlueprint( blueprint: Blueprint | undefined ): object | undefined { - if ( ! blueprint ) { - return undefined; - } - - return { - // Keep metadata but omit author (triggers Sentry's "auth" filter) - meta: blueprint.meta - ? { - title: blueprint.meta.title, - description: blueprint.meta.description, - } - : undefined, - - // Sanitize each step individually - steps: blueprint.steps?.map( sanitizeBlueprintStep ), - - // Safe configuration - features: blueprint.features, - preferredVersions: blueprint.preferredVersions, - landingPage: blueprint.landingPage, - login: typeof blueprint.login === 'boolean' ? blueprint.login : '', - }; -} - -/** - * Sanitizes RunCLIArgs to remove only truly sensitive data (passwords, tokens) - * while preserving configuration useful for debugging local development issues. - * Prepares data for Sentry by stringifying nested objects to avoid normalization limits. - */ -export function sanitizeRunCLIArgs( args: RunCLIArgs ): Record< string, unknown > { - return { - command: args.command, - php: args.php, - wp: args.wp, - port: args.port, - debug: args.debug, - verbosity: args.verbosity, - wordpressInstallMode: args.wordpressInstallMode, - skipSqliteSetup: args.skipSqliteSetup, - followSymlinks: args.followSymlinks, - internalCookieStore: args.internalCookieStore, - xdebug: args.xdebug, - experimentalDevtools: args.experimentalDevtools, - experimentalMultiWorker: args.experimentalMultiWorker, - 'site-url': args[ 'site-url' ], - outfile: args.outfile, - blueprintJson: args.blueprint - ? JSON.stringify( sanitizeBlueprint( args.blueprint ) ) - : undefined, - }; -} diff --git a/common/types/blueprint.ts b/common/types/blueprint.ts index 5661950bc0..0915be718a 100644 --- a/common/types/blueprint.ts +++ b/common/types/blueprint.ts @@ -1,128 +1 @@ -/** - * Local type definitions for WordPress Playground blueprints. - * These replace imports from @wp-playground/blueprints to avoid bundling PHP-WASM in the desktop app. - * - * Note: These are simplified versions of the upstream types, containing only the fields - * actually used by Studio. Keep them in sync with @wp-playground/blueprints if needed. - */ - -import type { SupportedPHPVersion } from './php-versions'; - -/** - * Extra libraries that can be preloaded into the Playground instance. - */ -export type ExtraLibrary = 'wp-cli'; - -/** - * PHP Constants to define on every request. - */ -export type PHPConstants = Record< string, string | boolean | number >; - -/** - * Reference to a file resource. - * Simplified version - the full type supports various resource types. - */ -export type FileReference = - | { resource: 'url'; url: string } - | { resource: 'vfs'; path: string } - | { resource: 'literal'; name: string; contents: string | Uint8Array } - | { resource: 'wordpress.org/themes'; slug: string } - | { resource: 'wordpress.org/plugins'; slug: string }; - -/** - * Step definition for a blueprint. - * This is a simplified union of the most commonly used step types in Studio. - */ -export type StepDefinition = { - step: string; - progress?: { - weight?: number; - caption?: string; - }; -} & Record< string, unknown >; - -/** - * Blueprint V1 declaration. - * Simplified version containing only fields used by Studio. - */ -export type BlueprintV1Declaration = { - /** - * The URL to navigate to after the blueprint has been run. - */ - landingPage?: string; - - /** - * Optional description. - * @deprecated Use meta.description instead. - */ - description?: string; - - /** - * Optional metadata. - */ - meta?: { - title: string; - description?: string; - author: string; - categories?: string[]; - }; - - /** - * The preferred PHP and WordPress versions to use. - */ - preferredVersions?: { - php: SupportedPHPVersion | 'latest'; - wp: string | 'latest'; - }; - - /** - * Feature flags. - */ - features?: { - intl?: boolean; - networking?: boolean; - }; - - /** - * Extra libraries to preload. - */ - extraLibraries?: ExtraLibrary[]; - - /** - * PHP Constants to define on every request. - */ - constants?: PHPConstants; - - /** - * WordPress plugins to install and activate. - */ - plugins?: Array< string | FileReference >; - - /** - * WordPress site options to define. - */ - siteOptions?: Record< string, string > & { - blogname?: string; - }; - - /** - * User to log in as. - */ - login?: - | boolean - | { - username: string; - password: string; - }; - - /** - * The steps to run after every other operation in this Blueprint. - */ - steps?: Array< StepDefinition | string | undefined | false | null >; -}; - -/** - * Blueprint type alias - can be a declaration or a bundle. - * In Studio, we primarily use the declaration form. - */ -export type Blueprint = BlueprintV1Declaration; +export type { Blueprint, BlueprintV1Declaration, StepDefinition } from '@wp-playground/blueprints'; diff --git a/electron.vite.config.ts b/electron.vite.config.ts index ba47eac2d7..1638932b06 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -47,13 +47,10 @@ export default defineConfig( { rollupOptions: { input: { index: resolve( __dirname, 'src/index.ts' ), - // Child process entry points removed - desktop now uses CLI for site operations - // See STU-960: Remove PHP-WASM dependencies from Studio }, output: { entryFileNames: '[name].js', }, - // PHP-WASM externals removed - no longer needed in desktop build }, }, }, diff --git a/src/components/tests/app.test.tsx b/src/components/tests/app.test.tsx index 09e46ea0b9..5ce462270b 100644 --- a/src/components/tests/app.test.tsx +++ b/src/components/tests/app.test.tsx @@ -10,7 +10,6 @@ import { rootReducer } from 'src/stores'; import { appVersionApi } from 'src/stores/app-version-api'; import { certificateTrustApi } from 'src/stores/certificate-trust-api'; import { installedAppsApi } from 'src/stores/installed-apps-api'; -import { setProviderConstants } from 'src/stores/provider-constants-slice'; import { connectedSitesApi } from 'src/stores/sync/connected-sites'; import { wpcomSitesApi } from 'src/stores/sync/wpcom-sites'; import { wordpressVersionsApi } from 'src/stores/wordpress-versions-api'; @@ -97,14 +96,6 @@ describe( 'App', () => { .concat( connectedSitesApi.middleware ) .concat( wpcomSitesApi.middleware ), } ); - store.dispatch( - setProviderConstants( { - defaultPhpVersion: '8.3', - defaultWordPressVersion: 'latest', - allowedPhpVersions: [ '8.0', '8.1', '8.2', '8.3' ], - minimumWordPressVersion: '6.2.6', - } ) - ); return render( diff --git a/src/components/tests/content-tab-settings.test.tsx b/src/components/tests/content-tab-settings.test.tsx index ff72bfff77..a16ea1d536 100644 --- a/src/components/tests/content-tab-settings.test.tsx +++ b/src/components/tests/content-tab-settings.test.tsx @@ -24,18 +24,7 @@ function snapshotTestReducer( state: RootState | undefined, action: UnknownActio } ); } - // Use the test reducer but preserve provider constants - const newState = testReducer( state, action ); - - // If we have provider constants in the current state, preserve them - if ( state?.providerConstants ) { - return { - ...newState, - providerConstants: state.providerConstants, - }; - } - - return newState; + return testReducer( state, action ); } const snapshotTestActions = { @@ -44,26 +33,12 @@ const snapshotTestActions = { }, }; -// Create test store with provider constants -let testStore = createTestStore( { - providerConstants: { - defaultPhpVersion: '8.3', - defaultWordPressVersion: 'latest', - allowedPhpVersions: [ '8.0', '8.1', '8.2', '8.3' ], - minimumWordPressVersion: '6.2.6', - }, -} ); +// Create test store +let testStore = createTestStore(); // We need to create a new store each time to avoid reducer conflicts function createCustomTestStore() { - const store = createTestStore( { - providerConstants: { - defaultPhpVersion: '8.3', - defaultWordPressVersion: 'latest', - allowedPhpVersions: [ '8.0', '8.1', '8.2', '8.3' ], - minimumWordPressVersion: '6.2.6', - }, - } ); + const store = createTestStore(); store.replaceReducer( snapshotTestReducer ); return store; } diff --git a/src/components/wp-version-selector/index.tsx b/src/components/wp-version-selector/index.tsx index 8189026d9c..ded3cb9258 100644 --- a/src/components/wp-version-selector/index.tsx +++ b/src/components/wp-version-selector/index.tsx @@ -2,17 +2,13 @@ import { SelectControl, Icon } from '@wordpress/components'; import { info } from '@wordpress/icons'; import { useI18n } from '@wordpress/react-i18n'; import { useEffect } from 'react'; +import { DEFAULT_WORDPRESS_VERSION, MINIMUM_WORDPRESS_VERSION } from 'common/constants'; import { isWordPressBetaVersion } from 'common/lib/wordpress-version-utils'; import offlineIcon from 'src/components/offline-icon'; import { Tooltip } from 'src/components/tooltip'; import { useOffline } from 'src/hooks/use-offline'; import { cx } from 'src/lib/cx'; import { isWordPressDevVersion } from 'src/lib/version-utils'; -import { useRootSelector } from 'src/stores'; -import { - selectDefaultWordPressVersion, - selectMinimumWordPressVersion, -} from 'src/stores/provider-constants-slice'; import { useGetWordPressVersions } from 'src/stores/wordpress-versions-api'; import { addWpVersionToList } from './add-wp-version-to-list'; @@ -42,19 +38,17 @@ export const WPVersionSelector = ( { const isOffline = useOffline(); const defaultOfflineMessage = __( 'Changing WordPress version requires an internet connection.' ); const message = offlineMessage || defaultOfflineMessage; - const minimumWordPressVersion = useRootSelector( selectMinimumWordPressVersion ); const { data: wpVersions = [] } = useGetWordPressVersions( { - minimumVersion: minimumWordPressVersion, + minimumVersion: MINIMUM_WORDPRESS_VERSION, } ); - const defaultWordPressVersion = useRootSelector( selectDefaultWordPressVersion ); // Force latest version if the user goes offline useEffect( () => { if ( isOffline ) { // Always force to latest when offline - onChange( defaultWordPressVersion ); + onChange( DEFAULT_WORDPRESS_VERSION ); } - }, [ isOffline, onChange, defaultWordPressVersion ] ); + }, [ isOffline, onChange ] ); let betaVersions: { label: string; value: string }[] = wpVersions.filter( ( version ) => version.isBeta || version.isDevelopment @@ -109,7 +103,7 @@ export const WPVersionSelector = ( { { wpVersions.length > 0 ? ( <> - diff --git a/src/custom-package-definitions.d.ts b/src/custom-package-definitions.d.ts index 358c394f8e..f94487acd6 100644 --- a/src/custom-package-definitions.d.ts +++ b/src/custom-package-definitions.d.ts @@ -39,3 +39,7 @@ declare module '*.wasm' { } declare module 'wpcom-xhr-request'; + +// TODO: Remove this once https://github.com/WordPress/wordpress-playground/pull/3035 has landed +// and a new `@wp-playground/storage` has been published to npm +declare module '@wp-playground/storage'; diff --git a/src/hooks/tests/use-add-site.test.tsx b/src/hooks/tests/use-add-site.test.tsx index e595efb50b..d3611af8db 100644 --- a/src/hooks/tests/use-add-site.test.tsx +++ b/src/hooks/tests/use-add-site.test.tsx @@ -2,13 +2,12 @@ import { renderHook, act } from '@testing-library/react'; import nock from 'nock'; import { Provider } from 'react-redux'; +import { DEFAULT_WORDPRESS_VERSION } from 'common/constants'; import { useSyncSites } from 'src/hooks/sync-sites'; import { useAddSite } from 'src/hooks/use-add-site'; import { useContentTabs } from 'src/hooks/use-content-tabs'; -import { DEFAULT_WORDPRESS_VERSION } from 'common/constants'; import { useSiteDetails } from 'src/hooks/use-site-details'; import { store } from 'src/stores'; -import { setProviderConstants } from 'src/stores/provider-constants-slice'; import type { SyncSite } from 'src/modules/sync/types'; jest.mock( 'src/hooks/use-site-details' ); @@ -54,16 +53,6 @@ describe( 'useAddSite', () => { beforeEach( () => { jest.clearAllMocks(); - // Prepopulate store with provider constants - store.dispatch( - setProviderConstants( { - defaultPhpVersion: '8.3', - defaultWordPressVersion: 'latest', - allowedPhpVersions: [ '8.0', '8.1', '8.2', '8.3' ], - minimumWordPressVersion: '5.9.9', - } ) - ); - ( useSiteDetails as jest.Mock ).mockReturnValue( { createSite: mockCreateSite, updateSite: mockUpdateSite, diff --git a/src/hooks/use-add-site.ts b/src/hooks/use-add-site.ts index 501448f5b3..7474414348 100644 --- a/src/hooks/use-add-site.ts +++ b/src/hooks/use-add-site.ts @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/electron/renderer'; import { useI18n } from '@wordpress/react-i18n'; import { useCallback, useMemo, useState } from 'react'; +import { DEFAULT_PHP_VERSION, DEFAULT_WORDPRESS_VERSION } from 'common/constants'; import { BlueprintValidationWarning } from 'common/lib/blueprint-validation'; import { generateCustomDomainFromSiteName, getDomainNameValidationError } from 'common/lib/domains'; import { useSyncSites } from 'src/hooks/sync-sites'; @@ -8,14 +9,9 @@ import { useContentTabs } from 'src/hooks/use-content-tabs'; import { useImportExport } from 'src/hooks/use-import-export'; import { useSiteDetails } from 'src/hooks/use-site-details'; import { getIpcApi } from 'src/lib/get-ipc-api'; -import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; import { useBlueprintDeeplink } from 'src/modules/add-site/hooks/use-blueprint-deeplink'; -import { useRootSelector } from 'src/stores'; -import { - selectDefaultPhpVersion, - selectDefaultWordPressVersion, -} from 'src/stores/provider-constants-slice'; import { useConnectSiteMutation } from 'src/stores/sync/connected-sites'; +import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; import type { SyncSite } from 'src/modules/sync/types'; import type { Blueprint } from 'src/stores/wpcom-api'; import type { SyncOption } from 'src/types'; @@ -32,8 +28,6 @@ export function useAddSite( options: UseAddSiteOptions = {} ) { const [ connectSite ] = useConnectSiteMutation(); const { pullSite } = useSyncSites(); const { setSelectedTab } = useContentTabs(); - const defaultPhpVersion = useRootSelector( selectDefaultPhpVersion ); - const defaultWordPressVersion = useRootSelector( selectDefaultWordPressVersion ); const [ error, setError ] = useState( '' ); const [ siteName, setSiteName ] = useState< string | null >( null ); const [ sitePath, setSitePath ] = useState( '' ); @@ -41,9 +35,9 @@ export function useAddSite( options: UseAddSiteOptions = {} ) { const [ doesPathContainWordPress, setDoesPathContainWordPress ] = useState( false ); const [ fileForImport, setFileForImport ] = useState< File | null >( null ); const [ phpVersion, setPhpVersion ] = useState< AllowedPHPVersion >( - defaultPhpVersion as AllowedPHPVersion + DEFAULT_PHP_VERSION as AllowedPHPVersion ); - const [ wpVersion, setWpVersion ] = useState( defaultWordPressVersion ); + const [ wpVersion, setWpVersion ] = useState< string >( DEFAULT_WORDPRESS_VERSION ); const [ useCustomDomain, setUseCustomDomain ] = useState( false ); const [ customDomain, setCustomDomain ] = useState< string | null >( null ); const [ customDomainError, setCustomDomainError ] = useState( '' ); @@ -85,8 +79,8 @@ export function useAddSite( options: UseAddSiteOptions = {} ) { setSitePath( '' ); setError( '' ); setDoesPathContainWordPress( false ); - setWpVersion( defaultWordPressVersion ); - setPhpVersion( defaultPhpVersion ); + setWpVersion( DEFAULT_WORDPRESS_VERSION ); + setPhpVersion( DEFAULT_PHP_VERSION ); setUseCustomDomain( false ); setCustomDomain( null ); setCustomDomainError( '' ); @@ -97,7 +91,7 @@ export function useAddSite( options: UseAddSiteOptions = {} ) { setBlueprintDeeplinkWarnings( undefined ); setSelectedRemoteSite( undefined ); clearDeeplinkState(); - }, [ clearDeeplinkState, defaultPhpVersion, defaultWordPressVersion ] ); + }, [ clearDeeplinkState ] ); const loadAllCustomDomains = useCallback( () => { getIpcApi() diff --git a/src/ipc-handlers.ts b/src/ipc-handlers.ts index b0f1e9bfdd..1ecfa5d890 100644 --- a/src/ipc-handlers.ts +++ b/src/ipc-handlers.ts @@ -1326,23 +1326,6 @@ export async function listLocalFileTree( } } -export async function getProviderConstants( _event: IpcMainInvokeEvent ) { - // Import directly to avoid circular dependencies at module load time - const { - DEFAULT_PHP_VERSION, - DEFAULT_WORDPRESS_VERSION, - MINIMUM_WORDPRESS_VERSION, - } = await import( 'common/constants' ); - const { SupportedPHPVersions } = await import( 'common/types/php-versions' ); - - return { - defaultPhpVersion: DEFAULT_PHP_VERSION, - defaultWordPressVersion: DEFAULT_WORDPRESS_VERSION, - allowedPhpVersions: [ ...SupportedPHPVersions ], - minimumWordPressVersion: MINIMUM_WORDPRESS_VERSION, - }; -} - export async function validateBlueprint( _event: IpcMainInvokeEvent, blueprintJson: Blueprint[ 'blueprint' ] diff --git a/src/ipc-utils.ts b/src/ipc-utils.ts index 8d2bf6661a..9773c28bb7 100644 --- a/src/ipc-utils.ts +++ b/src/ipc-utils.ts @@ -30,13 +30,6 @@ export interface IpcEvents { 'on-export': [ ImportExportEventData, string ]; 'on-import': [ ImportExportEventData, string ]; 'on-site-create-progress': [ { siteId: string; message: string } ]; - providerConstantsChanged: [ - { - defaultPhpVersion: string; - defaultWordPressVersion: string; - allowedPhpVersions: string[]; - }, - ]; 'site-context-menu-action': [ { action: string; siteId: string } ]; 'site-status-changed': [ { siteId: string; status: 'running' | 'stopped'; url: string } ]; 'snapshot-error': [ { operationId: crypto.UUID; data: SnapshotEventData } ]; diff --git a/src/lib/import-export/import/importers/importer.ts b/src/lib/import-export/import/importers/importer.ts index d7f5ebac7c..7ece5a597e 100644 --- a/src/lib/import-export/import/importers/importer.ts +++ b/src/lib/import-export/import/importers/importer.ts @@ -5,9 +5,9 @@ import fsPromises from 'fs/promises'; import path from 'path'; import { createInterface } from 'readline'; import { lstat, move } from 'fs-extra'; +import semver from 'semver'; import { DEFAULT_PHP_VERSION } from 'common/constants'; import { SupportedPHPVersionsList } from 'common/types/php-versions'; -import semver from 'semver'; import { getSiteUrl } from 'src/lib/get-site-url'; import { generateBackupFilename } from 'src/lib/import-export/export/generate-backup-filename'; import { ImportEvents } from 'src/lib/import-export/import/events'; @@ -294,9 +294,7 @@ abstract class BaseBackupImporter extends BaseImporter { const parsedVersion = `${ phpVersion.major }.${ phpVersion.minor }`; - return SupportedPHPVersionsList.includes( parsedVersion ) - ? parsedVersion - : DEFAULT_PHP_VERSION; + return SupportedPHPVersionsList.includes( parsedVersion ) ? parsedVersion : DEFAULT_PHP_VERSION; } } diff --git a/src/lib/test-utils.tsx b/src/lib/test-utils.tsx index 0c5f054b53..117456090c 100644 --- a/src/lib/test-utils.tsx +++ b/src/lib/test-utils.tsx @@ -6,17 +6,10 @@ import { rootReducer, RootState } from 'src/stores'; import { appVersionApi } from 'src/stores/app-version-api'; import { certificateTrustApi } from 'src/stores/certificate-trust-api'; import { installedAppsApi } from 'src/stores/installed-apps-api'; -import { setProviderConstants } from 'src/stores/provider-constants-slice'; import { wordpressVersionsApi } from 'src/stores/wordpress-versions-api'; import { wpcomApi, wpcomPublicApi } from 'src/stores/wpcom-api'; interface TestStoreOptions { - providerConstants?: { - defaultPhpVersion?: string; - defaultWordPressVersion?: string; - allowedPhpVersions?: string[]; - minimumWordPressVersion: string; - }; preloadedState?: Partial< RootState >; } @@ -34,18 +27,6 @@ export function createTestStore( options: TestStoreOptions = {} ) { .concat( certificateTrustApi.middleware ), } ); - // Set provider constants if provided - if ( options.providerConstants ) { - store.dispatch( - setProviderConstants( { - defaultPhpVersion: options.providerConstants.defaultPhpVersion || '', - defaultWordPressVersion: options.providerConstants.defaultWordPressVersion || '', - allowedPhpVersions: options.providerConstants.allowedPhpVersions || [], - minimumWordPressVersion: options.providerConstants.minimumWordPressVersion, - } ) - ); - } - return store; } diff --git a/src/modules/add-site/components/create-site-form.tsx b/src/modules/add-site/components/create-site-form.tsx index a18f4ace1f..a6ed0f61c3 100644 --- a/src/modules/add-site/components/create-site-form.tsx +++ b/src/modules/add-site/components/create-site-form.tsx @@ -4,7 +4,9 @@ import { __, sprintf, _n } from '@wordpress/i18n'; import { tip, cautionFilled, chevronRight, chevronDown, chevronLeft } from '@wordpress/icons'; import { useI18n } from '@wordpress/react-i18n'; import { FormEvent, useState, useEffect } from 'react'; +import { DEFAULT_WORDPRESS_VERSION } from 'common/constants'; import { generateCustomDomainFromSiteName } from 'common/lib/domains'; +import { SupportedPHPVersions } from 'common/types/php-versions'; import Button from 'src/components/button'; import FolderIcon from 'src/components/folder-icon'; import TextControlComponent from 'src/components/text-control'; @@ -12,13 +14,9 @@ import { WPVersionSelector } from 'src/components/wp-version-selector'; import { cx } from 'src/lib/cx'; import { getIpcApi } from 'src/lib/get-ipc-api'; import { getLocalizedLink } from 'src/lib/get-localized-link'; -import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; -import { useRootSelector, useI18nLocale } from 'src/stores'; +import { useI18nLocale } from 'src/stores'; import { useCheckCertificateTrustQuery } from 'src/stores/certificate-trust-api'; -import { - selectDefaultWordPressVersion, - selectAllowedPhpVersions, -} from 'src/stores/provider-constants-slice'; +import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; interface FormPathInputComponentProps { value: string; @@ -162,8 +160,6 @@ export const CreateSiteForm = ( { const { __, isRTL } = useI18n(); const locale = useI18nLocale(); const { data: isCertificateTrusted } = useCheckCertificateTrustQuery(); - const defaultWordPressVersion = useRootSelector( selectDefaultWordPressVersion ); - const allowedPhpVersions = useRootSelector( selectAllowedPhpVersions ); // If the custom domain is enabled and the root certificate is trusted, enable HTTPS useEffect( () => { @@ -279,10 +275,10 @@ export const CreateSiteForm = ( { - id="php-version-select" value={ phpVersion } - options={ allowedPhpVersions.map( ( version ) => ( { + options={ SupportedPHPVersions.map( ( version ) => ( { label: version, value: version, } ) ) } @@ -296,7 +292,7 @@ export const CreateSiteForm = ( { selectedValue={ wpVersion } onChange={ setWpVersion } fallbackOptions={ [ - { label: __( 'Latest' ), value: defaultWordPressVersion }, + { label: __( 'Latest' ), value: DEFAULT_WORDPRESS_VERSION }, ] } offlineMessage={ __( 'You are currently offline so your site will be created with the latest version. Selecting a different WordPress version requires an internet connection.' diff --git a/src/modules/add-site/index.tsx b/src/modules/add-site/index.tsx index 9d13b582bf..cde0fa9d0c 100644 --- a/src/modules/add-site/index.tsx +++ b/src/modules/add-site/index.tsx @@ -3,6 +3,7 @@ import { Navigator, useNavigator } from '@wordpress/components'; import { sprintf } from '@wordpress/i18n'; import { useI18n } from '@wordpress/react-i18n'; import { FormEvent, useCallback, useEffect, useMemo, useState } from 'react'; +import { MINIMUM_WORDPRESS_VERSION } from 'common/constants'; import { BlueprintValidationWarning } from 'common/lib/blueprint-validation'; import Button from 'src/components/button'; import { FullscreenModal } from 'src/components/fullscreen-modal'; @@ -12,9 +13,7 @@ import { useSiteDetails } from 'src/hooks/use-site-details'; import { generateSiteName } from 'src/lib/generate-site-name'; import { getIpcApi } from 'src/lib/get-ipc-api'; import { SyncSite } from 'src/modules/sync/types'; -import { useRootSelector } from 'src/stores'; import { formatRtkError } from 'src/stores/format-rtk-error'; -import { selectMinimumWordPressVersion } from 'src/stores/provider-constants-slice'; import { useGetWordPressVersions } from 'src/stores/wordpress-versions-api'; import { useGetBlueprints, Blueprint } from 'src/stores/wpcom-api'; import BlueprintDeeplink from './components/blueprint-deeplink'; @@ -350,9 +349,8 @@ export function AddSiteModalContent( { setIsDeeplinkFlow, } = addSiteProps; - const minimumWordPressVersion = useRootSelector( selectMinimumWordPressVersion ); const { data: versions = [] } = useGetWordPressVersions( { - minimumVersion: minimumWordPressVersion, + minimumVersion: MINIMUM_WORDPRESS_VERSION, } ); const latestStableVersion = versions.find( ( version ) => version.value === 'latest' ); diff --git a/src/modules/add-site/tests/add-site.test.tsx b/src/modules/add-site/tests/add-site.test.tsx index 2a90c84985..b2e2a8fa9d 100644 --- a/src/modules/add-site/tests/add-site.test.tsx +++ b/src/modules/add-site/tests/add-site.test.tsx @@ -115,14 +115,7 @@ jest.mock( 'src/stores/wpcom-api', () => { const mockUseGetBlueprints = useGetBlueprints as jest.MockedFunction< typeof useGetBlueprints >; const renderWithProvider = ( children: React.ReactElement ) => { - const store = createTestStore( { - providerConstants: { - defaultPhpVersion: '8.3', - defaultWordPressVersion: 'latest', - allowedPhpVersions: [ '8.0', '8.1', '8.2', '8.3' ], - minimumWordPressVersion: '6.2.6', - }, - } ); + const store = createTestStore(); return render( { children } ); }; diff --git a/src/modules/cli/lib/cli-site-creator.ts b/src/modules/cli/lib/cli-site-creator.ts index 7fd92eb14e..58f7455b15 100644 --- a/src/modules/cli/lib/cli-site-creator.ts +++ b/src/modules/cli/lib/cli-site-creator.ts @@ -3,9 +3,9 @@ import os from 'node:os'; import path from 'node:path'; import { z } from 'zod'; import { SiteCommandLoggerAction } from 'common/logger-actions'; -import type { Blueprint } from 'common/types/blueprint'; import { sendIpcEventToRenderer } from 'src/ipc-utils'; import { executeCliCommand } from './execute-command'; +import type { Blueprint } from 'common/types/blueprint'; const cliEventSchema = z.discriminatedUnion( 'action', [ z.object( { diff --git a/src/modules/site-settings/edit-site-details.tsx b/src/modules/site-settings/edit-site-details.tsx index a3a517498c..4bb0b5dbad 100644 --- a/src/modules/site-settings/edit-site-details.tsx +++ b/src/modules/site-settings/edit-site-details.tsx @@ -2,8 +2,10 @@ import { SelectControl } from '@wordpress/components'; import { useI18n } from '@wordpress/react-i18n'; import { FormEvent, useCallback, useEffect, useState } from 'react'; import stripAnsi from 'strip-ansi'; +import { DEFAULT_PHP_VERSION, DEFAULT_WORDPRESS_VERSION } from 'common/constants'; import { generateCustomDomainFromSiteName, getDomainNameValidationError } from 'common/lib/domains'; import { getWordPressVersionUrl } from 'common/lib/wordpress-version-utils'; +import { SupportedPHPVersions } from 'common/types/php-versions'; import Button from 'src/components/button'; import { ErrorInformation } from 'src/components/error-information'; import Modal from 'src/components/modal'; @@ -12,14 +14,8 @@ import { WPVersionSelector } from 'src/components/wp-version-selector'; import { useSiteDetails } from 'src/hooks/use-site-details'; import { cx } from 'src/lib/cx'; import { getIpcApi } from 'src/lib/get-ipc-api'; -import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; -import { useRootSelector } from 'src/stores'; import { useCheckCertificateTrustQuery } from 'src/stores/certificate-trust-api'; -import { - selectDefaultWordPressVersion, - selectAllowedPhpVersions, - selectDefaultPhpVersion, -} from 'src/stores/provider-constants-slice'; +import type { AllowedPHPVersion } from 'src/lib/wordpress-server-types'; type EditSiteDetailsProps = { currentWpVersion: string; @@ -30,9 +26,6 @@ const EditSiteDetails = ( { currentWpVersion, onSave }: EditSiteDetailsProps ) = const { __ } = useI18n(); const { updateSite, selectedSite, stopServer, startServer, isEditModalOpen, setIsEditModalOpen } = useSiteDetails(); - const defaultWordPressVersion = useRootSelector( selectDefaultWordPressVersion ); - const allowedPhpVersions = useRootSelector( selectAllowedPhpVersions ); - const defaultPhpVersion = useRootSelector( selectDefaultPhpVersion ); const [ errorUpdatingWpVersion, setErrorUpdatingWpVersion ] = useState< string | null >( null ); const [ isEditingSite, setIsEditingSite ] = useState( false ); const [ needsRestart, setNeedsRestart ] = useState( false ); @@ -46,15 +39,15 @@ const EditSiteDetails = ( { currentWpVersion, onSave }: EditSiteDetailsProps ) = }, [ isEditingSite, setIsEditModalOpen ] ); const [ siteName, setSiteName ] = useState( selectedSite?.name ?? '' ); const [ selectedPhpVersion, setSelectedPhpVersion ] = useState< AllowedPHPVersion >( - ( selectedSite?.phpVersion as AllowedPHPVersion ) ?? defaultPhpVersion + ( selectedSite?.phpVersion as AllowedPHPVersion ) ?? DEFAULT_PHP_VERSION ); const getEffectiveWpVersion = useCallback( () => // undefined means that this site was created before the isWpAutoUpdating option was introduced to Studio [ undefined, true ].includes( selectedSite?.isWpAutoUpdating ) - ? defaultWordPressVersion + ? DEFAULT_WORDPRESS_VERSION : currentWpVersion, - [ selectedSite, currentWpVersion, defaultWordPressVersion ] + [ selectedSite, currentWpVersion ] ); const [ selectedWpVersion, setSelectedWpVersion ] = useState( getEffectiveWpVersion() ); const [ useCustomDomain, setUseCustomDomain ] = useState( Boolean( selectedSite?.customDomain ) ); @@ -160,7 +153,7 @@ const EditSiteDetails = ( { currentWpVersion, onSave }: EditSiteDetailsProps ) = ...selectedSite, name: siteName, phpVersion: selectedPhpVersion, - isWpAutoUpdating: selectedWpVersion === defaultWordPressVersion, + isWpAutoUpdating: selectedWpVersion === DEFAULT_WORDPRESS_VERSION, customDomain: usedCustomDomain, enableHttps: !! usedCustomDomain && enableHttps, } ); @@ -226,15 +219,17 @@ const EditSiteDetails = ( { currentWpVersion, onSave }: EditSiteDetailsProps ) = className="flex flex-1 flex-col gap-1.5 leading-4" > { __( 'PHP version' ) } - id="php-version-select" disabled={ isEditingSite } value={ selectedPhpVersion } - options={ allowedPhpVersions.map( ( version ) => ( { + options={ SupportedPHPVersions.map( ( version ) => ( { label: version, value: version, } ) ) } - onChange={ ( version: AllowedPHPVersion ) => setSelectedPhpVersion( version ) } + onChange={ ( version ) => + setSelectedPhpVersion( version as AllowedPHPVersion ) + } __next40pxDefaultSize __nextHasNoMarginBottom /> diff --git a/src/modules/site-settings/tests/edit-site-details.test.tsx b/src/modules/site-settings/tests/edit-site-details.test.tsx index a4da6a5b52..e0e6ef5145 100644 --- a/src/modules/site-settings/tests/edit-site-details.test.tsx +++ b/src/modules/site-settings/tests/edit-site-details.test.tsx @@ -66,14 +66,7 @@ jest.mock( 'src/hooks/use-offline', () => ( { } ) ); const renderWithProvider = ( children: React.ReactElement ) => { - const store = createTestStore( { - providerConstants: { - defaultPhpVersion: '8.3', - defaultWordPressVersion: 'latest', - allowedPhpVersions: [ '8.0', '8.1', '8.2', '8.3' ], - minimumWordPressVersion: '6.2.6', - }, - } ); + const store = createTestStore(); return render( { children } ); }; diff --git a/src/preload.ts b/src/preload.ts index 1a8fb2bb43..1bad85ce69 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -128,7 +128,6 @@ const api: IpcApi = { comparePaths: ( path1, path2 ) => ipcRendererInvoke( 'comparePaths', path1, path2 ), listLocalFileTree: ( siteId, path, maxDepth ) => ipcRenderer.invoke( 'listLocalFileTree', siteId, path, maxDepth ), - getProviderConstants: () => ipcRendererInvoke( 'getProviderConstants' ), validateBlueprint: ( blueprintJson ) => ipcRendererInvoke( 'validateBlueprint', blueprintJson ), readBlueprintFile: ( filePath ) => ipcRendererInvoke( 'readBlueprintFile', filePath ), showSiteContextMenu: ( context ) => ipcRendererSend( 'showSiteContextMenu', context ), diff --git a/src/setup-wp-server-files.ts b/src/setup-wp-server-files.ts index ff3ec17e4e..0e3714cffb 100644 --- a/src/setup-wp-server-files.ts +++ b/src/setup-wp-server-files.ts @@ -2,7 +2,7 @@ import path from 'path'; import fs from 'fs-extra'; import semver from 'semver'; import { recursiveCopyDirectory } from 'common/lib/fs-utils'; -import { downloadWordPress, updateLatestWPCliVersion } from 'src/lib/download-utils'; +import { updateLatestWPCliVersion } from 'src/lib/download-utils'; import { getWordPressVersionPath, getSqlitePath, diff --git a/src/storage/user-data.ts b/src/storage/user-data.ts index e67775650f..642a5519ec 100644 --- a/src/storage/user-data.ts +++ b/src/storage/user-data.ts @@ -2,12 +2,12 @@ import { app } from 'electron'; import fs from 'fs'; import nodePath from 'node:path'; import * as Sentry from '@sentry/electron/main'; -import { SupportedPHPVersion, SupportedPHPVersions } from 'common/types/php-versions'; import { readFile, writeFile } from 'atomically'; import { LOCKFILE_STALE_TIME, LOCKFILE_WAIT_TIME } from 'common/constants'; import { isErrnoException } from 'common/lib/is-errno-exception'; import { lockFileAsync, unlockFileAsync } from 'common/lib/lockfile'; import { sortSites } from 'common/lib/sort-sites'; +import { SupportedPHPVersion, SupportedPHPVersions } from 'common/types/php-versions'; import { sanitizeUnstructuredData, sanitizeUserpath } from 'src/lib/sanitize-for-logging'; import { getUserDataFilePath, getUserDataLockFilePath } from 'src/storage/paths'; import type { PersistedUserData, UserData, WindowBounds } from 'src/storage/storage-types'; diff --git a/src/stores/index.ts b/src/stores/index.ts index 72cb2477c2..37a2714328 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -15,9 +15,6 @@ import { reducer as chatReducer } from 'src/stores/chat-slice'; import i18nReducer from 'src/stores/i18n-slice'; import { installedAppsApi } from 'src/stores/installed-apps-api'; import onboardingReducer from 'src/stores/onboarding-slice'; -import providerConstantsReducer, { - setProviderConstants, -} from 'src/stores/provider-constants-slice'; import { reducer as snapshotReducer, updateSnapshotLocally, @@ -36,7 +33,6 @@ export type RootState = { chat: ReturnType< typeof chatReducer >; installedAppsApi: ReturnType< typeof installedAppsApi.reducer >; onboarding: ReturnType< typeof onboardingReducer >; - providerConstants: ReturnType< typeof providerConstantsReducer >; snapshot: ReturnType< typeof snapshotReducer >; sync: ReturnType< typeof syncReducer >; connectedSitesApi: ReturnType< typeof connectedSitesApi.reducer >; @@ -97,7 +93,6 @@ export const rootReducer = combineReducers( { connectedSites: connectedSitesReducer, wpcomSitesApi: wpcomSitesApi.reducer, onboarding: onboardingReducer, - providerConstants: providerConstantsReducer, snapshot: snapshotReducer, sync: syncReducer, wordpressVersionsApi: wordpressVersionsApi.reducer, @@ -125,26 +120,8 @@ export const store = configureStore( { // Enable the refetchOnFocus behavior setupListeners( store.dispatch ); -// Listen for provider constants changes -window.addEventListener( 'providerConstantsChanged', ( event: Event ) => { - const customEvent = event as CustomEvent; - store.dispatch( setProviderConstants( customEvent.detail ) ); -} ); - -// Initialize provider constants when store is ready -async function initializeProviderConstants() { - try { - const constants = await getIpcApi().getProviderConstants(); - store.dispatch( setProviderConstants( constants ) ); - } catch ( error ) { - console.error( 'Error initializing provider constants:', error ); - } -} - -// Initialize provider constants immediately, but skip in test environment +// Initialize beta features on store initialization, but skip in test environment if ( typeof jest === 'undefined' && process.env.NODE_ENV !== 'test' ) { - void initializeProviderConstants(); - // Initialize beta features on store initialization only in non-test environment void store.dispatch( loadBetaFeatures() ); } diff --git a/src/stores/provider-constants-slice.ts b/src/stores/provider-constants-slice.ts deleted file mode 100644 index 2819e9596f..0000000000 --- a/src/stores/provider-constants-slice.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { RootState } from 'src/stores'; - -interface ProviderConstantsState { - defaultPhpVersion: string; - defaultWordPressVersion: string; - allowedPhpVersions: string[]; - minimumWordPressVersion: string; -} - -const initialState: ProviderConstantsState = { - defaultPhpVersion: '', - defaultWordPressVersion: '', - allowedPhpVersions: [], - minimumWordPressVersion: '', -}; - -const providerConstantsSlice = createSlice( { - name: 'providerConstants', - initialState, - reducers: { - setProviderConstants: ( state, action: PayloadAction< ProviderConstantsState > ) => { - return action.payload; - }, - }, -} ); - -export const { setProviderConstants } = providerConstantsSlice.actions; - -// Selectors -export const selectDefaultPhpVersion = ( state: RootState ) => - state.providerConstants.defaultPhpVersion; -export const selectDefaultWordPressVersion = ( state: RootState ) => - state.providerConstants.defaultWordPressVersion; -export const selectAllowedPhpVersions = ( state: RootState ) => - state.providerConstants.allowedPhpVersions; -export const selectMinimumWordPressVersion = ( state: RootState ) => - state.providerConstants.minimumWordPressVersion; -export const selectProviderConstants = ( state: RootState ) => state.providerConstants; - -export default providerConstantsSlice.reducer; From 9444ed36766c54e252a3205b56ed01e9099b185a Mon Sep 17 00:00:00 2001 From: bcotrim Date: Thu, 18 Dec 2025 09:48:32 +0000 Subject: [PATCH 3/8] move PHP-WASM packages to devDependencies --- cli/lib/cli-args-sanitizer.ts | 139 +++++++++ package-lock.json | 540 +++++++++++++--------------------- package.json | 7 +- 3 files changed, 341 insertions(+), 345 deletions(-) create mode 100644 cli/lib/cli-args-sanitizer.ts diff --git a/cli/lib/cli-args-sanitizer.ts b/cli/lib/cli-args-sanitizer.ts new file mode 100644 index 0000000000..7332f84c9f --- /dev/null +++ b/cli/lib/cli-args-sanitizer.ts @@ -0,0 +1,139 @@ +import type { RunCLIArgs } from '@wp-playground/cli'; +import type { Blueprint } from 'common/types/blueprint'; + +/** + * Sanitizes a Blueprint step to remove sensitive data while keeping useful debugging info. + */ +function sanitizeBlueprintStep( step: Blueprint[ 'steps' ][ number ] ): Record< string, unknown > { + const baseStep: Record< string, unknown > = { step: step.step }; + const stepRecord = step as Record< string, unknown >; + + // For steps that might contain secrets, only include safe fields + switch ( step.step ) { + case 'login': + // Omit login details, indicate it exists + return { ...baseStep, hasLogin: true }; + + case 'runPHP': + case 'runPHPWithOptions': + // Omit code (might contain secrets), indicate it exists + return { ...baseStep, hasCode: !! stepRecord.code }; + + case 'defineWpConfigConsts': + // Omit actual constants (might contain DB credentials, auth keys) + return { + ...baseStep, + constCount: + stepRecord.consts && typeof stepRecord.consts === 'object' + ? Object.keys( stepRecord.consts as object ).length + : 0, + }; + + case 'runSql': + // Omit SQL (might contain sensitive data) + return { ...baseStep, hasSql: !! stepRecord.sql }; + + case 'writeFile': + // Keep path for debugging, omit file content + return { + ...baseStep, + path: stepRecord.path, + hasData: !! stepRecord.data, + }; + + case 'request': + // Keep URL and method, omit headers and body (might contain auth tokens) + return { + ...baseStep, + url: + stepRecord.request && typeof stepRecord.request === 'object' + ? ( stepRecord.request as Record< string, unknown > ).url + : undefined, + method: + stepRecord.request && typeof stepRecord.request === 'object' + ? ( stepRecord.request as Record< string, unknown > ).method + : undefined, + }; + + case 'setSiteOptions': + case 'updateUserMeta': { + // Keep option/meta keys but not values (values might be API keys) + let keys: string[] = []; + if ( stepRecord.options && typeof stepRecord.options === 'object' ) { + keys = Object.keys( stepRecord.options as object ); + } else if ( stepRecord.meta && typeof stepRecord.meta === 'object' ) { + keys = Object.keys( stepRecord.meta as object ); + } + return { + ...baseStep, + keys, + }; + } + + case 'installPlugin': + case 'installTheme': + // These are safe - just WordPress.org slugs or URLs + return step as Record< string, unknown >; + + default: + // For other steps, include everything (they're generally safe) + return step as Record< string, unknown >; + } +} + +/** + * Sanitizes a Blueprint object to remove sensitive data (passwords, tokens, code) + * while preserving useful debugging information. + */ +export function sanitizeBlueprint( blueprint: Blueprint | undefined ): object | undefined { + if ( ! blueprint ) { + return undefined; + } + + return { + // Keep metadata but omit author (triggers Sentry's "auth" filter) + meta: blueprint.meta + ? { + title: blueprint.meta.title, + description: blueprint.meta.description, + } + : undefined, + + // Sanitize each step individually + steps: blueprint.steps?.map( sanitizeBlueprintStep ), + + // Safe configuration + features: blueprint.features, + preferredVersions: blueprint.preferredVersions, + landingPage: blueprint.landingPage, + login: typeof blueprint.login === 'boolean' ? blueprint.login : '', + }; +} + +/** + * Sanitizes RunCLIArgs to remove only truly sensitive data (passwords, tokens) + * while preserving configuration useful for debugging local development issues. + * Prepares data for Sentry by stringifying nested objects to avoid normalization limits. + */ +export function sanitizeRunCLIArgs( args: RunCLIArgs ): Record< string, unknown > { + return { + command: args.command, + php: args.php, + wp: args.wp, + port: args.port, + debug: args.debug, + verbosity: args.verbosity, + wordpressInstallMode: args.wordpressInstallMode, + skipSqliteSetup: args.skipSqliteSetup, + followSymlinks: args.followSymlinks, + internalCookieStore: args.internalCookieStore, + xdebug: args.xdebug, + experimentalDevtools: args.experimentalDevtools, + experimentalMultiWorker: args.experimentalMultiWorker, + 'site-url': args[ 'site-url' ], + outfile: args.outfile, + blueprintJson: args.blueprint + ? JSON.stringify( sanitizeBlueprint( args.blueprint ) ) + : undefined, + }; +} diff --git a/package-lock.json b/package-lock.json index efdf6614d8..7394ca02f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,6 @@ "@formatjs/intl-locale": "^3.4.5", "@formatjs/intl-localematcher": "^0.5.4", "@inquirer/prompts": "^7.10.1", - "@php-wasm/node": "^3.0.22", - "@php-wasm/scopes": "^3.0.22", - "@php-wasm/universal": "^3.0.22", "@reduxjs/toolkit": "^2.7.0", "@rive-app/react-canvas": "^4.12.0", "@sentry/electron": "^6.5.0", @@ -27,9 +24,6 @@ "@wordpress/dataviews": "^11.0.0", "@wordpress/i18n": "^6.9.0", "@wordpress/icons": "^11.3.0", - "@wp-playground/blueprints": "^3.0.22", - "@wp-playground/cli": "^3.0.22", - "@wp-playground/wordpress": "^3.0.22", "archiver": "^6.0.1", "atomically": "^2.0.3", "cli-table3": "^0.6.5", @@ -100,6 +94,7 @@ "@wordpress/components": "^30.9.0", "@wordpress/element": "^6.36.0", "@wordpress/react-i18n": "^4.36.0", + "@wp-playground/blueprints": "^3.0.22", "cross-env": "^7.0.3", "electron": "^39.2.7", "electron-devtools-installer": "^4.0.0", @@ -6428,6 +6423,7 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", + "dev": true, "dependencies": { "@octokit/auth-app": "^6.0.0", "@octokit/auth-unauthenticated": "^5.0.0", @@ -6445,6 +6441,7 @@ "version": "6.1.3", "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.3.tgz", "integrity": "sha512-dcaiteA6Y/beAlDLZOPNReN3FGHu+pARD6OHfh3T9f3EO09++ec+5wt3KtGGSSs2Mp5tI8fQwdMOEnrzBLfgUA==", + "dev": true, "dependencies": { "@octokit/auth-oauth-app": "^7.1.0", "@octokit/auth-oauth-user": "^4.1.0", @@ -6463,12 +6460,14 @@ "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/auth-app/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6478,6 +6477,7 @@ "version": "11.0.2-patch.1", "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", + "dev": true, "engines": { "node": "18 >=18.20 || 20 || >=22" } @@ -6486,6 +6486,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "dev": true, "dependencies": { "@octokit/auth-oauth-device": "^6.1.0", "@octokit/auth-oauth-user": "^4.1.0", @@ -6502,12 +6503,14 @@ "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6516,6 +6519,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "dev": true, "dependencies": { "@octokit/oauth-methods": "^4.1.0", "@octokit/request": "^8.3.1", @@ -6529,12 +6533,14 @@ "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6543,6 +6549,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "dev": true, "dependencies": { "@octokit/auth-oauth-device": "^6.1.0", "@octokit/oauth-methods": "^4.1.0", @@ -6558,12 +6565,14 @@ "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6572,6 +6581,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, "engines": { "node": ">= 18" } @@ -6580,6 +6590,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", + "dev": true, "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0" @@ -6592,6 +6603,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz", "integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==", + "dev": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -6608,12 +6620,14 @@ "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/core/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6622,6 +6636,7 @@ "version": "9.0.6", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "dev": true, "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" @@ -6633,12 +6648,14 @@ "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/endpoint/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6647,6 +6664,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "dev": true, "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", @@ -6659,12 +6677,14 @@ "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/graphql/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6673,6 +6693,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "dev": true, "dependencies": { "@octokit/auth-oauth-app": "^7.0.0", "@octokit/auth-oauth-user": "^4.0.0", @@ -6691,6 +6712,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "dev": true, "engines": { "node": ">= 18" } @@ -6699,6 +6721,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "dev": true, "dependencies": { "@octokit/oauth-authorization-url": "^6.0.2", "@octokit/request": "^8.3.1", @@ -6713,12 +6736,14 @@ "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6726,12 +6751,14 @@ "node_modules/@octokit/openapi-types": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true }, "node_modules/@octokit/plugin-paginate-graphql": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.1.tgz", "integrity": "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==", + "dev": true, "engines": { "node": ">= 18" }, @@ -6743,6 +6770,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "dev": true, "dependencies": { "@octokit/types": "^12.6.0" }, @@ -6757,6 +6785,7 @@ "version": "10.4.1", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "dev": true, "dependencies": { "@octokit/types": "^12.6.0" }, @@ -6771,6 +6800,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz", "integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==", + "dev": true, "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/types": "^13.0.0", @@ -6786,12 +6816,14 @@ "node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/plugin-retry/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6800,6 +6832,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "dev": true, "dependencies": { "@octokit/types": "^12.2.0", "bottleneck": "^2.15.3" @@ -6815,6 +6848,7 @@ "version": "8.4.1", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "dev": true, "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", @@ -6829,6 +6863,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "dev": true, "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", @@ -6841,12 +6876,14 @@ "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/request-error/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6854,12 +6891,14 @@ "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true }, "node_modules/@octokit/request/node_modules/@octokit/types": { "version": "13.10.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^24.2.0" } @@ -6868,6 +6907,7 @@ "version": "12.6.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dev": true, "dependencies": { "@octokit/openapi-types": "^20.0.0" } @@ -6876,6 +6916,7 @@ "version": "12.3.1", "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.1.tgz", "integrity": "sha512-BVwtWE3rRXB9IugmQTfKspqjNa8q+ab73ddkV9k1Zok3XbuOxJUi4lTYk5zBZDhfWb/Y2H+RO9Iggm25gsqeow==", + "dev": true, "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/webhooks-methods": "^4.1.0", @@ -6890,6 +6931,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==", + "dev": true, "engines": { "node": ">= 18" } @@ -6897,7 +6939,8 @@ "node_modules/@octokit/webhooks-types": { "version": "7.6.1", "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", - "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==" + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", + "dev": true }, "node_modules/@opentelemetry/api": { "version": "1.9.0", @@ -7788,6 +7831,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/fs-journal/-/fs-journal-3.0.22.tgz", "integrity": "sha512-0aRtl2G/yejbyAC6guesznFKsg2EN3QEAjjKOJZ+QJogVT3szys0td8tNcQ0fcHYoSJj9lS8yZ+84EjpWN4LzQ==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/logger": "3.0.22", @@ -7812,6 +7856,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -7826,6 +7871,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -7835,6 +7881,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -7847,6 +7894,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -7865,6 +7913,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -7874,6 +7923,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/logger/-/logger-3.0.22.tgz", "integrity": "sha512-AlomcaUmpBSSrkFNET5MOKVsqdTTID05nXWNKqgViRQaeepksIFukZYo1xm3XOAP/OhdKZ7IyblyfMSuStOVAg==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/node-polyfills": "3.0.22" @@ -7890,6 +7940,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/node/-/node-3.0.22.tgz", "integrity": "sha512-OlbCIGFB4ACHlha0C+MVYT47RKqulMUu35C1j6VdUAkYcen+QpzbXJGH4wMTBAkcw+q/jWUqllgGjDsoWDjj/w==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/logger": "3.0.22", @@ -7915,6 +7966,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/node-polyfills/-/node-polyfills-3.0.22.tgz", "integrity": "sha512-Q5T8n6wEQGTUn1eP61FmQdQO4rxavR3IeW95Fj++QIkMs9ZfllmuWepbu02rWP6unk6Do7IZoNprqRz7Lyc9og==", + "dev": true, "license": "GPL-2.0-or-later", "optionalDependencies": { "fs-ext": "2.1.1" @@ -7924,6 +7976,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -7938,6 +7991,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -7946,6 +8000,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -7958,6 +8013,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -7976,6 +8032,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -7985,6 +8042,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/progress/-/progress-3.0.22.tgz", "integrity": "sha512-jkiP4hPDtqN4bkSI7X2OSjhtSQdxLqznofI32vLASGQu3SaaZO6iaqf0JBtnbJQL4n1TgQrqIe2PA/cNkRUKYA==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/logger": "3.0.22", @@ -8002,6 +8060,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/scopes/-/scopes-3.0.22.tgz", "integrity": "sha512-BG2mdeQ3Xf9C1gZ6wpnS8gjGTbxnUG/EE0CCG2d0Vb3pDhjTMBbJIJx8y0Mly1OoQxv1xMjb68aXS+A0yEunZw==", + "dev": true, "license": "GPL-2.0-or-later", "engines": { "node": ">=20.18.3", @@ -8015,6 +8074,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/stream-compression/-/stream-compression-3.0.22.tgz", "integrity": "sha512-uM/spZwgbuY9ZcTiCBl0ZvG0DwCahVn73DHQY7JtiN6uTRe4IsjTQtoOCZPjFFhTFvnf+ZSSCHw4sE9+oTpezg==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/node-polyfills": "3.0.22", @@ -8028,6 +8088,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/universal/-/universal-3.0.22.tgz", "integrity": "sha512-fh0MovmoWsz2F01KWZ2a14Ou6G+yKMduLnLIiFIcUfFqoWFvu8WW+yM29CfcDqx56/9aCRWltueckdcNZ9871g==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/logger": "3.0.22", @@ -8049,6 +8110,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -8058,6 +8120,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/util/-/util-3.0.22.tgz", "integrity": "sha512-RX6yqg56xHx4/uxHXXFhrWtyj1+lVrlJL95Y3D/gkX+XcX2lrgAgRJSTrINhM9OZq7Amxz0DxDLQVglJi2Imfw==", + "dev": true, "engines": { "node": ">=20.18.3", "npm": ">=10.1.0" @@ -8070,6 +8133,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/web/-/web-3.0.22.tgz", "integrity": "sha512-BgfduJYdE0JIBTPjogDJiWqLdxM/JmWtAlKbmVlGtIeWGA9PJU9DMyhrzSeQkcx8zTN/wG3qDLgIrojJ2PII6A==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/fs-journal": "3.0.22", @@ -8095,6 +8159,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/web-service-worker/-/web-service-worker-3.0.22.tgz", "integrity": "sha512-OijEAI6/Rf6G9Do4E87OqGpCFW56uyU2Gl007IHJjV8cEOQLXVPfFpi0+VE4Tbr9Ba4NjB1GEFIUJJIHX08/3g==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/scopes": "3.0.22" @@ -8111,6 +8176,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -8125,6 +8191,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -8134,6 +8201,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8146,6 +8214,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -8164,96 +8233,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@php-wasm/xdebug-bridge": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/@php-wasm/xdebug-bridge/-/xdebug-bridge-3.0.22.tgz", - "integrity": "sha512-nYM3ryfYSxYjJYKkT65UmBoV/aS+I72lOL9k35TMLB+NCLSC3Z5srPZ8GoVxq9nJDVFs8I4a77SwUthA/q58PA==", - "license": "GPL-2.0-or-later", - "dependencies": { - "@php-wasm/logger": "3.0.22", - "@php-wasm/node": "3.0.22", - "@php-wasm/universal": "3.0.22", - "@wp-playground/common": "3.0.22", - "express": "4.21.2", - "ini": "4.1.2", - "wasm-feature-detect": "1.8.0", - "ws": "8.18.3", - "xml2js": "0.6.2", - "yargs": "17.7.2" - }, - "bin": { - "xdebug-bridge": "xdebug-bridge.js" - }, - "engines": { - "node": ">=20.18.3", - "npm": ">=10.1.0" - }, - "optionalDependencies": { - "fs-ext": "2.1.1" - } - }, - "node_modules/@php-wasm/xdebug-bridge/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@php-wasm/xdebug-bridge/node_modules/ini": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", - "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@php-wasm/xdebug-bridge/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@php-wasm/xdebug-bridge/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@php-wasm/xdebug-bridge/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -9913,6 +9893,7 @@ "version": "8.10.157", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.157.tgz", "integrity": "sha512-ofjcRCO1N7tMZDSO11u5bFHPDfUFD3Q9YK9g4S4w8UDKuG3CNlw2lNK1sd3Itdo7JORygZmG4h9ZykS8dlXvMA==", + "dev": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -9963,7 +9944,8 @@ "node_modules/@types/btoa-lite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", - "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==" + "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", + "dev": true }, "node_modules/@types/cacheable-request": { "version": "6.0.3", @@ -10204,6 +10186,7 @@ "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, "license": "MIT", "dependencies": { "@types/ms": "*", @@ -11714,6 +11697,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@wp-playground/blueprints/-/blueprints-3.0.22.tgz", "integrity": "sha512-Rx1b70k7RTeT7gkqVbQvHDgjXoqJHXPkyKh2XUnLg9CDhe/FNvbhYD/mFZMGI7JLqMlf2C5cCxdMUFcHSQuC8A==", + "dev": true, "dependencies": { "@php-wasm/logger": "3.0.22", "@php-wasm/node": "3.0.22", @@ -11758,6 +11742,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -11774,6 +11759,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -11788,6 +11774,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -11797,17 +11784,20 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/@wp-playground/blueprints/node_modules/pako": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true }, "node_modules/@wp-playground/blueprints/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11820,6 +11810,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -11838,145 +11829,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@wp-playground/cli": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/@wp-playground/cli/-/cli-3.0.22.tgz", - "integrity": "sha512-sWCtiX21Dh+8m8BRsSeumW2BcPpc36PkMDvMAJnLh7y8FmPPnqOY0rzOWBAmm19dHjCx8DiLuUZ7oo+est6g8A==", - "license": "GPL-2.0-or-later", - "dependencies": { - "@php-wasm/logger": "3.0.22", - "@php-wasm/node": "3.0.22", - "@php-wasm/progress": "3.0.22", - "@php-wasm/universal": "3.0.22", - "@php-wasm/util": "3.0.22", - "@php-wasm/xdebug-bridge": "3.0.22", - "@wp-playground/blueprints": "3.0.22", - "@wp-playground/common": "3.0.22", - "@wp-playground/storage": "3.0.22", - "@wp-playground/wordpress": "3.0.22", - "@zip.js/zip.js": "2.7.57", - "ajv": "8.12.0", - "async-lock": "1.4.1", - "clean-git-ref": "2.0.1", - "crc-32": "1.2.2", - "diff3": "0.0.4", - "express": "4.21.2", - "fast-xml-parser": "5.3.0", - "fs-extra": "11.1.1", - "ignore": "5.3.2", - "ini": "4.1.2", - "jsonc-parser": "3.3.1", - "minimisted": "2.0.1", - "octokit": "3.1.2", - "pako": "1.0.10", - "pify": "2.3.0", - "ps-man": "1.1.8", - "readable-stream": "3.6.2", - "sha.js": "2.4.12", - "simple-get": "4.0.1", - "tmp-promise": "3.0.3", - "wasm-feature-detect": "1.8.0", - "ws": "8.18.3", - "xml2js": "0.6.2", - "yargs": "17.7.2" - }, - "bin": { - "wp-playground-cli": "wp-playground.js" - }, - "optionalDependencies": { - "fs-ext": "2.1.1" - } - }, - "node_modules/@wp-playground/cli/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@wp-playground/cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wp-playground/cli/node_modules/ini": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", - "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@wp-playground/cli/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/@wp-playground/cli/node_modules/pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", - "license": "(MIT AND Zlib)" - }, - "node_modules/@wp-playground/cli/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wp-playground/cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wp-playground/cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -11986,6 +11839,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@wp-playground/common/-/common-3.0.22.tgz", "integrity": "sha512-iH/lmymV1d3xX6o64AxEnv/CKLpfo8bkifxTkIBSk9wKmbxaGUsjGO2uTP5W9abpKOkdnlY6Nm8ESh1OGW7DtQ==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/universal": "3.0.22", @@ -12004,6 +11858,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -12013,6 +11868,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@wp-playground/storage/-/storage-3.0.22.tgz", "integrity": "sha512-zjsxvfVNphvbGOzc1Q0EkaOxs9TokdPlncBk8ye5vzmNhvl0Glyfu3pErfHs/L/f0ftRw4eBk1VUgPCS8BxNDA==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/stream-compression": "3.0.22", @@ -12046,6 +11902,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -12060,12 +11917,14 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==", + "dev": true, "license": "MIT" }, "node_modules/@wp-playground/storage/node_modules/ini": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -12075,6 +11934,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -12084,6 +11944,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12096,6 +11957,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -12114,6 +11976,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -12123,6 +11986,7 @@ "version": "3.0.22", "resolved": "https://registry.npmjs.org/@wp-playground/wordpress/-/wordpress-3.0.22.tgz", "integrity": "sha512-FbQruz+dBA/sWq+Xf3SaTl3O7uWI4NYPovTzFnf/f1TPswjyYWGg8cGcb3xpGYr8pENOiPTkV2jKWX/V9WX/nw==", + "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@php-wasm/logger": "3.0.22", @@ -12148,6 +12012,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -12162,6 +12027,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -12171,6 +12037,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12183,6 +12050,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -12201,6 +12069,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -12239,6 +12108,7 @@ "version": "2.7.57", "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.57.tgz", "integrity": "sha512-BtonQ1/jDnGiMed6OkV6rZYW78gLmLswkHOzyMrMb+CAR7CZO8phOHO6c2qw6qb1g1betN7kwEHhhZk30dv+NA==", + "dev": true, "engines": { "bun": ">=0.7.0", "deno": ">=1.0.0", @@ -12344,6 +12214,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", @@ -12779,7 +12650,8 @@ "node_modules/async-lock": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", - "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "dev": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -12817,6 +12689,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -13055,7 +12928,8 @@ "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true }, "node_modules/big-integer": { "version": "1.6.52", @@ -13169,7 +13043,8 @@ "node_modules/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true }, "node_modules/bplist-creator": { "version": "0.0.8", @@ -13261,7 +13136,8 @@ "node_modules/btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==" + "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", + "dev": true }, "node_modules/buffer": { "version": "5.7.1", @@ -13299,7 +13175,8 @@ "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true }, "node_modules/buffer-from": { "version": "1.1.2", @@ -13512,6 +13389,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -13813,12 +13691,14 @@ "node_modules/clean-git-ref": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", - "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==" + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==", + "dev": true }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -14585,6 +14465,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -14599,6 +14480,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, "engines": { "node": ">=10" }, @@ -14662,6 +14544,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -14717,7 +14600,8 @@ "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true }, "node_modules/dequal": { "version": "2.0.3", @@ -14803,7 +14687,8 @@ "node_modules/diff3": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.4.tgz", - "integrity": "sha512-f1rQ7jXDn/3i37hdwRk9ohqcvLRH3+gEIgmA6qEM280WUOh7cOr3GXV8Jm5sPwUs46Nzl48SE8YNLGJoaLuodg==" + "integrity": "sha512-f1rQ7jXDn/3i37hdwRk9ohqcvLRH3+gEIgmA6qEM280WUOh7cOr3GXV8Jm5sPwUs46Nzl48SE8YNLGJoaLuodg==", + "dev": true }, "node_modules/dir-compare": { "version": "4.2.0", @@ -14921,6 +14806,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -16816,24 +16702,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.0.tgz", - "integrity": "sha512-gkWGshjYcQCF+6qtlrqBqELqNqnt4CxruY6UVAWWnqb3DQ6qaNFEIKqzYep1XzHLM/QtrHVCxyPOtTk4LTQ7Aw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", @@ -17022,6 +16890,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -17146,6 +17015,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/fs-ext/-/fs-ext-2.1.1.tgz", "integrity": "sha512-/TrISPOFhCkbgIRWK9lzscRzwPCu0PqtCcvMc9jsHKBgZGoqA0VzhspVht5Zu8lxaXjIYIBWILHpRotYkCCcQA==", + "dev": true, "hasInstallScript": true, "optional": true, "dependencies": { @@ -17159,6 +17029,7 @@ "version": "2.24.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==", + "dev": true, "license": "MIT", "optional": true }, @@ -17765,6 +17636,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -18080,6 +17952,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -18193,6 +18066,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "engines": { "node": ">=8" } @@ -18610,6 +18484,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -18989,6 +18864,7 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -19074,7 +18950,8 @@ "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/isbinaryfile": { "version": "4.0.10", @@ -20925,12 +20802,6 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "license": "MIT" - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -20965,6 +20836,7 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dev": true, "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -21042,6 +20914,7 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dev": true, "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -21052,6 +20925,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" @@ -21411,32 +21285,38 @@ "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -21459,7 +21339,8 @@ "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true }, "node_modules/lodash.throttle": { "version": "4.1.1", @@ -22760,6 +22641,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "dev": true, "dependencies": { "minimist": "^1.2.5" } @@ -23325,6 +23207,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", "integrity": "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==", + "dev": true, "dependencies": { "@octokit/app": "^14.0.2", "@octokit/core": "^5.0.0", @@ -23674,7 +23557,8 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true }, "node_modules/param-case": { "version": "3.0.4", @@ -24023,6 +23907,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -24167,6 +24052,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -24558,12 +24444,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ps-man": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ps-man/-/ps-man-1.1.8.tgz", - "integrity": "sha512-ZKDPZwHLYVWIk/Q75N7jCFbuQyokSg2+3WBlt8l35S/uBvxoc+LiRUbb3RUt83pwW82dzwiCpoQIHd9PAxUzHg==", - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -24578,6 +24458,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "engines": { "node": ">=6" } @@ -25279,6 +25160,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -25287,6 +25169,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -25710,12 +25593,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/sax": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", - "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", - "license": "BlueOak-1.0.0" - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -25876,6 +25753,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -25935,6 +25813,7 @@ "version": "2.4.12", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.4", @@ -25955,6 +25834,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -26085,6 +25965,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, "funding": [ { "type": "github", @@ -26104,6 +25985,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, "funding": [ { "type": "github", @@ -26688,18 +26570,6 @@ "node": ">=0.8.0" } }, - "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/stubborn-fs": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz", @@ -27415,6 +27285,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.14" @@ -27424,6 +27295,8 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "optional": true, "dependencies": { "tmp": "^0.2.0" } @@ -27451,6 +27324,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, "license": "MIT", "dependencies": { "isarray": "^2.0.5", @@ -27465,6 +27339,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -27831,6 +27706,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -28159,6 +28035,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "dev": true, "dependencies": { "@types/jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.2" @@ -28167,7 +28044,8 @@ "node_modules/universal-user-agent": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true }, "node_modules/universalify": { "version": "2.0.1", @@ -28386,6 +28264,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -29206,7 +29085,8 @@ "node_modules/wasm-feature-detect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", - "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==" + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "dev": true }, "node_modules/watchpack": { "version": "2.4.4", @@ -29521,6 +29401,7 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -29606,6 +29487,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -29650,6 +29532,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -29693,6 +29576,7 @@ "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -29720,28 +29604,6 @@ "node": ">=18" } }, - "node_modules/xml2js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", - "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", diff --git a/package.json b/package.json index 32c4e3517d..363cd40dad 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@wordpress/components": "^30.9.0", "@wordpress/element": "^6.36.0", "@wordpress/react-i18n": "^4.36.0", + "@wp-playground/blueprints": "^3.0.22", "cross-env": "^7.0.3", "electron": "^39.2.7", "electron-devtools-installer": "^4.0.0", @@ -110,9 +111,6 @@ "@formatjs/intl-locale": "^3.4.5", "@formatjs/intl-localematcher": "^0.5.4", "@inquirer/prompts": "^7.10.1", - "@php-wasm/node": "^3.0.22", - "@php-wasm/scopes": "^3.0.22", - "@php-wasm/universal": "^3.0.22", "@reduxjs/toolkit": "^2.7.0", "@rive-app/react-canvas": "^4.12.0", "@sentry/electron": "^6.5.0", @@ -122,9 +120,6 @@ "@wordpress/dataviews": "^11.0.0", "@wordpress/i18n": "^6.9.0", "@wordpress/icons": "^11.3.0", - "@wp-playground/blueprints": "^3.0.22", - "@wp-playground/cli": "^3.0.22", - "@wp-playground/wordpress": "^3.0.22", "archiver": "^6.0.1", "atomically": "^2.0.3", "cli-table3": "^0.6.5", From 49d15d53d44d46bf879ed9b743d640b4128a04e7 Mon Sep 17 00:00:00 2001 From: bcotrim Date: Thu, 18 Dec 2025 09:55:03 +0000 Subject: [PATCH 4/8] remove unnecessary PHP-WASM ignore patterns from forge config --- forge.config.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/forge.config.ts b/forge.config.ts index 951af9653e..c1238a4aff 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -54,10 +54,6 @@ const config: ForgeConfig = { /^\/wp-files/, /^\/dist\/cli/, /^\/dist\/playground-cli/, - // PHP-WASM packages - these are only needed in CLI, not in desktop app - // See STU-960: Remove PHP-WASM dependencies from Studio - /^\/node_modules\/@php-wasm/, - /^\/node_modules\/@wp-playground/, ], }, rebuildConfig: {}, From 07bc3c911a79fa4de68e3cb2d6ce87ffd904af58 Mon Sep 17 00:00:00 2001 From: bcotrim Date: Thu, 18 Dec 2025 09:59:16 +0000 Subject: [PATCH 5/8] remove PHP-WASM references from Jest config --- jest-setup.ts | 5 +---- jest.config.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/jest-setup.ts b/jest-setup.ts index 711b130ab2..c19dc5b652 100644 --- a/jest-setup.ts +++ b/jest-setup.ts @@ -12,8 +12,7 @@ if ( typeof globalThis.TextDecoder === 'undefined' ) { } // We need this polyfill because the `ReadableStream` class is -// used by `@php-wasm/universal` and it's not available in the Jest environment. -// Import ponyfill to avoid global pollution issues with php-wasm 1.2.3 +// not available in the Jest environment. const streams = require( 'web-streams-polyfill/dist/ponyfill.js' ); // Assign to global only if not already available @@ -75,8 +74,6 @@ if ( typeof window !== 'undefined' ) { /** * Mock `crypto.subtle.generateKey` as it's not implemented in JSDOM * https://github.com/jsdom/jsdom/issues/1612 - * - * `crypto.subtle.generateKey` is required by `@php-wasm/web` */ Object.defineProperty( global.crypto, 'subtle', { value: { generateKey: jest.fn() }, diff --git a/jest.config.ts b/jest.config.ts index aa1f9ef6ef..4b14a69e2b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -15,7 +15,7 @@ module.exports = { '^.+\\.m?js$': [ 'babel-jest', { presets: [ '@babel/preset-env' ] } ], "^.+\\.svg$": 'jest-transform-stub', }, - transformIgnorePatterns: [ 'node_modules/(?!(@php-wasm|@wp-playground)/)' ], + transformIgnorePatterns: [ 'node_modules/' ], moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', '^cli/(.*)$': '/cli/$1', From 68552af0eafe29017abfeb766488e920c7d6a158 Mon Sep 17 00:00:00 2001 From: bcotrim Date: Thu, 18 Dec 2025 10:10:14 +0000 Subject: [PATCH 6/8] update documentation for PHP-WASM removal --- docs/ai-instructions.md | 57 +++++++++++---------------- docs/testing-with-local-playground.md | 50 ++++++++++++++++------- src/lib/download-utils.ts | 2 - src/lib/server-files-paths.ts | 2 - 4 files changed, 58 insertions(+), 53 deletions(-) diff --git a/docs/ai-instructions.md b/docs/ai-instructions.md index 25a09a2df3..6a3a653985 100644 --- a/docs/ai-instructions.md +++ b/docs/ai-instructions.md @@ -152,7 +152,7 @@ See the site management commands (create, list, start, etc) in `cli/commands/sit ## WordPress Studio - Architecture Overview -WordPress Studio is a desktop application for creating, managing, and testing WordPress sites locally. It's built as an Electron desktop application with a React renderer, powered by WordPress Playground and PHP WASM. +WordPress Studio is a desktop application for creating, managing, and testing WordPress sites locally. It's built as an Electron desktop application with a React renderer. The CLI component uses WordPress Playground (PHP WASM) to run WordPress sites. ## High-Level Architecture @@ -162,7 +162,6 @@ WordPress Studio is a desktop application for creating, managing, and testing Wo ├─────────────────────────────────────────────────────┤ │ • IPC Handler Layer (ipc-handlers.ts) │ │ • Site Server Management (site-server.ts) │ -│ • WordPress Provider Abstraction │ │ • Storage & User Data Management │ │ • OAuth / Authentication │ │ • Sync Operations (WordPress.com / Pressable) │ @@ -175,11 +174,11 @@ WordPress Studio is a desktop application for creating, managing, and testing Wo └────────────────┬─┘ └───────────────┘ │ ┌──────────────────────▼──────────────────────┐ -│ WORDPRESS PROVIDERS │ +│ STUDIO CLI │ ├──────────────────────────────────────────────┤ -│ • Playground-CLI Provider (with Blueprints) │ -│ • WP-Now Provider (fallback) │ -│ • PHP WASM Runtime │ +│ • WordPress Playground (@wp-playground/cli) │ +│ • PHP WASM Runtime (@php-wasm/*) │ +│ • Blueprint Support │ │ • Server Process Management │ └─────────────────────────────────────────────┘ ``` @@ -198,12 +197,10 @@ WordPress Studio is a desktop application for creating, managing, and testing Wo - **`storage/`** - User data persistence and app state storage - **`stores/`** - Redux RTK stores (centralized state management) - **`lib/`** - Utility libraries and business logic - - `wordpress-provider/` - Abstraction layer for WordPress runtime (Playground vs WP-Now) - `import-export/` - Site backup/restore functionality - `sync/` - WordPress.com sync operations - `certificate-manager.ts` - HTTPS certificate generation for custom domains - `proxy-server.ts` - Local HTTP proxy for custom domain routing - - `wp-cli-process.ts` - WP-CLI command execution wrapper - **`components/`** - React UI components - `root.tsx` - Root component with all context providers - `app.tsx` - Main app layout @@ -266,15 +263,11 @@ window.ipcApi.installStudioCli() // Install the CLI window.ipcApi.uninstallStudioCli() // Uninstall the CLI ``` -### 3. WordPress Provider Pattern (Strategy Pattern) -Two implementations for running WordPress: -- **PlaygroundCliProvider**: Uses `@wp-playground/cli` with Blueprint support (feature-gated) -- **WpNowProvider**: Fallback provider with core functionality - -Both implement the `WordPressProvider` interface with methods: -- `startServer()` - Start a WordPress site -- `setupWordPressSite()` - Initialize WordPress installation -- `createServerProcess()` - Create server child process +### 3. CLI Server Process +The desktop app delegates WordPress operations to the CLI: +- **CliServerProcess** (`src/modules/cli/lib/cli-server-process.ts`): Spawns CLI as child process +- **Site operations**: Start/stop servers, run WP-CLI commands +- **Blueprint support**: Passed to CLI for site initialization ### 4. Redux Store Architecture (RTK) ```typescript @@ -284,7 +277,6 @@ Both implement the `WordPressProvider` interface with methods: - connectedSites: Connected WordPress.com sites - snapshot: Site snapshots/backups - onboarding: First-run experience state -- provider constants: WordPress/PHP versions - RTK Query APIs for data fetching: - wpcomApi: WordPress.com API calls - installedAppsApi: System apps detection, CLI installation status @@ -338,11 +330,12 @@ Redux State Update / Re-render ### Main Process - **Electron 38** - Desktop framework +- **express** - Lightweight HTTP server for sites + +### CLI (runs WordPress sites) +- **@wp-playground/cli** - WordPress Playground CLI - **@php-wasm/node** - PHP runtime in Node.js -- **@php-wasm/universal** - Universal PHP WASM - **@wp-playground/blueprints** - Blueprint compilation -- **@wp-playground/cli** - Playground CLI integration -- **express** - Lightweight HTTP server for sites ### Development - **electron-vite** - Electron build orchestration @@ -410,19 +403,13 @@ Storage is protected by file locking (`lockAppdata()` / `unlockAppdata()`). - Renderer uses `` context - CLI translates via `loadTranslations()` in CLI bootstrap -## WordPress Playground Integration - -### Blueprints -- Complex site configurations declaratively defined -- Compiled to runtime executable by `@wp-playground/blueprints` -- Feature detection/filtering: `filterUnsupportedBlueprintFeatures()` in `src/lib/blueprint-features.ts` -- Passed to server startup for automatic setup +## WordPress Playground Integration (CLI) -### PHP WASM Runtime -- `@php-wasm/node` provides PHP runtime in Node.js -- Runs in child processes via `WorkerThreads` -- WP-CLI integration: `WpCliProcess` class wraps CLI commands -- Server process: Handles PHP execution and WordPress HTTP requests +The CLI component uses WordPress Playground to run WordPress sites: +- **@wp-playground/cli**: Runs WordPress in Node.js using PHP WASM +- **Blueprints**: Declarative site configurations passed to CLI for setup +- **Feature filtering**: `filterUnsupportedBlueprintFeatures()` removes unsupported steps +- The desktop app spawns the CLI as a child process via `CliServerProcess` ## Sync & WordPress.com Integration @@ -474,12 +461,12 @@ Local component state used for temporary UI interactions. - **Code Splitting**: Vendor and Sentry chunks extracted separately - **CSS Code Split**: Separate CSS files for better caching -- **WASM Bundling**: WASM files included as external assets +- **CLI Separation**: PHP WASM (~1GB) only in CLI, not in desktop app bundle - **Port Finder**: Efficient port availability checking with caching - **Snapshot System**: Browser-like snapshots for fast site restoration --- -Last Updated: 2025-11-10 +Last Updated: 2025-12-18 Repository: https://github.com/Automattic/studio License: GPLv2 or later diff --git a/docs/testing-with-local-playground.md b/docs/testing-with-local-playground.md index c70aae2765..0b0ecbfc65 100644 --- a/docs/testing-with-local-playground.md +++ b/docs/testing-with-local-playground.md @@ -1,6 +1,8 @@ # Testing with Local Playground Packages -When developing features that require changes to WordPress Playground packages (`@wp-playground/*` or `@php-wasm/*`), you can test Studio with locally built Playground packages. +When developing features that require changes to WordPress Playground packages (`@wp-playground/*` or `@php-wasm/*`), you can test Studio's CLI with locally built Playground packages. + +**Note:** The desktop app does not directly use PHP-WASM packages - only the CLI does. These instructions are for testing CLI functionality. ## Prerequisites @@ -29,19 +31,18 @@ npm install npm run build ``` -### 2. Update Studio's package.json +### 2. Update CLI's package.json -Replace the existing Playground dependencies with local file references. These packages are grouped together at the end of `dependencies` in `package.json` for easier replacement: +Replace the existing Playground dependencies in `cli/package.json` with local file references: ```json { "dependencies": { - "@php-wasm/node": "file:../wordpress-playground/dist/packages/php-wasm/node", - "@php-wasm/scopes": "file:../wordpress-playground/dist/packages/php-wasm/scopes", - "@php-wasm/universal": "file:../wordpress-playground/dist/packages/php-wasm/universal", - "@wp-playground/blueprints": "file:../wordpress-playground/dist/packages/playground/blueprints", - "@wp-playground/cli": "file:../wordpress-playground/dist/packages/playground/cli", - "@wp-playground/wordpress": "file:../wordpress-playground/dist/packages/playground/wordpress" + "@php-wasm/universal": "file:../../wordpress-playground/dist/packages/php-wasm/universal", + "@wp-playground/blueprints": "file:../../wordpress-playground/dist/packages/playground/blueprints", + "@wp-playground/cli": "file:../../wordpress-playground/dist/packages/playground/cli", + "@wp-playground/common": "file:../../wordpress-playground/dist/packages/playground/common", + "@wp-playground/storage": "file:../../wordpress-playground/dist/packages/playground/storage" } } ``` @@ -72,10 +73,21 @@ done ```bash cd /path/to/studio +rm -rf cli/node_modules +cd cli && npm install && cd .. npm install ``` -### 5. Start Studio +### 5. Build and test the CLI + +```bash +npm run cli:build +node dist/cli/main.js site create --name test-site +``` + +### 6. Start Studio (optional) + +If you want to test the full desktop app with CLI changes: ```bash npm start @@ -91,17 +103,27 @@ After making changes to Playground: npx nx build playground-cli # or other package name ``` -2. Restart Studio (restart `npm start`) +2. Rebuild the CLI: + ```bash + cd /path/to/studio + npm run cli:build + ``` + +3. If testing with the desktop app, restart `npm start` ## Reverting to npm Packages To go back to using the published npm packages: -1. Restore both `package.json` and `package-lock.json`: +1. Restore `cli/package.json`: + ```bash + git checkout cli/package.json + ``` +2. Reinstall CLI dependencies: ```bash - git checkout package.json package-lock.json + rm -rf cli/node_modules + cd cli && npm install && cd .. ``` -2. Run `npm install` 3. In the Playground repo, restore the original `node_modules` symlinks: ```bash cd /path/to/wordpress-playground diff --git a/src/lib/download-utils.ts b/src/lib/download-utils.ts index b9f314d618..cd653730d1 100644 --- a/src/lib/download-utils.ts +++ b/src/lib/download-utils.ts @@ -1,8 +1,6 @@ /** * Download utilities for WordPress, WP-CLI, and SQLite command * - * These replace the functions from vendor/wp-now to remove that dependency. - * See STU-960: Remove PHP-WASM dependencies from Studio */ import { IncomingMessage } from 'http'; diff --git a/src/lib/server-files-paths.ts b/src/lib/server-files-paths.ts index 691468d210..d265d39b2d 100644 --- a/src/lib/server-files-paths.ts +++ b/src/lib/server-files-paths.ts @@ -1,8 +1,6 @@ /** * Path utilities for server files (WordPress versions, WP-CLI, SQLite) * - * These replace the functions from vendor/wp-now to remove that dependency. - * See STU-960: Remove PHP-WASM dependencies from Studio */ import os from 'os'; From 92ffd243f14a1f64cdced5974f7306be88df99de Mon Sep 17 00:00:00 2001 From: bcotrim Date: Thu, 18 Dec 2025 10:32:06 +0000 Subject: [PATCH 7/8] remove outdated playground-cli-workflow design doc --- docs/design-docs/playground-cli-workflow.md | 236 -------------------- 1 file changed, 236 deletions(-) delete mode 100644 docs/design-docs/playground-cli-workflow.md diff --git a/docs/design-docs/playground-cli-workflow.md b/docs/design-docs/playground-cli-workflow.md deleted file mode 100644 index 07a2c4e246..0000000000 --- a/docs/design-docs/playground-cli-workflow.md +++ /dev/null @@ -1,236 +0,0 @@ -# Studio Playground CLI Workflow Diagram - -## About this doc - -This document outlines the design and implementation details for the flow of creating and starting a site with Blueprints. Also, information about the integration of Playground CLI, which replaces wp-now. It covers the high-level approach, data flow and details of teh implementation. - -## Context - -Before we used wp-now but since we are implementing support of Blueprints v2, we had to move to Playground CLI, since wp-now supported only Blueprints v1. - -## Site Creation and Startup Flow - -```mermaid -graph TB - Start([User clicks Add Site]) --> Options[Add Site Options Screen] - - Options --> |Create Empty Site| CreatePath[Create Site Path] - Options --> |Start from Blueprint| BlueprintSelect[Blueprint Selector] - Options --> |Import from Backup| BackupSelect[Backup File Selector] - - BlueprintSelect --> |Select Blueprint| CreatePath - BackupSelect --> |Select File| CreatePath - - CreatePath --> FillDetails["Fill Site Details
Name Path PHP Version
WP Version Custom Domain HTTPS"] - - FillDetails --> Submit[Submit Form] - - Submit --> CreateSite["createSite IPC Handler
src ipc-handlers ts line 182"] - - CreateSite --> ValidatePath{Path Valid?} - ValidatePath --> |No| Error[Show Error] - ValidatePath --> |Yes| GetPort["portFinder getOpenPort"] - - GetPort --> CreateServer["SiteServer create
src site-server ts line 74"] - - CreateServer --> SetMeta["Set Server Metadata
wpVersion and blueprint"] - - SetMeta --> CheckProvider{Blueprints
Enabled?} - - CheckProvider --> |Yes| UsePlaygroundCLI["Use PlaygroundCliProvider
src lib wordpress-provider playground-cli"] - CheckProvider --> |No| UseWpNow[Use WpNowProvider] - - UsePlaygroundCLI --> SetupWP["setupWordPressSite
playground-cli-provider ts line 116"] - - SetupWP --> CheckOnline{Online?} - - CheckOnline --> |Offline| CopyBundled["Copy bundled WP files
from resources wp-files latest"] - CheckOnline --> |Online| StartSetupMode["Start in Setup Mode
isSetupMode true"] - - CopyBundled --> InstallSQLite[Install SQLite Integration] - - StartSetupMode --> CreateInstance["provider startServer
Creates WordPressServerInstance
with PlaygroundCliOptions"] - - CreateInstance --> CreateProcess["provider createServerProcess
Creates PlaygroundServerProcess"] - - CreateProcess --> StartProcess["serverProcess start
playground-server-process ts line 27"] - - StartProcess --> ForkUtility["utilityProcess fork
Spawns child process"] - - ForkUtility --> ChildProcess[playground-server-process-child ts] - - ChildProcess --> RunCLI["runCLI from wp-playground cli"] - - RunCLI --> SetupMode{Setup Mode?} - - SetupMode --> |Yes| RunBlueprint["Command run-blueprint
Installs WP if needed
Runs blueprint steps
Process exits on completion"] - SetupMode --> |No| StartServer["Command server
wordpressInstallMode: install-from-existing-files-if-needed
Keeps process running"] - - RunBlueprint --> MountDirs["Mount Directories
wordpress to site path
internal studio mu-plugins
internal shared mu-plugins"] - - StartServer --> MountDirs - - MountDirs --> ConfigureWP["Configure WordPress
Set site title
Set admin password
Update site URL"] - - ConfigureWP --> SaveSite["Save to userData
sites array"] - - SaveSite --> SiteReady([Site Ready]) - - InstallSQLite --> SaveSite -``` - -## Starting an Existing Site - -```mermaid -graph TB - Start([User clicks Start Site]) --> LoadSite[Load Site from userData] - - LoadSite --> GetServer["SiteServer get by id"] - - GetServer --> StartSite["startSite
src site-server ts"] - - StartSite --> CheckProvider{Blueprints
Enabled?} - - CheckProvider --> |Yes| UsePlaygroundCLI[PlaygroundCliProvider] - CheckProvider --> |No| UseWpNow[WpNowProvider] - - UsePlaygroundCLI --> CreateServerInstance["provider startServer
Returns WordPressServerInstance"] - - CreateServerInstance --> CreateServerProcess["provider createServerProcess
Returns PlaygroundServerProcess"] - - CreateServerProcess --> StartProcess["serverProcess start"] - - StartProcess --> ForkChild["Fork utility process
playground-server-process-child ts"] - - ForkChild --> RunServer["runCLI with server command
wordpressInstallMode: install-from-existing-files-if-needed
port assigned port
mount paths"] - - RunServer --> WaitReady[Wait for ready message] - - WaitReady --> SendStart["Send start-server message
to child process"] - - SendStart --> ServerRunning["Server Running
at http 127.0.0.1 port"] - - ServerRunning --> UpdateStatus["Update site status
running true"] - - UpdateStatus --> Ready([Site Accessible]) -``` - -## Key Components - -### 1. **Provider System** (`src/lib/wordpress-provider/`) - -- **Provider Selection**: Based on `enableBlueprints` feature flag - - `true` → PlaygroundCliProvider - - `false` → WpNowProvider (legacy) -- **Provider Interface**: WordPressProvider with methods: - - setupWordPressSite - Initial WP setup - - startServer - Create server instance - - createServerProcess - Create process handler - -### 2. **PlaygroundCliProvider** (`playground-cli-provider.ts`) - -- **Responsibilities**: - - Creates `PlaygroundCliOptions` with port, PHP version, document root - - Handles offline mode with bundled WordPress files - - Manages blueprint execution in setup mode - - Returns `WordPressServerInstance` with configuration - -### 3. **PlaygroundServerProcess** (`playground-server-process.ts`) - -- **Process Management**: - - Uses Electron's utilityProcess.fork for child process - - Message-based IPC communication - - Handles start/stop/run-php commands - - Manages process lifecycle and cleanup - -### 4. **Child Process** (`playground-server-process-child.ts`) - -- **Playground CLI Integration**: - - Imports @wp-playground/cli package - - Runs either run-blueprint (setup) or server (runtime) - - Mounts local directories into Playground VFS - - Handles PHP execution requests - -### 5. **Mount Points** - -``` -Host System → Playground VFS -- Site Path → /wordpress -- Studio MU Plugins → /internal/studio/mu-plugins -- Loader Plugin → /internal/shared/mu-plugins/99-studio-loader.php -``` - -### 6. **Setup vs Runtime Modes** - -| Mode | Command | Purpose | Process Behavior | -| ------------ | ------------- | ------------------------- | ------------------- | -| Setup Mode | run-blueprint | Install WP, run blueprint | Exits on completion | -| Runtime Mode | server | Run existing site | Keeps running | - -### 7. **Blueprint Support** - -- Blueprints passed through `meta.blueprint` in SiteServer -- Executed during setup mode via Playground CLI -- Support for both WPCOM blueprints and file imports - -## Communication Flow - -```mermaid -sequenceDiagram - participant UI as UI/Renderer - participant Main as Main Process - participant Server as SiteServer - participant Provider as PlaygroundCliProvider - participant Process as PlaygroundServerProcess - participant Child as Child Process - participant CLI as @wp-playground/cli - - UI->>Main: createSite IPC - Main->>Server: SiteServer create - Server->>Provider: setupWordPressSite - Provider->>Provider: startServer - Provider->>Process: new PlaygroundServerProcess - Process->>Process: start - Process->>Child: fork utility process - Child->>CLI: runCLI - CLI->>CLI: Setup WordPress - CLI->>Child: ready - Child->>Process: ready message - Process->>Provider: resolved - Provider->>Server: setup complete - Server->>Main: site created - Main->>UI: site details -``` - -## Error Handling - -1. **Offline Mode**: Falls back to bundled WordPress files -2. **Process Timeouts**: 60s for setup mode, 30s for normal operations -3. **Exit Handling**: Graceful cleanup on process exit -4. **Message Queue**: Handles pending messages on unexpected exit - -## File Structure - -``` -src/lib/wordpress-provider/ -├── index.ts # Provider factory & exports -├── types.ts # Shared interfaces -├── playground-cli/ -│ ├── index.ts # Public exports -│ ├── playground-cli-provider.ts # Main provider implementation -│ ├── playground-server-process.ts # Process wrapper -│ ├── playground-server-process-child.ts # Child process code -│ └── mu-plugins.ts # MU plugin management -└── wp-now/ # Legacy provider -``` - -## Notes for Team Members - -1. **Feature Flag**: The Playground CLI provider is activated when `enableBlueprints` feature flag is true -2. **Process Architecture**: Uses Electron's utility process for isolation -3. **VFS Mounting**: All file access goes through Playground's virtual filesystem -4. **Blueprint Execution**: Happens during site creation, not on every start -5. **SQLite Integration**: Automatically installed if wp-config.php doesn't exist -6. **Port Management**: Uses portFinder to avoid conflicts -7. **Password Generation**: Automatic secure password for admin user -8. **Custom Domains**: Handled via proxy server and hosts file modifications From e21e57a39f2ea4e4fa9863ca3b4731a1f315f95a Mon Sep 17 00:00:00 2001 From: bcotrim Date: Thu, 18 Dec 2025 10:33:38 +0000 Subject: [PATCH 8/8] trigger ci e2e