wp-studio 1.7.7-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE.md +257 -0
  2. package/README.md +87 -0
  3. package/dist/cli/_events-BeOo0LuG.js +116 -0
  4. package/dist/cli/appdata-07CF2rhg.js +21090 -0
  5. package/dist/cli/archive-xDmkN4wb.js +15942 -0
  6. package/dist/cli/browser-CgWK-yoe.js +44 -0
  7. package/dist/cli/certificate-manager-DdBumKZp.js +250 -0
  8. package/dist/cli/create-BHVhkvTx.js +80 -0
  9. package/dist/cli/create-ZS29BDDi.js +40999 -0
  10. package/dist/cli/delete-BgQn-elT.js +56 -0
  11. package/dist/cli/delete-g8pgaLna.js +132 -0
  12. package/dist/cli/get-wordpress-version-BwSCJujO.js +18 -0
  13. package/dist/cli/index-7pbG_s_U.js +434 -0
  14. package/dist/cli/index-BXRYeCYG.js +1393 -0
  15. package/dist/cli/index-T3F1GwxX.js +2668 -0
  16. package/dist/cli/is-errno-exception-t38xF2pB.js +6 -0
  17. package/dist/cli/list-BE_UBjL5.js +105 -0
  18. package/dist/cli/list-DKz0XxM7.js +1032 -0
  19. package/dist/cli/logger-actions-OaIvl-ai.js +45 -0
  20. package/dist/cli/login-B4PkfKOu.js +82 -0
  21. package/dist/cli/logout-BC9gKlTj.js +48 -0
  22. package/dist/cli/main.js +5 -0
  23. package/dist/cli/mu-plugins-GEfKsl5U.js +530 -0
  24. package/dist/cli/passwords-DyzWd9Xi.js +80 -0
  25. package/dist/cli/process-manager-daemon.js +327 -0
  26. package/dist/cli/process-manager-ipc-AUZeYYDT.js +454 -0
  27. package/dist/cli/proxy-daemon.js +197 -0
  28. package/dist/cli/run-wp-cli-command-BctnMDWG.js +88 -0
  29. package/dist/cli/sequential-BQFuixXz.js +46 -0
  30. package/dist/cli/server-files-C_oy-mnI.js +26 -0
  31. package/dist/cli/set-DknhAZpw.js +327 -0
  32. package/dist/cli/site-utils-CfsabjUn.js +243 -0
  33. package/dist/cli/snapshots-6XE53y_F.js +874 -0
  34. package/dist/cli/sqlite-integration-H4OwSlwR.js +83 -0
  35. package/dist/cli/start-CRJqm09_.js +90 -0
  36. package/dist/cli/status-CWNHIOaY.js +44 -0
  37. package/dist/cli/status-CWWx9jYF.js +110 -0
  38. package/dist/cli/stop-CQosmjqA.js +117 -0
  39. package/dist/cli/update-BgL2HKHW.js +101 -0
  40. package/dist/cli/validation-error-DqLxqQuA.js +40 -0
  41. package/dist/cli/wordpress-server-child.js +514 -0
  42. package/dist/cli/wordpress-server-ipc-Dwsg9jSb.js +140 -0
  43. package/dist/cli/wordpress-server-manager-CtiuJqEb.js +566 -0
  44. package/dist/cli/wordpress-version-utils-B6UVeTh_.js +51 -0
  45. package/dist/cli/wp-UGSnlkN0.js +103 -0
  46. package/package.json +73 -0
  47. package/patches/@wp-playground+wordpress+3.1.12.patch +28 -0
  48. package/scripts/postinstall-npm.mjs +38 -0
@@ -0,0 +1,530 @@
1
+ import { readdir, unlink, mkdtemp, writeFile } from "fs/promises";
2
+ import { tmpdir } from "os";
3
+ import { join } from "path";
4
+ async function createLoaderMuPlugin() {
5
+ try {
6
+ const tempDir = await mkdtemp(join(tmpdir(), "studio-loader-"));
7
+ const loaderPath = join(tempDir, "99-studio-loader.php");
8
+ const loaderContent = `<?php
9
+ /**
10
+ * Studio MU-Plugins Loader
11
+ * Loads Studio-specific mu-plugins from /internal/studio/mu-plugins/
12
+ */
13
+
14
+ // Define database constants if not already defined. It fixes the error
15
+ // for imported sites that don't have those defined e.g. WP Cloud and
16
+ // include plugins which try to access those directly e.g. Mailpoet
17
+ if ( ! defined( 'DB_NAME' ) ) define( 'DB_NAME', 'database_name_here' );
18
+ if ( ! defined( 'DB_USER' ) ) define( 'DB_USER', 'username_here' );
19
+ if ( ! defined( 'DB_PASSWORD' ) ) define( 'DB_PASSWORD', 'password_here' );
20
+ if ( ! defined( 'DB_HOST' ) ) define( 'DB_HOST', 'localhost' );
21
+ if ( ! defined( 'DB_CHARSET' ) ) define( 'DB_CHARSET', 'utf8' );
22
+ if ( ! defined( 'DB_COLLATE' ) ) define( 'DB_COLLATE', '' );
23
+
24
+ // Set environment type to local if not already defined
25
+ if ( ! defined( 'WP_ENVIRONMENT_TYPE' ) ) define( 'WP_ENVIRONMENT_TYPE', 'local' );
26
+
27
+ $studio_mu_plugins_dir = '/internal/studio/mu-plugins';
28
+
29
+ if ( is_dir( $studio_mu_plugins_dir ) ) {
30
+ $files = glob( $studio_mu_plugins_dir . '/*.php' );
31
+ if ( $files ) {
32
+ // Sort files to ensure consistent loading order
33
+ sort( $files );
34
+ foreach ( $files as $file ) {
35
+ if ( is_file( $file ) ) {
36
+ require_once $file;
37
+ }
38
+ }
39
+ }
40
+ }
41
+ `;
42
+ await writeFile(loaderPath, loaderContent);
43
+ return loaderPath;
44
+ } catch (error) {
45
+ throw new Error(`Failed to create loader mu-plugin: ${error}`);
46
+ }
47
+ }
48
+ function getStandardMuPlugins(options) {
49
+ const muPlugins = [];
50
+ muPlugins.push({
51
+ filename: "0-tmp-fix-qm-plugin-sapi.php",
52
+ content: `<?php
53
+ // This is a temporary fix for a Query Manager plugin, which isn't rendered in wp-admin if sapi is "cli" (it's the case for wordpress-playground).
54
+ // See https://github.com/WordPress/wordpress-playground/pull/2424#issuecomment-3686951491
55
+ // It's not the best fix, but it's simple and for consistency it's the same as used in wordpress-playground (https://github.com/WordPress/wordpress-playground/pull/2415)
56
+ define('QM_TESTS', true);
57
+ `
58
+ });
59
+ muPlugins.push({
60
+ filename: "0-https-for-reverse-proxy.php",
61
+ content: `<?php
62
+ // See https://developer.wordpress.org/advanced-administration/security/https/#using-a-reverse-proxy
63
+ if( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false ){
64
+ $_SERVER['HTTPS'] = 'on';
65
+ }
66
+ `
67
+ });
68
+ muPlugins.push({
69
+ filename: "0-redirect-to-siteurl-constant.php",
70
+ content: `<?php
71
+ // See https://core.trac.wordpress.org/ticket/33821#comment:10
72
+ add_action( 'init', function() {
73
+ if ( ! defined( 'WP_SITEURL' ) ) {
74
+ return;
75
+ }
76
+
77
+ $current_host = $_SERVER['HTTP_HOST'] ?? '';
78
+
79
+ if ( preg_match( '/^localhost:\\d+$/', $current_host ) ) {
80
+ $wp_siteurl_host = parse_url( WP_SITEURL, PHP_URL_HOST );
81
+ $wp_siteurl_port = parse_url( WP_SITEURL, PHP_URL_PORT );
82
+ $wp_siteurl_host_with_port = $wp_siteurl_host;
83
+ if ( $wp_siteurl_port ) {
84
+ $wp_siteurl_host_with_port .= ':' . $wp_siteurl_port;
85
+ }
86
+
87
+ if ( $current_host === $wp_siteurl_host_with_port ) {
88
+ return;
89
+ }
90
+
91
+ $requested_uri = $_SERVER['REQUEST_URI'] ?? '/';
92
+ wp_redirect( rtrim( WP_SITEURL, '/' ) . $requested_uri, 302 );
93
+ exit;
94
+ }
95
+ });
96
+ `
97
+ });
98
+ muPlugins.push({
99
+ filename: "0-allowed-redirect-hosts.php",
100
+ content: `<?php
101
+ // Needed because gethostbyname( <host> ) returns
102
+ // a private network IP address for some reason.
103
+ add_filter( 'allowed_redirect_hosts', function( $hosts ) {
104
+ $redirect_hosts = array(
105
+ 'wordpress.org',
106
+ 'api.wordpress.org',
107
+ 'downloads.wordpress.org',
108
+ 'themes.svn.wordpress.org',
109
+ 'fonts.gstatic.com',
110
+ );
111
+ return array_merge( $hosts, $redirect_hosts );
112
+ } );
113
+ add_filter('http_request_host_is_external', '__return_true', 20, 3 );
114
+ `
115
+ });
116
+ muPlugins.push({
117
+ filename: "0-thumbnails.php",
118
+ content: `<?php
119
+ // Facilitates the taking of screenshots to be used as thumbnails.
120
+ if ( isset( $_GET['studio-hide-adminbar'] ) ) {
121
+ add_filter( 'show_admin_bar', '__return_false' );
122
+ }
123
+ `
124
+ });
125
+ muPlugins.push({
126
+ filename: "0-check-theme-availability.php",
127
+ content: `<?php
128
+ function check_current_theme_availability() {
129
+ // Get the current theme's directory
130
+ $current_theme = wp_get_theme();
131
+ $theme_dir = get_theme_root() . '/' . $current_theme->stylesheet;
132
+
133
+ if ( !is_dir( $theme_dir ) ) {
134
+ $all_themes = wp_get_themes();
135
+ $available_themes = [];
136
+
137
+ foreach ($all_themes as $theme_slug => $theme_obj) {
138
+ if ($theme_slug != $current_theme->get_stylesheet()) {
139
+ $available_themes[$theme_slug] = $theme_obj;
140
+ }
141
+ }
142
+
143
+ if (!empty($available_themes)) {
144
+ $new_theme_slug = array_keys($available_themes)[0];
145
+ switch_theme($new_theme_slug);
146
+ }
147
+ }
148
+ }
149
+ add_action('after_setup_theme', 'check_current_theme_availability');
150
+ `
151
+ });
152
+ muPlugins.push({
153
+ filename: "0-permalinks.php",
154
+ content: `<?php
155
+ // Support permalinks without "index.php"
156
+ add_filter( 'got_url_rewrite', '__return_true' );
157
+ `
158
+ });
159
+ muPlugins.push({
160
+ filename: "0-wp-admin-trailing-slash.php",
161
+ content: `<?php
162
+ /**
163
+ * Add trailing slash to wp-admin URLs
164
+ * This ensures /wp-admin redirects to /wp-admin/
165
+ */
166
+ add_action( 'init', function() {
167
+ $request_uri = $_SERVER['REQUEST_URI'] ?? '';
168
+
169
+ // Check if this is a wp-admin request without trailing slash
170
+ if ( $request_uri === '/wp-admin' || preg_match( '#^/wp-admin$#', $request_uri ) ) {
171
+ $redirect_url = '/wp-admin/';
172
+
173
+ // Preserve query string if present
174
+ if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
175
+ $redirect_url .= '?' . $_SERVER['QUERY_STRING'];
176
+ }
177
+
178
+ wp_redirect( $redirect_url, 301 );
179
+ exit;
180
+ }
181
+ }, 1 );
182
+ `
183
+ });
184
+ muPlugins.push({
185
+ filename: "0-deactivate-jetpack-modules.php",
186
+ content: `<?php
187
+ // Disable Jetpack Protect 2FA for local auto-login purpose
188
+ add_action( 'jetpack_active_modules', 'jetpack_deactivate_modules' );
189
+ function jetpack_deactivate_modules( $active ) {
190
+ if ( ( $index = array_search('protect', $active, true) ) !== false ) {
191
+ unset( $active[ $index ] );
192
+ }
193
+ return $active;
194
+ }
195
+ `
196
+ });
197
+ muPlugins.push({
198
+ filename: "0-suppress-dns-get-record-warnings.php",
199
+ content: `<?php
200
+ set_error_handler(function($severity, $message, $file, $line) {
201
+ if ($severity === E_WARNING && strpos($message, "dns_get_record(): dns_get_record() always returns an empty array in PHP.wasm.") === 0) {
202
+ return true;
203
+ }
204
+ return false;
205
+ });
206
+ `
207
+ });
208
+ if (!options.isWpAutoUpdating) {
209
+ muPlugins.push({
210
+ filename: "0-disable-auto-updates.php",
211
+ content: `<?php
212
+ // Disable auto-updates
213
+ add_filter( 'allow_dev_auto_core_updates', '__return_false' );
214
+ add_filter( 'allow_minor_auto_core_updates', '__return_false' );
215
+ add_filter( 'allow_major_auto_core_updates', '__return_false' );
216
+ `
217
+ });
218
+ } else {
219
+ muPlugins.push({
220
+ filename: "0-enable-auto-updates.php",
221
+ content: `<?php
222
+ // Enable auto-updates
223
+ add_filter( 'allow_dev_auto_core_updates', '__return_true' );
224
+ add_filter( 'allow_minor_auto_core_updates', '__return_true' );
225
+ add_filter( 'allow_major_auto_core_updates', '__return_true' );
226
+ `
227
+ });
228
+ }
229
+ muPlugins.push({
230
+ filename: "0-http-request-timeout.php",
231
+ content: `<?php
232
+ // Use low-speed timeout instead of hard timeout to handle both large downloads and stalled connections
233
+ // - Allows large plugin downloads (e.g., Jetpack 33MB) to complete with reasonable internet speeds
234
+ // - Fails fast if connection stalls (speed drops below 1KB/s for 30 seconds)
235
+ // - Provides quick feedback for genuinely broken/unresponsive servers
236
+ // Match WordPress core's timeout for plugin downloads (300s)
237
+ add_filter( 'http_request_timeout', function() {
238
+ return 300; // 5 minutes - matches WordPress core, low-speed timeout catches stalls
239
+ } );
240
+
241
+ add_action('http_api_curl', function($curl, $url, $options) {
242
+ // Abort if connection can't be established within 30 seconds
243
+ curl_setopt( $curl, CURLOPT_CONNECTTIMEOUT, 30 );
244
+
245
+ // Abort if speed drops below 1KB/s for 30 consecutive seconds
246
+ // This allows slow but steady downloads while catching truly stalled connections
247
+ curl_setopt( $curl, CURLOPT_LOW_SPEED_LIMIT, 1024 ); // 1KB/s minimum
248
+ curl_setopt( $curl, CURLOPT_LOW_SPEED_TIME, 30 ); // Must stay above limit for 30s
249
+ return $curl;
250
+ }, 1, 3);
251
+ `
252
+ });
253
+ muPlugins.push({
254
+ filename: "0-tmp-fix-hide-plugins-spinner.php",
255
+ content: `<?php
256
+ // This is a temporary fix for a page-optimize bug that causes spinner icons to show all the time in the plugins list auto-update column
257
+
258
+ add_action( 'admin_enqueue_scripts', 'studio_patch_auto_update_spinner_style', 999 );
259
+ function studio_patch_auto_update_spinner_style() {
260
+ $current_screen = get_current_screen();
261
+ if ( isset( $current_screen->id ) && 'plugins' === $current_screen->id ) {
262
+ wp_add_inline_style(
263
+ 'dashicons',
264
+ '.toggle-auto-update .dashicons.hidden { display: none; }'
265
+ );
266
+ }
267
+ }
268
+ `
269
+ });
270
+ muPlugins.push({
271
+ filename: "0-sqlite-command.php",
272
+ content: `<?php
273
+ // Ensure SQLite command can find the plugin
274
+ add_filter( 'sqlite_command_sqlite_plugin_directories', function( $directories ) {
275
+ $directories[] = '/wordpress/wp-content/mu-plugins/sqlite-database-integration';
276
+ return $directories;
277
+ } );
278
+ `
279
+ });
280
+ muPlugins.push({
281
+ filename: "0-studio-cli-commands.php",
282
+ content: `<?php
283
+ /**
284
+ * Studio WP-CLI Commands
285
+ *
286
+ * Provides custom WP-CLI commands for Studio functionality.
287
+ */
288
+ if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
289
+ return;
290
+ }
291
+
292
+ /**
293
+ * Gets theme details for the current site.
294
+ *
295
+ * ## EXAMPLES
296
+ *
297
+ * wp studio get-theme-details
298
+ *
299
+ * @when after_wp_load
300
+ */
301
+ WP_CLI::add_command( 'studio get-theme-details', function() {
302
+ $theme = wp_get_theme();
303
+ $result = [
304
+ 'name' => $theme->get( 'Name' ),
305
+ 'path' => $theme->get_stylesheet_directory(),
306
+ 'slug' => $theme->get_stylesheet(),
307
+ 'isBlockTheme' => $theme->is_block_theme(),
308
+ 'supportsWidgets' => current_theme_supports( 'widgets' ),
309
+ 'supportsMenus' => (bool) ( get_registered_nav_menus() || current_theme_supports( 'menus' ) ),
310
+ ];
311
+ echo json_encode( $result );
312
+ } );
313
+ `
314
+ });
315
+ muPlugins.push({
316
+ filename: "0-studio-admin-api.php",
317
+ content: `<?php
318
+ /**
319
+ * Studio Admin API
320
+ *
321
+ * Provides a persistent endpoint for admin operations that can reuse
322
+ * the already-loaded WordPress instance, avoiding the overhead of
323
+ * loading wp-load.php multiple times.
324
+ *
325
+ * This endpoint should only be accessible locally.
326
+ */
327
+
328
+ // Check if this is an API request before WordPress routing
329
+ $is_api_request = isset( $_GET['studio-admin-api'] ) ||
330
+ strpos( $_SERVER['REQUEST_URI'] ?? '', 'studio-admin-api' ) !== false;
331
+
332
+ if ( ! $is_api_request ) {
333
+ return;
334
+ }
335
+
336
+ add_action( 'plugins_loaded', function() {
337
+
338
+ // Security: Only allow POST requests with the correct action
339
+ if ( $_SERVER['REQUEST_METHOD'] !== 'POST' || empty( $_POST['action'] ) ) {
340
+ status_header( 400 );
341
+ header( 'Content-Type: application/json' );
342
+ echo json_encode( [ 'error' => 'Invalid request' ] );
343
+ exit;
344
+ }
345
+
346
+ $action = $_POST['action'];
347
+ $result = null;
348
+
349
+ switch ( $action ) {
350
+ case 'set_admin_password':
351
+ $has_password = ! empty( $_POST['password'] );
352
+ $username = ! empty( $_POST['username'] ) ? sanitize_user( $_POST['username'] ) : 'admin';
353
+ // Fallback to 'admin' if sanitize_user() strips all characters (e.g., !@#$%)
354
+ if ( empty( $username ) ) {
355
+ $username = 'admin';
356
+ }
357
+
358
+ $user = get_user_by( 'login', $username );
359
+ $provided_email = ! empty( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : '';
360
+
361
+ if ( $user ) {
362
+ if ( $has_password ) {
363
+ wp_set_password( $_POST['password'], $user->ID );
364
+ }
365
+ if ( $provided_email ) {
366
+ wp_update_user( array( 'ID' => $user->ID, 'user_email' => $provided_email ) );
367
+ }
368
+ } else {
369
+ // Creating a new user requires a password
370
+ if ( ! $has_password ) {
371
+ status_header( 400 );
372
+ header( 'Content-Type: application/json' );
373
+ echo json_encode( [ 'error' => 'Password is required to create a new admin user' ] );
374
+ exit;
375
+ }
376
+ // WordPress doesn't support renaming user_login, so we create a new admin user.
377
+ // The old user is left intact — this is intentional.
378
+ // Generate a unique email to avoid conflicts with existing users
379
+ $email = $provided_email ? $provided_email : 'admin@localhost.com';
380
+ if ( ! $provided_email ) {
381
+ $counter = 1;
382
+ while ( email_exists( $email ) && $counter < 100 ) {
383
+ $email = 'admin' . $counter . '@localhost.com';
384
+ $counter++;
385
+ }
386
+ }
387
+
388
+ $user_data = array(
389
+ 'user_login' => $username,
390
+ 'user_pass' => $_POST['password'],
391
+ 'user_email' => $email,
392
+ 'role' => 'administrator',
393
+ );
394
+ $insert_result = wp_insert_user( $user_data );
395
+ if ( is_wp_error( $insert_result ) ) {
396
+ status_header( 400 );
397
+ header( 'Content-Type: application/json' );
398
+ echo json_encode( [ 'error' => $insert_result->get_error_message() ] );
399
+ exit;
400
+ }
401
+ }
402
+ // Store the admin username in options for auto-login to use
403
+ update_option( 'studio_admin_username', $username );
404
+ $result = [ 'success' => true ];
405
+ break;
406
+
407
+ default:
408
+ status_header( 400 );
409
+ header( 'Content-Type: application/json' );
410
+ echo json_encode( [ 'error' => 'Unknown action' ] );
411
+ exit;
412
+ }
413
+
414
+ status_header( 200 );
415
+ header( 'Content-Type: application/json' );
416
+ echo json_encode( $result );
417
+ exit;
418
+ }, 1 );
419
+ `
420
+ });
421
+ muPlugins.push({
422
+ filename: "0-auto-login.php",
423
+ content: `<?php
424
+ /**
425
+ * Auto-Login Endpoint
426
+ *
427
+ * Provides /studio-auto-login endpoint for automatic authentication
428
+ * Usage: /studio-auto-login?redirect_to=/wp-admin/
429
+ */
430
+
431
+ // Intercept requests to /studio-auto-login
432
+ add_action( 'init', function() {
433
+ $request_uri = $_SERVER['REQUEST_URI'] ?? '';
434
+ if ( strpos( $request_uri, '/studio-auto-login' ) === false ) {
435
+ return;
436
+ }
437
+
438
+ if ( is_user_logged_in() ) {
439
+ $redirect_url = isset( $_GET['redirect_to'] ) ? $_GET['redirect_to'] : home_url();
440
+ wp_safe_redirect( $redirect_url );
441
+ exit;
442
+ }
443
+
444
+ $username = get_option( 'studio_admin_username', 'admin' );
445
+ $user = get_user_by( 'login', $username );
446
+ if ( ! $user ) {
447
+ wp_die( 'Auto-login failed: admin user not found' );
448
+ }
449
+
450
+ wp_set_current_user( $user->ID, $user->user_login );
451
+ wp_set_auth_cookie( $user->ID, true, is_ssl() );
452
+ do_action( 'wp_login', $user->user_login, $user );
453
+ $redirect_url = isset( $_GET['redirect_to'] ) ? $_GET['redirect_to'] : home_url();
454
+ wp_safe_redirect( $redirect_url );
455
+ exit;
456
+
457
+ }, 1 );
458
+ `
459
+ });
460
+ return muPlugins;
461
+ }
462
+ async function createMuPluginsDirectory(options) {
463
+ try {
464
+ const tempDir = await mkdtemp(join(tmpdir(), "studio-mu-plugins-"));
465
+ const muPlugins = getStandardMuPlugins({
466
+ isWpAutoUpdating: options.isWpAutoUpdating
467
+ });
468
+ for (const plugin of muPlugins) {
469
+ const pluginPath = join(tempDir, plugin.filename);
470
+ await writeFile(pluginPath, plugin.content);
471
+ }
472
+ return tempDir;
473
+ } catch (error) {
474
+ throw new Error(`Failed to create mu-plugins directory: ${error}`);
475
+ }
476
+ }
477
+ async function getMuPlugins(options) {
478
+ const studioMuPluginsHostPath = await createMuPluginsDirectory(options);
479
+ const loaderMuPluginHostPath = await createLoaderMuPlugin();
480
+ return [studioMuPluginsHostPath, loaderMuPluginHostPath];
481
+ }
482
+ const LEGACY_MU_PLUGIN_FILENAMES = [
483
+ // Current mu-plugins (from getStandardMuPlugins)
484
+ "0-allowed-redirect-hosts.php",
485
+ "0-auto-login.php",
486
+ "0-check-theme-availability.php",
487
+ "0-deactivate-jetpack-modules.php",
488
+ "0-disable-auto-updates.php",
489
+ "0-enable-auto-updates.php",
490
+ "0-http-request-timeout.php",
491
+ "0-https-for-reverse-proxy.php",
492
+ "0-permalinks.php",
493
+ "0-redirect-to-siteurl-constant.php",
494
+ "0-sqlite-command.php",
495
+ "0-studio-admin-api.php",
496
+ "0-studio-cli-commands.php",
497
+ "0-suppress-dns-get-record-warnings.php",
498
+ "0-thumbnails.php",
499
+ "0-tmp-fix-hide-plugins-spinner.php",
500
+ "0-tmp-fix-qm-plugin-sapi.php",
501
+ "0-wp-admin-trailing-slash.php",
502
+ // Retired mu-plugins from older Studio versions
503
+ "0-32bit-integer-warnings.php",
504
+ "0-dns-functions.php",
505
+ "0-sqlite.php",
506
+ "0-wp-config-constants-polyfill.php"
507
+ ];
508
+ async function cleanupLegacyMuPlugins(sitePath) {
509
+ const muPluginsDir = join(sitePath, "wp-content", "mu-plugins");
510
+ let entries;
511
+ try {
512
+ entries = await readdir(muPluginsDir);
513
+ } catch {
514
+ return;
515
+ }
516
+ const legacySet = new Set(LEGACY_MU_PLUGIN_FILENAMES);
517
+ const filesToRemove = entries.filter((name) => legacySet.has(name));
518
+ await Promise.all(
519
+ filesToRemove.map(async (name) => {
520
+ try {
521
+ await unlink(join(muPluginsDir, name));
522
+ } catch {
523
+ }
524
+ })
525
+ );
526
+ }
527
+ export {
528
+ cleanupLegacyMuPlugins as c,
529
+ getMuPlugins as g
530
+ };
@@ -0,0 +1,80 @@
1
+ import { __ } from "@wordpress/i18n";
2
+ var cjs = {};
3
+ var hasRequiredCjs;
4
+ function requireCjs() {
5
+ if (hasRequiredCjs) return cjs;
6
+ hasRequiredCjs = 1;
7
+ (function(exports$1) {
8
+ Object.defineProperty(exports$1, "__esModule", { value: true });
9
+ exports$1.generatePassword = exports$1.EXTRA_SPECIAL_CHARS = exports$1.SPECIAL_CHARS = exports$1.DIGITS = exports$1.ALPHABET = void 0;
10
+ exports$1.ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
11
+ exports$1.DIGITS = "0123456789";
12
+ exports$1.SPECIAL_CHARS = "!@#$%^&*()";
13
+ exports$1.EXTRA_SPECIAL_CHARS = "-_ []{}<>~`+=,.;:/?|";
14
+ function generatePassword({ length = 24, useNumbers = true, useSpecialChars = true, useExtraSpecialChars = false } = {}) {
15
+ let characterPool = exports$1.ALPHABET;
16
+ if (useNumbers) {
17
+ characterPool += exports$1.DIGITS;
18
+ }
19
+ if (useSpecialChars) {
20
+ characterPool += exports$1.SPECIAL_CHARS;
21
+ }
22
+ if (useExtraSpecialChars) {
23
+ characterPool += exports$1.EXTRA_SPECIAL_CHARS;
24
+ }
25
+ const randomNumber = new Uint8Array(1);
26
+ let password = "";
27
+ for (let i = 0; i < length; i++) {
28
+ do {
29
+ globalThis.crypto.getRandomValues(randomNumber);
30
+ } while (randomNumber[0] >= characterPool.length);
31
+ password += characterPool[randomNumber[0]];
32
+ }
33
+ return password;
34
+ }
35
+ exports$1.generatePassword = generatePassword;
36
+ })(cjs);
37
+ return cjs;
38
+ }
39
+ var cjsExports = /* @__PURE__ */ requireCjs();
40
+ function createPassword() {
41
+ return encodePassword(cjsExports.generatePassword());
42
+ }
43
+ function encodePassword(password) {
44
+ const bytes = new TextEncoder().encode(password);
45
+ const binary = String.fromCharCode(...bytes);
46
+ return btoa(binary);
47
+ }
48
+ function decodePassword(encodedPassword) {
49
+ const binary = atob(encodedPassword);
50
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
51
+ return new TextDecoder().decode(bytes);
52
+ }
53
+ function validateAdminEmail(email) {
54
+ if (!email.trim()) {
55
+ return __("Admin email cannot be empty.");
56
+ }
57
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
58
+ return __("Please enter a valid email address.");
59
+ }
60
+ return "";
61
+ }
62
+ function validateAdminUsername(username) {
63
+ if (!username.trim()) {
64
+ return __("Admin username cannot be empty.");
65
+ }
66
+ if (!/^[a-zA-Z0-9_.@-]+$/.test(username)) {
67
+ return __("Username can only contain letters, numbers, and _.@- characters.");
68
+ }
69
+ if (username.length > 60) {
70
+ return __("Username must be 60 characters or fewer.");
71
+ }
72
+ return "";
73
+ }
74
+ export {
75
+ validateAdminEmail as a,
76
+ createPassword as c,
77
+ decodePassword as d,
78
+ encodePassword as e,
79
+ validateAdminUsername as v
80
+ };