@automattic/vip 3.21.0 → 3.21.2

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.
package/README.md CHANGED
@@ -17,4 +17,4 @@ VIP-CLI is a tool for interacting with and managing your [WordPress VIP applicat
17
17
  - In the [WordPress VIP Lobby](https://lobby.vip.wordpress.com/) find announcements related to [VIP-CLI](https://lobby.vip.wordpress.com/?s=vip-cli) and [API](https://lobby.vip.wordpress.com/?s=vip%20go%20api).
18
18
  - Find instructions for using [VIP-CLI](https://docs.wpvip.com/vip-cli/) in [WordPress VIP's Documentation](https://docs.wpvip.com/).
19
19
  - [Changelog](https://github.com/Automattic/vip-cli/blob/trunk/docs/CHANGELOG.md) file for VIP-CLI is available.
20
- - [VIP Cloud Changelog](https://wpvipchangelog.wordpress.com/) logs changes to the [VIP-CLI](https://wpvipchangelog.wordpress.com/?s=cli) and other aspects of the platform.
20
+ - Refer to the [VIP Platform Changelog](https://docs.wpvip.com/changelogs/) for [changes to the VIP-CLI](https://docs.wpvip.com/changelogs/vip-cli/) and other aspects of the platform.
@@ -160,6 +160,10 @@ services:
160
160
  services:
161
161
  image: elasticsearch:8.18.2
162
162
  command: /usr/local/bin/docker-entrypoint.sh
163
+ deploy:
164
+ resources:
165
+ limits:
166
+ memory: 1GB
163
167
  environment:
164
168
  ELASTICSEARCH_IS_DEDICATED_NODE: 'no'
165
169
  ELASTICSEARCH_CLUSTER_NAME: 'bespin'
@@ -7,12 +7,13 @@ var _devEnvironmentCli = require("../lib/dev-environment/dev-environment-cli");
7
7
  var _envVars = require("../lib/dev-environment/env-vars");
8
8
  var _logging = require("../lib/envvar/logging");
9
9
  var _tracker = require("../lib/tracker");
10
+ var _utils = require("../lib/utils");
10
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
- const exampleUsage = 'vip dev-env envvar delete';
12
- const usage = 'vip dev-env envvar delete -s vip-local';
12
+ const exampleUsage = 'vip dev-env envvar delete --slug=example-site';
13
+ const usage = 'vip dev-env envvar delete';
13
14
  const examples = [{
14
15
  usage: `${exampleUsage} MY_VARIABLE`,
15
- description: 'Delete the environment variable "MY_VARIABLE" from the environment.'
16
+ description: 'Delete the environment variable "MY_VARIABLE" from the local environment.'
16
17
  }];
17
18
  async function deleteEnvVarCommand(args, opt) {
18
19
  (0, _logging.debug)('args: %o, opt: %o', args, opt);
@@ -26,7 +27,7 @@ async function deleteEnvVarCommand(args, opt) {
26
27
  const data = await (0, _envVars.readEnvFile)(slug);
27
28
  const envVars = [];
28
29
  data.forEach(line => {
29
- const [key] = line.split('=', 2).map(part => part.trim());
30
+ const [key] = (0, _utils.splitKeyValueString)(line);
30
31
  if (key !== name) {
31
32
  envVars.push(line);
32
33
  } else {
@@ -8,15 +8,16 @@ var _devEnvironmentCli = require("../lib/dev-environment/dev-environment-cli");
8
8
  var _envVars = require("../lib/dev-environment/env-vars");
9
9
  var _logging = require("../lib/envvar/logging");
10
10
  var _tracker = require("../lib/tracker");
11
+ var _utils = require("../lib/utils");
11
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
- const exampleUsage = 'vip dev-env envvar get-all';
13
- const usage = 'vip dev-env envvar get-all -s vip-local';
13
+ const exampleUsage = 'vip dev-env envvar get-all --slug=example-site';
14
+ const usage = 'vip dev-env envvar get-all';
14
15
  const examples = [{
15
16
  usage: exampleUsage,
16
- description: 'Retrieve a list of all environment variables in the default table format.'
17
+ description: 'Retrieve a list of all local environment variables in the default table format.'
17
18
  }, {
18
19
  usage: `${exampleUsage} --format=csv`,
19
- description: 'Retrieve a list of all environment variables in CSV format.'
20
+ description: 'Retrieve a list of all local environment variables in CSV format.'
20
21
  }];
21
22
  async function getAllEnvVarsCommand(args, opt) {
22
23
  (0, _logging.debug)('args: %o, opt: %o', args, opt);
@@ -28,7 +29,7 @@ async function getAllEnvVarsCommand(args, opt) {
28
29
  try {
29
30
  const data = await (0, _envVars.readEnvFile)(slug);
30
31
  const envVars = data.map(line => {
31
- const [key, value] = line.split('=', 2).map(part => part.trim());
32
+ const [key, value] = (0, _utils.splitKeyValueString)(line);
32
33
  return {
33
34
  name: key,
34
35
  value: (0, _envVars.parseEnvValue)(value)
@@ -7,12 +7,13 @@ var _devEnvironmentCli = require("../lib/dev-environment/dev-environment-cli");
7
7
  var _envVars = require("../lib/dev-environment/env-vars");
8
8
  var _logging = require("../lib/envvar/logging");
9
9
  var _tracker = require("../lib/tracker");
10
+ var _utils = require("../lib/utils");
10
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
- const exampleUsage = 'vip dev-env envvar get';
12
- const usage = 'vip dev-env envvar get -s vip-local';
12
+ const exampleUsage = 'vip dev-env envvar get --slug=example-site';
13
+ const usage = 'vip dev-env envvar get';
13
14
  const examples = [{
14
15
  usage: `${exampleUsage} MY_VARIABLE`,
15
- description: 'Retrieve the value of the environment variable "MY_VARIABLE".'
16
+ description: 'Retrieve the value of the local environment variable "MY_VARIABLE".'
16
17
  }];
17
18
  async function getEnvVarsCommand(args, opt) {
18
19
  (0, _logging.debug)('args: %o, opt: %o', args, opt);
@@ -23,7 +24,7 @@ async function getEnvVarsCommand(args, opt) {
23
24
  await (0, _tracker.trackEvent)(`${trackingPrefix}execute`, trackingInfo);
24
25
  try {
25
26
  const data = await (0, _envVars.readEnvFile)(slug);
26
- const envVar = data.map(line => line.split('=', 2)).find(([key]) => name === key.trim());
27
+ const envVar = data.map(line => (0, _utils.splitKeyValueString)(line)).find(([key]) => name === key.trim());
27
28
  if (undefined === envVar) {
28
29
  process.stderr.write(_chalk.default.yellow(`The environment variable "${name}" does not exist\n`));
29
30
  process.exitCode = 1;
@@ -8,11 +8,12 @@ var _devEnvironmentCli = require("../lib/dev-environment/dev-environment-cli");
8
8
  var _envVars = require("../lib/dev-environment/env-vars");
9
9
  var _logging = require("../lib/envvar/logging");
10
10
  var _tracker = require("../lib/tracker");
11
+ var _utils = require("../lib/utils");
11
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
13
  const usage = 'vip dev-env envvar list';
13
14
  const examples = [{
14
- usage: 'vip dev-env envvar list -s vip-local',
15
- description: 'List the names of all environment variables.'
15
+ usage: 'vip dev-env envvar list --slug=example-site',
16
+ description: 'List the names of all environment variables on the local environment in table format.'
16
17
  }];
17
18
  async function listEnvVarsCommand(args, opt) {
18
19
  (0, _logging.debug)('args: %o, opt: %o', args, opt);
@@ -24,7 +25,7 @@ async function listEnvVarsCommand(args, opt) {
24
25
  try {
25
26
  const data = await (0, _envVars.readEnvFile)(slug);
26
27
  const envVars = data.map(line => {
27
- const [key] = line.split('=', 2);
28
+ const [key] = (0, _utils.splitKeyValueString)(line);
28
29
  return {
29
30
  name: key.trim()
30
31
  };
@@ -10,18 +10,19 @@ var _input = require("../lib/envvar/input");
10
10
  var _logging = require("../lib/envvar/logging");
11
11
  var _readFile = require("../lib/envvar/read-file");
12
12
  var _tracker = require("../lib/tracker");
13
+ var _utils = require("../lib/utils");
13
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
- const exampleUsage = 'vip dev-env envvar set';
15
- const usage = 'vip dev-env envvar set -s vip-local';
15
+ const exampleUsage = 'vip dev-env envvar set --slug=example-site';
16
+ const usage = 'vip dev-env envvar set';
16
17
  const examples = [{
17
18
  usage: `${exampleUsage} MY_VARIABLE`,
18
- description: 'Add or update the environment variable "MY_VARIABLE" and assign its value at the prompt.'
19
+ description: 'Add or update the local environment variable "MY_VARIABLE" and assign its value at the prompt.'
19
20
  }, {
20
21
  usage: `${exampleUsage} MY_VARIABLE MY_VALUE`,
21
- description: 'Add or update the environment variable "MY_VARIABLE" and assign its value to "MY_VALUE".'
22
+ description: 'Add or update the local environment variable "MY_VARIABLE" and assign its value to "MY_VALUE".'
22
23
  }, {
23
24
  usage: `${exampleUsage} MULTILINE_ENV_VAR --from-file=envvar-value.txt`,
24
- description: 'Add or update the environment variable "MULTILINE_ENV_VAR" and assign the multiline contents of local file envvar-value.txt as its value.'
25
+ description: 'Add or update the local environment variable "MULTILINE_ENV_VAR" and assign the multiline contents of local file envvar-value.txt as its value.'
25
26
  }];
26
27
  async function deleteEnvVarCommand(args, opt) {
27
28
  (0, _logging.debug)('args: %o, opt: %o', args, opt);
@@ -51,7 +52,7 @@ async function deleteEnvVarCommand(args, opt) {
51
52
  newValue = (0, _envVars.quoteEnvValue)(newValue);
52
53
  let replaced = false;
53
54
  const envVars = data.map(line => {
54
- const [key] = line.split('=', 2).map(part => part.trim());
55
+ const [key] = (0, _utils.splitKeyValueString)(line);
55
56
  if (key === name) {
56
57
  replaced = true;
57
58
  return `${key}=${newValue}`;
@@ -4,24 +4,24 @@
4
4
  var _command = _interopRequireDefault(require("../lib/cli/command"));
5
5
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
6
6
  const usage = 'vip dev-env envvar';
7
- const exampleUsage = 'vip dev-env envvar --slug vip-local';
7
+ const exampleUsage = 'vip dev-env envvar';
8
8
  const examples = [{
9
- usage: `${exampleUsage} set MY_VARIABLE`,
10
- description: 'Add or update the environment variable "MY_VARIABLE" and assign its value at the prompt.'
9
+ usage: `${exampleUsage} set --slug=example-site MY_VARIABLE`,
10
+ description: 'Add or update the local environment variable "MY_VARIABLE" and assign its value at the prompt.'
11
11
  }, {
12
- usage: `${exampleUsage} get MY_VARIABLE`,
13
- description: 'Retrieve the value of the environment variable "MY_VARIABLE".'
12
+ usage: `${exampleUsage} get --slug=example-site MY_VARIABLE`,
13
+ description: 'Retrieve the value of the local environment variable "MY_VARIABLE".'
14
14
  }, {
15
- usage: `${exampleUsage} get-all`,
16
- description: 'Retrieve a list of all environment variables in the default table format.'
15
+ usage: `${exampleUsage} get-all --slug=example-site`,
16
+ description: 'Retrieve a list of all local environment variables in the default table format.'
17
17
  }, {
18
- usage: `${exampleUsage} list`,
19
- description: 'List the names of all environment variables.'
18
+ usage: `${exampleUsage} list --slug=example-site`,
19
+ description: 'List the names of all local environment variables.'
20
20
  }, {
21
- usage: `${exampleUsage} delete MY_VARIABLE`,
22
- description: 'Delete the environment variable "MY_VARIABLE" from the environment.'
21
+ usage: `${exampleUsage} delete --slug=example-site MY_VARIABLE`,
22
+ description: 'Delete the local environment variable "MY_VARIABLE" from the environment.'
23
23
  }];
24
24
  (0, _command.default)({
25
25
  requiredArgs: 0,
26
26
  usage
27
- }).command('delete', 'Delete an environment variable.').command('get', 'Retrieve the value of an environment variable.').command('get-all', 'Retrieve the names and values of all environment variables.').command('list', 'List the names of all environment variables.').command('set', 'Add or update an environment variable.').examples(examples).argv(process.argv);
27
+ }).command('delete', 'Delete a local environment variable.').command('get', 'Retrieve the value of a local environment variable.').command('get-all', 'Retrieve the names and values of all local environment variables.').command('list', 'List the names of all local environment variables.').command('set', 'Add or update a local environment variable that begins with an uppercase letter and only includes the allowed characters A-Z, 0-9, or _.').examples(examples).argv(process.argv);
@@ -12,22 +12,22 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
12
12
  const usage = 'vip dev-env sync sql';
13
13
  const examples = [{
14
14
  usage: `vip @example-app.develop dev-env sync sql --slug=example-site`,
15
- description: 'Sync the database of the develop environment in the "example-app" application to a local environment named "example-site".'
15
+ description: 'Sync the entire database of the develop environment in the "example-app" application to a local environment named "example-site".'
16
16
  }, {
17
17
  usage: `vip @example-app.develop dev-env sync sql --slug=example-site --table=wp_posts --table=wp_comments`,
18
- description: 'Sync only the wp_posts and wp_comments tables from the database of the develop environment in the "example-app" application to a local environment named "example-site".'
18
+ description: 'Sync only the wp_posts and wp_comments tables from the database of the develop environment to a local environment named "example-site".'
19
19
  }, {
20
20
  usage: `vip @example-app.develop dev-env sync sql --slug=example-site --table=wp_posts,wp_comments`,
21
- description: 'Sync only the wp_posts and wp_comments tables using comma-separated syntax from the database of the develop environment in the "example-app" application to a local environment named "example-site".'
21
+ description: 'Use comma-separated syntax to specify that only the wp_posts and wp_comments tables are synced.'
22
22
  }, {
23
23
  usage: `vip @example-app.develop dev-env sync sql --slug=example-site --site-id=2 --site-id=3`,
24
- description: 'Sync only the tables for the network sites with IDs 2 and 3 from the database of the develop environment in the "example-app" application to a local environment named "example-site".'
24
+ description: 'Sync only the tables related to network site ID 2 and network site ID 3.'
25
25
  }, {
26
26
  usage: `vip @example-app.develop dev-env sync sql --slug=example-site --site-id=2,3`,
27
- description: 'Sync only the tables for the network sites with IDs 2 and 3 using comma-separated syntax from the database of the develop environment in the "example-app" application to a local environment named "example-site".'
27
+ description: 'Use comma-separated syntax to specify that only the tables related to network site ID 2 and network site ID 3 are synced.'
28
28
  }, {
29
29
  usage: `vip @example-app.develop dev-env sync sql --slug=example-site --config-file=~/dev-env-sync-config.json`,
30
- description: 'Use the specified config file to determine what to sync from the database of the develop environment in the "example-app" application to a local environment named "example-site".'
30
+ description: 'Reference a local configuration file that specifies the data to sync to a local environment.'
31
31
  }];
32
32
  const appQuery = `
33
33
  id,
@@ -43,6 +43,7 @@ const appQuery = `
43
43
  uniqueLabel
44
44
  isMultisite
45
45
  wpSitesSDS(first:500) {
46
+ total
46
47
  nodes {
47
48
  id
48
49
  blogId
@@ -58,7 +59,7 @@ const appQuery = `
58
59
  requiredArgs: 0,
59
60
  module: 'dev-env-sync-sql',
60
61
  usage
61
- }).option('slug', 'A unique name for a local environment. Default is "vip-local".', undefined, _devEnvironmentCli.processSlug).option('table', 'A table to sync from the remote environment to the local environment. Multiple tables can be specified with multiple --table flags or as a comma-separated list.').option('site-id', 'The ID of a network site to sync from the remote environment to the local environment. Multiple site IDs can be specified with multiple --site-id flags or as a comma-separated list.').option('wpcli-command', 'The WP-CLI command to run on the remote environment to retrieve the database export configuration.').option('config-file', 'The backup copy config file to use for the sync.', undefined).option('force', 'Skip validations.', undefined, _devEnvironmentCli.processBooleanOption).examples(examples).argv(process.argv, async (arg, opt) => {
62
+ }).option('slug', 'A unique name for a local environment. Default is "vip-local".', undefined, _devEnvironmentCli.processSlug).option('table', 'The name of a table to include in the partial database sync. Accepts a string value and can be passed more than once with a different value, or add multiple values in a comma-separated list.').option('site-id', 'The ID of a network site to include in the partial database sync. Accepts an integer value (can be passed more than once with different values), or multiple integer values in a comma-separated list.').option('wpcli-command', 'Run a custom WP-CLI command that has logic to retrieve specific data for the partial database export.').option('config-file', 'A local configuration file that specifies the data to include in the partial database sync. Accepts a relative or absolute path to the file.', undefined).option('force', 'Skip validations.', undefined, _devEnvironmentCli.processBooleanOption).examples(examples).argv(process.argv, async (arg, opt) => {
62
63
  const {
63
64
  app,
64
65
  env,
@@ -6,7 +6,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
6
6
  const usage = 'vip dev-env sync';
7
7
  const examples = [{
8
8
  usage: `vip @example-app.develop dev-env sync sql --slug=example-site`,
9
- description: 'Sync the database of the develop environment in the "example-app" application to a local environment named "example-site".'
9
+ description: 'Sync the entire database of the develop environment in the "example-app" application to a local environment named "example-site".'
10
10
  }];
11
11
  (0, _command.default)({
12
12
  requiredArgs: 1,
@@ -14,22 +14,22 @@ const examples = [{
14
14
  description: 'Download an archived copy of the most recent database backup for an environment to a specific file path.'
15
15
  }, {
16
16
  usage: 'vip @example-app.develop export sql --generate-backup',
17
- description: 'Generate a fresh database backup for an environment and download a copy of that backup.'
17
+ description: 'Generate a fresh database backup for an environment and download an archived copy of that backup.'
18
18
  }, {
19
19
  usage: 'vip @example-app.develop export sql --table=wp_posts --table=wp_comments',
20
- description: 'Generate a database backup including only the wp_posts and wp_comments tables, and download a copy of that backup.'
20
+ description: 'Generate and download an archived partial database export file that includes only the wp_posts and wp_comments tables.'
21
21
  }, {
22
22
  usage: 'vip @example-app.develop export sql --table=wp_posts,wp_comments',
23
- description: 'Generate a database backup including only the wp_posts and wp_comments tables using comma-separated syntax, and download a copy of that backup.'
23
+ description: 'Use comma-separated syntax to generate and download a partial database export file that includes only the wp_posts and wp_comments tables.'
24
24
  }, {
25
25
  usage: 'vip @example-app.develop export sql --site-id=2 --site-id=3',
26
- description: 'Generate a database backup including only the tables related to the network sites with IDs 2 and 3, and download a copy of that backup.'
26
+ description: 'Generate and download a partial database export file that includes only the tables related to network site ID 2 and network site ID 3.'
27
27
  }, {
28
28
  usage: 'vip @example-app.develop export sql --site-id=2,3',
29
- description: 'Generate a database backup including only the tables related to the network sites with IDs 2 and 3 using comma-separated syntax, and download a copy of that backup.'
29
+ description: 'Use comma-separated syntax to generate and download a partial database export file that includes only the tables related to the network site ID 2 and network site ID 3.'
30
30
  }, {
31
31
  usage: 'vip @example-app.develop export sql --config-file=~/db-export-config.json',
32
- description: 'Generate a database backup using the specified config file, and download a copy of that backup.'
32
+ description: 'Reference a local configuration file that specifies the data to include in the partial database export file that is generated and dowloaded.'
33
33
  }];
34
34
  const appQuery = `
35
35
  id,
@@ -52,7 +52,7 @@ const appQuery = `
52
52
  module: 'export-sql',
53
53
  requiredArgs: 0,
54
54
  usage: 'vip export sql'
55
- }).option('output', 'Download the file to a specific local directory path with a custom file name.').option('table', 'A table to export from the remote environment. Multiple tables can be specified with multiple --table flags or as a comma-separated list.').option('site-id', 'The ID of a network site to export from the remote environment. Multiple site IDs can be specified with multiple --site-id flags or as a comma-separated list.').option('wpcli-command', 'The WP-CLI command to run on the remote environment to retrieve the database export configuration.').option('config-file', 'The backup copy config file to use for the export.', undefined).option('generate-backup', 'Generate a fresh database backup and export a copy of that backup.').examples(examples).argv(process.argv, async (arg, {
55
+ }).option('output', 'Download the file to a specific local directory path with a custom file name.').option('table', 'The name of a table to include in the partial database export. Accepts a string value and can be passed more than once with a different value, or add multiple values in a comma-separated list.').option('site-id', 'The ID of a network site to include in the partial database export. Accepts an integer value and can be passed more than once with a different value, or add multiple values in a comma-separated list.').option('wpcli-command', 'Run a custom WP-CLI command that has logic to retrieve specific data for the partial database export.').option('config-file', 'A local configuration file that specifies the data to include in the partial database export. Accepts a relative or absolute path to the file.', undefined).option('generate-backup', 'Generate a fresh database backup and export an archived copy of that backup.').examples(examples).argv(process.argv, async (arg, {
56
56
  app,
57
57
  env,
58
58
  output,
@@ -153,7 +153,7 @@ async function gates(app, env, fileNameOrURL, isUrl = false, md5 = null) {
153
153
  exit.withError('The provided MD5 hash is invalid. It should be a 32-character hexadecimal string.');
154
154
  }
155
155
  if (!isUrl && md5) {
156
- console.log(_chalk.default.yellowBright('The --md5 parameter is only used for URL imports. It will be ignored for local file imports.'));
156
+ console.log(_chalk.default.yellowBright('The --md5 parameter is only valid for imports from a remote URL. This option will be ignored.'));
157
157
  }
158
158
  if (!isUrl) {
159
159
  const fileName = fileNameOrURL;
@@ -249,42 +249,37 @@ const examples = [
249
249
  // `sql` subcommand
250
250
  {
251
251
  usage: 'vip @example-app.develop import sql file.sql',
252
- description: 'Import the local SQL database backup file "file.sql" to the develop environment of the "example-app" application.'
252
+ description: 'Import a local SQL database file named "file.sql" to the develop environment of the "example-app" application.'
253
253
  },
254
- // URL import
255
- {
256
- usage: 'vip @example-app.develop import sql https://example.org/file.sql',
257
- description: 'Import a remote SQL database backup file from the URL with MD5 hash verification to the develop environment of the "example-app" application.'
258
- },
259
- // URL import with HTTP Basic Auth
260
- {
261
- usage: 'vip @example-app.develop import sql https://username:password@example.org/file.sql',
262
- description: 'Import a remote SQL database backup file from a URL that requires HTTP Basic Authentication.'
263
- },
264
- // `search-replace` flag
254
+ // `search-replace` option
265
255
  {
266
256
  usage: 'vip @example-app.develop import sql file.sql --search-replace="from.example.com,to.example.com" --search-replace="example.com/from,example.com/to"',
267
- description: 'Perform multiple search and replace operations on the SQL database file during the import process.'
257
+ description: 'Perform multiple search and replace operations on a local SQL database file during the import process.'
268
258
  },
269
- // `in-place` flag
259
+ // `in-place` option
270
260
  {
271
261
  usage: 'vip @example-app.develop import sql file.sql --search-replace="https://from.example.com,https://to.example.com" --in-place',
272
- description: 'Perform a search and replace operation on "file.sql" locally, save the changes, then import the updated file.'
262
+ description: 'Perform a search and replace operation on a local file named "file.sql", save the changes to the file, then import the updated file.'
273
263
  },
274
- // `output` flag
264
+ // `output` option
275
265
  {
276
266
  usage: 'vip @example-app.develop import sql file.sql --search-replace="https://from.example.com,https://to.example.com" --output="updated-file.sql"',
277
- description: 'Create a copy of the imported file with the completed search and replace operations and save it locally to a file named "updated-file.sql".'
267
+ description: 'At the completion of the import of a local SQL database file that was modified by a search and replace operation, create a copy of the file in its updated state and save it locally to a new file named "updated-file.sql".'
268
+ },
269
+ // remote URL import
270
+ {
271
+ usage: 'vip @example-app.develop import sql https://example.com/file.sql --md5=5d41402abc4b2a76b9719d911017c592',
272
+ description: 'Import a SQL database file from a remote URL and verify the integrity of its contents with an MD5 hash.'
278
273
  },
279
- // URL import with headers
274
+ // remote URL import with HTTP basic auth
280
275
  {
281
- usage: 'vip @example-app.develop import sql https://example.org/file.sql --header "Authorization: Bearer token"',
282
- description: 'Import a remote SQL database backup file from a URL with custom authorization header.'
276
+ usage: 'vip @example-app.develop import sql https://username:password@example.com/file.sql',
277
+ description: 'Access and import a remote SQL database file by formatting the URL with valid HTTP basic authentication credentials.'
283
278
  },
284
- // URL import with multiple headers
279
+ // remote URL import with headers
285
280
  {
286
- usage: 'vip @example-app.develop import sql https://example.org/file.sql --header "Authorization: Bearer token" --header "User-Agent: VIP CLI"',
287
- description: 'Import a remote SQL database backup file from a URL with multiple custom headers.'
281
+ usage: 'vip @example-app.develop import sql https://example.com/file.sql --header="Authorization: bearer-token-value"',
282
+ description: 'Access and import a SQL database file located at a remote URL by passing a valid authorization header and bearer token.'
288
283
  },
289
284
  // `sql status` subcommand
290
285
  {
@@ -419,7 +414,7 @@ const displayPlaybook = ({
419
414
  requiredArgs: 1,
420
415
  module: 'import-sql',
421
416
  usage
422
- }).command('status', 'Check the status of a SQL database import currently in progress.').option('skip-validate', 'Do not perform file validation prior to import. If the file contains unsupported entries, the import is likely to fail.').option('search-replace', 'Search for a string in the SQL file and replace it with a new string. Separate the values by a comma only; no spaces (e.g. --search-replace="from,to").').option('in-place', 'Overwrite the local input file with the results of the search and replace operation prior to import.').option('output', 'The local file path to save a copy of the results from the search and replace operation when the --search-replace option is passed. Ignored when used with the --in-place option.').option('skip-maintenance-mode', 'Imports data without putting the environment in maintenance mode. Available only for unlaunched environments. Caution: This may cause site instability during import.').option('md5', 'MD5 hash of the remote SQL file for verification. If not provided, the verification will not be performed.').option('header', 'Add a header to the request when downloading from a URL. Format: "Name: Value". Can be used multiple times.').examples(examples)
417
+ }).command('status', 'Check the status of a SQL database import currently in progress.').option('skip-validate', 'Do not perform file validation prior to import. If the file contains unsupported entries, the import is likely to fail.').option('search-replace', 'Search for a string in a local or remote SQL database file and replace it with a new string. Separate the values by a comma only; no spaces (e.g. --search-replace="from,to"). Can be passed more than once.').option('in-place', 'Overwrite a local SQL database file with the results of a search and replace operation prior to import.').option('output', 'Save the results of a --search-replace operation that is run against a local SQL database file to a copy of that file. Accepts a local file path. Ignored when used with the --in-place option.').option('skip-maintenance-mode', 'Prevent an unlaunched environment from going into maintenance mode during the import of a local or remote SQL database file. Skipping maintenance mode can cause site instability during import.').option('md5', 'Verify the integrity of a remote SQL database file. Accepts an MD5 hash value.').option('header', 'Pass a header name and value (Formatted as "Name: Value") in a request for a remote SQL database file. Can be passed more than once for multiple headers and values.').examples(examples)
423
418
  // eslint-disable-next-line complexity
424
419
  .argv(process.argv, async (arg, opts) => {
425
420
  const {
@@ -444,14 +439,14 @@ const displayPlaybook = ({
444
439
  // Parse and validate headers
445
440
  const headers = parseHeaders(header);
446
441
  if (!isUrl && headers.length > 0) {
447
- console.log(_chalk.default.yellowBright('The --header parameter is only used for URL imports. It will be ignored for local file imports.'));
442
+ console.log(_chalk.default.yellowBright('The --header option is only valid for imports from a remote URL. This option will be ignored.'));
448
443
  }
449
444
  if (isUrl && opts.inPlace) {
450
- console.log(_chalk.default.yellowBright('The --in-place option is not supported when importing from a URL. This option will be ignored.'));
445
+ console.log(_chalk.default.yellowBright('The --in-place option is only valid for imports from a remote URL. This option will be ignored.'));
451
446
  opts.inPlace = false;
452
447
  }
453
448
  if (isUrl && opts.output) {
454
- console.log(_chalk.default.yellowBright('The --output option is not supported when importing from a URL. This option will be ignored.'));
449
+ console.log(_chalk.default.yellowBright('The --output option is only valid for imports of a local file. This option will be ignored.'));
455
450
  opts.output = undefined;
456
451
  }
457
452
  debug('Options: ', opts);
@@ -387,7 +387,7 @@ const examples = [{
387
387
  crlfDelay: Infinity
388
388
  };
389
389
  if (isSubShell) {
390
- subShellSettings.prompt = (0, _chalk.default)`{bold.yellowBright ${promptIdentifier}:}{blue ~}$ `;
390
+ subShellSettings.prompt = _chalk.default.bold.yellowBright(`${promptIdentifier}:`) + _chalk.default.blue('~') + '$ ';
391
391
  subShellSettings.historySize = 200;
392
392
  }
393
393
  const subShellRl = _readline.default.createInterface(subShellSettings);
@@ -8,9 +8,11 @@ var _vipSearchReplace = require("@automattic/vip-search-replace");
8
8
  var _chalk = _interopRequireDefault(require("chalk"));
9
9
  var _debug = _interopRequireDefault(require("debug"));
10
10
  var _fs = _interopRequireDefault(require("fs"));
11
+ var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
11
12
  var _promises = require("node:stream/promises");
12
13
  var _devEnvImportSql = require("./dev-env-import-sql");
13
14
  var _exportSql = require("./export-sql");
15
+ var _api = _interopRequireDefault(require("../lib/api"));
14
16
  var _backupStorageAvailability = require("../lib/backup-storage-availability/backup-storage-availability");
15
17
  var exit = _interopRequireWildcard(require("../lib/cli/exit"));
16
18
  var _clientFileUploader = require("../lib/client-file-uploader");
@@ -83,6 +85,24 @@ async function extractSiteUrls(sqlFile) {
83
85
  readInterface.on('error', reject);
84
86
  });
85
87
  }
88
+ const SITE_URLS_QUERY = (0, _graphqlTag.default)`
89
+ query SiteUrlsQuery($appId: Int!, $environmentId: Int!, $after: String, $first: Int!) {
90
+ app(id: $appId) {
91
+ environments(id: $environmentId) {
92
+ wpSitesSDS(after: $after, first: $first) {
93
+ total
94
+ nextCursor
95
+ nodes {
96
+ id
97
+ blogId
98
+ homeUrl
99
+ siteUrl
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ `;
86
106
  class DevEnvSyncSQLCommand {
87
107
  tmpDir;
88
108
  siteUrls = [];
@@ -90,6 +110,7 @@ class DevEnvSyncSQLCommand {
90
110
  liveBackupCopyCLIOptions;
91
111
  _track;
92
112
  _sqlDumpType;
113
+ sdsSiteUrls = [];
93
114
 
94
115
  /**
95
116
  * Creates a new instance of the command
@@ -167,13 +188,51 @@ class DevEnvSyncSQLCommand {
167
188
  await (0, _promises.pipeline)(streams);
168
189
  _fs.default.renameSync(outputFile, this.sqlFile);
169
190
  }
191
+ async getSiteUrlsFromSDS() {
192
+ if (this.env.isMultisite && (!this.env.wpSitesSDS?.nodes || !this.env.wpSitesSDS?.total || this.env.wpSitesSDS.nodes.length < this.env.wpSitesSDS.total)) {
193
+ return this.fetchAllSites(Number(this.app.id), Number(this.env.id));
194
+ }
195
+ return this.env.wpSitesSDS?.nodes?.filter(node => Boolean(node)) ?? [];
196
+ }
197
+ fetchSitesPage(api, appId, environmentId, after) {
198
+ return api.query({
199
+ query: SITE_URLS_QUERY,
200
+ variables: {
201
+ first: 100,
202
+ after,
203
+ appId,
204
+ environmentId
205
+ }
206
+ });
207
+ }
208
+ async fetchAllSites(appId, environmentId) {
209
+ const api = (0, _api.default)();
210
+ let after = null;
211
+ const allSites = [];
212
+ let total;
213
+ console.log('Fetching list of sites for database sync...');
214
+ do {
215
+ // eslint-disable-next-line no-await-in-loop
216
+ const res = await this.fetchSitesPage(api, appId, environmentId, after);
217
+ if (res.data.app?.environments?.[0]?.wpSitesSDS?.nodes) {
218
+ const wpSitesSDS = res.data.app.environments[0].wpSitesSDS;
219
+ allSites.push(...res.data.app.environments[0].wpSitesSDS.nodes.filter(node => Boolean(node)));
220
+ after = wpSitesSDS.nextCursor;
221
+ total = Number(wpSitesSDS.total);
222
+ console.log(`Fetched ${allSites.length} of ${total} sites...`);
223
+ } else {
224
+ after = null;
225
+ }
226
+ } while (after);
227
+ return allSites;
228
+ }
170
229
  generateSearchReplaceMap() {
171
230
  this.searchReplaceMap = {};
172
231
  for (const url of this.siteUrls) {
173
232
  this.searchReplaceMap[stripProtocol(url)] = stripProtocol(replaceDomain(url, this.landoDomain));
174
233
  }
175
- const networkSites = this.env.wpSitesSDS?.nodes;
176
- if (!networkSites) return;
234
+ const networkSites = this.sdsSiteUrls;
235
+ if (!networkSites.length) return;
177
236
  const primaryUrl = networkSites.find(site => site?.blogId === 1)?.homeUrl;
178
237
  const primaryDomain = primaryUrl ? new URL(primaryUrl).hostname : '';
179
238
  debug('Network sites: %j, primary URL: %s, primary domain: %s', networkSites.map(site => ({
@@ -218,8 +277,8 @@ class DevEnvSyncSQLCommand {
218
277
  await importCommand.run();
219
278
  }
220
279
  fixBlogsTableQuery() {
221
- const networkSites = this.env.wpSitesSDS?.nodes;
222
- if (!networkSites) {
280
+ const networkSites = this.sdsSiteUrls;
281
+ if (!networkSites.length) {
223
282
  return '';
224
283
  }
225
284
  const prologue = `
@@ -301,6 +360,17 @@ DROP PROCEDURE vip_sync_update_blog_domains;
301
360
  });
302
361
  exit.withError(`Error extracting site URLs: ${error.message}`);
303
362
  }
363
+ try {
364
+ this.sdsSiteUrls = await this.getSiteUrlsFromSDS();
365
+ } catch (err) {
366
+ const error = err;
367
+ await this.track('error', {
368
+ error_type: 'get_site_urls_from_sds',
369
+ error_message: error.message,
370
+ stack: error.stack
371
+ });
372
+ exit.withError(`Error getting site URLs from SDS: ${error.message}`);
373
+ }
304
374
  console.log('Generating search-replace configuration...');
305
375
  this.generateSearchReplaceMap();
306
376
  try {
@@ -335,7 +335,7 @@ class ExportSQLCommand {
335
335
  if (this.liveBackupCopyCLIOptions?.useLiveBackupCopy) {
336
336
  const result = await this.generateLiveBackupCopy();
337
337
  url = result.url;
338
- size = result.size;
338
+ size = Number(result.size);
339
339
  } else {
340
340
  url = await this.runBackup();
341
341
  const exportJob = await this.getExportJob();
@@ -349,7 +349,7 @@ class ExportSQLCommand {
349
349
  size = Number(bytesWrittenMeta.value);
350
350
  }
351
351
  const storageConfirmed = await this.progressTracker.handleContinuePrompt(async setPromptShown => {
352
- const status = await this.confirmEnoughStorage(size);
352
+ const status = await this.confirmEnoughStorage(Number(size));
353
353
  if (status.isPromptShown) {
354
354
  setPromptShown();
355
355
  }