@automattic/vip 3.4.0 → 3.4.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.
@@ -39,6 +39,8 @@ services:
39
39
  command: nginx -g "daemon off;"
40
40
  environment:
41
41
  LANDO_NEEDS_EXEC: 1
42
+ LANDO_WEBROOT_USER: nginx
43
+ LANDO_WEBROOT_GROUP: nginx
42
44
  volumes:
43
45
  - ./nginx/extra.conf:/etc/nginx/conf.extra/extra.conf
44
46
  <% wpVolumes() %>
@@ -20,6 +20,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
20
20
  * Internal dependencies
21
21
  */
22
22
  const debug = (0, _debug.default)('@automattic/vip:bin:vip-app-deploy-validate');
23
+ const baseUsage = 'vip app deploy validate';
23
24
  async function appDeployValidateCmd(arg = [], opts = {}) {
24
25
  const app = opts.app;
25
26
  const env = opts.env;
@@ -37,14 +38,15 @@ async function appDeployValidateCmd(arg = [], opts = {}) {
37
38
  } else {
38
39
  await (0, _customDeploy2.validateTarFile)(fileName);
39
40
  }
40
- console.log(_chalk.default.green('✓ Compressed file has been successfully validated with no errors!'));
41
+ console.log(_chalk.default.green('✓ Compressed file has been successfully validated with no errors.'));
41
42
  }
42
43
 
43
44
  // Command examples for the `vip app deploy validate` help prompt
44
45
  const examples = [{
45
- usage: 'vip app @mysite.develop deploy validate <file.zip|file.tar.gz>',
46
- description: 'Validate the compressed file to see if it can be deployed to your site'
46
+ usage: 'vip app deploy validate file.tar.gz',
47
+ description: 'Validate the directory structure of the local archived file named "file.tar.gz".'
47
48
  }];
48
49
  void (0, _command.default)({
49
- requiredArgs: 1
50
- }).examples(examples).option('app', 'The application name or ID').option('env', 'The environment name or ID').argv(process.argv, appDeployValidateCmd);
50
+ requiredArgs: 1,
51
+ usage: baseUsage
52
+ }).examples(examples).argv(process.argv, appDeployValidateCmd);
@@ -35,6 +35,7 @@ const START_DEPLOY_MUTATION = (0, _graphqlTag.default)`
35
35
  }
36
36
  `;
37
37
  const debug = (0, _debug.default)('@automattic/vip:bin:vip-app-deploy');
38
+ const baseUsage = 'vip app deploy';
38
39
  const DEPLOY_PREFLIGHT_PROGRESS_STEPS = [{
39
40
  id: 'upload',
40
41
  name: 'Uploading file'
@@ -221,15 +222,16 @@ Processing the file for deployment to your environment...
221
222
  const examples = [
222
223
  // `app` subcommand
223
224
  {
224
- usage: 'vip app @mysite.develop deploy file.zip',
225
- description: 'Deploy the given compressed file to your site'
225
+ usage: 'WPVIP_DEPLOY_TOKEN=1234 vip @example-app.develop app deploy file.zip',
226
+ description: 'Deploy a local archived file named "file.zip" that contains application code to a VIP Platform environment that has Custom Deployment enabled.'
226
227
  }, {
227
- usage: 'vip app @mysite.develop deploy file.zip --message "This is a deploy message"',
228
- description: 'Deploy the given compressed file to your site'
228
+ usage: 'WPVIP_DEPLOY_TOKEN=1234 vip @example-app.develop app deploy file.tgz --message="A description for this deploy"',
229
+ description: 'Add a descriptive message for the Custom Deployment of the archived file named "file.tgz".'
229
230
  }, {
230
- usage: 'vip app @mysite.develop deploy file.zip --skip-confirmation',
231
- description: 'Deploy the given compressed file to your site without prompting'
231
+ usage: 'WPVIP_DEPLOY_TOKEN=1234 vip @example-app.develop app deploy file.tar.gz --skip-confirmation',
232
+ description: 'Skip the confirmation prompt for the Custom Deployment of the archived file named "file.tar.gz" to the environment.'
232
233
  }];
233
234
  void (0, _command.default)({
234
- requiredArgs: 1
235
- }).command('validate', 'Validate a file before deploying in Custom Deployments').examples(examples).option('message', 'Custom message for deploy').option('skip-confirmation', 'Skip confirmation prompt').option('force', 'Skip confirmation prompt (deprecated)').option('app', 'The application name or ID').option('env', 'The environment name or ID').argv(process.argv, appDeployCmd);
235
+ requiredArgs: 1,
236
+ usage: baseUsage
237
+ }).command('validate', 'Validate the directory structure of an archived file.').examples(examples).option('message', 'Add a description of a deployment.').option('skip-confirmation', 'Skip the confirmation prompt.').option('force', 'Skip confirmation prompt (deprecated)').option('app', 'Target an application. Accepts a string value for the application name or an integer for the application ID.').option('env', 'Target an environment. Accepts a string value for the environment type.').argv(process.argv, appDeployCmd);
@@ -6,27 +6,32 @@ var _api = _interopRequireDefault(require("../lib/api"));
6
6
  var _command = _interopRequireDefault(require("../lib/cli/command"));
7
7
  var _tracker = require("../lib/tracker");
8
8
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
+ const baseUsage = 'vip app list';
9
10
  (0, _command.default)({
10
- format: true
11
- }).argv(process.argv, async () => {
11
+ format: true,
12
+ usage: baseUsage
13
+ }).examples([{
14
+ usage: baseUsage + '\n' + ' - ┌──────┬─────────────────┬─────────────────────────────────┐\n' + ' - │ id │ name │ repo │\n' + ' - ┌──────┬─────────────────┬─────────────────────────────────┐\n' + ' - │ 8886 │ example-app │ wpcomvip/my-org-example-app │\n' + ' - ┌──────┬─────────────────┬─────────────────────────────────┐\n' + ' - │ 4325 │ mytestmultisite │ wpcomvip/my-org-mytestmultisite │\n' + ' - └──────┴─────────────────┴─────────────────────────────────┘\n',
15
+ description: 'Retrieve a list of applications that can be accessed by the current authenticated VIP-CLI user.'
16
+ }]).argv(process.argv, async () => {
12
17
  const api = (0, _api.default)();
13
18
  await (0, _tracker.trackEvent)('app_list_command_execute');
14
19
  let response;
15
20
  try {
16
21
  response = await api.query({
17
22
  query: (0, _graphqlTag.default)`
18
- query Apps($first: Int, $after: String) {
19
- apps(first: $first, after: $after) {
20
- total
21
- nextCursor
22
- edges {
23
- id
24
- name
25
- repo
23
+ query Apps($first: Int, $after: String) {
24
+ apps(first: $first, after: $after) {
25
+ total
26
+ nextCursor
27
+ edges {
28
+ id
29
+ name
30
+ repo
31
+ }
26
32
  }
27
33
  }
28
- }
29
- `,
34
+ `,
30
35
  variables: {
31
36
  first: 100,
32
37
  after: null // TODO make dynamic
@@ -11,7 +11,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
11
11
  (0, _command.default)({
12
12
  requiredArgs: 1,
13
13
  format: true
14
- }).example('vip app <app>', 'Pass an app name or ID to get details about that app').example('vip app 123', 'Get details about the app with ID 123').example('vip app vip-test', 'Get details about the app named vip-test').example('vip app @mysite.develop deploy <file.zip>', 'Deploy the given compressed file to your site').command('list', 'List your VIP applications').command('deploy', 'Deploy to your app from a file').argv(process.argv, async arg => {
14
+ }).example('vip app list', 'Retrieve a list of applications that can be accessed by the current authenticated VIP-CLI user.').example('vip app example-app', 'Retrieve information about the application named "example-app" and its environments.').example('WPVIP_DEPLOY_TOKEN=1234 vip @example-app.develop app deploy file.zip', 'Deploy a local archived file named "file.zip" that contains application code to a VIP Platform environment that has Custom Deployment enabled.').command('list', 'Retrieve a list of applications that can be accessed by the current authenticated VIP-CLI user.').command('deploy', 'Deploy an archived file of application code to an environment that has Custom Deployment enabled.').argv(process.argv, async arg => {
15
15
  await (0, _tracker.trackEvent)('app_command_execute');
16
16
  let res;
17
17
  try {
@@ -11,12 +11,14 @@ var _tracker = require("../lib/tracker");
11
11
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
12
12
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
13
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
+ const usage = 'vip cache purge-url';
15
+ const exampleUsage = 'vip @example-app.develop cache purge-url';
14
16
  const examples = [{
15
- usage: 'vip cache purge-url <URL>',
16
- description: 'Purge a URL from page cache'
17
+ usage: `${exampleUsage} https://example-app-develop.go-vip.co/sample-page/` + '\n - Purged URL: https://example-app.develop.go-vip.co/sample-page/',
18
+ description: 'Purge the page cache for a single URL.'
17
19
  }, {
18
- usage: 'vip cache purge-url --from-file=/dev/vip/urls.txt',
19
- description: 'Purge multiple URLs from page cache'
20
+ usage: `${exampleUsage} --from-file=./urls.txt`,
21
+ description: 'Purge the page cache for multiple URLs, each listed on a single line in a local file.'
20
22
  }];
21
23
  async function cachePurgeCommand(urls = [], opt = {}) {
22
24
  const trackingParams = {
@@ -59,5 +61,5 @@ async function cachePurgeCommand(urls = [], opt = {}) {
59
61
  appQuery: _cachePurge.appQuery,
60
62
  envContext: true,
61
63
  wildcardCommand: true,
62
- usage: 'vip cache purge-url <URL>'
63
- }).option('from-file', 'Read URLs from file (useful to purge multiple URLs)').examples(examples).argv(process.argv, cachePurgeCommand);
64
+ usage
65
+ }).option('from-file', 'Read one or more URLs from a file, each listed on a single line.').examples(examples).argv(process.argv, cachePurgeCommand);
@@ -3,6 +3,16 @@
3
3
 
4
4
  var _command = _interopRequireDefault(require("../lib/cli/command"));
5
5
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
6
+ const usage = 'vip cache';
7
+ const exampleUsage = 'vip @example-app.develop cache';
8
+ const examples = [{
9
+ usage: `${exampleUsage} purge-url https://example-app-develop.go-vip.co/sample-page/` + '\n - Purged URL: https://example-app.develop.go-vip.co/sample-page/',
10
+ description: 'Purge the page cache for a single URL.'
11
+ }, {
12
+ usage: `${exampleUsage} purge-url --from-file=./urls.txt`,
13
+ description: 'Purge the page cache for multiple URLs, each listed on a single line in a local file.'
14
+ }];
6
15
  (0, _command.default)({
7
- requiredArgs: 1
8
- }).command('purge-url', 'Purge page cache').argv(process.argv);
16
+ requiredArgs: 1,
17
+ usage
18
+ }).command('purge-url', 'Purge page cache for one or more URLs.').examples(examples).argv(process.argv);
@@ -46,7 +46,7 @@ async function deleteEnvVarCommand(arg, opt) {
46
46
  await (0, _tracker.trackEvent)('envvar_delete_user_cancelled_input', trackingParams);
47
47
  (0, _input.cancel)();
48
48
  });
49
- if (!(await (0, _input.confirm)(`Are you sure? ${_chalk.default.bold.red('Deletion is permanent')} (y/N)`))) {
49
+ if (!(await (0, _input.confirm)(`Are you sure? ${_chalk.default.bold.red('Deletion is permanent')}`))) {
50
50
  await (0, _tracker.trackEvent)('envvar_delete_user_cancelled_confirmation', trackingParams);
51
51
  (0, _input.cancel)();
52
52
  }
@@ -65,7 +65,7 @@ async function setEnvVarCommand(arg, opt) {
65
65
  console.log('===== Received value printed above =====');
66
66
  console.log();
67
67
  }
68
- if (!(await (0, _input.confirm)(`Please ${_chalk.default.bold('confirm')} the input value above (y/N)`))) {
68
+ if (!(await (0, _input.confirm)(`Please ${_chalk.default.bold('confirm')} the input value above`))) {
69
69
  await (0, _tracker.trackEvent)('envvar_set_user_cancelled_confirmation', trackingParams);
70
70
  (0, _input.cancel)();
71
71
  }
package/dist/bin/vip.js CHANGED
@@ -22,7 +22,7 @@ const tokenURL = 'https://dashboard.wpvip.com/me/cli/token';
22
22
  const customDeployToken = process.env.WPVIP_DEPLOY_TOKEN;
23
23
  const runCmd = async function () {
24
24
  const cmd = (0, _command.default)();
25
- cmd.command('logout', 'Log out the current authenticated VIP-CLI user.').command('app', 'List and modify your VIP applications').command('backup', 'Generate a backup of an environment.').command('cache', 'Manage page cache for your VIP applications').command('config', 'Manage environment configurations.').command('dev-env', 'Create and manage VIP Local Development Environments.').command('export', 'Export a copy of data associated with an environment.').command('import', 'Import media or SQL files into your VIP applications').command('logs', 'Get logs from your VIP applications').command('search-replace', 'Perform search and replace tasks on files').command('slowlogs', 'Retrieve MySQL slow query logs from an environment.').command('db', "Access an environment's database.").command('sync', 'Sync production to a development environment').command('whoami', 'Retrieve details about the current authenticated VIP-CLI user.').command('validate', 'Validate your VIP application and environment').command('wp', 'Run WP CLI commands against an environment');
25
+ cmd.command('logout', 'Log out the current authenticated VIP-CLI user.').command('app', 'Interact with applications that the current authenticated VIP-CLI user has permission to access.').command('backup', 'Generate a backup of an environment.').command('cache', 'Manage page cache for an environment.').command('config', 'Manage environment configurations.').command('dev-env', 'Create and manage VIP Local Development Environments.').command('export', 'Export a copy of data associated with an environment.').command('import', 'Import media or SQL files into your VIP applications').command('logs', 'Get logs from your VIP applications').command('search-replace', 'Perform search and replace tasks on files').command('slowlogs', 'Retrieve MySQL slow query logs from an environment.').command('db', "Access an environment's database.").command('sync', 'Sync production to a development environment').command('whoami', 'Retrieve details about the current authenticated VIP-CLI user.').command('validate', 'Validate your VIP application and environment').command('wp', 'Run WP CLI commands against an environment');
26
26
  cmd.argv(process.argv);
27
27
  };
28
28
 
@@ -13,6 +13,7 @@ var _backupStorageAvailability = require("../lib/backup-storage-availability/bac
13
13
  var exit = _interopRequireWildcard(require("../lib/cli/exit"));
14
14
  var _format = require("../lib/cli/format");
15
15
  var _progress = require("../lib/cli/progress");
16
+ var _retry = require("../lib/retry");
16
17
  var _utils = require("../lib/utils");
17
18
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
18
19
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -83,7 +84,7 @@ const CREATE_EXPORT_JOB_MUTATION = exports.CREATE_EXPORT_JOB_MUTATION = (0, _gra
83
84
  * @param {number} envId Environment ID
84
85
  * @return {Promise} A promise which resolves to the latest backup and job status
85
86
  */
86
- async function fetchLatestBackupAndJobStatus(appId, envId) {
87
+ async function fetchLatestBackupAndJobStatusBase(appId, envId) {
87
88
  const api = (0, _api.default)();
88
89
  const response = await api.query({
89
90
  query: BACKUP_AND_JOB_STATUS_QUERY,
@@ -107,6 +108,13 @@ async function fetchLatestBackupAndJobStatus(appId, envId) {
107
108
  jobs
108
109
  };
109
110
  }
111
+ async function fetchLatestBackupAndJobStatus(appId, envId) {
112
+ return await (0, _retry.retry)({
113
+ retryOnlyIf: options => {
114
+ return (options.error.message || '').indexOf('Unexpected token < in JSON at position 0') !== -1;
115
+ }
116
+ }, () => fetchLatestBackupAndJobStatusBase(appId, envId));
117
+ }
110
118
 
111
119
  /**
112
120
  * Generates a download link for a backup
@@ -512,7 +512,10 @@ async function promptForWordPress(defaultObject) {
512
512
 
513
513
  // image with selection
514
514
  const tagChoices = await getTagChoices();
515
- let option = defaultObject?.tag ?? tagChoices[0].value;
515
+ if (typeof defaultObject === 'string' && !tagChoices.find(choice => choice.value === defaultObject)) {
516
+ throw new Error(`Unknown or unsupported WordPress version: ${defaultObject}.`);
517
+ }
518
+ let option = typeof defaultObject === 'string' ? defaultObject : defaultObject?.tag ?? tagChoices[0].value;
516
519
  if (isStdinTTY) {
517
520
  const message = `${messagePrefix}Which version would you like`;
518
521
  const selectTag = new _enquirer.Select({
@@ -577,7 +577,7 @@ async function maybeUpdateWordPressImage(lando, slug) {
577
577
  type: 'select',
578
578
  name: 'upgrade',
579
579
  message: 'Would you like to upgrade WordPress? ',
580
- choices: ['no', 'yes', "no (don't ask anymore)"]
580
+ choices: ['yes', 'no', "no (don't ask anymore)"]
581
581
  });
582
582
 
583
583
  // If the user takes the new WP version path
@@ -585,7 +585,7 @@ async function maybeUpdateWordPressImage(lando, slug) {
585
585
  console.log('Upgrading from: ' + _chalk.default.yellow(currentWordPressTag) + ' to:');
586
586
 
587
587
  // Select a new image
588
- const choice = await (0, _devEnvironmentCli.promptForWordPress)(null);
588
+ const choice = await (0, _devEnvironmentCli.promptForWordPress)(newestWordPressImage?.tag ?? null);
589
589
  const version = versions.find(({
590
590
  tag
591
591
  }) => tag.trim() === choice.tag.trim());
@@ -404,7 +404,8 @@ async function checkEnvHealth(lando, instancePath) {
404
404
  }
405
405
  if (urlsToScan.length) {
406
406
  scanResults = scanResults.concat(await app.scanUrls(urlsToScan, {
407
- max: 1
407
+ max: 1,
408
+ waitCodes: [502, 504]
408
409
  }));
409
410
  }
410
411
  const result = {};
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.EXPONENTIAL_BACKOFF_STARTING_IN_50_MS = exports.EXPONENTIAL_BACKOFF_STARTING_IN_200_MS = exports.EXPONENTIAL_BACKOFF_STARTING_IN_100_MS = void 0;
5
+ exports.exponentialBackoff = exponentialBackoff;
6
+ exports.retry = retry;
7
+ // copied over from our internal lib
8
+
9
+ const EXPONENTIAL_BACKOFF_STARTING_IN_50_MS = exports.EXPONENTIAL_BACKOFF_STARTING_IN_50_MS = exponentialBackoff(50);
10
+ const EXPONENTIAL_BACKOFF_STARTING_IN_100_MS = exports.EXPONENTIAL_BACKOFF_STARTING_IN_100_MS = exponentialBackoff(100);
11
+ const EXPONENTIAL_BACKOFF_STARTING_IN_200_MS = exports.EXPONENTIAL_BACKOFF_STARTING_IN_200_MS = exponentialBackoff(200);
12
+ const MAX_INTERVAL_BETWEEN_ATTEMPTS = 600000; // 10 minutes
13
+
14
+ const NOOP = async () => {};
15
+ const DEFAULT_OPTIONS = {
16
+ maxRetries: 4,
17
+ interval: EXPONENTIAL_BACKOFF_STARTING_IN_100_MS,
18
+ retryOnlyIf: () => Promise.resolve(true),
19
+ onRetry: NOOP,
20
+ onFailedAttempt: NOOP
21
+ };
22
+ /**
23
+ * We don't need a strict Error instance, which can help in cases (probably
24
+ * just tests) where code throws an object instead of an Error instance. We
25
+ * really just need to check for an object with a message property.
26
+ */
27
+ function isErrorLike(value) {
28
+ return value instanceof Error || null !== value && 'object' === typeof value && 'message' in value;
29
+ }
30
+
31
+ /**
32
+ * Execute a <task> and retries <options.maxRetries> times while it fails.
33
+ * @param {Object} [options] - The options.
34
+ * @param {number} [options.maxRetries] - The max number of retry attempts to be executed.
35
+ * @param {function|number} [options.interval] - The time to wait between retries in milliseconds.
36
+ * It can be either a number or a function.
37
+ * Function arguments: retryAttemptNumber.
38
+ * Expected return: a number representing the time in milliseconds.
39
+ * @param {function} [options.retryOnlyIf] - A function used to determine if a retry should be attempted.
40
+ * Arguments: Object<{ error, attemptNumber }>.
41
+ * Expected return: boolean|Promise<boolean>.
42
+ * @param {function} [options.onRetry] - A callback function executed right before a retry attempt.
43
+ * Arguments: Object<{ retryNumber: attemptNumber }>.
44
+ * Expected return: void|Promise<void>.
45
+ * @param {function} [options.onFailedAttempt] - A callback function executed right after a failed attempt.
46
+ * Arguments: Object<{ error, attemptNumber, attemptDuration }>.
47
+ * Expected return: void|Promise<void>.
48
+ * @param {function} task - The task to be executed.
49
+ * @return {any} - The value returned by the provided <task> function when it is successfully executed
50
+ */
51
+
52
+ async function retry(optionsOrTask, task) {
53
+ let options = optionsOrTask;
54
+ if (arguments.length < 3 && typeof optionsOrTask === 'function') {
55
+ task = optionsOrTask;
56
+ options = {};
57
+ }
58
+ const finalOptions = {
59
+ ...DEFAULT_OPTIONS,
60
+ ...options
61
+ };
62
+ const {
63
+ maxRetries,
64
+ interval,
65
+ retryOnlyIf,
66
+ onRetry,
67
+ onFailedAttempt
68
+ } = finalOptions;
69
+ validateTask(task);
70
+ validateOptions(finalOptions);
71
+ const maxAttempts = maxRetries + 1;
72
+ for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber++) {
73
+ /* eslint-disable no-await-in-loop */
74
+ const attemptStart = Date.now();
75
+ try {
76
+ return await task();
77
+ } catch (caughtError) {
78
+ const attemptEnd = Date.now();
79
+ const attemptDuration = attemptEnd - attemptStart;
80
+ const attemptStartTime = new Date(attemptStart);
81
+ const attemptEndTime = new Date(attemptEnd);
82
+ const error = isErrorLike(caughtError) ? caughtError : new Error(String(caughtError));
83
+ await onFailedAttempt({
84
+ error,
85
+ attemptNumber,
86
+ attemptDuration,
87
+ attemptStartTime,
88
+ attemptEndTime
89
+ });
90
+ const shouldRetry = await retryOnlyIf({
91
+ error,
92
+ attemptNumber
93
+ });
94
+ if (attemptNumber === maxAttempts || shouldRetry !== true) {
95
+ throw error;
96
+ }
97
+ await awaitInterval(interval, attemptNumber);
98
+ await onRetry({
99
+ retryNumber: attemptNumber
100
+ });
101
+ }
102
+ }
103
+ throw new Error('Error, wrong retry options set');
104
+ }
105
+ function isValidInterval(interval) {
106
+ if ('function' === typeof interval) {
107
+ return true;
108
+ }
109
+ return typeof interval === 'number' && Number.isInteger(interval) && interval >= 0 && interval <= MAX_INTERVAL_BETWEEN_ATTEMPTS;
110
+ }
111
+ async function awaitInterval(interval, attemptNumber) {
112
+ let newInterval;
113
+ if (typeof interval === 'function') {
114
+ newInterval = interval(attemptNumber);
115
+ if (!isValidInterval(newInterval)) {
116
+ throw new Error(`Invalid calculated interval for retry attempt ${attemptNumber}: "${newInterval}" (type: ${typeof newInterval})`);
117
+ }
118
+ } else {
119
+ newInterval = interval;
120
+ }
121
+ return new Promise(resolve => setTimeout(resolve, newInterval));
122
+ }
123
+ function validateTask(task) {
124
+ if (typeof task !== 'function') {
125
+ throw new Error('Invalid task: it should be a function');
126
+ }
127
+ }
128
+ function validateOptions({
129
+ maxRetries,
130
+ interval,
131
+ onFailedAttempt,
132
+ retryOnlyIf
133
+ }) {
134
+ if (!Number.isInteger(maxRetries) || maxRetries < 0) {
135
+ throw new Error('Invalid option "maxRetries": it should be an integer number >= 0');
136
+ }
137
+ if (!isValidInterval(interval)) {
138
+ const definition = `an integer number between 0 and ${MAX_INTERVAL_BETWEEN_ATTEMPTS}`;
139
+ throw new Error(`Invalid option "interval": it should be either ${definition}, or a synchronous function which returns ${definition}`);
140
+ }
141
+ if (!isValidInterval(interval)) {
142
+ throw new Error('Invalid option "interval": it should be either an integer number >= 0, or a synchronous function which returns an integer number >= 0');
143
+ }
144
+ if (typeof onFailedAttempt !== 'function') {
145
+ throw new Error('Invalid option "onFailedAttempt": it should be a function');
146
+ }
147
+ if (typeof retryOnlyIf !== 'function') {
148
+ throw new Error('Invalid option "retryOnlyIf": it should be a function');
149
+ }
150
+ }
151
+ function exponentialBackoff(startMilliseconds) {
152
+ return retryAttemptNumber => Math.pow(2, retryAttemptNumber - 1) * startMilliseconds;
153
+ }
@@ -15,9 +15,9 @@ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return
15
15
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
16
16
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
17
17
  const errorMessages = {
18
- missingThemes: 'Missing `themes` directory from root folder!',
18
+ missingThemes: 'Missing `themes` directory from root folder.',
19
19
  symlink: 'Symlink detected: ',
20
- singleRootDir: 'The compressed file must contain a single root directory!',
20
+ singleRootDir: 'The compressed file must contain a single root directory.',
21
21
  invalidExt: 'Invalid file extension. Please provide a .zip, .tar.gz, or a .tgz file.',
22
22
  invalidChars: (filename, invalidChars) => `Filename ${filename} contains disallowed characters: ${invalidChars}`
23
23
  };
@@ -271,9 +271,15 @@ const checks = {
271
271
  recommendation: ''
272
272
  },
273
273
  siteHomeUrlLando: {
274
- matcher: "'(siteurl|home)',\\s?'(.*?)'",
274
+ matcher: "'(siteurl|home)',\\s?'([^']+)'",
275
275
  matchHandler: (lineNumber, results, expectedDomain) => {
276
- const foundDomain = results[2].replace(/https?:\/\//, '');
276
+ let foundDomain = results[2];
277
+ if (!/^https?:\/\//i.test(foundDomain)) {
278
+ return {
279
+ falsePositive: true
280
+ };
281
+ }
282
+ foundDomain = foundDomain.replace(/^https?:\/\//, '');
277
283
  if (!foundDomain.trim()) {
278
284
  return {
279
285
  falsePositive: true
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## Changelog
2
2
 
3
+ ### 3.4.1
4
+
5
+ * Updating vip cache commands descriptions and examples to follow the VIP-CLI style guide
6
+ * Dev-env: Upgrade WordPress upgrade prompt to "yes" and pre-select latest version not trunk
7
+ * build(deps-dev): bump @types/node from 18.19.36 to 18.19.37
8
+ * fix(dev-env): retry healthcheck only on 502 and 504 status codes
9
+ * fix(dev-env): set the user/group for `nginx` container to `nginx:nginx`
10
+
11
+ **Full Changelog**: https://github.com/Automattic/vip-cli/compare/3.4.0...3.4.1
12
+
3
13
  ### 3.4.0
4
14
 
5
15
  * Updating command option descriptions to follow the VIP-CLI style guide
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@automattic/vip",
9
- "version": "3.4.0",
9
+ "version": "3.4.2",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -132,7 +132,7 @@
132
132
  "@types/single-line-log": "1.1.2",
133
133
  "@types/tar": "^6.1.13",
134
134
  "@types/update-notifier": "^6.0.8",
135
- "@types/uuid": "^9.0.7",
135
+ "@types/uuid": "^10.0.0",
136
136
  "@types/xml2js": "^0.4.14",
137
137
  "dockerode": "^4.0.0",
138
138
  "eslint": "^8.35.0",
@@ -3884,9 +3884,9 @@
3884
3884
  "dev": true
3885
3885
  },
3886
3886
  "node_modules/@types/node": {
3887
- "version": "18.19.36",
3888
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.36.tgz",
3889
- "integrity": "sha512-tX1BNmYSWEvViftB26VLNxT6mEr37M7+ldUtq7rlKnv4/2fKYsJIOmqJAjT6h1DNuwQjIKgw3VJ/Dtw3yiTIQw==",
3887
+ "version": "18.19.39",
3888
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz",
3889
+ "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==",
3890
3890
  "dependencies": {
3891
3891
  "undici-types": "~5.26.4"
3892
3892
  }
@@ -3992,9 +3992,9 @@
3992
3992
  }
3993
3993
  },
3994
3994
  "node_modules/@types/uuid": {
3995
- "version": "9.0.8",
3996
- "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
3997
- "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
3995
+ "version": "10.0.0",
3996
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
3997
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
3998
3998
  "dev": true
3999
3999
  },
4000
4000
  "node_modules/@types/xml2js": {
@@ -12643,9 +12643,9 @@
12643
12643
  }
12644
12644
  },
12645
12645
  "node_modules/typescript": {
12646
- "version": "5.4.5",
12647
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
12648
- "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
12646
+ "version": "5.5.2",
12647
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
12648
+ "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
12649
12649
  "dev": true,
12650
12650
  "bin": {
12651
12651
  "tsc": "bin/tsc",
@@ -16306,9 +16306,9 @@
16306
16306
  "dev": true
16307
16307
  },
16308
16308
  "@types/node": {
16309
- "version": "18.19.36",
16310
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.36.tgz",
16311
- "integrity": "sha512-tX1BNmYSWEvViftB26VLNxT6mEr37M7+ldUtq7rlKnv4/2fKYsJIOmqJAjT6h1DNuwQjIKgw3VJ/Dtw3yiTIQw==",
16309
+ "version": "18.19.39",
16310
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz",
16311
+ "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==",
16312
16312
  "requires": {
16313
16313
  "undici-types": "~5.26.4"
16314
16314
  }
@@ -16415,9 +16415,9 @@
16415
16415
  }
16416
16416
  },
16417
16417
  "@types/uuid": {
16418
- "version": "9.0.8",
16419
- "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
16420
- "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
16418
+ "version": "10.0.0",
16419
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
16420
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
16421
16421
  "dev": true
16422
16422
  },
16423
16423
  "@types/xml2js": {
@@ -22653,9 +22653,9 @@
22653
22653
  }
22654
22654
  },
22655
22655
  "typescript": {
22656
- "version": "5.4.5",
22657
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
22658
- "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
22656
+ "version": "5.5.2",
22657
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
22658
+ "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
22659
22659
  "dev": true
22660
22660
  },
22661
22661
  "typical": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "description": "The VIP Javascript library & CLI",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -131,7 +131,7 @@
131
131
  "@types/single-line-log": "1.1.2",
132
132
  "@types/tar": "^6.1.13",
133
133
  "@types/update-notifier": "^6.0.8",
134
- "@types/uuid": "^9.0.7",
134
+ "@types/uuid": "^10.0.0",
135
135
  "@types/xml2js": "^0.4.14",
136
136
  "dockerode": "^4.0.0",
137
137
  "eslint": "^8.35.0",