wp-studio 1.7.10 → 1.7.11

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 (45) hide show
  1. package/dist/cli/{_events-BcapW3eh.mjs → _events-B8xQ_baD.mjs} +4 -5
  2. package/dist/cli/appdata-D-luHxJU.mjs +19 -0
  3. package/dist/cli/{certificate-manager-SVYcCL_i.mjs → certificate-manager-v-yNLDFJ.mjs} +134 -15
  4. package/dist/cli/{delete-D1924O3o.mjs → delete-BG-E-HsW.mjs} +3 -3
  5. package/dist/cli/{helpers-oQuItT8n.mjs → helpers-CIAgfdq8.mjs} +2 -2
  6. package/dist/cli/{index-4lan3TI_.mjs → index-Bej4fL6n.mjs} +31 -4
  7. package/dist/cli/{index-BjzOJKPi.mjs → index-Dhun0W1n.mjs} +90 -52
  8. package/dist/cli/{index-DRQnCQvM.mjs → index-ul3DeWvy.mjs} +1308 -1109
  9. package/dist/cli/{list-DOFyyV1f.mjs → list-ck0oK4vb.mjs} +3 -3
  10. package/dist/cli/{login-BtPZeZ4G.mjs → login-Dz63Zdfn.mjs} +3 -3
  11. package/dist/cli/{logout-Cr631QzG.mjs → logout-CLUKQeZh.mjs} +2 -3
  12. package/dist/cli/main.mjs +2 -2
  13. package/dist/cli/{paths-CqXGLB7R.mjs → paths-D7DniT1Q.mjs} +7 -6
  14. package/dist/cli/plugin/skills/rank-me-up/SKILL.md +166 -0
  15. package/dist/cli/plugin/skills/site-spec/SKILL.md +4 -0
  16. package/dist/cli/process-manager-daemon.mjs +29 -5
  17. package/dist/cli/{process-manager-ipc-BisO0qtU.mjs → process-manager-ipc-GCdebuBH.mjs} +4 -1
  18. package/dist/cli/proxy-daemon.mjs +1 -1
  19. package/dist/cli/{prune-pm-logs-COryxqeo.mjs → prune-pm-logs-Dm_Bwi7l.mjs} +1 -1
  20. package/dist/cli/{resume-BwDwdJtq.mjs → resume-BSIOJnyM.mjs} +4 -15
  21. package/dist/cli/{rewrite-wp-cli-post-content-2zlfFnKT.mjs → rewrite-wp-cli-post-content-Beo5_Ojo.mjs} +32 -531
  22. package/dist/cli/{set-D5eeqHbp.mjs → set-CtDZnARG.mjs} +2 -3
  23. package/dist/cli/{set-DYnzUz_G.mjs → set-PJvs-Yw5.mjs} +4 -5
  24. package/dist/cli/{status-DNvMZBqD.mjs → status-DU07aAtD.mjs} +2 -2
  25. package/dist/cli/well-known-paths-QcSJNi_l.mjs +95 -0
  26. package/dist/cli/wordpress-server-child.mjs +5 -3
  27. package/dist/cli/{wp-DD2-QiiP.mjs → wp-_X-h-yuW.mjs} +2 -2
  28. package/dist/cli/wp-files/latest/available-site-translations.json +1 -1
  29. package/dist/cli/wp-files/sqlite-database-integration/admin-page.php +1 -2
  30. package/dist/cli/wp-files/sqlite-database-integration/constants.php +0 -5
  31. package/dist/cli/wp-files/sqlite-database-integration/load.php +1 -1
  32. package/dist/cli/wp-files/sqlite-database-integration/readme.txt +6 -3
  33. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/database/sqlite/class-wp-pdo-mysql-on-sqlite.php +22 -3
  34. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/database/version.php +1 -1
  35. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php +41 -89
  36. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/db.php +2 -24
  37. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/install-functions.php +7 -16
  38. package/package.json +2 -1
  39. package/dist/cli/well-known-paths-BYA1Bw5o.mjs +0 -214
  40. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-lexer.php +0 -2575
  41. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php +0 -899
  42. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-query-rewriter.php +0 -343
  43. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-token.php +0 -327
  44. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-translator.php +0 -4543
  45. package/dist/cli/wp-files/sqlite-database-integration/wp-includes/sqlite/php-polyfills.php +0 -68
@@ -49,17 +49,18 @@ import semver from "semver";
49
49
  import yargs from "yargs";
50
50
  import * as path from "path";
51
51
  import path__default from "path";
52
- import { L as LoggerError, a as LOCKFILE_STALE_TIME, b as LOCKFILE_WAIT_TIME, g as getDefaultExportFromCjs, c as LatestSupportedPHPVersion, D as DEFAULT_PHP_VERSION, A as ARCHIVER_OPTIONS, d as Logger, S as SupportedPHPVersionsList, P as PLAYGROUND_CLI_MAX_TIMEOUT, e as PLAYGROUND_CLI_INACTIVITY_TIMEOUT, f as PLAYGROUND_CLI_ACTIVITY_CHECK_INTERVAL, h as getServerFilesPath, i as commonjsGlobal, j as getSharedConfigPath, k as getConfigDirectory, l as SHARED_CONFIG_LOCKFILE_NAME, m as DEMO_SITE_EXPIRATION_DAYS, H as HOUR_MS, n as DAY_MS, o as DEMO_SITE_SIZE_LIMIT_BYTES, p as DEMO_SITE_SIZE_LIMIT_GB, M as MINIMUM_WORDPRESS_VERSION, q as DEFAULT_WORDPRESS_VERSION$1, r as emitProgress, s as setProgressCallback, t as getProgressCallback, u as getCliConfigPath } from "./well-known-paths-BYA1Bw5o.mjs";
52
+ import { L as LOCKFILE_STALE_TIME, a as LOCKFILE_WAIT_TIME, b as LatestSupportedPHPVersion, D as DEFAULT_PHP_VERSION, A as ARCHIVER_OPTIONS, S as SupportedPHPVersionsList, P as PLAYGROUND_CLI_MAX_TIMEOUT, c as PLAYGROUND_CLI_INACTIVITY_TIMEOUT, d as PLAYGROUND_CLI_ACTIVITY_CHECK_INTERVAL, g as getServerFilesPath, e as getSharedConfigPath, f as getConfigDirectory, h as SHARED_CONFIG_LOCKFILE_NAME, i as DEMO_SITE_EXPIRATION_DAYS, H as HOUR_MS, j as DAY_MS, k as DEMO_SITE_SIZE_LIMIT_BYTES, l as DEMO_SITE_SIZE_LIMIT_GB, M as MINIMUM_WORDPRESS_VERSION, m as DEFAULT_WORDPRESS_VERSION$1, n as getCliConfigPath } from "./well-known-paths-QcSJNi_l.mjs";
53
+ import * as fs from "fs";
54
+ import fs__default, { promises, constants, existsSync } from "fs";
55
+ import ignore from "ignore";
56
+ import { D as DEPLOY_IGNORE_DEFAULTS, a as arePathsEqual, i as isWordPressDirectory, g as getWordPressVersionPath, r as recursiveCopyDirectory, b as getWordPressVersionUrl, I as IS_JSPI_AVAILABLE, c as cleanupLegacyMuPlugins, d as getMuPlugins, e as getWpCliPharPath, f as getSqliteCommandPath, k as keepSqliteIntegrationUpdated, h as calculateDirectorySizeForArchive, p as pathExists, j as isWordPressBetaVersion, l as isWordPressDevVersion, m as getWpFilesPath, n as getLanguagePacksPath, o as isEmptyDir, q as decodePassword, s as getAiInstructionsPath, v as validateAdminUsername, t as validateAdminEmail, u as encodePassword, w as createPassword, x as isValidWordPressVersion, y as isWordPressVersionAtLeast, z as getUnsupportedWpCliPostContentMessage } from "./rewrite-wp-cli-post-content-Beo5_Ojo.mjs";
57
+ import { i as isErrnoException } from "./wordpress-server-ipc-D-8mLjOF.mjs";
53
58
  import { z } from "zod";
54
- import { a as arePathsEqual, i as isWordPressDirectory, g as getWordPressVersionPath, r as recursiveCopyDirectory, b as getWordPressVersionUrl, I as IS_JSPI_AVAILABLE, c as cleanupLegacyMuPlugins, d as getMuPlugins, e as getWpCliPharPath, f as getSqliteCommandPath, k as keepSqliteIntegrationUpdated, h as createDeployIgnoreFilter, j as calculateDirectorySizeForArchive, p as pathExists, l as isWordPressBetaVersion, m as isWordPressDevVersion, n as getWpFilesPath, o as getSqlitePluginPath, q as getLanguagePacksPath, s as getAiInstructionsPath, t as getPhpMyAdminPath, u as getSqliteVersionFromInstallation, v as isEmptyDir, w as decodePassword, x as validateAdminUsername, y as validateAdminEmail, z as encodePassword, A as createPassword, B as isValidWordPressVersion, C as isWordPressVersionAtLeast, D as getUnsupportedWpCliPostContentMessage } from "./rewrite-wp-cli-post-content-2zlfFnKT.mjs";
55
- import { l as lockCliConfig, r as readCliConfig, s as saveCliConfig, u as unlockCliConfig, a as lockFileAsync, b as unlockFileAsync, c as authTokenSchema, h as hideDirectoryOnWindows, S as SNAPSHOT_EVENTS, d as requireSignalExit, e as StatsMetric, f as StatsGroup, g as updateCliConfigWithPartial, i as generateSiteCertificate, j as SITE_EVENTS, k as deleteSiteCertificate, m as updateCheckSchema } from "./certificate-manager-SVYcCL_i.mjs";
56
- import require$$0$7, { spawn } from "child_process";
59
+ import { l as lockCliConfig, r as readCliConfig, L as LoggerError, s as saveCliConfig, u as unlockCliConfig, a as lockFileAsync, b as unlockFileAsync, c as Logger, S as SITE_EVENTS, g as getDefaultExportFromCjs, d as commonjsGlobal, e as authTokenSchema, h as hideDirectoryOnWindows, f as SNAPSHOT_EVENTS, i as requireSignalExit, j as StatsMetric, k as StatsGroup, m as updateCliConfigWithPartial, n as generateSiteCertificate, o as deleteSiteCertificate, p as emitProgress, q as setProgressCallback, t as getProgressCallback, v as updateCheckSchema } from "./certificate-manager-v-yNLDFJ.mjs";
60
+ import require$$0$7, { spawn, execFile } from "child_process";
57
61
  import crypto$1 from "crypto";
58
62
  import { EventEmitter } from "events";
59
- import * as fs from "fs";
60
- import fs__default, { constants } from "fs";
61
- import { i as isErrnoException } from "./wordpress-server-ipc-D-8mLjOF.mjs";
62
- import { P as PROCESS_MANAGER_HOME, p as processDescriptionSchema, S as SocketRequestClient, a as PROCESS_MANAGER_EVENTS_SOCKET_PATH, b as PROCESS_MANAGER_CONTROL_SOCKET_PATH, d as daemonResponseSchema, c as SocketMessageDecoder, e as SocketStreamClient, f as daemonEventSchema } from "./process-manager-ipc-BisO0qtU.mjs";
63
+ import { P as PROCESS_MANAGER_HOME, p as processDescriptionSchema, S as SocketRequestClient, a as PROCESS_MANAGER_EVENTS_SOCKET_PATH, b as PROCESS_MANAGER_CONTROL_SOCKET_PATH, d as daemonResponseSchema, c as SocketMessageDecoder, e as SocketStreamClient, f as daemonEventSchema } from "./process-manager-ipc-GCdebuBH.mjs";
63
64
  import fs$1, { readFile as readFile$2, cp } from "fs/promises";
64
65
  import os, { tmpdir, platform } from "os";
65
66
  import archiver from "archiver";
@@ -81,12 +82,12 @@ import trash from "trash";
81
82
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
82
83
  import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
83
84
  import { z as z$1 } from "zod/v4";
85
+ import require$$2, { fileURLToPath } from "url";
84
86
  import require$$0$1 from "tty";
85
87
  import require$$1$1 from "constants";
86
88
  import require$$0$2 from "assert";
87
89
  import require$$0$3 from "http";
88
90
  import require$$1$3 from "https";
89
- import require$$2 from "url";
90
91
  import fs$2 from "node:fs";
91
92
  import crypto$2 from "node:crypto";
92
93
  import require$$2$1 from "node:events";
@@ -122,6 +123,20 @@ function suppressPunycodeWarning() {
122
123
  }
123
124
  });
124
125
  }
126
+ const DEPLOY_IGNORE_FILENAME = ".deployignore";
127
+ async function createDeployIgnoreFilter(rootPath, basePatterns = DEPLOY_IGNORE_DEFAULTS) {
128
+ const ig = ignore().add(basePatterns);
129
+ const deployIgnorePath = path__default.join(rootPath, DEPLOY_IGNORE_FILENAME);
130
+ try {
131
+ const content = await promises.readFile(deployIgnorePath, "utf-8");
132
+ ig.add(content);
133
+ } catch (error2) {
134
+ if (!isErrnoException(error2) || error2.code !== "ENOENT") {
135
+ console.warn(`Failed to read ${deployIgnorePath}:`, error2);
136
+ }
137
+ }
138
+ return ig;
139
+ }
125
140
  const BackupExtractEvents = {
126
141
  BACKUP_EXTRACT_START: "backup_extract_start",
127
142
  BACKUP_EXTRACT_PROGRESS: "backup_extract_progress",
@@ -153,6 +168,13 @@ const ImportEvents = {
153
168
  ...ValidatorEvents,
154
169
  ...ImporterEvents
155
170
  };
171
+ const importerTypeSchema = z.union([
172
+ z.literal("jetpack"),
173
+ z.literal("local"),
174
+ z.literal("playground"),
175
+ z.literal("sql"),
176
+ z.literal("wpress")
177
+ ]);
156
178
  const backupExtractProgressEventDataSchema = z.object({
157
179
  progress: z.number().optional(),
158
180
  processedFiles: z.number().optional(),
@@ -177,10 +199,11 @@ const importWpContentProgressEventDataSchema = z.object({
177
199
  processedBytes: z.number().optional(),
178
200
  totalBytes: z.number().optional()
179
201
  });
180
- z.union([
202
+ const nullOrUndefined = z.union([z.undefined(), z.null()]);
203
+ const importEventTupleSchema = z.union([
181
204
  z.tuple([
182
205
  z.literal(BackupExtractEvents.BACKUP_EXTRACT_START),
183
- backupExtractProgressEventDataSchema.or(z.undefined())
206
+ backupExtractProgressEventDataSchema.or(nullOrUndefined)
184
207
  ]),
185
208
  z.tuple([
186
209
  z.literal(BackupExtractEvents.BACKUP_EXTRACT_PROGRESS),
@@ -192,31 +215,32 @@ z.union([
192
215
  ]),
193
216
  z.tuple([
194
217
  z.literal(BackupExtractEvents.BACKUP_EXTRACT_COMPLETE),
195
- backupExtractProgressEventDataSchema.or(z.undefined())
218
+ backupExtractProgressEventDataSchema.or(nullOrUndefined)
196
219
  ]),
197
220
  z.tuple([z.literal(BackupExtractEvents.BACKUP_EXTRACT_WARNING), z.string()]),
198
221
  z.tuple([z.literal(BackupExtractEvents.BACKUP_EXTRACT_ERROR), z.unknown()]),
199
- z.tuple([z.literal(ValidatorEvents.IMPORT_VALIDATION_START), z.undefined()]),
200
- z.tuple([z.literal(ValidatorEvents.IMPORT_VALIDATION_COMPLETE), z.undefined()]),
222
+ z.tuple([z.literal(ValidatorEvents.IMPORT_VALIDATION_START), nullOrUndefined]),
223
+ z.tuple([z.literal(ValidatorEvents.IMPORT_VALIDATION_COMPLETE), nullOrUndefined]),
201
224
  z.tuple([z.literal(ValidatorEvents.IMPORT_VALIDATION_ERROR), z.unknown()]),
202
- z.tuple([z.literal(ImporterEvents.IMPORT_START), z.undefined()]),
203
- z.tuple([z.literal(ImporterEvents.IMPORT_DATABASE_START), z.undefined()]),
225
+ z.tuple([z.literal(ImporterEvents.IMPORT_START), importerTypeSchema]),
226
+ z.tuple([z.literal(ImporterEvents.IMPORT_DATABASE_START), nullOrUndefined]),
204
227
  z.tuple([
205
228
  z.literal(ImporterEvents.IMPORT_DATABASE_PROGRESS),
206
229
  importDatabaseProgressEventDataSchema
207
230
  ]),
208
- z.tuple([z.literal(ImporterEvents.IMPORT_DATABASE_COMPLETE), z.undefined()]),
209
- z.tuple([z.literal(ImporterEvents.IMPORT_WP_CONTENT_START), z.undefined()]),
231
+ z.tuple([z.literal(ImporterEvents.IMPORT_DATABASE_COMPLETE), nullOrUndefined]),
232
+ z.tuple([z.literal(ImporterEvents.IMPORT_WP_CONTENT_START), nullOrUndefined]),
210
233
  z.tuple([
211
234
  z.literal(ImporterEvents.IMPORT_WP_CONTENT_PROGRESS),
212
235
  importWpContentProgressEventDataSchema
213
236
  ]),
214
- z.tuple([z.literal(ImporterEvents.IMPORT_WP_CONTENT_COMPLETE), z.undefined()]),
215
- z.tuple([z.literal(ImporterEvents.IMPORT_META_START), z.undefined()]),
216
- z.tuple([z.literal(ImporterEvents.IMPORT_META_COMPLETE), z.undefined()]),
217
- z.tuple([z.literal(ImporterEvents.IMPORT_COMPLETE), z.undefined()]),
218
- z.tuple([z.literal(ImporterEvents.IMPORT_ERROR), z.unknown()])
237
+ z.tuple([z.literal(ImporterEvents.IMPORT_WP_CONTENT_COMPLETE), nullOrUndefined]),
238
+ z.tuple([z.literal(ImporterEvents.IMPORT_META_START), nullOrUndefined]),
239
+ z.tuple([z.literal(ImporterEvents.IMPORT_META_COMPLETE), nullOrUndefined]),
240
+ z.tuple([z.literal(ImporterEvents.IMPORT_COMPLETE), importerTypeSchema]),
241
+ z.tuple([z.literal(ImporterEvents.IMPORT_ERROR), z.string()])
219
242
  ]);
243
+ z.object({ event: importEventTupleSchema });
220
244
  const ExportEvents = {
221
245
  EXPORT_START: "export_start",
222
246
  EXPORT_COMPLETE: "export_complete",
@@ -246,25 +270,39 @@ const backupCreateProgressEventDataSchema = z.object({
246
270
  })
247
271
  })
248
272
  });
249
- z.union([
250
- z.tuple([z.literal(ExportEvents.EXPORT_START), z.undefined()]),
251
- z.tuple([z.literal(ExportEvents.EXPORT_COMPLETE), z.undefined()]),
273
+ const exportEventTupleSchema = z.union([
274
+ z.tuple([z.literal(ExportEvents.EXPORT_START), nullOrUndefined]),
275
+ z.tuple([z.literal(ExportEvents.EXPORT_COMPLETE), nullOrUndefined]),
252
276
  z.tuple([z.literal(ExportEvents.EXPORT_ERROR), z.unknown().nullable()]),
253
- z.tuple([z.literal(ExportEvents.BACKUP_CREATE_START), z.undefined()]),
277
+ z.tuple([z.literal(ExportEvents.BACKUP_CREATE_START), nullOrUndefined]),
254
278
  z.tuple([
255
279
  z.literal(ExportEvents.BACKUP_CREATE_PROGRESS),
256
280
  backupCreateProgressEventDataSchema
257
281
  ]),
258
- z.tuple([z.literal(ExportEvents.BACKUP_CREATE_COMPLETE), z.undefined()]),
259
- z.tuple([z.literal(ExportEvents.WP_CONTENT_EXPORT_START), z.undefined()]),
260
- z.tuple([z.literal(ExportEvents.WP_CONTENT_EXPORT_PROGRESS), z.undefined()]),
261
- z.tuple([z.literal(ExportEvents.WP_CONTENT_EXPORT_COMPLETE), z.undefined()]),
262
- z.tuple([z.literal(ExportEvents.DATABASE_EXPORT_START), z.undefined()]),
263
- z.tuple([z.literal(ExportEvents.DATABASE_EXPORT_PROGRESS), z.undefined()]),
264
- z.tuple([z.literal(ExportEvents.DATABASE_EXPORT_COMPLETE), z.undefined()]),
265
- z.tuple([z.literal(ExportEvents.CONFIG_EXPORT_START), z.undefined()]),
266
- z.tuple([z.literal(ExportEvents.CONFIG_EXPORT_COMPLETE), z.undefined()])
282
+ z.tuple([z.literal(ExportEvents.BACKUP_CREATE_COMPLETE), nullOrUndefined]),
283
+ z.tuple([z.literal(ExportEvents.WP_CONTENT_EXPORT_START), nullOrUndefined]),
284
+ z.tuple([z.literal(ExportEvents.WP_CONTENT_EXPORT_PROGRESS), nullOrUndefined]),
285
+ z.tuple([z.literal(ExportEvents.WP_CONTENT_EXPORT_COMPLETE), nullOrUndefined]),
286
+ z.tuple([z.literal(ExportEvents.DATABASE_EXPORT_START), nullOrUndefined]),
287
+ z.tuple([z.literal(ExportEvents.DATABASE_EXPORT_PROGRESS), nullOrUndefined]),
288
+ z.tuple([z.literal(ExportEvents.DATABASE_EXPORT_COMPLETE), nullOrUndefined]),
289
+ z.tuple([z.literal(ExportEvents.CONFIG_EXPORT_START), nullOrUndefined]),
290
+ z.tuple([z.literal(ExportEvents.CONFIG_EXPORT_COMPLETE), nullOrUndefined])
267
291
  ]);
292
+ z.object({ event: exportEventTupleSchema });
293
+ const SYNC_POLL_INTERVAL_MS = 3e3;
294
+ const SYNC_MAX_STALLED_ATTEMPTS = 200;
295
+ const SYNC_PUSH_SIZE_LIMIT_GB = 5;
296
+ const SYNC_PUSH_SIZE_LIMIT_BYTES = SYNC_PUSH_SIZE_LIMIT_GB * 1024 * 1024 * 1024;
297
+ const SYNC_IGNORE_DEFAULTS = [
298
+ ...DEPLOY_IGNORE_DEFAULTS,
299
+ // Studio-internal sync exclusions
300
+ "database",
301
+ "db.php",
302
+ "debug.log",
303
+ "sqlite-database-integration",
304
+ "cache"
305
+ ];
268
306
  var AuthCommandLoggerAction = /* @__PURE__ */ ((AuthCommandLoggerAction2) => {
269
307
  AuthCommandLoggerAction2["LOGIN"] = "login";
270
308
  AuthCommandLoggerAction2["LOGOUT"] = "logout";
@@ -400,6 +438,20 @@ async function updateSiteAutoStart(siteId, autoStart) {
400
438
  await unlockCliConfig();
401
439
  }
402
440
  }
441
+ async function updateSitePhpVersion(siteId, phpVersion) {
442
+ try {
443
+ await lockCliConfig();
444
+ const config = await readCliConfig();
445
+ const site = config.sites.find((s) => s.id === siteId);
446
+ if (!site) {
447
+ throw new LoggerError(__("Site not found"));
448
+ }
449
+ site.phpVersion = phpVersion;
450
+ await saveCliConfig(config);
451
+ } finally {
452
+ await unlockCliConfig();
453
+ }
454
+ }
403
455
  async function removeSiteFromConfig(siteId) {
404
456
  try {
405
457
  await lockCliConfig();
@@ -649,767 +701,6 @@ async function emitCliEvent(payload) {
649
701
  } catch {
650
702
  }
651
703
  }
652
- function parseJsonFromPhpOutput(output) {
653
- try {
654
- return JSON.parse(output);
655
- } catch {
656
- }
657
- const objectStart = output.indexOf("{");
658
- const arrayStart = output.indexOf("[");
659
- let startIndex;
660
- if (objectStart === -1 && arrayStart === -1) {
661
- throw new SyntaxError(`No JSON found in output: ${output.substring(0, 100)}`);
662
- } else if (objectStart === -1) {
663
- startIndex = arrayStart;
664
- } else if (arrayStart === -1) {
665
- startIndex = objectStart;
666
- } else {
667
- startIndex = Math.min(objectStart, arrayStart);
668
- }
669
- return JSON.parse(output.substring(startIndex));
670
- }
671
- const DB_DEFAULTS = [
672
- ["DB_NAME", "database_name_here"],
673
- ["DB_USER", "username_here"],
674
- ["DB_PASSWORD", "password_here"],
675
- ["DB_HOST", "localhost"],
676
- ["DB_CHARSET", "utf8mb4"],
677
- ["DB_COLLATE", ""]
678
- ];
679
- function buildDefaultDbBlockRegex() {
680
- const commentLine = "[ \\t]*(?:\\/\\/[^\\r\\n]*|\\/\\*\\*[^*]*(?:\\*(?!\\/)[^*]*)*\\*\\/)[ \\t]*\\r?\\n";
681
- const optionalComments = `(?:${commentLine})*`;
682
- const blankLines = "(?:[ \\t]*\\r?\\n)*";
683
- const definePatterns = DB_DEFAULTS.map(([name, value]) => {
684
- const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
685
- return `${optionalComments}[ \\t]*define\\([ \\t]*'${name}'[ \\t]*,[ \\t]*'${escapedValue}'[ \\t]*\\)[ \\t]*;[ \\t]*\\r?\\n`;
686
- });
687
- return new RegExp(definePatterns.join(blankLines));
688
- }
689
- const DB_SETTINGS_BLOCK_REGEX = buildDefaultDbBlockRegex();
690
- const REPLACEMENT_COMMENT = normalizeLineEndings(`/**
691
- * Database connection information is automatically provided.
692
- * There is no need to set or change the following database configuration
693
- * values:
694
- * DB_HOST
695
- * DB_NAME
696
- * DB_USER
697
- * DB_PASSWORD
698
- * DB_CHARSET
699
- * DB_COLLATE
700
- */`);
701
- function normalizeLineEndings(content) {
702
- return content.replace(/\n/g, "\r\n");
703
- }
704
- function hasDefaultDbBlock(content) {
705
- return DB_SETTINGS_BLOCK_REGEX.test(content);
706
- }
707
- function removeDbConstants(content) {
708
- return content.replace(DB_SETTINGS_BLOCK_REGEX, REPLACEMENT_COMMENT + "\n");
709
- }
710
- const openZip$1 = promisify(yauzl.open);
711
- async function extractZip(zipPath, destinationFolder) {
712
- const zipFile = await openZip$1(zipPath, { lazyEntries: true });
713
- const openReadStream = promisify(zipFile.openReadStream.bind(zipFile));
714
- const resolvedDestination = path__default.resolve(destinationFolder);
715
- return new Promise((resolve, reject) => {
716
- zipFile.on("entry", async (entry) => {
717
- if (entry.fileName.endsWith("/")) {
718
- zipFile.readEntry();
719
- return;
720
- }
721
- const normalizedPath = path__default.normalize(entry.fileName);
722
- const fullPath = path__default.join(resolvedDestination, normalizedPath);
723
- if (!fullPath.startsWith(resolvedDestination + path__default.sep)) {
724
- console.warn(`Skipping invalid path: ${entry.fileName}`);
725
- zipFile.readEntry();
726
- return;
727
- }
728
- try {
729
- let onError = function(error2) {
730
- if (!readStream.destroyed) {
731
- readStream.destroy();
732
- }
733
- if (!writeStream.destroyed) {
734
- writeStream.destroy();
735
- }
736
- reject(error2);
737
- };
738
- await fs__default.promises.mkdir(path__default.dirname(fullPath), { recursive: true });
739
- const readStream = await openReadStream(entry);
740
- const writeStream = fs__default.createWriteStream(fullPath);
741
- readStream.once("error", onError);
742
- writeStream.once("error", onError);
743
- writeStream.once("finish", () => {
744
- zipFile.readEntry();
745
- });
746
- readStream.pipe(writeStream);
747
- } catch (error2) {
748
- reject(error2);
749
- }
750
- });
751
- zipFile.on("end", () => {
752
- resolve();
753
- });
754
- zipFile.on("error", reject);
755
- zipFile.readEntry();
756
- });
757
- }
758
- var ignore$1;
759
- var hasRequiredIgnore;
760
- function requireIgnore() {
761
- if (hasRequiredIgnore) return ignore$1;
762
- hasRequiredIgnore = 1;
763
- function makeArray(subject) {
764
- return Array.isArray(subject) ? subject : [subject];
765
- }
766
- const EMPTY = "";
767
- const SPACE = " ";
768
- const ESCAPE = "\\";
769
- const REGEX_TEST_BLANK_LINE = /^\s+$/;
770
- const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
771
- const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
772
- const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
773
- const REGEX_SPLITALL_CRLF = /\r?\n/g;
774
- const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/;
775
- const SLASH = "/";
776
- let TMP_KEY_IGNORE = "node-ignore";
777
- if (typeof Symbol !== "undefined") {
778
- TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
779
- }
780
- const KEY_IGNORE = TMP_KEY_IGNORE;
781
- const define = (object, key, value) => Object.defineProperty(object, key, { value });
782
- const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
783
- const RETURN_FALSE = () => false;
784
- const sanitizeRange = (range2) => range2.replace(
785
- REGEX_REGEXP_RANGE,
786
- (match2, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match2 : EMPTY
787
- );
788
- const cleanRangeBackSlash = (slashes) => {
789
- const { length } = slashes;
790
- return slashes.slice(0, length - length % 2);
791
- };
792
- const REPLACERS = [
793
- [
794
- // remove BOM
795
- // TODO:
796
- // Other similar zero-width characters?
797
- /^\uFEFF/,
798
- () => EMPTY
799
- ],
800
- // > Trailing spaces are ignored unless they are quoted with backslash ("\")
801
- [
802
- // (a\ ) -> (a )
803
- // (a ) -> (a)
804
- // (a ) -> (a)
805
- // (a \ ) -> (a )
806
- /((?:\\\\)*?)(\\?\s+)$/,
807
- (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
808
- ],
809
- // replace (\ ) with ' '
810
- // (\ ) -> ' '
811
- // (\\ ) -> '\\ '
812
- // (\\\ ) -> '\\ '
813
- [
814
- /(\\+?)\s/g,
815
- (_, m1) => {
816
- const { length } = m1;
817
- return m1.slice(0, length - length % 2) + SPACE;
818
- }
819
- ],
820
- // Escape metacharacters
821
- // which is written down by users but means special for regular expressions.
822
- // > There are 12 characters with special meanings:
823
- // > - the backslash \,
824
- // > - the caret ^,
825
- // > - the dollar sign $,
826
- // > - the period or dot .,
827
- // > - the vertical bar or pipe symbol |,
828
- // > - the question mark ?,
829
- // > - the asterisk or star *,
830
- // > - the plus sign +,
831
- // > - the opening parenthesis (,
832
- // > - the closing parenthesis ),
833
- // > - and the opening square bracket [,
834
- // > - the opening curly brace {,
835
- // > These special characters are often called "metacharacters".
836
- [
837
- /[\\$.|*+(){^]/g,
838
- (match2) => `\\${match2}`
839
- ],
840
- [
841
- // > a question mark (?) matches a single character
842
- /(?!\\)\?/g,
843
- () => "[^/]"
844
- ],
845
- // leading slash
846
- [
847
- // > A leading slash matches the beginning of the pathname.
848
- // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
849
- // A leading slash matches the beginning of the pathname
850
- /^\//,
851
- () => "^"
852
- ],
853
- // replace special metacharacter slash after the leading slash
854
- [
855
- /\//g,
856
- () => "\\/"
857
- ],
858
- [
859
- // > A leading "**" followed by a slash means match in all directories.
860
- // > For example, "**/foo" matches file or directory "foo" anywhere,
861
- // > the same as pattern "foo".
862
- // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
863
- // > under directory "foo".
864
- // Notice that the '*'s have been replaced as '\\*'
865
- /^\^*\\\*\\\*\\\//,
866
- // '**/foo' <-> 'foo'
867
- () => "^(?:.*\\/)?"
868
- ],
869
- // starting
870
- [
871
- // there will be no leading '/'
872
- // (which has been replaced by section "leading slash")
873
- // If starts with '**', adding a '^' to the regular expression also works
874
- /^(?=[^^])/,
875
- function startingReplacer() {
876
- return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
877
- }
878
- ],
879
- // two globstars
880
- [
881
- // Use lookahead assertions so that we could match more than one `'/**'`
882
- /\\\/\\\*\\\*(?=\\\/|$)/g,
883
- // Zero, one or several directories
884
- // should not use '*', or it will be replaced by the next replacer
885
- // Check if it is not the last `'/**'`
886
- (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
887
- ],
888
- // normal intermediate wildcards
889
- [
890
- // Never replace escaped '*'
891
- // ignore rule '\*' will match the path '*'
892
- // 'abc.*/' -> go
893
- // 'abc.*' -> skip this rule,
894
- // coz trailing single wildcard will be handed by [trailing wildcard]
895
- /(^|[^\\]+)(\\\*)+(?=.+)/g,
896
- // '*.js' matches '.js'
897
- // '*.js' doesn't match 'abc'
898
- (_, p1, p2) => {
899
- const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
900
- return p1 + unescaped;
901
- }
902
- ],
903
- [
904
- // unescape, revert step 3 except for back slash
905
- // For example, if a user escape a '\\*',
906
- // after step 3, the result will be '\\\\\\*'
907
- /\\\\\\(?=[$.|*+(){^])/g,
908
- () => ESCAPE
909
- ],
910
- [
911
- // '\\\\' -> '\\'
912
- /\\\\/g,
913
- () => ESCAPE
914
- ],
915
- [
916
- // > The range notation, e.g. [a-zA-Z],
917
- // > can be used to match one of the characters in a range.
918
- // `\` is escaped by step 3
919
- /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
920
- (match2, leadEscape, range2, endEscape, close) => leadEscape === ESCAPE ? `\\[${range2}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range2)}${endEscape}]` : "[]" : "[]"
921
- ],
922
- // ending
923
- [
924
- // 'js' will not match 'js.'
925
- // 'ab' will not match 'abc'
926
- /(?:[^*])$/,
927
- // WTF!
928
- // https://git-scm.com/docs/gitignore
929
- // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
930
- // which re-fixes #24, #38
931
- // > If there is a separator at the end of the pattern then the pattern
932
- // > will only match directories, otherwise the pattern can match both
933
- // > files and directories.
934
- // 'js*' will not match 'a.js'
935
- // 'js/' will not match 'a.js'
936
- // 'js' will match 'a.js' and 'a.js/'
937
- (match2) => /\/$/.test(match2) ? `${match2}$` : `${match2}(?=$|\\/$)`
938
- ],
939
- // trailing wildcard
940
- [
941
- /(\^|\\\/)?\\\*$/,
942
- (_, p1) => {
943
- const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
944
- return `${prefix}(?=$|\\/$)`;
945
- }
946
- ]
947
- ];
948
- const regexCache = /* @__PURE__ */ Object.create(null);
949
- const makeRegex = (pattern, ignoreCase) => {
950
- let source2 = regexCache[pattern];
951
- if (!source2) {
952
- source2 = REPLACERS.reduce(
953
- (prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
954
- pattern
955
- );
956
- regexCache[pattern] = source2;
957
- }
958
- return ignoreCase ? new RegExp(source2, "i") : new RegExp(source2);
959
- };
960
- const isString = (subject) => typeof subject === "string";
961
- const checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
962
- const splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF);
963
- class IgnoreRule {
964
- constructor(origin, pattern, negative, regex) {
965
- this.origin = origin;
966
- this.pattern = pattern;
967
- this.negative = negative;
968
- this.regex = regex;
969
- }
970
- }
971
- const createRule = (pattern, ignoreCase) => {
972
- const origin = pattern;
973
- let negative = false;
974
- if (pattern.indexOf("!") === 0) {
975
- negative = true;
976
- pattern = pattern.substr(1);
977
- }
978
- pattern = pattern.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
979
- const regex = makeRegex(pattern, ignoreCase);
980
- return new IgnoreRule(
981
- origin,
982
- pattern,
983
- negative,
984
- regex
985
- );
986
- };
987
- const throwError = (message2, Ctor) => {
988
- throw new Ctor(message2);
989
- };
990
- const checkPath = (path2, originalPath, doThrow) => {
991
- if (!isString(path2)) {
992
- return doThrow(
993
- `path must be a string, but got \`${originalPath}\``,
994
- TypeError
995
- );
996
- }
997
- if (!path2) {
998
- return doThrow(`path must not be empty`, TypeError);
999
- }
1000
- if (checkPath.isNotRelative(path2)) {
1001
- const r = "`path.relative()`d";
1002
- return doThrow(
1003
- `path should be a ${r} string, but got "${originalPath}"`,
1004
- RangeError
1005
- );
1006
- }
1007
- return true;
1008
- };
1009
- const isNotRelative = (path2) => REGEX_TEST_INVALID_PATH.test(path2);
1010
- checkPath.isNotRelative = isNotRelative;
1011
- checkPath.convert = (p) => p;
1012
- class Ignore {
1013
- constructor({
1014
- ignorecase = true,
1015
- ignoreCase = ignorecase,
1016
- allowRelativePaths = false
1017
- } = {}) {
1018
- define(this, KEY_IGNORE, true);
1019
- this._rules = [];
1020
- this._ignoreCase = ignoreCase;
1021
- this._allowRelativePaths = allowRelativePaths;
1022
- this._initCache();
1023
- }
1024
- _initCache() {
1025
- this._ignoreCache = /* @__PURE__ */ Object.create(null);
1026
- this._testCache = /* @__PURE__ */ Object.create(null);
1027
- }
1028
- _addPattern(pattern) {
1029
- if (pattern && pattern[KEY_IGNORE]) {
1030
- this._rules = this._rules.concat(pattern._rules);
1031
- this._added = true;
1032
- return;
1033
- }
1034
- if (checkPattern(pattern)) {
1035
- const rule = createRule(pattern, this._ignoreCase);
1036
- this._added = true;
1037
- this._rules.push(rule);
1038
- }
1039
- }
1040
- // @param {Array<string> | string | Ignore} pattern
1041
- add(pattern) {
1042
- this._added = false;
1043
- makeArray(
1044
- isString(pattern) ? splitPattern(pattern) : pattern
1045
- ).forEach(this._addPattern, this);
1046
- if (this._added) {
1047
- this._initCache();
1048
- }
1049
- return this;
1050
- }
1051
- // legacy
1052
- addPattern(pattern) {
1053
- return this.add(pattern);
1054
- }
1055
- // | ignored : unignored
1056
- // negative | 0:0 | 0:1 | 1:0 | 1:1
1057
- // -------- | ------- | ------- | ------- | --------
1058
- // 0 | TEST | TEST | SKIP | X
1059
- // 1 | TESTIF | SKIP | TEST | X
1060
- // - SKIP: always skip
1061
- // - TEST: always test
1062
- // - TESTIF: only test if checkUnignored
1063
- // - X: that never happen
1064
- // @param {boolean} whether should check if the path is unignored,
1065
- // setting `checkUnignored` to `false` could reduce additional
1066
- // path matching.
1067
- // @returns {TestResult} true if a file is ignored
1068
- _testOne(path2, checkUnignored) {
1069
- let ignored = false;
1070
- let unignored = false;
1071
- this._rules.forEach((rule) => {
1072
- const { negative } = rule;
1073
- if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
1074
- return;
1075
- }
1076
- const matched = rule.regex.test(path2);
1077
- if (matched) {
1078
- ignored = !negative;
1079
- unignored = negative;
1080
- }
1081
- });
1082
- return {
1083
- ignored,
1084
- unignored
1085
- };
1086
- }
1087
- // @returns {TestResult}
1088
- _test(originalPath, cache, checkUnignored, slices) {
1089
- const path2 = originalPath && checkPath.convert(originalPath);
1090
- checkPath(
1091
- path2,
1092
- originalPath,
1093
- this._allowRelativePaths ? RETURN_FALSE : throwError
1094
- );
1095
- return this._t(path2, cache, checkUnignored, slices);
1096
- }
1097
- _t(path2, cache, checkUnignored, slices) {
1098
- if (path2 in cache) {
1099
- return cache[path2];
1100
- }
1101
- if (!slices) {
1102
- slices = path2.split(SLASH);
1103
- }
1104
- slices.pop();
1105
- if (!slices.length) {
1106
- return cache[path2] = this._testOne(path2, checkUnignored);
1107
- }
1108
- const parent = this._t(
1109
- slices.join(SLASH) + SLASH,
1110
- cache,
1111
- checkUnignored,
1112
- slices
1113
- );
1114
- return cache[path2] = parent.ignored ? parent : this._testOne(path2, checkUnignored);
1115
- }
1116
- ignores(path2) {
1117
- return this._test(path2, this._ignoreCache, false).ignored;
1118
- }
1119
- createFilter() {
1120
- return (path2) => !this.ignores(path2);
1121
- }
1122
- filter(paths) {
1123
- return makeArray(paths).filter(this.createFilter());
1124
- }
1125
- // @returns {TestResult}
1126
- test(path2) {
1127
- return this._test(path2, this._testCache, true);
1128
- }
1129
- }
1130
- const factory = (options) => new Ignore(options);
1131
- const isPathValid = (path2) => checkPath(path2 && checkPath.convert(path2), path2, RETURN_FALSE);
1132
- factory.isPathValid = isPathValid;
1133
- factory.default = factory;
1134
- ignore$1 = factory;
1135
- if (
1136
- // Detect `process` so that it can run in browsers.
1137
- typeof process !== "undefined" && (process.env && process.env.IGNORE_TEST_WIN32 || process.platform === "win32")
1138
- ) {
1139
- const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
1140
- checkPath.convert = makePosix;
1141
- const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
1142
- checkPath.isNotRelative = (path2) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path2) || isNotRelative(path2);
1143
- }
1144
- return ignore$1;
1145
- }
1146
- var ignoreExports = requireIgnore();
1147
- const ignore = /* @__PURE__ */ getDefaultExportFromCjs(ignoreExports);
1148
- async function downloadFile(url, destinationPath) {
1149
- try {
1150
- await fs__default.promises.mkdir(path__default.dirname(destinationPath), { recursive: true });
1151
- } catch (error2) {
1152
- if (isErrnoException(error2) && error2.code !== "EEXIST") {
1153
- throw error2;
1154
- }
1155
- }
1156
- const response2 = await fetch(url);
1157
- if (!response2.ok) {
1158
- throw new Error(`Request failed with status code: ${response2.status}`);
1159
- }
1160
- if (!response2.body) {
1161
- throw new Error("Download response did not include a readable body.");
1162
- }
1163
- await response2.body.pipeTo(Writable.toWeb(fs__default.createWriteStream(destinationPath)));
1164
- }
1165
- const IGNORE_PATTERNS = [".DS_Store", "Thumbs.db"];
1166
- const IGNORE_INSTANCE = ignore().add(IGNORE_PATTERNS);
1167
- async function collectDirectoryMetadata(directoryPath, basePath = directoryPath) {
1168
- const files = /* @__PURE__ */ new Map();
1169
- const entries = await fs__default.promises.readdir(directoryPath, { withFileTypes: true });
1170
- for (const entry of entries) {
1171
- const fullPath = path__default.join(directoryPath, entry.name);
1172
- const relativePath = path__default.relative(basePath, fullPath);
1173
- if (IGNORE_INSTANCE.ignores(relativePath)) {
1174
- continue;
1175
- }
1176
- if (entry.isDirectory()) {
1177
- const nestedFiles = await collectDirectoryMetadata(fullPath, basePath);
1178
- for (const [key, value] of nestedFiles) {
1179
- files.set(key, value);
1180
- }
1181
- continue;
1182
- }
1183
- if (!entry.isFile()) {
1184
- continue;
1185
- }
1186
- const stats = await fs__default.promises.lstat(fullPath);
1187
- files.set(relativePath, { size: stats.size, mtimeMs: Math.floor(stats.mtimeMs) });
1188
- }
1189
- return files;
1190
- }
1191
- async function areDirectoriesDifferentBySizeAndMtime(sourceDirectoryPath, targetDirectoryPath) {
1192
- if (!fs__default.existsSync(sourceDirectoryPath) || !fs__default.existsSync(targetDirectoryPath)) {
1193
- return true;
1194
- }
1195
- const [sourceFiles, targetFiles] = await Promise.all([
1196
- collectDirectoryMetadata(sourceDirectoryPath),
1197
- collectDirectoryMetadata(targetDirectoryPath)
1198
- ]);
1199
- if (sourceFiles.size !== targetFiles.size) {
1200
- return true;
1201
- }
1202
- for (const [relativePath, sourceMetadata] of sourceFiles) {
1203
- const targetMetadata = targetFiles.get(relativePath);
1204
- if (!targetMetadata) {
1205
- return true;
1206
- }
1207
- if (sourceMetadata.size !== targetMetadata.size || sourceMetadata.mtimeMs !== targetMetadata.mtimeMs) {
1208
- return true;
1209
- }
1210
- }
1211
- return false;
1212
- }
1213
- const MINIMUM_SUPPORTED_WP_VERSION = 6;
1214
- const DEFAULT_WORDPRESS_VERSION = "latest";
1215
- async function downloadWordPress(wordPressVersion = DEFAULT_WORDPRESS_VERSION, overwrite = false) {
1216
- const finalFolder = getWordPressVersionPath(wordPressVersion);
1217
- if (!overwrite && fs__default.existsSync(finalFolder)) {
1218
- return;
1219
- }
1220
- const tempDir = path__default.join(os.tmpdir(), "wordpress-download-", crypto$1.randomUUID());
1221
- await fs__default.promises.mkdir(tempDir, { recursive: true });
1222
- try {
1223
- const tmpDownloadPath = path__default.join(os.tmpdir(), `wordpress-${crypto$1.randomUUID()}.zip`);
1224
- try {
1225
- await downloadFile(getWordPressVersionUrl(wordPressVersion), tmpDownloadPath);
1226
- await extractZip(tmpDownloadPath, tempDir);
1227
- } finally {
1228
- await fs__default.promises.rm(tmpDownloadPath, { force: true });
1229
- }
1230
- const wpSourcePath = path__default.join(tempDir, "wordpress");
1231
- await fs__default.promises.mkdir(path__default.dirname(finalFolder), { recursive: true });
1232
- await fs__default.promises.cp(wpSourcePath, finalFolder, {
1233
- recursive: true,
1234
- verbatimSymlinks: true
1235
- });
1236
- } finally {
1237
- try {
1238
- await fs__default.promises.rm(tempDir, { force: true, recursive: true });
1239
- } catch {
1240
- }
1241
- }
1242
- }
1243
- async function fetchWordPressVersions$1() {
1244
- try {
1245
- const response2 = await fetch("https://api.wordpress.org/core/stable-check/1.0/");
1246
- const versionsStatus = await response2.json();
1247
- const versions = Object.keys(versionsStatus).filter((item) => {
1248
- const version2 = semver.coerce(item);
1249
- const minVersion = semver.coerce(MINIMUM_SUPPORTED_WP_VERSION);
1250
- return version2 && minVersion && semver.gte(version2, minVersion);
1251
- }).sort((a, b) => {
1252
- const versionA = semver.coerce(a);
1253
- const versionB = semver.coerce(b);
1254
- if (!versionA || !versionB) {
1255
- return 0;
1256
- }
1257
- return semver.compare(versionA, versionB);
1258
- }).reverse();
1259
- const latestVersion = Object.keys(versionsStatus).find(
1260
- (index) => versionsStatus[index] === "latest"
1261
- );
1262
- return { versions, latest: latestVersion };
1263
- } catch (exception) {
1264
- return { versions: [], latest: DEFAULT_WORDPRESS_VERSION };
1265
- }
1266
- }
1267
- async function getWordPressVersionFromInstallation(installationPath) {
1268
- try {
1269
- const versionFileContent = await fs__default.promises.readFile(
1270
- path__default.join(installationPath, "wp-includes", "version.php"),
1271
- "utf8"
1272
- );
1273
- const matches = versionFileContent.match(/\$wp_version\s*=\s*'([0-9a-zA-Z.-]+)'/);
1274
- return matches?.[1];
1275
- } catch (err) {
1276
- return null;
1277
- }
1278
- }
1279
- async function updateLatestWordPressVersion() {
1280
- let shouldOverwrite = false;
1281
- const latestVersionPath = getWordPressVersionPath("latest");
1282
- const latestVersionFiles = fs__default.existsSync(latestVersionPath) ? await fs__default.promises.readdir(latestVersionPath) : [];
1283
- if (latestVersionFiles.length !== 0) {
1284
- const installedVersion = await getWordPressVersionFromInstallation(latestVersionPath);
1285
- const wordPressVersions = await fetchWordPressVersions$1();
1286
- const latestVersion = wordPressVersions.latest ?? DEFAULT_WORDPRESS_VERSION;
1287
- if (installedVersion && latestVersion !== "latest" && installedVersion !== latestVersion) {
1288
- await recursiveCopyDirectory(
1289
- latestVersionPath,
1290
- getWordPressVersionPath(installedVersion)
1291
- );
1292
- shouldOverwrite = true;
1293
- }
1294
- }
1295
- await downloadWordPress("latest", shouldOverwrite);
1296
- }
1297
- const processIdAllocator = new ProcessIdAllocator();
1298
- const PLAYGROUND_INTERNAL_SHARED_FOLDER = "/internal/shared";
1299
- function createNoopSpawnHandler() {
1300
- return createSpawnHandler(async (args, processApi) => {
1301
- await new Promise((resolve) => setTimeout(resolve, 1));
1302
- processApi.exit(1);
1303
- });
1304
- }
1305
- async function runWpCliCommand(siteFolder, phpVersion, args) {
1306
- const id = await loadNodeRuntime(phpVersion, {
1307
- followSymlinks: true,
1308
- withRedis: IS_JSPI_AVAILABLE,
1309
- withMemcached: IS_JSPI_AVAILABLE,
1310
- emscriptenOptions: {
1311
- processId: processIdAllocator.claim()
1312
- }
1313
- });
1314
- const php = new PHP(id);
1315
- try {
1316
- await php.setSapiName("cli");
1317
- php.defineConstant("WP_SQLITE_AST_DRIVER", true);
1318
- php.mkdir("/wordpress");
1319
- await php.mount("/wordpress", createNodeFsMountHandler(siteFolder));
1320
- php.writeFile("/tmp/ca-bundle.crt", rootCertificates.join("\n"));
1321
- await setPhpIniEntries(php, {
1322
- "openssl.cafile": "/tmp/ca-bundle.crt",
1323
- allow_url_fopen: 1
1324
- });
1325
- await php.setSpawnHandler(createNoopSpawnHandler());
1326
- await cleanupLegacyMuPlugins(siteFolder);
1327
- const [studioMuPluginsHostPath, loaderMuPluginHostPath] = await getMuPlugins({
1328
- isWpAutoUpdating: false
1329
- });
1330
- await php.mount(
1331
- "/internal/studio/mu-plugins",
1332
- createNodeFsMountHandler(studioMuPluginsHostPath)
1333
- );
1334
- await php.mount(
1335
- PLAYGROUND_INTERNAL_SHARED_FOLDER + "/mu-plugins/99-studio-loader.php",
1336
- createNodeFsMountHandler(loaderMuPluginHostPath)
1337
- );
1338
- await php.mount("/tmp/wp-cli.phar", createNodeFsMountHandler(getWpCliPharPath()));
1339
- await php.mount("/tmp/sqlite-command", createNodeFsMountHandler(getSqliteCommandPath()));
1340
- await setupPlatformLevelMuPlugins(php);
1341
- const response2 = await php.cli(["php", "/tmp/wp-cli.phar", "--path=/wordpress", ...args]);
1342
- return {
1343
- response: response2,
1344
- [Symbol.dispose]() {
1345
- php.exit();
1346
- }
1347
- };
1348
- } catch (error2) {
1349
- php.exit();
1350
- throw new Error(__("An error occurred while running the WP-CLI command."));
1351
- }
1352
- }
1353
- async function runGlobalWpCliCommand(args) {
1354
- const id = await loadNodeRuntime(LatestSupportedPHPVersion, {
1355
- followSymlinks: true,
1356
- withRedis: false,
1357
- withMemcached: false,
1358
- emscriptenOptions: {
1359
- processId: processIdAllocator.claim()
1360
- }
1361
- });
1362
- const php = new PHP(id);
1363
- try {
1364
- await php.setSapiName("cli");
1365
- php.writeFile("/tmp/ca-bundle.crt", rootCertificates.join("\n"));
1366
- await setPhpIniEntries(php, {
1367
- "openssl.cafile": "/tmp/ca-bundle.crt",
1368
- allow_url_fopen: 1
1369
- });
1370
- await php.setSpawnHandler(createNoopSpawnHandler());
1371
- await php.mount("/tmp/wp-cli.phar", createNodeFsMountHandler(getWpCliPharPath()));
1372
- const response2 = await php.cli(["php", "/tmp/wp-cli.phar", ...args]);
1373
- return {
1374
- response: response2,
1375
- [Symbol.dispose]() {
1376
- php.exit();
1377
- }
1378
- };
1379
- } catch (error2) {
1380
- php.exit();
1381
- throw new Error(__("An error occurred while running the WP-CLI command."));
1382
- }
1383
- }
1384
- class ImportExportEventEmitter extends EventEmitter {
1385
- on(eventName, listener) {
1386
- return super.on(eventName, listener);
1387
- }
1388
- off(eventName, listener) {
1389
- return super.off(eventName, listener);
1390
- }
1391
- emit(eventName, ...args) {
1392
- return super.emit(eventName, ...args);
1393
- }
1394
- }
1395
- const LATIN = "a-z";
1396
- const CYRILLIC = "а-яё";
1397
- const ARABIC = "\\u0600-\\u06FF";
1398
- const HEBREW = "\\u0590-\\u05FF";
1399
- const CHINESE = "\\u4e00-\\u9fa5";
1400
- const JAPANESE_HIRAGANA = "\\u3040-\\u309F";
1401
- const JAPANESE_KATAKANA = "\\u30A0-\\u30FF";
1402
- const KOREAN_HANGUL = "\\uAC00-\\uD7AF";
1403
- const KOREAN_JAMO = "\\u1100-\\u11FF";
1404
- const NUMBERS = "0-9";
1405
- const WHITELISTED_SYMBOLS = "_\\- ";
1406
- const ALLOWED_CHARS = new RegExp(
1407
- `[^${LATIN}${NUMBERS}${CYRILLIC}${ARABIC}${HEBREW}${CHINESE}${JAPANESE_HIRAGANA}${JAPANESE_KATAKANA}${KOREAN_HANGUL}${KOREAN_JAMO}${WHITELISTED_SYMBOLS}]`,
1408
- "gi"
1409
- );
1410
- const sanitizeFolderName = (filename) => {
1411
- return String(filename).replace(/ł/g, "l").replace(/Ł/g, "L").normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(ALLOWED_CHARS, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-");
1412
- };
1413
704
  function toDate(argument) {
1414
705
  const argStr = Object.prototype.toString.call(argument);
1415
706
  if (argument instanceof Date || typeof argument === "object" && argStr === "[object Date]") {
@@ -3138,10 +2429,381 @@ function isSameMonth(dateLeft, dateRight) {
3138
2429
  const _dateRight = toDate(dateRight);
3139
2430
  return _dateLeft.getFullYear() === _dateRight.getFullYear() && _dateLeft.getMonth() === _dateRight.getMonth();
3140
2431
  }
2432
+ const LATIN = "a-z";
2433
+ const CYRILLIC = "а-яё";
2434
+ const ARABIC = "\\u0600-\\u06FF";
2435
+ const HEBREW = "\\u0590-\\u05FF";
2436
+ const CHINESE = "\\u4e00-\\u9fa5";
2437
+ const JAPANESE_HIRAGANA = "\\u3040-\\u309F";
2438
+ const JAPANESE_KATAKANA = "\\u30A0-\\u30FF";
2439
+ const KOREAN_HANGUL = "\\uAC00-\\uD7AF";
2440
+ const KOREAN_JAMO = "\\u1100-\\u11FF";
2441
+ const NUMBERS = "0-9";
2442
+ const WHITELISTED_SYMBOLS = "_\\- ";
2443
+ const ALLOWED_CHARS = new RegExp(
2444
+ `[^${LATIN}${NUMBERS}${CYRILLIC}${ARABIC}${HEBREW}${CHINESE}${JAPANESE_HIRAGANA}${JAPANESE_KATAKANA}${KOREAN_HANGUL}${KOREAN_JAMO}${WHITELISTED_SYMBOLS}]`,
2445
+ "gi"
2446
+ );
2447
+ const sanitizeFolderName = (filename) => {
2448
+ return String(filename).replace(/ł/g, "l").replace(/Ł/g, "L").normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(ALLOWED_CHARS, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-");
2449
+ };
3141
2450
  function generateBackupFilename(name) {
3142
2451
  const timestamp = format(/* @__PURE__ */ new Date(), "yyyy-MM-dd-HH-mm-ss");
3143
2452
  return sanitizeFolderName(`studio-backup-${name}-${timestamp}`);
3144
2453
  }
2454
+ function parseJsonFromPhpOutput(output) {
2455
+ try {
2456
+ return JSON.parse(output);
2457
+ } catch {
2458
+ }
2459
+ const objectStart = output.indexOf("{");
2460
+ const arrayStart = output.indexOf("[");
2461
+ let startIndex;
2462
+ if (objectStart === -1 && arrayStart === -1) {
2463
+ throw new SyntaxError(`No JSON found in output: ${output.substring(0, 100)}`);
2464
+ } else if (objectStart === -1) {
2465
+ startIndex = arrayStart;
2466
+ } else if (arrayStart === -1) {
2467
+ startIndex = objectStart;
2468
+ } else {
2469
+ startIndex = Math.min(objectStart, arrayStart);
2470
+ }
2471
+ return JSON.parse(output.substring(startIndex));
2472
+ }
2473
+ const DB_DEFAULTS = [
2474
+ ["DB_NAME", "database_name_here"],
2475
+ ["DB_USER", "username_here"],
2476
+ ["DB_PASSWORD", "password_here"],
2477
+ ["DB_HOST", "localhost"],
2478
+ ["DB_CHARSET", "utf8mb4"],
2479
+ ["DB_COLLATE", ""]
2480
+ ];
2481
+ function buildDefaultDbBlockRegex() {
2482
+ const commentLine = "[ \\t]*(?:\\/\\/[^\\r\\n]*|\\/\\*\\*[^*]*(?:\\*(?!\\/)[^*]*)*\\*\\/)[ \\t]*\\r?\\n";
2483
+ const optionalComments = `(?:${commentLine})*`;
2484
+ const blankLines = "(?:[ \\t]*\\r?\\n)*";
2485
+ const definePatterns = DB_DEFAULTS.map(([name, value]) => {
2486
+ const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2487
+ return `${optionalComments}[ \\t]*define\\([ \\t]*'${name}'[ \\t]*,[ \\t]*'${escapedValue}'[ \\t]*\\)[ \\t]*;[ \\t]*\\r?\\n`;
2488
+ });
2489
+ return new RegExp(definePatterns.join(blankLines));
2490
+ }
2491
+ const DB_SETTINGS_BLOCK_REGEX = buildDefaultDbBlockRegex();
2492
+ const REPLACEMENT_COMMENT = normalizeLineEndings(`/**
2493
+ * Database connection information is automatically provided.
2494
+ * There is no need to set or change the following database configuration
2495
+ * values:
2496
+ * DB_HOST
2497
+ * DB_NAME
2498
+ * DB_USER
2499
+ * DB_PASSWORD
2500
+ * DB_CHARSET
2501
+ * DB_COLLATE
2502
+ */`);
2503
+ function normalizeLineEndings(content) {
2504
+ return content.replace(/\n/g, "\r\n");
2505
+ }
2506
+ function hasDefaultDbBlock(content) {
2507
+ return DB_SETTINGS_BLOCK_REGEX.test(content);
2508
+ }
2509
+ function removeDbConstants(content) {
2510
+ return content.replace(DB_SETTINGS_BLOCK_REGEX, REPLACEMENT_COMMENT + "\n");
2511
+ }
2512
+ const openZip$1 = promisify(yauzl.open);
2513
+ async function extractZip(zipPath, destinationFolder) {
2514
+ const zipFile = await openZip$1(zipPath, { lazyEntries: true });
2515
+ const openReadStream = promisify(zipFile.openReadStream.bind(zipFile));
2516
+ const resolvedDestination = path__default.resolve(destinationFolder);
2517
+ return new Promise((resolve, reject) => {
2518
+ zipFile.on("entry", async (entry) => {
2519
+ if (entry.fileName.endsWith("/")) {
2520
+ zipFile.readEntry();
2521
+ return;
2522
+ }
2523
+ const normalizedPath = path__default.normalize(entry.fileName);
2524
+ const fullPath = path__default.join(resolvedDestination, normalizedPath);
2525
+ if (!fullPath.startsWith(resolvedDestination + path__default.sep)) {
2526
+ console.warn(`Skipping invalid path: ${entry.fileName}`);
2527
+ zipFile.readEntry();
2528
+ return;
2529
+ }
2530
+ try {
2531
+ let onError = function(error2) {
2532
+ if (!readStream.destroyed) {
2533
+ readStream.destroy();
2534
+ }
2535
+ if (!writeStream.destroyed) {
2536
+ writeStream.destroy();
2537
+ }
2538
+ reject(error2);
2539
+ };
2540
+ await fs__default.promises.mkdir(path__default.dirname(fullPath), { recursive: true });
2541
+ const readStream = await openReadStream(entry);
2542
+ const writeStream = fs__default.createWriteStream(fullPath);
2543
+ readStream.once("error", onError);
2544
+ writeStream.once("error", onError);
2545
+ writeStream.once("finish", () => {
2546
+ zipFile.readEntry();
2547
+ });
2548
+ readStream.pipe(writeStream);
2549
+ } catch (error2) {
2550
+ reject(error2);
2551
+ }
2552
+ });
2553
+ zipFile.on("end", () => {
2554
+ resolve();
2555
+ });
2556
+ zipFile.on("error", reject);
2557
+ zipFile.readEntry();
2558
+ });
2559
+ }
2560
+ async function downloadFile(url, destinationPath) {
2561
+ try {
2562
+ await fs__default.promises.mkdir(path__default.dirname(destinationPath), { recursive: true });
2563
+ } catch (error2) {
2564
+ if (isErrnoException(error2) && error2.code !== "EEXIST") {
2565
+ throw error2;
2566
+ }
2567
+ }
2568
+ const response2 = await fetch(url);
2569
+ if (!response2.ok) {
2570
+ throw new Error(`Request failed with status code: ${response2.status}`);
2571
+ }
2572
+ if (!response2.body) {
2573
+ throw new Error("Download response did not include a readable body.");
2574
+ }
2575
+ await response2.body.pipeTo(Writable.toWeb(fs__default.createWriteStream(destinationPath)));
2576
+ }
2577
+ const IGNORE_PATTERNS = [".DS_Store", "Thumbs.db"];
2578
+ const IGNORE_INSTANCE = ignore().add(IGNORE_PATTERNS);
2579
+ async function collectDirectoryMetadata(directoryPath, basePath = directoryPath) {
2580
+ const files = /* @__PURE__ */ new Map();
2581
+ const entries = await fs__default.promises.readdir(directoryPath, { withFileTypes: true });
2582
+ for (const entry of entries) {
2583
+ const fullPath = path__default.join(directoryPath, entry.name);
2584
+ const relativePath = path__default.relative(basePath, fullPath);
2585
+ if (IGNORE_INSTANCE.ignores(relativePath)) {
2586
+ continue;
2587
+ }
2588
+ if (entry.isDirectory()) {
2589
+ const nestedFiles = await collectDirectoryMetadata(fullPath, basePath);
2590
+ for (const [key, value] of nestedFiles) {
2591
+ files.set(key, value);
2592
+ }
2593
+ continue;
2594
+ }
2595
+ if (!entry.isFile()) {
2596
+ continue;
2597
+ }
2598
+ const stats = await fs__default.promises.lstat(fullPath);
2599
+ files.set(relativePath, { size: stats.size, mtimeMs: Math.floor(stats.mtimeMs) });
2600
+ }
2601
+ return files;
2602
+ }
2603
+ async function areDirectoriesDifferentBySizeAndMtime(sourceDirectoryPath, targetDirectoryPath) {
2604
+ if (!fs__default.existsSync(sourceDirectoryPath) || !fs__default.existsSync(targetDirectoryPath)) {
2605
+ return true;
2606
+ }
2607
+ const [sourceFiles, targetFiles] = await Promise.all([
2608
+ collectDirectoryMetadata(sourceDirectoryPath),
2609
+ collectDirectoryMetadata(targetDirectoryPath)
2610
+ ]);
2611
+ if (sourceFiles.size !== targetFiles.size) {
2612
+ return true;
2613
+ }
2614
+ for (const [relativePath, sourceMetadata] of sourceFiles) {
2615
+ const targetMetadata = targetFiles.get(relativePath);
2616
+ if (!targetMetadata) {
2617
+ return true;
2618
+ }
2619
+ if (sourceMetadata.size !== targetMetadata.size || sourceMetadata.mtimeMs !== targetMetadata.mtimeMs) {
2620
+ return true;
2621
+ }
2622
+ }
2623
+ return false;
2624
+ }
2625
+ const MINIMUM_SUPPORTED_WP_VERSION = 6;
2626
+ const DEFAULT_WORDPRESS_VERSION = "latest";
2627
+ async function downloadWordPress(wordPressVersion = DEFAULT_WORDPRESS_VERSION, overwrite = false) {
2628
+ const finalFolder = getWordPressVersionPath(wordPressVersion);
2629
+ if (!overwrite && fs__default.existsSync(finalFolder)) {
2630
+ return;
2631
+ }
2632
+ const tempDir = path__default.join(os.tmpdir(), "wordpress-download-", crypto$1.randomUUID());
2633
+ await fs__default.promises.mkdir(tempDir, { recursive: true });
2634
+ try {
2635
+ const tmpDownloadPath = path__default.join(os.tmpdir(), `wordpress-${crypto$1.randomUUID()}.zip`);
2636
+ try {
2637
+ await downloadFile(getWordPressVersionUrl(wordPressVersion), tmpDownloadPath);
2638
+ await extractZip(tmpDownloadPath, tempDir);
2639
+ } finally {
2640
+ await fs__default.promises.rm(tmpDownloadPath, { force: true });
2641
+ }
2642
+ const wpSourcePath = path__default.join(tempDir, "wordpress");
2643
+ await fs__default.promises.mkdir(path__default.dirname(finalFolder), { recursive: true });
2644
+ await fs__default.promises.cp(wpSourcePath, finalFolder, {
2645
+ recursive: true,
2646
+ verbatimSymlinks: true
2647
+ });
2648
+ } finally {
2649
+ try {
2650
+ await fs__default.promises.rm(tempDir, { force: true, recursive: true });
2651
+ } catch {
2652
+ }
2653
+ }
2654
+ }
2655
+ async function fetchWordPressVersions$1() {
2656
+ try {
2657
+ const response2 = await fetch("https://api.wordpress.org/core/stable-check/1.0/");
2658
+ const versionsStatus = await response2.json();
2659
+ const versions = Object.keys(versionsStatus).filter((item) => {
2660
+ const version2 = semver.coerce(item);
2661
+ const minVersion = semver.coerce(MINIMUM_SUPPORTED_WP_VERSION);
2662
+ return version2 && minVersion && semver.gte(version2, minVersion);
2663
+ }).sort((a, b) => {
2664
+ const versionA = semver.coerce(a);
2665
+ const versionB = semver.coerce(b);
2666
+ if (!versionA || !versionB) {
2667
+ return 0;
2668
+ }
2669
+ return semver.compare(versionA, versionB);
2670
+ }).reverse();
2671
+ const latestVersion = Object.keys(versionsStatus).find(
2672
+ (index) => versionsStatus[index] === "latest"
2673
+ );
2674
+ return { versions, latest: latestVersion };
2675
+ } catch (exception) {
2676
+ return { versions: [], latest: DEFAULT_WORDPRESS_VERSION };
2677
+ }
2678
+ }
2679
+ async function getWordPressVersionFromInstallation(installationPath) {
2680
+ try {
2681
+ const versionFileContent = await fs__default.promises.readFile(
2682
+ path__default.join(installationPath, "wp-includes", "version.php"),
2683
+ "utf8"
2684
+ );
2685
+ const matches = versionFileContent.match(/\$wp_version\s*=\s*'([0-9a-zA-Z.-]+)'/);
2686
+ return matches?.[1];
2687
+ } catch (err) {
2688
+ return null;
2689
+ }
2690
+ }
2691
+ async function updateLatestWordPressVersion() {
2692
+ let shouldOverwrite = false;
2693
+ const latestVersionPath = getWordPressVersionPath("latest");
2694
+ const latestVersionFiles = fs__default.existsSync(latestVersionPath) ? await fs__default.promises.readdir(latestVersionPath) : [];
2695
+ if (latestVersionFiles.length !== 0) {
2696
+ const installedVersion = await getWordPressVersionFromInstallation(latestVersionPath);
2697
+ const wordPressVersions = await fetchWordPressVersions$1();
2698
+ const latestVersion = wordPressVersions.latest ?? DEFAULT_WORDPRESS_VERSION;
2699
+ if (installedVersion && latestVersion !== "latest" && installedVersion !== latestVersion) {
2700
+ await recursiveCopyDirectory(
2701
+ latestVersionPath,
2702
+ getWordPressVersionPath(installedVersion)
2703
+ );
2704
+ shouldOverwrite = true;
2705
+ }
2706
+ }
2707
+ await downloadWordPress("latest", shouldOverwrite);
2708
+ }
2709
+ const processIdAllocator = new ProcessIdAllocator();
2710
+ const PLAYGROUND_INTERNAL_SHARED_FOLDER = "/internal/shared";
2711
+ function createNoopSpawnHandler() {
2712
+ return createSpawnHandler(async (args, processApi) => {
2713
+ await new Promise((resolve) => setTimeout(resolve, 1));
2714
+ processApi.exit(1);
2715
+ });
2716
+ }
2717
+ async function runWpCliCommand(siteFolder, phpVersion, args) {
2718
+ const id = await loadNodeRuntime(phpVersion, {
2719
+ followSymlinks: true,
2720
+ withRedis: IS_JSPI_AVAILABLE,
2721
+ withMemcached: IS_JSPI_AVAILABLE,
2722
+ emscriptenOptions: {
2723
+ processId: processIdAllocator.claim()
2724
+ }
2725
+ });
2726
+ const php = new PHP(id);
2727
+ try {
2728
+ await php.setSapiName("cli");
2729
+ php.defineConstant("DB_NAME", "wordpress");
2730
+ php.mkdir("/wordpress");
2731
+ await php.mount("/wordpress", createNodeFsMountHandler(siteFolder));
2732
+ php.writeFile("/tmp/ca-bundle.crt", rootCertificates.join("\n"));
2733
+ await setPhpIniEntries(php, {
2734
+ "openssl.cafile": "/tmp/ca-bundle.crt",
2735
+ allow_url_fopen: 1
2736
+ });
2737
+ await php.setSpawnHandler(createNoopSpawnHandler());
2738
+ await cleanupLegacyMuPlugins(siteFolder);
2739
+ const [studioMuPluginsHostPath, loaderMuPluginHostPath] = await getMuPlugins({
2740
+ isWpAutoUpdating: false
2741
+ });
2742
+ await php.mount(
2743
+ "/internal/studio/mu-plugins",
2744
+ createNodeFsMountHandler(studioMuPluginsHostPath)
2745
+ );
2746
+ await php.mount(
2747
+ PLAYGROUND_INTERNAL_SHARED_FOLDER + "/mu-plugins/99-studio-loader.php",
2748
+ createNodeFsMountHandler(loaderMuPluginHostPath)
2749
+ );
2750
+ await php.mount("/tmp/wp-cli.phar", createNodeFsMountHandler(getWpCliPharPath()));
2751
+ await php.mount("/tmp/sqlite-command", createNodeFsMountHandler(getSqliteCommandPath()));
2752
+ await setupPlatformLevelMuPlugins(php);
2753
+ const response2 = await php.cli(["php", "/tmp/wp-cli.phar", "--path=/wordpress", ...args]);
2754
+ return {
2755
+ response: response2,
2756
+ [Symbol.dispose]() {
2757
+ php.exit();
2758
+ }
2759
+ };
2760
+ } catch (error2) {
2761
+ php.exit();
2762
+ throw new Error(__("An error occurred while running the WP-CLI command."));
2763
+ }
2764
+ }
2765
+ async function runGlobalWpCliCommand(args) {
2766
+ const id = await loadNodeRuntime(LatestSupportedPHPVersion, {
2767
+ followSymlinks: true,
2768
+ withRedis: false,
2769
+ withMemcached: false,
2770
+ emscriptenOptions: {
2771
+ processId: processIdAllocator.claim()
2772
+ }
2773
+ });
2774
+ const php = new PHP(id);
2775
+ try {
2776
+ await php.setSapiName("cli");
2777
+ php.writeFile("/tmp/ca-bundle.crt", rootCertificates.join("\n"));
2778
+ await setPhpIniEntries(php, {
2779
+ "openssl.cafile": "/tmp/ca-bundle.crt",
2780
+ allow_url_fopen: 1
2781
+ });
2782
+ await php.setSpawnHandler(createNoopSpawnHandler());
2783
+ await php.mount("/tmp/wp-cli.phar", createNodeFsMountHandler(getWpCliPharPath()));
2784
+ const response2 = await php.cli(["php", "/tmp/wp-cli.phar", ...args]);
2785
+ return {
2786
+ response: response2,
2787
+ [Symbol.dispose]() {
2788
+ php.exit();
2789
+ }
2790
+ };
2791
+ } catch (error2) {
2792
+ php.exit();
2793
+ throw new Error(__("An error occurred while running the WP-CLI command."));
2794
+ }
2795
+ }
2796
+ class ImportExportEventEmitter extends EventEmitter {
2797
+ on(eventName, listener) {
2798
+ return super.on(eventName, listener);
2799
+ }
2800
+ off(eventName, listener) {
2801
+ return super.off(eventName, listener);
2802
+ }
2803
+ emit(eventName, ...args) {
2804
+ return super.emit(eventName, ...args);
2805
+ }
2806
+ }
3145
2807
  async function exportDatabaseToFile(siteFolder, finalDestination) {
3146
2808
  var _stack = [];
3147
2809
  try {
@@ -3397,6 +3059,9 @@ class DefaultExporter extends ImportExportEventEmitter {
3397
3059
  if (!fs__default.existsSync(fullPath)) {
3398
3060
  continue;
3399
3061
  }
3062
+ if (this.options.ignoreFilter?.ignores(archivePath)) {
3063
+ continue;
3064
+ }
3400
3065
  const stat = await fs$1.stat(fullPath);
3401
3066
  if (stat.isDirectory()) {
3402
3067
  this.archiveBuilder.directory(fullPath, archivePath, (entry) => {
@@ -3405,13 +3070,13 @@ class DefaultExporter extends ImportExportEventEmitter {
3405
3070
  this.options.site.path,
3406
3071
  entryPathRelativeToArchiveRoot
3407
3072
  );
3408
- if (this.isExactPathExcluded(entryPathRelativeToArchiveRoot) || this.isPathExcludedByPattern(fullEntryPathOnDisk)) {
3073
+ if (this.isExactPathExcluded(entryPathRelativeToArchiveRoot) || this.isPathExcludedByPattern(fullEntryPathOnDisk) || this.options.ignoreFilter?.ignores(entryPathRelativeToArchiveRoot)) {
3409
3074
  return false;
3410
3075
  }
3411
3076
  return entry;
3412
3077
  });
3413
3078
  } else {
3414
- if (this.isExactPathExcluded(archivePath)) {
3079
+ if (this.isExactPathExcluded(archivePath) || this.options.ignoreFilter?.ignores(archivePath)) {
3415
3080
  continue;
3416
3081
  }
3417
3082
  this.archiveBuilder.file(fullPath, { name: archivePath });
@@ -3459,8 +3124,12 @@ class DefaultExporter extends ImportExportEventEmitter {
3459
3124
  this.getSitePlugins(this.options.site.path),
3460
3125
  this.getSiteThemes(this.options.site.path)
3461
3126
  ]);
3462
- studioJson.plugins = plugins;
3463
- studioJson.themes = themes;
3127
+ studioJson.plugins = this.options.ignoreFilter ? plugins.filter(
3128
+ (p) => !this.options.ignoreFilter.ignores(`wp-content/plugins/${p.name}`)
3129
+ ) : plugins;
3130
+ studioJson.themes = this.options.ignoreFilter ? themes.filter(
3131
+ (t) => !this.options.ignoreFilter.ignores(`wp-content/themes/${t.name}`)
3132
+ ) : themes;
3464
3133
  const tempDir = await fs$1.mkdtemp(path__default.join(os.tmpdir(), "studio-export-"));
3465
3134
  const studioJsonPath = path__default.join(tempDir, "meta.json");
3466
3135
  await fs$1.writeFile(studioJsonPath, JSON.stringify(studioJson, null, 2));
@@ -3607,6 +3276,48 @@ function untildify(path2) {
3607
3276
  return process.platform === "win32" ? path2 : path2.replace(/^~/, os$1.homedir());
3608
3277
  }
3609
3278
  const logger$c = new Logger();
3279
+ function sendIpcEvent$1(eventTuple) {
3280
+ const ipcEvent = { event: eventTuple };
3281
+ process.send(ipcEvent);
3282
+ }
3283
+ function handleExportIpc(emitter2) {
3284
+ emitter2.on(ExportEvents.EXPORT_START, () => {
3285
+ sendIpcEvent$1([ExportEvents.EXPORT_START, void 0]);
3286
+ });
3287
+ emitter2.on(ExportEvents.BACKUP_CREATE_START, () => {
3288
+ sendIpcEvent$1([ExportEvents.BACKUP_CREATE_START, void 0]);
3289
+ });
3290
+ emitter2.on(ExportEvents.WP_CONTENT_EXPORT_START, () => {
3291
+ sendIpcEvent$1([ExportEvents.WP_CONTENT_EXPORT_START, void 0]);
3292
+ });
3293
+ emitter2.on(ExportEvents.WP_CONTENT_EXPORT_COMPLETE, () => {
3294
+ sendIpcEvent$1([ExportEvents.WP_CONTENT_EXPORT_COMPLETE, void 0]);
3295
+ });
3296
+ emitter2.on(ExportEvents.DATABASE_EXPORT_START, () => {
3297
+ sendIpcEvent$1([ExportEvents.DATABASE_EXPORT_START, void 0]);
3298
+ });
3299
+ emitter2.on(ExportEvents.DATABASE_EXPORT_COMPLETE, () => {
3300
+ sendIpcEvent$1([ExportEvents.DATABASE_EXPORT_COMPLETE, void 0]);
3301
+ });
3302
+ emitter2.on(ExportEvents.BACKUP_CREATE_PROGRESS, (progressData) => {
3303
+ sendIpcEvent$1([ExportEvents.BACKUP_CREATE_PROGRESS, progressData]);
3304
+ });
3305
+ emitter2.on(ExportEvents.BACKUP_CREATE_COMPLETE, () => {
3306
+ sendIpcEvent$1([ExportEvents.BACKUP_CREATE_COMPLETE, void 0]);
3307
+ });
3308
+ emitter2.on(ExportEvents.CONFIG_EXPORT_START, () => {
3309
+ sendIpcEvent$1([ExportEvents.CONFIG_EXPORT_START, void 0]);
3310
+ });
3311
+ emitter2.on(ExportEvents.CONFIG_EXPORT_COMPLETE, () => {
3312
+ sendIpcEvent$1([ExportEvents.CONFIG_EXPORT_COMPLETE, void 0]);
3313
+ });
3314
+ emitter2.on(ExportEvents.EXPORT_COMPLETE, () => {
3315
+ sendIpcEvent$1([ExportEvents.EXPORT_COMPLETE, void 0]);
3316
+ });
3317
+ emitter2.on(ExportEvents.EXPORT_ERROR, (error2) => {
3318
+ sendIpcEvent$1([ExportEvents.EXPORT_ERROR, error2]);
3319
+ });
3320
+ }
3610
3321
  function handleExportEvents(emitter2) {
3611
3322
  emitter2.on(ExportEvents.EXPORT_START, () => {
3612
3323
  logger$c.reportStart(SiteCommandLoggerAction.EXPORT_SITE, __("Starting export…"));
@@ -3651,7 +3362,7 @@ function handleExportEvents(emitter2) {
3651
3362
  throw new LoggerError(__("Export failed"), error2 instanceof Error ? error2 : void 0);
3652
3363
  });
3653
3364
  }
3654
- async function runCommand$e(siteFolder, exportPath, mode) {
3365
+ async function runCommand$e(siteFolder, exportPath, mode = "full", splitDbDumpByTable = false, includeOnlyPaths, applyDeployIgnore = false) {
3655
3366
  try {
3656
3367
  logger$c.reportStart(SiteCommandLoggerAction.START_DAEMON, __("Starting process daemon…"));
3657
3368
  await connectToDaemon();
@@ -3668,17 +3379,27 @@ async function runCommand$e(siteFolder, exportPath, mode) {
3668
3379
  const includes = { database: true, wpContent: true };
3669
3380
  if (mode === "db") {
3670
3381
  includes.wpContent = false;
3382
+ } else if (mode === "content") {
3383
+ includes.database = false;
3671
3384
  }
3385
+ const ignoreFilter = applyDeployIgnore ? await createDeployIgnoreFilter(site.path, SYNC_IGNORE_DEFAULTS) : void 0;
3672
3386
  const exporter = await getExporter({
3673
3387
  site,
3674
3388
  backupFile: exportPath,
3675
3389
  phpVersion: DEFAULT_PHP_VERSION,
3676
- includes
3390
+ includes,
3391
+ splitDatabaseDumpByTable: splitDbDumpByTable,
3392
+ specificSelectionPaths: includeOnlyPaths,
3393
+ ignoreFilter
3677
3394
  });
3678
3395
  if (!exporter) {
3679
3396
  throw new LoggerError(__("No suitable exporter found for the provided backup file"));
3680
3397
  }
3681
- handleExportEvents(exporter);
3398
+ if (process.send) {
3399
+ handleExportIpc(exporter);
3400
+ } else {
3401
+ handleExportEvents(exporter);
3402
+ }
3682
3403
  await exporter.export();
3683
3404
  logger$c.reportSuccess(sprintf(__("%s successfully exported"), exportPath));
3684
3405
  } finally {
@@ -3705,18 +3426,38 @@ const registerCommand$e = (yargs2) => {
3705
3426
  normalize: true,
3706
3427
  demandOption: false,
3707
3428
  description: __(
3708
- "Path to the export file. Full-site exports use .zip or .tar.gz. Database-only exports use .sql."
3429
+ "Path to the export file. All exports can use .zip or .tar.gz. Database-only exports can also use .sql."
3709
3430
  ),
3710
3431
  coerce: (value) => {
3711
3432
  return path__default.resolve(untildify(value));
3712
3433
  }
3713
3434
  }).option("mode", {
3714
3435
  type: "string",
3715
- choices: ["full", "db"],
3436
+ choices: ["full", "content", "db"],
3716
3437
  default: "full",
3717
3438
  description: __(
3718
- "Export the full site or just the database. Default exports full site."
3439
+ "Export the full site, just the content, or just the database. Default exports full site."
3719
3440
  )
3441
+ }).option("split-db-dump-by-table", {
3442
+ type: "boolean",
3443
+ default: false,
3444
+ description: __("Split the database dump by table"),
3445
+ hidden: true
3446
+ }).option("include-only", {
3447
+ type: "array",
3448
+ description: __("Include only the specified paths in the export"),
3449
+ coerce: (value) => {
3450
+ if (!Array.isArray(value)) {
3451
+ throw new Error(__("include-only must be an array"));
3452
+ }
3453
+ return value.map(String);
3454
+ },
3455
+ hidden: true
3456
+ }).option("apply-deploy-ignore", {
3457
+ type: "boolean",
3458
+ default: false,
3459
+ description: __("Apply .deployignore patterns when exporting"),
3460
+ hidden: true
3720
3461
  });
3721
3462
  },
3722
3463
  handler: async (argv) => {
@@ -3725,24 +3466,26 @@ const registerCommand$e = (yargs2) => {
3725
3466
  const timestamp = getTimestamp();
3726
3467
  if (argv.exportFile) {
3727
3468
  exportFile = argv.exportFile;
3728
- } else if (argv.mode === "full") {
3469
+ } else if (argv.mode === "full" || argv.mode === "content") {
3729
3470
  exportFile = path__default.join(process.cwd(), `studio-backup-${timestamp}.zip`);
3730
3471
  } else {
3731
3472
  exportFile = path__default.join(process.cwd(), `studio-backup-${timestamp}.sql`);
3732
3473
  }
3733
- if (argv.mode === "full" && !exportFile.endsWith(".zip") && !exportFile.endsWith(".tar.gz")) {
3474
+ if ((argv.mode === "full" || argv.mode === "content") && !exportFile.endsWith(".zip") && !exportFile.endsWith(".tar.gz")) {
3734
3475
  throw new LoggerError(
3735
3476
  __(
3736
3477
  "Invalid export file extension. Must be .zip or .tar.gz when exporting the full site."
3737
3478
  )
3738
3479
  );
3739
3480
  }
3740
- if (argv.mode === "db" && !exportFile.endsWith(".sql")) {
3741
- throw new LoggerError(
3742
- __("Invalid export file extension. Must be .sql when exporting database only.")
3743
- );
3744
- }
3745
- await runCommand$e(argv.path, exportFile, argv.mode);
3481
+ await runCommand$e(
3482
+ argv.path,
3483
+ exportFile,
3484
+ argv.mode,
3485
+ argv.splitDbDumpByTable,
3486
+ argv.includeOnly,
3487
+ argv.applyDeployIgnore
3488
+ );
3746
3489
  } catch (error2) {
3747
3490
  if (error2 instanceof LoggerError) {
3748
3491
  logger$c.reportError(error2);
@@ -3761,11 +3504,7 @@ class BackupHandlerSql extends ImportExportEventEmitter {
3761
3504
  async extractFiles(file, extractionDirectory) {
3762
3505
  const fileName = path__default.basename(file.path);
3763
3506
  const destPath = path__default.join(extractionDirectory, fileName);
3764
- this.emit(ImportEvents.BACKUP_EXTRACT_START, {
3765
- progress: 0,
3766
- totalFiles: 1,
3767
- processedFiles: 0
3768
- });
3507
+ this.emit(ImportEvents.BACKUP_EXTRACT_START);
3769
3508
  this.emit(ImportEvents.BACKUP_EXTRACT_FILE_START, {
3770
3509
  progress: 0,
3771
3510
  processedFiles: 0,
@@ -3995,11 +3734,7 @@ class BackupHandlerWpress extends ImportExportEventEmitter {
3995
3734
  const fileNames = await this.listFiles(file);
3996
3735
  this.totalFiles = fileNames.length;
3997
3736
  this.processedFiles = 0;
3998
- this.emit(ImportEvents.BACKUP_EXTRACT_START, {
3999
- progress: 0,
4000
- totalFiles: this.totalFiles,
4001
- processedFiles: 0
4002
- });
3737
+ this.emit(ImportEvents.BACKUP_EXTRACT_START);
4003
3738
  const inputFile = await fs.promises.open(file.path, "r");
4004
3739
  let header;
4005
3740
  try {
@@ -4346,7 +4081,6 @@ class BaseBackupImporter extends BaseImporter {
4346
4081
  this.shouldCleanUpBeforeImport = true;
4347
4082
  }
4348
4083
  async import(site) {
4349
- this.emit(ImportEvents.IMPORT_START);
4350
4084
  try {
4351
4085
  if (this.shouldCleanUpBeforeImport) {
4352
4086
  await this.moveExistingWpContentToTrash(site.path);
@@ -4363,18 +4097,19 @@ class BaseBackupImporter extends BaseImporter {
4363
4097
  await this.createEmptyDatabase(dbPath);
4364
4098
  await this.importDatabase(site, this.backup.sqlFiles);
4365
4099
  }
4366
- this.emit(ImportEvents.IMPORT_COMPLETE);
4367
4100
  return {
4368
4101
  extractionDirectory: this.backup.extractionDirectory,
4369
4102
  sqlFiles: this.backup.sqlFiles,
4370
4103
  wpContentFiles: this.backup.wpContentFiles,
4371
4104
  wpContentDirectory: this.backup.wpContentDirectory,
4372
4105
  wpConfig: this.backup.wpConfig,
4373
- meta: this.meta,
4374
- importerType: this.constructor.name
4106
+ meta: this.meta
4375
4107
  };
4376
4108
  } catch (error2) {
4377
- this.emit(ImportEvents.IMPORT_ERROR, error2);
4109
+ this.emit(
4110
+ ImportEvents.IMPORT_ERROR,
4111
+ error2 instanceof Error ? error2.message : String(error2)
4112
+ );
4378
4113
  throw error2;
4379
4114
  }
4380
4115
  }
@@ -4520,6 +4255,12 @@ class JetpackImporter extends BaseBackupImporter {
4520
4255
  this.emit(ImportEvents.IMPORT_META_COMPLETE);
4521
4256
  }
4522
4257
  }
4258
+ async import(site) {
4259
+ this.emit(ImportEvents.IMPORT_START, "jetpack");
4260
+ const result = await super.import(site);
4261
+ this.emit(ImportEvents.IMPORT_COMPLETE, "jetpack");
4262
+ return result;
4263
+ }
4523
4264
  }
4524
4265
  class LocalImporter extends BaseBackupImporter {
4525
4266
  async parseMetaFile() {
@@ -4541,6 +4282,12 @@ class LocalImporter extends BaseBackupImporter {
4541
4282
  this.emit(ImportEvents.IMPORT_META_COMPLETE);
4542
4283
  }
4543
4284
  }
4285
+ async import(site) {
4286
+ this.emit(ImportEvents.IMPORT_START, "local");
4287
+ const result = await super.import(site);
4288
+ this.emit(ImportEvents.IMPORT_COMPLETE, "local");
4289
+ return result;
4290
+ }
4544
4291
  }
4545
4292
  class PlaygroundImporter extends BaseBackupImporter {
4546
4293
  async importDatabase(site, sqlFiles) {
@@ -4559,23 +4306,31 @@ class PlaygroundImporter extends BaseBackupImporter {
4559
4306
  async parseMetaFile() {
4560
4307
  return void 0;
4561
4308
  }
4309
+ async import(site) {
4310
+ this.emit(ImportEvents.IMPORT_START, "playground");
4311
+ const result = await super.import(site);
4312
+ this.emit(ImportEvents.IMPORT_COMPLETE, "playground");
4313
+ return result;
4314
+ }
4562
4315
  }
4563
4316
  class SQLImporter extends BaseImporter {
4564
4317
  async import(site) {
4565
- this.emit(ImportEvents.IMPORT_START);
4318
+ this.emit(ImportEvents.IMPORT_START, "sql");
4566
4319
  try {
4567
4320
  await this.importDatabase(site, this.backup.sqlFiles);
4568
- this.emit(ImportEvents.IMPORT_COMPLETE);
4321
+ this.emit(ImportEvents.IMPORT_COMPLETE, "sql");
4569
4322
  return {
4570
4323
  extractionDirectory: this.backup.extractionDirectory,
4571
4324
  sqlFiles: this.backup.sqlFiles,
4572
4325
  wpConfig: this.backup.wpConfig,
4573
4326
  wpContentFiles: this.backup.wpContentFiles,
4574
- wpContentDirectory: this.backup.wpContentDirectory,
4575
- importerType: this.constructor.name
4327
+ wpContentDirectory: this.backup.wpContentDirectory
4576
4328
  };
4577
4329
  } catch (error2) {
4578
- this.emit(ImportEvents.IMPORT_ERROR, error2);
4330
+ this.emit(
4331
+ ImportEvents.IMPORT_ERROR,
4332
+ error2 instanceof Error ? error2.message : String(error2)
4333
+ );
4579
4334
  throw error2;
4580
4335
  }
4581
4336
  }
@@ -4655,6 +4410,12 @@ class WpressImporter extends BaseBackupImporter {
4655
4410
  await this.addSqlToActivatePlugins(sqlFiles);
4656
4411
  await super.importDatabase(site, sqlFiles);
4657
4412
  }
4413
+ async import(site) {
4414
+ this.emit(ImportEvents.IMPORT_START, "wpress");
4415
+ const result = await super.import(site);
4416
+ this.emit(ImportEvents.IMPORT_COMPLETE, "wpress");
4417
+ return result;
4418
+ }
4658
4419
  }
4659
4420
  class JetpackValidator extends ImportExportEventEmitter {
4660
4421
  canHandle(fileList) {
@@ -4951,43 +4712,101 @@ async function startWordPressServer(site, logger2, options) {
4951
4712
  if (site.enableDebugDisplay) {
4952
4713
  serverConfig.enableDebugDisplay = true;
4953
4714
  }
4954
- const processDesc = await startProcess(processName, wordPressServerChildPath);
4955
- await waitForReadyMessage(processDesc.pmId);
4956
- await sendMessage(
4957
- processDesc.pmId,
4958
- processName,
4959
- {
4960
- topic: "start-server",
4961
- data: { config: serverConfig }
4962
- },
4963
- { logger: logger2 }
4964
- );
4965
- return processDesc;
4715
+ const readyOrExit = await subscribeForReadyOrExit(processName);
4716
+ try {
4717
+ const processDesc = await startProcess(processName, wordPressServerChildPath);
4718
+ await readyOrExit.waitFor(processDesc.pmId);
4719
+ await sendMessage(
4720
+ processDesc.pmId,
4721
+ processName,
4722
+ {
4723
+ topic: "start-server",
4724
+ data: { config: serverConfig }
4725
+ },
4726
+ { logger: logger2 }
4727
+ );
4728
+ return processDesc;
4729
+ } finally {
4730
+ readyOrExit.dispose();
4731
+ }
4732
+ }
4733
+ function buildChildExitedError(processName, stderrTail) {
4734
+ let message2 = `WordPress server child process "${processName}" exited before becoming ready.`;
4735
+ if (stderrTail?.trim()) {
4736
+ message2 += `
4737
+ ${stderrTail.trimEnd()}`;
4738
+ }
4739
+ return new Error(message2);
4966
4740
  }
4967
- async function waitForReadyMessage(pmId) {
4741
+ async function subscribeForReadyOrExit(processName) {
4968
4742
  const bus = await getDaemonBus();
4969
- let timeoutId;
4970
- let readyHandler;
4971
- let abortListener;
4972
- return new Promise((resolve, reject) => {
4973
- timeoutId = setTimeout(() => {
4974
- reject(new Error("Timeout waiting for ready message from WordPress server child"));
4975
- }, PLAYGROUND_CLI_INACTIVITY_TIMEOUT);
4976
- readyHandler = (packet) => {
4977
- if (packet.process.pm_id === pmId && packet.raw.topic === "ready") {
4978
- resolve();
4743
+ const pendingReady = [];
4744
+ const pendingExits = [];
4745
+ let onReady = () => {
4746
+ };
4747
+ let onExit2 = () => {
4748
+ };
4749
+ let waiting = false;
4750
+ const messageHandler = (packet) => {
4751
+ if (packet.process.name !== processName || packet.raw.topic !== "ready") {
4752
+ return;
4753
+ }
4754
+ if (waiting) {
4755
+ onReady();
4756
+ } else {
4757
+ pendingReady.push(packet);
4758
+ }
4759
+ };
4760
+ const eventHandler = (event) => {
4761
+ if (event.process.name !== processName || event.event !== "exit") {
4762
+ return;
4763
+ }
4764
+ if (waiting) {
4765
+ onExit2(event.stderrTail);
4766
+ } else {
4767
+ pendingExits.push(event);
4768
+ }
4769
+ };
4770
+ bus.on("process-message", messageHandler);
4771
+ bus.on("process-event", eventHandler);
4772
+ const waitFor = (pmId) => {
4773
+ waiting = true;
4774
+ let timeoutId;
4775
+ let abortListener;
4776
+ return new Promise((resolve, reject) => {
4777
+ timeoutId = setTimeout(() => {
4778
+ reject(new Error("Timeout waiting for ready message from WordPress server child"));
4779
+ }, PLAYGROUND_CLI_INACTIVITY_TIMEOUT);
4780
+ abortListener = () => {
4781
+ reject(new Error("Operation aborted"));
4782
+ };
4783
+ onReady = () => resolve();
4784
+ onExit2 = (stderrTail) => reject(buildChildExitedError(processName, stderrTail));
4785
+ abortController.signal.addEventListener("abort", abortListener);
4786
+ const bufferedExit = pendingExits.find((event) => event.process.pm_id === pmId);
4787
+ if (bufferedExit) {
4788
+ onExit2(bufferedExit.stderrTail);
4789
+ return;
4979
4790
  }
4980
- };
4981
- abortListener = () => {
4982
- reject(new Error("Operation aborted"));
4983
- };
4984
- abortController.signal.addEventListener("abort", abortListener);
4985
- bus.on("process-message", readyHandler);
4986
- }).finally(() => {
4987
- clearTimeout(timeoutId);
4988
- abortController.signal.removeEventListener("abort", abortListener);
4989
- bus.off("process-message", readyHandler);
4990
- });
4791
+ const bufferedReady = pendingReady.find((packet) => packet.process.pm_id === pmId);
4792
+ if (bufferedReady) {
4793
+ onReady();
4794
+ }
4795
+ }).finally(() => {
4796
+ clearTimeout(timeoutId);
4797
+ abortController.signal.removeEventListener("abort", abortListener);
4798
+ onReady = () => {
4799
+ };
4800
+ onExit2 = () => {
4801
+ };
4802
+ waiting = false;
4803
+ });
4804
+ };
4805
+ const dispose = () => {
4806
+ bus.off("process-message", messageHandler);
4807
+ bus.off("process-event", eventHandler);
4808
+ };
4809
+ return { waitFor, dispose };
4991
4810
  }
4992
4811
  const messageActivityTrackers = /* @__PURE__ */ new Map();
4993
4812
  async function sendMessage(pmId, processName, message2, options = {}) {
@@ -5141,20 +4960,25 @@ async function runBlueprint(site, logger2, options) {
5141
4960
  if (site.enableDebugDisplay) {
5142
4961
  serverConfig.enableDebugDisplay = true;
5143
4962
  }
5144
- const processDesc = await startProcess(processName, wordPressServerChildPath);
4963
+ const readyOrExit = await subscribeForReadyOrExit(processName);
5145
4964
  try {
5146
- await waitForReadyMessage(processDesc.pmId);
5147
- await sendMessage(
5148
- processDesc.pmId,
5149
- processName,
5150
- {
5151
- topic: "run-blueprint",
5152
- data: { config: serverConfig }
5153
- },
5154
- { logger: logger2 }
5155
- );
4965
+ const processDesc = await startProcess(processName, wordPressServerChildPath);
4966
+ try {
4967
+ await readyOrExit.waitFor(processDesc.pmId);
4968
+ await sendMessage(
4969
+ processDesc.pmId,
4970
+ processName,
4971
+ {
4972
+ topic: "run-blueprint",
4973
+ data: { config: serverConfig }
4974
+ },
4975
+ { logger: logger2 }
4976
+ );
4977
+ } finally {
4978
+ await stopProcess(processName);
4979
+ }
5156
4980
  } finally {
5157
- await stopProcess(processName);
4981
+ readyOrExit.dispose();
5158
4982
  }
5159
4983
  }
5160
4984
  const wpCliResultSchema = z.object({
@@ -5192,6 +5016,69 @@ async function setupWordPressFilesOnly(sitePath) {
5192
5016
  }
5193
5017
  await recursiveCopyDirectory(bundledWpPath, sitePath);
5194
5018
  }
5019
+ function sendIpcEvent(eventTuple) {
5020
+ const ipcEvent = { event: eventTuple };
5021
+ process.send(ipcEvent);
5022
+ }
5023
+ function handleImportIpc(emitter2) {
5024
+ emitter2.on(ValidatorEvents.IMPORT_VALIDATION_START, () => {
5025
+ sendIpcEvent([ValidatorEvents.IMPORT_VALIDATION_START, void 0]);
5026
+ });
5027
+ emitter2.on(ValidatorEvents.IMPORT_VALIDATION_COMPLETE, () => {
5028
+ sendIpcEvent([ValidatorEvents.IMPORT_VALIDATION_COMPLETE, void 0]);
5029
+ });
5030
+ emitter2.on(ValidatorEvents.IMPORT_VALIDATION_ERROR, (error2) => {
5031
+ sendIpcEvent([ValidatorEvents.IMPORT_VALIDATION_ERROR, error2]);
5032
+ });
5033
+ emitter2.on(BackupExtractEvents.BACKUP_EXTRACT_START, () => {
5034
+ sendIpcEvent([BackupExtractEvents.BACKUP_EXTRACT_START, void 0]);
5035
+ });
5036
+ emitter2.on(BackupExtractEvents.BACKUP_EXTRACT_PROGRESS, (progressData) => {
5037
+ sendIpcEvent([BackupExtractEvents.BACKUP_EXTRACT_PROGRESS, progressData]);
5038
+ });
5039
+ emitter2.on(BackupExtractEvents.BACKUP_EXTRACT_COMPLETE, () => {
5040
+ sendIpcEvent([BackupExtractEvents.BACKUP_EXTRACT_COMPLETE, void 0]);
5041
+ });
5042
+ emitter2.on(BackupExtractEvents.BACKUP_EXTRACT_WARNING, (warningMessage) => {
5043
+ sendIpcEvent([BackupExtractEvents.BACKUP_EXTRACT_WARNING, warningMessage]);
5044
+ });
5045
+ emitter2.on(BackupExtractEvents.BACKUP_EXTRACT_ERROR, (error2) => {
5046
+ sendIpcEvent([BackupExtractEvents.BACKUP_EXTRACT_ERROR, error2]);
5047
+ });
5048
+ emitter2.on(ImporterEvents.IMPORT_START, (importerType) => {
5049
+ sendIpcEvent([ImporterEvents.IMPORT_START, importerType]);
5050
+ });
5051
+ emitter2.on(ImporterEvents.IMPORT_DATABASE_START, () => {
5052
+ sendIpcEvent([ImporterEvents.IMPORT_DATABASE_START, void 0]);
5053
+ });
5054
+ emitter2.on(ImporterEvents.IMPORT_DATABASE_PROGRESS, (progressData) => {
5055
+ sendIpcEvent([ImporterEvents.IMPORT_DATABASE_PROGRESS, progressData]);
5056
+ });
5057
+ emitter2.on(ImporterEvents.IMPORT_DATABASE_COMPLETE, () => {
5058
+ sendIpcEvent([ImporterEvents.IMPORT_DATABASE_COMPLETE, void 0]);
5059
+ });
5060
+ emitter2.on(ImporterEvents.IMPORT_WP_CONTENT_START, () => {
5061
+ sendIpcEvent([ImporterEvents.IMPORT_WP_CONTENT_START, void 0]);
5062
+ });
5063
+ emitter2.on(ImporterEvents.IMPORT_WP_CONTENT_PROGRESS, (progressData) => {
5064
+ sendIpcEvent([ImporterEvents.IMPORT_WP_CONTENT_PROGRESS, progressData]);
5065
+ });
5066
+ emitter2.on(ImporterEvents.IMPORT_WP_CONTENT_COMPLETE, () => {
5067
+ sendIpcEvent([ImporterEvents.IMPORT_WP_CONTENT_COMPLETE, void 0]);
5068
+ });
5069
+ emitter2.on(ImporterEvents.IMPORT_META_START, () => {
5070
+ sendIpcEvent([ImporterEvents.IMPORT_META_START, void 0]);
5071
+ });
5072
+ emitter2.on(ImporterEvents.IMPORT_META_COMPLETE, () => {
5073
+ sendIpcEvent([ImporterEvents.IMPORT_META_COMPLETE, void 0]);
5074
+ });
5075
+ emitter2.on(ImporterEvents.IMPORT_COMPLETE, (importerType) => {
5076
+ sendIpcEvent([ImporterEvents.IMPORT_COMPLETE, importerType]);
5077
+ });
5078
+ emitter2.on(ImporterEvents.IMPORT_ERROR, (error2) => {
5079
+ sendIpcEvent([ImporterEvents.IMPORT_ERROR, error2]);
5080
+ });
5081
+ }
5195
5082
  function handleImportEvents(emitter2) {
5196
5083
  emitter2.on(ValidatorEvents.IMPORT_VALIDATION_START, () => {
5197
5084
  logger$b.reportSuccess(sprintf(__("Started import…")));
@@ -5290,10 +5177,10 @@ function handleImportEvents(emitter2) {
5290
5177
  logger$b.reportSuccess(__("Site imported successfully"));
5291
5178
  });
5292
5179
  emitter2.on(ImporterEvents.IMPORT_ERROR, (error2) => {
5293
- throw new LoggerError(__("Import failed"), error2 instanceof Error ? error2 : void 0);
5180
+ throw new LoggerError(__("Import failed"), new Error(error2));
5294
5181
  });
5295
5182
  }
5296
- async function runCommand$d(siteFolder, importFile) {
5183
+ async function runCommand$d(siteFolder, importFile, alwaysStartServer = false) {
5297
5184
  let site;
5298
5185
  let wasServerRunning = false;
5299
5186
  let importError;
@@ -5325,16 +5212,26 @@ async function runCommand$d(siteFolder, importFile) {
5325
5212
  { path: importFile, type: getBackupFileType(importFile) },
5326
5213
  DEFAULT_IMPORTER_OPTIONS
5327
5214
  );
5328
- handleImportEvents(importer);
5329
- await importer.import(site);
5215
+ if (process.send) {
5216
+ handleImportIpc(importer);
5217
+ } else {
5218
+ handleImportEvents(importer);
5219
+ }
5220
+ const importResult = await importer.import(site);
5221
+ const importedPhpVersion = importResult.meta?.phpVersion;
5222
+ if (importedPhpVersion && importedPhpVersion !== site.phpVersion) {
5223
+ await updateSitePhpVersion(site.id, importedPhpVersion);
5224
+ site.phpVersion = importedPhpVersion;
5225
+ }
5330
5226
  const siteUrl = getSiteUrl(site);
5331
5227
  await fetch(siteUrl).catch(() => {
5332
5228
  });
5229
+ await emitCliEvent({ event: SITE_EVENTS.UPDATED, data: { siteId: site.id } });
5333
5230
  } catch (error2) {
5334
5231
  importError = error2;
5335
5232
  } finally {
5336
5233
  try {
5337
- if (site && wasServerRunning) {
5234
+ if (site && (wasServerRunning || alwaysStartServer)) {
5338
5235
  logger$b.reportStart(
5339
5236
  SiteCommandLoggerAction.INSTALL_SQLITE,
5340
5237
  __("Setting up SQLite integration, if needed…")
@@ -5374,11 +5271,15 @@ const registerCommand$d = (yargs2) => {
5374
5271
  coerce: (value) => {
5375
5272
  return path__default.resolve(untildify(value));
5376
5273
  }
5274
+ }).option("start-server", {
5275
+ type: "boolean",
5276
+ default: false,
5277
+ hidden: true
5377
5278
  });
5378
5279
  },
5379
5280
  handler: async (argv) => {
5380
5281
  try {
5381
- await runCommand$d(argv.path, argv.importFile);
5282
+ await runCommand$d(argv.path, argv.importFile, argv.startServer);
5382
5283
  } catch (error2) {
5383
5284
  if (error2 instanceof LoggerError) {
5384
5285
  logger$b.reportError(error2);
@@ -5406,14 +5307,68 @@ function getMcpServerConfig() {
5406
5307
  function getMcpServerConfigJson() {
5407
5308
  return JSON.stringify(getMcpServerConfig(), null, 2);
5408
5309
  }
5310
+ const DEFAULT_BROWSER_ARGS = ["--ignore-certificate-errors"];
5409
5311
  let browserPromise = null;
5312
+ const execFileAsync = promisify(execFile);
5313
+ function buildChromiumLaunchAttempts(chromium) {
5314
+ const attempts = [];
5315
+ const executablePath = chromium.executablePath();
5316
+ if (executablePath && existsSync(executablePath)) {
5317
+ attempts.push({
5318
+ args: DEFAULT_BROWSER_ARGS,
5319
+ executablePath
5320
+ });
5321
+ }
5322
+ attempts.push({
5323
+ args: DEFAULT_BROWSER_ARGS
5324
+ });
5325
+ return attempts;
5326
+ }
5327
+ async function installPlaywrightChromium() {
5328
+ const packageJsonPath = fileURLToPath(import.meta.resolve("playwright/package.json"));
5329
+ const cliPath = path__default.join(path__default.dirname(packageJsonPath), "cli.js");
5330
+ await execFileAsync(process.execPath, [cliPath, "install", "chromium"], {
5331
+ env: {
5332
+ ...process.env,
5333
+ CI: process.env.CI ?? "1"
5334
+ },
5335
+ maxBuffer: 10 * 1024 * 1024
5336
+ });
5337
+ }
5338
+ async function ensurePlaywrightChromiumInstalled(chromium, installBrowser = installPlaywrightChromium) {
5339
+ const executablePath = chromium.executablePath();
5340
+ if (existsSync(executablePath)) {
5341
+ return null;
5342
+ }
5343
+ try {
5344
+ await installBrowser();
5345
+ } catch (error2) {
5346
+ return `Studio MCP could not auto-install Playwright Chromium. ${error2 instanceof Error ? error2.message : String(error2)}`;
5347
+ }
5348
+ if (!existsSync(chromium.executablePath())) {
5349
+ return "Studio MCP attempted to install Playwright Chromium, but the browser executable is still unavailable.";
5350
+ }
5351
+ return null;
5352
+ }
5410
5353
  async function getSharedBrowser() {
5411
5354
  if (!browserPromise) {
5412
5355
  browserPromise = (async () => {
5413
5356
  const { chromium } = await import("playwright");
5414
- const browser2 = await chromium.launch({
5415
- args: ["--ignore-certificate-errors"]
5416
- });
5357
+ const launchErrors = [];
5358
+ let browser2 = await tryLaunchChromium(chromium, launchErrors);
5359
+ let installError = null;
5360
+ if (!browser2) {
5361
+ installError = await ensurePlaywrightChromiumInstalled(chromium);
5362
+ if (!installError) {
5363
+ browser2 = await tryLaunchChromium(chromium, launchErrors);
5364
+ }
5365
+ }
5366
+ if (!browser2) {
5367
+ const repairGuidance = installError ?? "If Playwright Chromium is missing, run `studio mcp` again with network access so Studio can install it automatically.";
5368
+ throw new Error(
5369
+ `Unable to launch a browser for Studio MCP screenshot/validation tools. Tried ${launchErrors.map((error2) => error2.split(": ", 1)[0]).join(", ")}. ${repairGuidance} Launch errors: ${launchErrors.join(" | ")}`
5370
+ );
5371
+ }
5417
5372
  const cleanup2 = () => {
5418
5373
  browser2.close().catch(() => {
5419
5374
  });
@@ -5435,6 +5390,21 @@ async function closeSharedBrowser() {
5435
5390
  browserPromise = null;
5436
5391
  }
5437
5392
  }
5393
+ async function tryLaunchChromium(chromium, launchErrors) {
5394
+ for (const attempt of buildChromiumLaunchAttempts(chromium)) {
5395
+ if (!attempt) {
5396
+ continue;
5397
+ }
5398
+ const attemptedTarget = attempt.executablePath ? `executablePath=${attempt.executablePath}` : "playwright-default";
5399
+ try {
5400
+ return await chromium.launch(attempt);
5401
+ } catch (error2) {
5402
+ launchErrors.push(
5403
+ `${attemptedTarget}: ${error2 instanceof Error ? error2.message : String(error2)}`
5404
+ );
5405
+ }
5406
+ }
5407
+ }
5438
5408
  async function getPageDiagnostics(page) {
5439
5409
  try {
5440
5410
  const url = page.url();
@@ -5732,7 +5702,7 @@ function collectMetricsScript() {
5732
5702
  fonts
5733
5703
  };
5734
5704
  }
5735
- const AUDIT_VIEWPORT = { width: 1040, height: 1248 };
5705
+ const AUDIT_VIEWPORT$1 = { width: 1040, height: 1248 };
5736
5706
  const POST_LOAD_DELAY_MS = 2e3;
5737
5707
  async function auditPerformance(siteUrl, urlPath = "/") {
5738
5708
  const targetUrl = `${siteUrl.replace(/\/+$/, "")}${urlPath}`;
@@ -5740,7 +5710,7 @@ async function auditPerformance(siteUrl, urlPath = "/") {
5740
5710
  try {
5741
5711
  const browser2 = await getSharedBrowser();
5742
5712
  const page = await browser2.newPage({
5743
- viewport: AUDIT_VIEWPORT,
5713
+ viewport: AUDIT_VIEWPORT$1,
5744
5714
  ignoreHTTPSErrors: true
5745
5715
  });
5746
5716
  try {
@@ -5773,6 +5743,297 @@ async function auditPerformance(siteUrl, urlPath = "/") {
5773
5743
  };
5774
5744
  }
5775
5745
  }
5746
+ function collectSeoScript(origin) {
5747
+ function text2(el) {
5748
+ return el?.textContent?.trim() ?? "";
5749
+ }
5750
+ function attr(selector, name) {
5751
+ const el = document.querySelector(selector);
5752
+ return el ? el.getAttribute(name) : null;
5753
+ }
5754
+ const titleEl = document.querySelector("title");
5755
+ const titleText = text2(titleEl);
5756
+ const description = attr('meta[name="description" i]', "content");
5757
+ const canonical = attr('link[rel="canonical" i]', "href");
5758
+ const robots = attr('meta[name="robots" i]', "content");
5759
+ const viewport = attr('meta[name="viewport" i]', "content");
5760
+ const htmlLang = document.documentElement.getAttribute("lang");
5761
+ const charset = document.characterSet || attr("meta[charset]", "charset") || attr('meta[http-equiv="Content-Type" i]', "content");
5762
+ const openGraph = {};
5763
+ for (const meta of Array.from(
5764
+ document.querySelectorAll('meta[property^="og:" i]')
5765
+ )) {
5766
+ const property = meta.getAttribute("property");
5767
+ const content = meta.getAttribute("content");
5768
+ if (property && content) {
5769
+ openGraph[property.toLowerCase()] = content;
5770
+ }
5771
+ }
5772
+ const twitter = {};
5773
+ for (const meta of Array.from(
5774
+ document.querySelectorAll('meta[name^="twitter:" i]')
5775
+ )) {
5776
+ const name = meta.getAttribute("name");
5777
+ const content = meta.getAttribute("content");
5778
+ if (name && content) {
5779
+ twitter[name.toLowerCase()] = content;
5780
+ }
5781
+ }
5782
+ const headingCounts = { h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 };
5783
+ const h1Texts = [];
5784
+ const seenLevels = [];
5785
+ const skippedLevels = [];
5786
+ for (const el of Array.from(
5787
+ document.querySelectorAll("h1, h2, h3, h4, h5, h6")
5788
+ )) {
5789
+ const level = Number(el.tagName.substring(1));
5790
+ const tag = el.tagName.toLowerCase();
5791
+ headingCounts[tag]++;
5792
+ if (level === 1) {
5793
+ h1Texts.push(text2(el).slice(0, 200));
5794
+ }
5795
+ const previous = seenLevels[seenLevels.length - 1];
5796
+ if (previous !== void 0 && level > previous + 1) {
5797
+ skippedLevels.push(
5798
+ `${tag} after h${previous} (skipped ${level - previous - 1} level(s))`
5799
+ );
5800
+ }
5801
+ seenLevels.push(level);
5802
+ }
5803
+ let imgTotal = 0;
5804
+ let imgWithAlt = 0;
5805
+ let imgWithoutAlt = 0;
5806
+ let imgEmptyAlt = 0;
5807
+ const missingAltUrls = [];
5808
+ for (const img of Array.from(document.querySelectorAll("img"))) {
5809
+ imgTotal++;
5810
+ const alt = img.getAttribute("alt");
5811
+ if (alt === null) {
5812
+ imgWithoutAlt++;
5813
+ if (missingAltUrls.length < 20) {
5814
+ missingAltUrls.push(img.currentSrc || img.src || "(no src)");
5815
+ }
5816
+ } else if (alt.trim() === "") {
5817
+ imgEmptyAlt++;
5818
+ } else {
5819
+ imgWithAlt++;
5820
+ }
5821
+ }
5822
+ let internal = 0;
5823
+ let external = 0;
5824
+ let nofollow = 0;
5825
+ let emptyText = 0;
5826
+ const emptyTextHrefs = [];
5827
+ for (const a of Array.from(document.querySelectorAll("a[href]"))) {
5828
+ const href = a.getAttribute("href") ?? "";
5829
+ if (!href || href.startsWith("#") || href.startsWith("mailto:") || href.startsWith("tel:")) {
5830
+ continue;
5831
+ }
5832
+ let isExternal = false;
5833
+ try {
5834
+ const url = new URL(href, origin);
5835
+ isExternal = url.origin !== origin;
5836
+ } catch {
5837
+ continue;
5838
+ }
5839
+ if (isExternal) {
5840
+ external++;
5841
+ } else {
5842
+ internal++;
5843
+ }
5844
+ const rel = (a.getAttribute("rel") ?? "").toLowerCase();
5845
+ if (rel.split(/\s+/).includes("nofollow")) {
5846
+ nofollow++;
5847
+ }
5848
+ const visibleText = (a.textContent ?? "").trim();
5849
+ const hasImage = a.querySelector('img[alt]:not([alt=""])');
5850
+ const ariaLabel = a.getAttribute("aria-label")?.trim();
5851
+ if (!visibleText && !hasImage && !ariaLabel) {
5852
+ emptyText++;
5853
+ if (emptyTextHrefs.length < 20) {
5854
+ emptyTextHrefs.push(href);
5855
+ }
5856
+ }
5857
+ }
5858
+ const jsonLdTypes = [];
5859
+ let jsonLdCount = 0;
5860
+ for (const script of Array.from(
5861
+ document.querySelectorAll('script[type="application/ld+json"]')
5862
+ )) {
5863
+ jsonLdCount++;
5864
+ try {
5865
+ const parsed = JSON.parse(script.textContent ?? "");
5866
+ const items = Array.isArray(parsed) ? parsed : [parsed];
5867
+ for (const item of items) {
5868
+ const graph = item?.["@graph"];
5869
+ const nodes = Array.isArray(graph) ? graph : [item];
5870
+ for (const node2 of nodes) {
5871
+ const t = node2?.["@type"];
5872
+ if (typeof t === "string") {
5873
+ jsonLdTypes.push(t);
5874
+ } else if (Array.isArray(t)) {
5875
+ for (const v of t) {
5876
+ if (typeof v === "string") {
5877
+ jsonLdTypes.push(v);
5878
+ }
5879
+ }
5880
+ }
5881
+ }
5882
+ }
5883
+ } catch {
5884
+ }
5885
+ }
5886
+ const wordCount = (document.body.innerText || "").trim().split(/\s+/).filter(Boolean).length;
5887
+ return {
5888
+ title: titleText || null,
5889
+ description,
5890
+ canonical,
5891
+ robots,
5892
+ viewport,
5893
+ htmlLang,
5894
+ charset,
5895
+ openGraph,
5896
+ twitter,
5897
+ headingCounts,
5898
+ h1Texts,
5899
+ skippedLevels,
5900
+ images: {
5901
+ total: imgTotal,
5902
+ withAlt: imgWithAlt,
5903
+ withoutAlt: imgWithoutAlt,
5904
+ emptyAlt: imgEmptyAlt,
5905
+ missingAltUrls
5906
+ },
5907
+ links: {
5908
+ totalInternal: internal,
5909
+ totalExternal: external,
5910
+ nofollow,
5911
+ emptyText,
5912
+ emptyTextHrefs
5913
+ },
5914
+ structuredData: { jsonLdCount, jsonLdTypes: Array.from(new Set(jsonLdTypes)) },
5915
+ wordCount
5916
+ };
5917
+ }
5918
+ async function checkResource(browser2, url) {
5919
+ const context = await browser2.newContext({ ignoreHTTPSErrors: true });
5920
+ try {
5921
+ const response2 = await context.request.get(url, {
5922
+ failOnStatusCode: false,
5923
+ timeout: 1e4
5924
+ });
5925
+ const ok = response2.status() >= 200 && response2.status() < 400;
5926
+ const body = ok ? await response2.text() : "";
5927
+ return { ok, body };
5928
+ } catch {
5929
+ return { ok: false, body: "" };
5930
+ } finally {
5931
+ await context.close();
5932
+ }
5933
+ }
5934
+ function robotsBlocksAll(robotsTxt) {
5935
+ const lines = robotsTxt.split(/\r?\n/);
5936
+ let inGlobalGroup = false;
5937
+ for (const line of lines) {
5938
+ const trimmed = line.split("#")[0].trim();
5939
+ if (!trimmed) {
5940
+ continue;
5941
+ }
5942
+ const [rawKey, ...rest] = trimmed.split(":");
5943
+ const key = rawKey.trim().toLowerCase();
5944
+ const value = rest.join(":").trim();
5945
+ if (key === "user-agent") {
5946
+ inGlobalGroup = value === "*";
5947
+ } else if (inGlobalGroup && key === "disallow" && value === "/") {
5948
+ return true;
5949
+ }
5950
+ }
5951
+ return false;
5952
+ }
5953
+ const AUDIT_VIEWPORT = { width: 1040, height: 1248 };
5954
+ async function auditSeo(siteUrl, urlPath = "/") {
5955
+ const origin = siteUrl.replace(/\/+$/, "");
5956
+ const targetUrl = `${origin}${urlPath}`;
5957
+ try {
5958
+ const browser2 = await getSharedBrowser();
5959
+ const page = await browser2.newPage({
5960
+ viewport: AUDIT_VIEWPORT,
5961
+ ignoreHTTPSErrors: true
5962
+ });
5963
+ try {
5964
+ const response2 = await page.goto(targetUrl, {
5965
+ waitUntil: "networkidle",
5966
+ timeout: 3e4
5967
+ });
5968
+ const httpStatus = response2?.status() ?? 0;
5969
+ const collected = await page.evaluate(collectSeoScript, origin);
5970
+ const [robotsResult, sitemapAtRoot, sitemapIndex] = await Promise.all([
5971
+ checkResource(browser2, `${origin}/robots.txt`),
5972
+ checkResource(browser2, `${origin}/sitemap.xml`),
5973
+ checkResource(browser2, `${origin}/sitemap_index.xml`)
5974
+ ]);
5975
+ let sitemapUrl = null;
5976
+ if (sitemapAtRoot.ok) {
5977
+ sitemapUrl = `${origin}/sitemap.xml`;
5978
+ } else if (sitemapIndex.ok) {
5979
+ sitemapUrl = `${origin}/sitemap_index.xml`;
5980
+ } else if (robotsResult.ok) {
5981
+ const match2 = robotsResult.body.match(/^\s*Sitemap:\s*(\S+)/im);
5982
+ if (match2) {
5983
+ sitemapUrl = match2[1];
5984
+ }
5985
+ }
5986
+ const metrics = {
5987
+ url: targetUrl,
5988
+ httpStatus,
5989
+ wordCount: collected.wordCount,
5990
+ meta: {
5991
+ title: collected.title,
5992
+ titleLength: collected.title?.length ?? 0,
5993
+ description: collected.description,
5994
+ descriptionLength: collected.description?.length ?? 0,
5995
+ canonical: collected.canonical,
5996
+ robots: collected.robots,
5997
+ viewport: collected.viewport,
5998
+ htmlLang: collected.htmlLang,
5999
+ charset: collected.charset
6000
+ },
6001
+ social: {
6002
+ openGraph: collected.openGraph,
6003
+ twitter: collected.twitter
6004
+ },
6005
+ headings: {
6006
+ counts: collected.headingCounts,
6007
+ h1Texts: collected.h1Texts,
6008
+ skippedLevels: collected.skippedLevels
6009
+ },
6010
+ images: collected.images,
6011
+ links: collected.links,
6012
+ structuredData: collected.structuredData,
6013
+ resources: {
6014
+ robotsTxtFound: robotsResult.ok,
6015
+ robotsTxtBlocksAll: robotsResult.ok && robotsBlocksAll(robotsResult.body),
6016
+ sitemapFound: sitemapUrl !== null,
6017
+ sitemapUrl
6018
+ }
6019
+ };
6020
+ return {
6021
+ url: targetUrl,
6022
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6023
+ metrics
6024
+ };
6025
+ } finally {
6026
+ await page.close();
6027
+ }
6028
+ } catch (error2) {
6029
+ return {
6030
+ url: targetUrl,
6031
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6032
+ metrics: null,
6033
+ error: error2 instanceof Error ? error2.message : String(error2)
6034
+ };
6035
+ }
6036
+ }
5776
6037
  var src$2 = { exports: {} };
5777
6038
  var browser = { exports: {} };
5778
6039
  var ms;
@@ -26236,6 +26497,30 @@ async function deleteAllSnapshotsForUserFromConfig(userId) {
26236
26497
  await unlockCliConfig();
26237
26498
  }
26238
26499
  }
26500
+ async function pruneExpiredOrphanedSnapshots(userId, isExpired) {
26501
+ try {
26502
+ await lockCliConfig();
26503
+ const config = await readCliConfig();
26504
+ const siteIds = new Set(config.sites.map((s) => s.id));
26505
+ const filtered = config.snapshots.filter((snapshot) => {
26506
+ if (snapshot.userId !== userId) {
26507
+ return true;
26508
+ }
26509
+ if (siteIds.has(snapshot.localSiteId)) {
26510
+ return true;
26511
+ }
26512
+ return !isExpired(snapshot);
26513
+ });
26514
+ const pruned = config.snapshots.length - filtered.length;
26515
+ if (pruned > 0) {
26516
+ config.snapshots = filtered;
26517
+ await saveCliConfig(config);
26518
+ }
26519
+ return pruned;
26520
+ } finally {
26521
+ await unlockCliConfig();
26522
+ }
26523
+ }
26239
26524
  async function setSnapshotInConfig(snapshotUrl, updates) {
26240
26525
  try {
26241
26526
  await lockCliConfig();
@@ -26473,7 +26758,31 @@ const _delete$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
26473
26758
  registerCommand: registerCommand$b,
26474
26759
  runCommand: runCommand$b
26475
26760
  }, Symbol.toStringTag, { value: "Module" }));
26476
- async function runCommand$a(siteFolder, outputFormat) {
26761
+ function buildSnapshotTable(snapshots) {
26762
+ const colWidths = getColumnWidths([0.4, 0.25, 0.175, 0.175]);
26763
+ const table = new CliTable3({
26764
+ head: [__("URL"), __("Site Name"), __("Updated"), __("Expires in")],
26765
+ wordWrap: true,
26766
+ wrapOnWordBoundary: false,
26767
+ colWidths,
26768
+ style: {
26769
+ head: [],
26770
+ border: []
26771
+ }
26772
+ });
26773
+ for (const snapshot of snapshots) {
26774
+ const durationUntilExpiry = formatDurationUntilExpiry(snapshot.date);
26775
+ const url = `https://${snapshot.url}`;
26776
+ table.push([
26777
+ { href: url, content: url },
26778
+ snapshot.name,
26779
+ format(snapshot.date, "yyyy-MM-dd HH:mm"),
26780
+ durationUntilExpiry
26781
+ ]);
26782
+ }
26783
+ return table.toString();
26784
+ }
26785
+ async function runCommand$a(siteFolder, outputFormat, all = false) {
26477
26786
  const logger2 = new Logger();
26478
26787
  try {
26479
26788
  if (outputFormat === "json") {
@@ -26484,7 +26793,9 @@ async function runCommand$a(siteFolder, outputFormat) {
26484
26793
  return;
26485
26794
  }
26486
26795
  logger2.reportStart(PreviewCommandLoggerAction.VALIDATE, __("Validating…"));
26487
- await getSiteByFolder(siteFolder);
26796
+ if (!all) {
26797
+ await getSiteByFolder(siteFolder);
26798
+ }
26488
26799
  const token = await readAuthToken();
26489
26800
  if (!token) {
26490
26801
  throw new LoggerError(
@@ -26492,7 +26803,8 @@ async function runCommand$a(siteFolder, outputFormat) {
26492
26803
  );
26493
26804
  }
26494
26805
  logger2.reportStart(PreviewCommandLoggerAction.LOAD, __("Loading preview sites…"));
26495
- const snapshots = await getSnapshotsFromConfig(token.id, siteFolder);
26806
+ await pruneExpiredOrphanedSnapshots(token.id, isSnapshotExpired);
26807
+ const snapshots = await getSnapshotsFromConfig(token.id, all ? void 0 : siteFolder);
26496
26808
  if (snapshots.length === 0) {
26497
26809
  logger2.reportSuccess(__("No preview sites found"));
26498
26810
  return;
@@ -26512,30 +26824,41 @@ async function runCommand$a(siteFolder, outputFormat) {
26512
26824
  } else {
26513
26825
  logger2.reportSuccess(snapshotsMessage);
26514
26826
  }
26515
- if (outputFormat === "table") {
26516
- const colWidths = getColumnWidths([0.4, 0.25, 0.175, 0.175]);
26517
- const table = new CliTable3({
26518
- head: [__("URL"), __("Site Name"), __("Updated"), __("Expires in")],
26519
- wordWrap: true,
26520
- wrapOnWordBoundary: false,
26521
- colWidths,
26522
- style: {
26523
- head: [],
26524
- border: []
26525
- }
26526
- });
26827
+ if (all) {
26828
+ const config = await readCliConfig();
26829
+ const siteById = new Map(config.sites.map((site) => [site.id, site]));
26830
+ const unknownSiteLabel = __("Unknown site");
26831
+ const snapshotsByLocalSiteId = /* @__PURE__ */ new Map();
26527
26832
  for (const snapshot of snapshots) {
26528
- const durationUntilExpiry = formatDurationUntilExpiry(snapshot.date);
26529
- const url = `https://${snapshot.url}`;
26530
- table.push([
26531
- { href: url, content: url },
26532
- snapshot.name,
26533
- format(snapshot.date, "yyyy-MM-dd HH:mm"),
26534
- durationUntilExpiry
26535
- ]);
26536
- }
26537
- console.log(table.toString());
26833
+ const key = siteById.has(snapshot.localSiteId) ? snapshot.localSiteId : unknownSiteLabel;
26834
+ const bucket = snapshotsByLocalSiteId.get(key) ?? [];
26835
+ bucket.push(snapshot);
26836
+ snapshotsByLocalSiteId.set(key, bucket);
26837
+ }
26838
+ const sortedGroups = [...snapshotsByLocalSiteId.entries()].map(([key, siteSnapshots]) => {
26839
+ const site = siteById.get(key);
26840
+ const displayName = site ? `${site.name} — ${site.path}` : unknownSiteLabel;
26841
+ return { displayName, siteSnapshots };
26842
+ }).sort((a, b) => {
26843
+ if (b.siteSnapshots.length !== a.siteSnapshots.length) {
26844
+ return b.siteSnapshots.length - a.siteSnapshots.length;
26845
+ }
26846
+ return a.displayName.localeCompare(b.displayName);
26847
+ });
26848
+ const sections = sortedGroups.map(({ displayName, siteSnapshots }) => {
26849
+ const header = sprintf(
26850
+ /* translators: 1: Local site name (may include path for disambiguation). 2: Number of preview sites for that local site. */
26851
+ _n("%1$s (%2$d preview site)", "%1$s (%2$d preview sites)", siteSnapshots.length),
26852
+ displayName,
26853
+ siteSnapshots.length
26854
+ );
26855
+ return `${header}
26856
+ ${buildSnapshotTable(siteSnapshots)}`;
26857
+ });
26858
+ console.log(sections.join("\n\n"));
26859
+ return;
26538
26860
  }
26861
+ console.log(buildSnapshotTable(snapshots));
26539
26862
  } catch (error2) {
26540
26863
  if (error2 instanceof LoggerError) {
26541
26864
  logger2.reportError(error2);
@@ -26555,10 +26878,14 @@ const registerCommand$a = (yargs2) => {
26555
26878
  choices: ["table", "json"],
26556
26879
  default: "table",
26557
26880
  description: __("Output format")
26881
+ }).option("all", {
26882
+ type: "boolean",
26883
+ default: false,
26884
+ description: __("List preview sites for all local sites, grouped by site")
26558
26885
  });
26559
26886
  },
26560
26887
  handler: async (argv) => {
26561
- await runCommand$a(argv.path, argv.format);
26888
+ await runCommand$a(argv.path, argv.format, argv.all);
26562
26889
  }
26563
26890
  });
26564
26891
  };
@@ -26665,21 +26992,6 @@ const update = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
26665
26992
  registerCommand: registerCommand$9,
26666
26993
  runCommand: runCommand$9
26667
26994
  }, Symbol.toStringTag, { value: "Module" }));
26668
- const SYNC_POLL_INTERVAL_MS = 3e3;
26669
- const SYNC_MAX_STALLED_ATTEMPTS = 200;
26670
- const SYNC_PUSH_SIZE_LIMIT_GB = 5;
26671
- const SYNC_PUSH_SIZE_LIMIT_BYTES = SYNC_PUSH_SIZE_LIMIT_GB * 1024 * 1024 * 1024;
26672
- const SYNC_EXCLUSIONS = [
26673
- "database",
26674
- "db.php",
26675
- "debug.log",
26676
- "sqlite-database-integration",
26677
- ".DS_Store",
26678
- "Thumbs.db",
26679
- ".git",
26680
- "node_modules",
26681
- "cache"
26682
- ];
26683
26995
  const sitesEndpointSiteSchema = z.object({
26684
26996
  ID: z.number(),
26685
26997
  is_wpcom_atomic: z.boolean(),
@@ -27102,15 +27414,6 @@ async function fetchRemoteFileTree(token, remoteSiteId, rewindId, treePath = "wp
27102
27414
  throw wrapError(__("Failed to fetch remote file tree"), error2);
27103
27415
  }
27104
27416
  }
27105
- const shouldExcludeFromSync = (itemName) => {
27106
- if (itemName.startsWith(".")) {
27107
- return true;
27108
- }
27109
- if (SYNC_EXCLUSIONS.includes(itemName)) {
27110
- return true;
27111
- }
27112
- return false;
27113
- };
27114
27417
  const shouldLimitDepth = (relativePath) => {
27115
27418
  const normalizedPath = relativePath.replace(/^wp-content\//, "");
27116
27419
  if (normalizedPath.match(/^plugins\/[^/]+\/?$/)) {
@@ -27136,17 +27439,27 @@ function categorizePath(relativePath) {
27136
27439
  }
27137
27440
  return "contents";
27138
27441
  }
27139
- async function listLocalFileTree(sitePath, relativePath, maxDepth = 2, currentDepth = 0) {
27442
+ const shouldExcludeFromSync = (relativePath, deployIgnore) => {
27443
+ const itemName = path__default.basename(relativePath);
27444
+ if (itemName.startsWith(".")) {
27445
+ return true;
27446
+ }
27447
+ return deployIgnore.ignores(relativePath);
27448
+ };
27449
+ async function listLocalFileTree(sitePath, relativePath, maxDepth = 2, currentDepth = 0, deployIgnore) {
27450
+ if (!deployIgnore) {
27451
+ deployIgnore = await createDeployIgnoreFilter(sitePath, SYNC_IGNORE_DEFAULTS);
27452
+ }
27140
27453
  const fullPath = path__default.join(sitePath, relativePath);
27141
27454
  try {
27142
27455
  const entries = await fs__default.promises.readdir(fullPath, { withFileTypes: true });
27143
27456
  const result = [];
27144
27457
  for (const entry of entries) {
27145
- if (shouldExcludeFromSync(entry.name)) {
27458
+ const itemPath = path__default.join(relativePath, entry.name).replace(/\\/g, "/");
27459
+ if (shouldExcludeFromSync(itemPath, deployIgnore)) {
27146
27460
  continue;
27147
27461
  }
27148
27462
  const isDirectory = entry.isDirectory();
27149
- const itemPath = path__default.join(relativePath, entry.name).replace(/\\/g, "/");
27150
27463
  const directoryEntry = {
27151
27464
  name: entry.name,
27152
27465
  isDirectory,
@@ -27159,7 +27472,8 @@ async function listLocalFileTree(sitePath, relativePath, maxDepth = 2, currentDe
27159
27472
  sitePath,
27160
27473
  itemPath,
27161
27474
  maxDepth,
27162
- currentDepth + 1
27475
+ currentDepth + 1,
27476
+ deployIgnore
27163
27477
  );
27164
27478
  } catch {
27165
27479
  directoryEntry.children = [];
@@ -34069,13 +34383,15 @@ async function runCommand$7(siteFolder, syncOptions, remoteSiteIdentifier) {
34069
34383
  wpContent: optionsToSync.includes("uploads") || optionsToSync.includes("plugins") || optionsToSync.includes("themes") || optionsToSync.includes("contents")
34070
34384
  };
34071
34385
  }
34386
+ const deployIgnore = await createDeployIgnoreFilter(site.path, SYNC_IGNORE_DEFAULTS);
34072
34387
  const exporter = await getExporter({
34073
34388
  site,
34074
34389
  backupFile: archivePath,
34075
34390
  includes,
34076
- phpVersion: DEFAULT_PHP_VERSION,
34391
+ phpVersion: site.phpVersion,
34077
34392
  splitDatabaseDumpByTable: true,
34078
- specificSelectionPaths
34393
+ specificSelectionPaths,
34394
+ ignoreFilter: deployIgnore
34079
34395
  });
34080
34396
  if (!exporter) {
34081
34397
  throw new LoggerError(__("No suitable exporter found for the provided backup file"));
@@ -48382,40 +48698,6 @@ function getInstallTypeLaunchStatGroups() {
48382
48698
  };
48383
48699
  }
48384
48700
  }
48385
- async function copySourceDirectoryIfNewerOrMissing({
48386
- sourceDirectoryPath,
48387
- targetDirectoryPath,
48388
- readSourceVersion,
48389
- readTargetVersion
48390
- }) {
48391
- if (!fs__default.existsSync(sourceDirectoryPath)) {
48392
- return;
48393
- }
48394
- let sourceVersion;
48395
- let shouldCopy = false;
48396
- try {
48397
- sourceVersion = await readSourceVersion();
48398
- if (!sourceVersion) {
48399
- return;
48400
- }
48401
- } catch {
48402
- return;
48403
- }
48404
- try {
48405
- const targetVersion = await readTargetVersion();
48406
- const isSourceVersionNewer = targetVersion && semver.gt(sourceVersion, targetVersion);
48407
- shouldCopy = Boolean(!targetVersion || isSourceVersionNewer);
48408
- } catch {
48409
- shouldCopy = true;
48410
- }
48411
- if (shouldCopy) {
48412
- try {
48413
- await fs__default.promises.rm(targetDirectoryPath, { recursive: true, force: true });
48414
- } catch {
48415
- }
48416
- await recursiveCopyDirectory(sourceDirectoryPath, targetDirectoryPath);
48417
- }
48418
- }
48419
48701
  async function copyBundledLatestWpVersion() {
48420
48702
  const bundledWpVersionPath = path__default.join(getWpFilesPath(), "latest", "wordpress");
48421
48703
  const bundledWpVersion = await getWordPressVersionFromInstallation(bundledWpVersionPath);
@@ -48436,110 +48718,6 @@ async function copyBundledLatestWpVersion() {
48436
48718
  await recursiveCopyDirectory(bundledWpVersionPath, latestWpVersionPath);
48437
48719
  }
48438
48720
  }
48439
- const SQLITE_FILENAME = "sqlite-database-integration";
48440
- async function copyBundledSqlite() {
48441
- const sourceSqlitePath = path__default.join(getWpFilesPath(), SQLITE_FILENAME);
48442
- const targetSqlitePath = getSqlitePluginPath();
48443
- await copySourceDirectoryIfNewerOrMissing({
48444
- sourceDirectoryPath: sourceSqlitePath,
48445
- targetDirectoryPath: targetSqlitePath,
48446
- readSourceVersion: async () => semver.coerce(await getSqliteVersionFromInstallation(sourceSqlitePath), {
48447
- includePrerelease: true
48448
- }),
48449
- readTargetVersion: async () => semver.coerce(await getSqliteVersionFromInstallation(targetSqlitePath), {
48450
- includePrerelease: true
48451
- })
48452
- });
48453
- }
48454
- async function copyBundledWpCli() {
48455
- const sourceWpCLIPath = path__default.join(getWpFilesPath(), "wp-cli", "wp-cli.phar");
48456
- const sourceStats = await fs__default.promises.lstat(sourceWpCLIPath);
48457
- let shouldCopy = false;
48458
- try {
48459
- const targetStats = await fs__default.promises.lstat(getWpCliPharPath());
48460
- shouldCopy = sourceStats.size !== targetStats.size || Math.floor(sourceStats.mtimeMs) !== Math.floor(targetStats.mtimeMs);
48461
- } catch {
48462
- shouldCopy = true;
48463
- }
48464
- if (shouldCopy) {
48465
- await fs__default.promises.cp(sourceWpCLIPath, getWpCliPharPath(), {
48466
- mode: fs__default.constants.COPYFILE_FICLONE,
48467
- preserveTimestamps: true
48468
- });
48469
- }
48470
- }
48471
- async function copyBundledSqliteCommand() {
48472
- await copySourceDirectoryIfNewerOrMissing({
48473
- sourceDirectoryPath: path__default.join(getWpFilesPath(), "sqlite-command"),
48474
- targetDirectoryPath: getSqliteCommandPath(),
48475
- readSourceVersion: async () => {
48476
- const versionFilePath = path__default.join(getWpFilesPath(), "sqlite-command", "version");
48477
- return semver.coerce(fs__default.readFileSync(versionFilePath, "utf8"));
48478
- },
48479
- readTargetVersion: async () => {
48480
- const versionFilePath = path__default.join(getSqliteCommandPath(), "version");
48481
- return semver.coerce(fs__default.readFileSync(versionFilePath, "utf8"));
48482
- }
48483
- });
48484
- }
48485
- async function copyBundledTranslations() {
48486
- const sourceTranslationsPath = path__default.join(
48487
- getWpFilesPath(),
48488
- "latest",
48489
- "available-site-translations.json"
48490
- );
48491
- const targetTranslationsPath = path__default.join(
48492
- getWordPressVersionPath("latest"),
48493
- "available-site-translations.json"
48494
- );
48495
- const sourceStats = await fs__default.promises.lstat(sourceTranslationsPath);
48496
- let shouldCopy = false;
48497
- try {
48498
- const targetStats = await fs__default.promises.lstat(targetTranslationsPath);
48499
- shouldCopy = sourceStats.size !== targetStats.size || Math.floor(sourceStats.mtimeMs) !== Math.floor(targetStats.mtimeMs);
48500
- } catch {
48501
- shouldCopy = true;
48502
- }
48503
- if (shouldCopy) {
48504
- await fs__default.promises.cp(sourceTranslationsPath, targetTranslationsPath, {
48505
- mode: fs__default.constants.COPYFILE_FICLONE,
48506
- preserveTimestamps: true
48507
- });
48508
- }
48509
- }
48510
- async function copyBundledAiInstructions() {
48511
- const sourceAiInstructionsPath = path__default.join(getWpFilesPath(), "skills");
48512
- if (!fs__default.existsSync(sourceAiInstructionsPath)) {
48513
- return;
48514
- }
48515
- const isSourceDirectoryDifferent = await areDirectoriesDifferentBySizeAndMtime(
48516
- sourceAiInstructionsPath,
48517
- getAiInstructionsPath()
48518
- );
48519
- if (isSourceDirectoryDifferent) {
48520
- try {
48521
- await fs__default.promises.rm(getAiInstructionsPath(), { recursive: true, force: true });
48522
- } catch {
48523
- }
48524
- await recursiveCopyDirectory(sourceAiInstructionsPath, getAiInstructionsPath());
48525
- }
48526
- }
48527
- async function copyBundledPhpMyAdmin() {
48528
- await copySourceDirectoryIfNewerOrMissing({
48529
- sourceDirectoryPath: path__default.join(getWpFilesPath(), "phpmyadmin"),
48530
- targetDirectoryPath: getPhpMyAdminPath(),
48531
- readSourceVersion: async () => {
48532
- const composerFilePath = path__default.join(getWpFilesPath(), "phpmyadmin", "composer.json");
48533
- const composerFile = JSON.parse(fs__default.readFileSync(composerFilePath, "utf8"));
48534
- return semver.coerce(composerFile.version);
48535
- },
48536
- readTargetVersion: async () => {
48537
- const composerFilePath = path__default.join(getPhpMyAdminPath(), "composer.json");
48538
- const composerFile = JSON.parse(fs__default.readFileSync(composerFilePath, "utf8"));
48539
- return semver.coerce(composerFile.version);
48540
- }
48541
- });
48542
- }
48543
48721
  async function copyBundledLanguagePacks() {
48544
48722
  const sourceLanguagePacksPath = path__default.join(getWpFilesPath(), "latest", "languages");
48545
48723
  if (!fs__default.existsSync(sourceLanguagePacksPath)) {
@@ -48561,13 +48739,7 @@ async function copyBundledLanguagePacks() {
48561
48739
  async function setupServerFiles() {
48562
48740
  const steps = [
48563
48741
  ["WordPress version", copyBundledLatestWpVersion],
48564
- ["SQLite integration", copyBundledSqlite],
48565
- ["WP-CLI", copyBundledWpCli],
48566
- ["SQLite command", copyBundledSqliteCommand],
48567
- ["translations", copyBundledTranslations],
48568
- ["language packs", copyBundledLanguagePacks],
48569
- ["AI instructions", copyBundledAiInstructions],
48570
- ["phpMyAdmin", copyBundledPhpMyAdmin]
48742
+ ["language packs", copyBundledLanguagePacks]
48571
48743
  ];
48572
48744
  for (const [name, step] of steps) {
48573
48745
  try {
@@ -74525,8 +74697,7 @@ const defaultTranslation = {
74525
74697
  const SKIP_LOCALE_TAGS = ["formal", "informal"];
74526
74698
  function getLatestVersionTranslations() {
74527
74699
  const latestVersionTranslationsPath = path__default.join(
74528
- getServerFilesPath(),
74529
- "wordpress-versions",
74700
+ getWpFilesPath(),
74530
74701
  "latest",
74531
74702
  "available-site-translations.json"
74532
74703
  );
@@ -76704,6 +76875,33 @@ const auditPerformanceTool = tool(
76704
76875
  }
76705
76876
  }
76706
76877
  );
76878
+ const auditSeoTool = tool(
76879
+ "rank_me_up",
76880
+ "Runs an on-page SEO audit on a WordPress site page. Returns title and meta description, canonical/robots/viewport tags, Open Graph and Twitter cards, heading structure, image alt-text coverage, internal/external link counts, JSON-LD structured data, and robots.txt/sitemap.xml availability. The site must be running. Use this to identify on-page SEO issues.",
76881
+ {
76882
+ nameOrPath: z$1.string().describe("The site name or file system path — the site must be running"),
76883
+ path: z$1.string().optional().describe('URL path to audit (e.g., "/", "/about"). Defaults to "/".')
76884
+ },
76885
+ async (args) => {
76886
+ try {
76887
+ const site = await resolveSite(args.nameOrPath);
76888
+ const siteUrl = getSiteUrl(site);
76889
+ const urlPath = args.path ?? "/";
76890
+ emitProgress(`Auditing SEO of ${siteUrl}${urlPath}…`);
76891
+ const result = await auditSeo(siteUrl, urlPath);
76892
+ if (result.error) {
76893
+ emitProgress(`Audit failed: ${result.error.slice(0, 80)}`);
76894
+ return errorResult(`SEO audit failed: ${result.error}`);
76895
+ }
76896
+ emitProgress(`SEO audit complete for ${urlPath}`);
76897
+ return textResult(JSON.stringify(result, null, 2));
76898
+ } catch (error2) {
76899
+ return errorResult(
76900
+ `SEO audit failed: ${error2 instanceof Error ? error2.message : String(error2)}`
76901
+ );
76902
+ }
76903
+ }
76904
+ );
76707
76905
  const pushSiteTool = tool(
76708
76906
  "site_push",
76709
76907
  "Pushes a local WordPress site to a WordPress.com site. Requires WordPress.com authentication (studio auth login). Exports the local site, uploads it, and imports it on the remote site. This can take several minutes depending on site size.",
@@ -76840,6 +77038,7 @@ const studioToolDefinitions = [
76840
77038
  takeScreenshotTool,
76841
77039
  installTaxonomyScriptsTool,
76842
77040
  auditPerformanceTool,
77041
+ auditSeoTool,
76843
77042
  pushSiteTool,
76844
77043
  pullSiteTool,
76845
77044
  importSiteTool,
@@ -77045,7 +77244,7 @@ function formatUpdateBanner(currentVersion, latestVersion) {
77045
77244
  });
77046
77245
  return ["", top, ...paddedLines, bottom, ""].join("\n");
77047
77246
  }
77048
- const version = "1.7.10";
77247
+ const version = "1.7.11";
77049
77248
  suppressPunycodeWarning();
77050
77249
  async function main() {
77051
77250
  await setupUpdateNotifier(version);
@@ -77076,9 +77275,9 @@ async function main() {
77076
77275
  }
77077
77276
  }).middleware(async () => {
77078
77277
  const { runMigrations } = await import("./migration-B2m6ZEJz.mjs");
77079
- const { migrations } = await import("./index-4lan3TI_.mjs");
77278
+ const { migrations } = await import("./index-Bej4fL6n.mjs");
77080
77279
  await runMigrations(migrations);
77081
- const { prunePmLogs } = await import("./prune-pm-logs-COryxqeo.mjs");
77280
+ const { prunePmLogs } = await import("./prune-pm-logs-Dm_Bwi7l.mjs");
77082
77281
  await prunePmLogs();
77083
77282
  }).middleware(async (argv) => {
77084
77283
  if (!argv.avoidTelemetry) {
@@ -77110,9 +77309,9 @@ async function main() {
77110
77309
  { registerCommand: registerAuthLogoutCommand },
77111
77310
  { registerCommand: registerAuthStatusCommand }
77112
77311
  ] = await Promise.all([
77113
- import("./login-BtPZeZ4G.mjs"),
77114
- import("./logout-Cr631QzG.mjs"),
77115
- import("./status-DNvMZBqD.mjs")
77312
+ import("./login-Dz63Zdfn.mjs"),
77313
+ import("./logout-CLUKQeZh.mjs"),
77314
+ import("./status-DU07aAtD.mjs")
77116
77315
  ]);
77117
77316
  registerAuthLoginCommand(authYargs);
77118
77317
  registerAuthLogoutCommand(authYargs);
@@ -77120,7 +77319,7 @@ async function main() {
77120
77319
  authYargs.version(false).demandCommand(1, __("You must provide a valid auth command"));
77121
77320
  });
77122
77321
  const studioCodeCommandBuilder = async (aiYargs) => {
77123
- const { registerCommand: registerAiCommand } = await import("./index-BjzOJKPi.mjs").then((n) => n.i);
77322
+ const { registerCommand: registerAiCommand } = await import("./index-Dhun0W1n.mjs").then((n) => n.i);
77124
77323
  registerAiCommand(aiYargs);
77125
77324
  aiYargs.command("sessions", __("Manage code sessions"), async (sessionsYargs) => {
77126
77325
  const [
@@ -77128,9 +77327,9 @@ async function main() {
77128
77327
  { registerCommand: registerAiSessionsListCommand },
77129
77328
  { registerCommand: registerAiSessionsResumeCommand }
77130
77329
  ] = await Promise.all([
77131
- import("./delete-D1924O3o.mjs"),
77132
- import("./list-DOFyyV1f.mjs"),
77133
- import("./resume-BwDwdJtq.mjs")
77330
+ import("./delete-BG-E-HsW.mjs"),
77331
+ import("./list-ck0oK4vb.mjs"),
77332
+ import("./resume-BSIOJnyM.mjs")
77134
77333
  ]);
77135
77334
  sessionsYargs.option("path", {
77136
77335
  hidden: true
@@ -77167,7 +77366,7 @@ async function main() {
77167
77366
  Promise.resolve().then(() => list$2),
77168
77367
  Promise.resolve().then(() => _delete$1),
77169
77368
  Promise.resolve().then(() => update),
77170
- import("./set-D5eeqHbp.mjs")
77369
+ import("./set-CtDZnARG.mjs")
77171
77370
  ]);
77172
77371
  registerPreviewCreateCommand(previewYargs);
77173
77372
  registerPreviewListCommand(previewYargs);
@@ -77194,7 +77393,7 @@ async function main() {
77194
77393
  Promise.resolve().then(() => start),
77195
77394
  Promise.resolve().then(() => stop),
77196
77395
  Promise.resolve().then(() => _delete),
77197
- import("./set-DYnzUz_G.mjs")
77396
+ import("./set-PJvs-Yw5.mjs")
77198
77397
  ]);
77199
77398
  registerSiteStatusCommand(sitesYargs);
77200
77399
  registerSiteCreateCommand(sitesYargs);
@@ -77216,7 +77415,7 @@ async function main() {
77216
77415
  });
77217
77416
  },
77218
77417
  handler: async (argv) => {
77219
- const { commandHandler: wpCliCommandHandler } = await import("./wp-DD2-QiiP.mjs");
77418
+ const { commandHandler: wpCliCommandHandler } = await import("./wp-_X-h-yuW.mjs");
77220
77419
  return wpCliCommandHandler(argv);
77221
77420
  }
77222
77421
  }).command({
@@ -77224,7 +77423,7 @@ async function main() {
77224
77423
  describe: false,
77225
77424
  // Hidden command
77226
77425
  handler: async () => {
77227
- const { commandHandler: eventsCommandHandler } = await import("./_events-BcapW3eh.mjs");
77426
+ const { commandHandler: eventsCommandHandler } = await import("./_events-B8xQ_baD.mjs");
77228
77427
  return eventsCommandHandler();
77229
77428
  }
77230
77429
  }).demandCommand(1, __("You must provide a valid command")).strict();