@automattic/vip 2.19.2 → 2.20.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  ## Changelog
2
2
 
3
+ ### 2.20.0 (23 Sep 2022)
4
+
5
+ - #1121 Add support for custom PHP images
6
+ - #1136 SQL Import: Add validation for unique_checks being disabled
7
+ - #1131 SQL Import - Add ALTER TABLE statement validation
8
+ - #1134 [dev-env] add app and env to tracks when creating env
9
+ - #1135 [dev-env] Make xdebug config not break during wp update
10
+ - #1098 Include options into reading software config
11
+ - #1114 [dev-env] Verify current user is a docker user
12
+ - #1130 Update/exec help
13
+ - #1125 add validation output
14
+ - #1127 Run wp-cli as www-data
15
+ - #1120 Update php-fpm image names
16
+ - #1126 Update memcached to 1.6-alpine3.16
17
+ - #1124 [dev-env] Supress Setup wizard during update
18
+ - #1111 [dev-env] prompt for es index after db import
19
+ - #1119 Use --yes instead of --force
20
+ - #1115 [dev-env] reclasify most common error
21
+ - #1122 [dev-env] support xdebug_config environment variable
22
+ - #1129 Fix ESLint/Flow/SonarScan issues
23
+ - #1133 Add PHP 8.2 image
24
+ - #1141 SQL validation: Change formatting to make it more readable
25
+
3
26
  ### 2.19.2 (23 Sep 2022)
4
27
  - #1116 Reverted #1049 changes
5
28
 
@@ -40,9 +40,11 @@ services:
40
40
  command: run.sh
41
41
  working_dir: /wp
42
42
  environment:
43
- XDEBUG: <%= xdebug ? 'enable' : 'disable' %>
44
43
  STATSD: <%= statsd ? 'enable' : 'disable' %>
45
-
44
+ XDEBUG: <%= xdebug ? 'enable' : 'disable' %>
45
+ <% if ( xdebugConfig ) { %>
46
+ XDEBUG_CONFIG: "<%= xdebugConfig %>"
47
+ <% } %>
46
48
  LANDO_NO_USER_PERMS: 'enable'
47
49
 
48
50
 
@@ -78,7 +80,7 @@ services:
78
80
  memcached:
79
81
  type: memcached:custom
80
82
  overrides:
81
- image: memcached:1.6-alpine3.14
83
+ image: memcached:1.6-alpine3.16
82
84
  command: docker-entrypoint.sh memcached
83
85
 
84
86
  <% if ( phpmyadmin ) { %>
@@ -175,15 +177,9 @@ tooling:
175
177
  wp:
176
178
  service: php
177
179
  description: "Run WP-CLI command"
178
- user: root
179
- cmd:
180
- - wp --allow-root
181
-
182
- add-site:
183
- service: php
184
- description: "Add site to a multisite installation"
180
+ user: www-data
185
181
  cmd:
186
- - bash /dev-tools/add-site.sh
182
+ - wp
187
183
 
188
184
  db:
189
185
  service: php
@@ -28,12 +28,13 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
28
28
 
29
29
  // Command examples
30
30
  const examples = [{
31
- usage: 'vip @mysite.develop config software get wordpress --format json',
32
- description: 'Read current software settings for WordPress in JSON format'
31
+ usage: 'vip @mysite.develop config software get wordpress --include available_versions --format json',
32
+ description: 'Read current software settings for WordPress in JSON format including available versions'
33
33
  }, {
34
34
  usage: 'vip @mysite.develop config software get',
35
35
  description: 'Read current software settings for all components'
36
36
  }];
37
+ const VALID_INCLUDES = ['available_versions'];
37
38
  (0, _command.default)({
38
39
  appContext: true,
39
40
  appQuery: _software.appQuery,
@@ -42,7 +43,7 @@ const examples = [{
42
43
  wildcardCommand: true,
43
44
  format: true,
44
45
  usage: 'vip @mysite.develop config software get <wordpress|php|nodejs|muplugins>'
45
- }).examples(examples).argv(process.argv, async (arg, opt) => {
46
+ }).option('include', `Extra information to be included. Valida values: ${VALID_INCLUDES.join(',')}`).examples(examples).argv(process.argv, async (arg, opt) => {
46
47
  var _opt$env;
47
48
 
48
49
  const trackingInfo = {
@@ -50,6 +51,22 @@ const examples = [{
50
51
  args: JSON.stringify(arg)
51
52
  };
52
53
  await (0, _tracker.trackEvent)('config_software_get_execute', trackingInfo);
54
+ let include = [];
55
+
56
+ if (opt.include) {
57
+ if (Array.isArray(opt.include)) {
58
+ include = opt.include;
59
+ } else {
60
+ include = [opt.include];
61
+ }
62
+
63
+ const invalidIncludes = include.filter(includeKey => !VALID_INCLUDES.includes(includeKey));
64
+
65
+ if (invalidIncludes.length > 0) {
66
+ throw new _userError.default(`Invalid include value(s): ${invalidIncludes.join(',')}`);
67
+ }
68
+ }
69
+
53
70
  const {
54
71
  softwareSettings
55
72
  } = opt.env;
@@ -72,19 +89,7 @@ const examples = [{
72
89
  chosenSettings = [softwareSettings.wordpress, softwareSettings.php, softwareSettings.muplugins, softwareSettings.nodejs];
73
90
  }
74
91
 
75
- const preFormatted = chosenSettings.filter(softwareSetting => !!softwareSetting).map(softwareSetting => {
76
- let version = softwareSetting.current.version;
77
-
78
- if (softwareSetting.slug === 'wordpress' && !softwareSetting.pinned) {
79
- version += ' (managed updates)';
80
- }
81
-
82
- return {
83
- name: softwareSetting.name,
84
- slug: softwareSetting.slug,
85
- version
86
- };
87
- });
92
+ const preFormatted = chosenSettings.filter(softwareSetting => !!softwareSetting).map(softwareSetting => (0, _software.formatSoftwareSettings)(softwareSetting, include, opt.format));
88
93
  console.log((0, _format.formatData)(preFormatted, opt.format));
89
94
  await (0, _tracker.trackEvent)('config_software_get_success', trackingInfo);
90
95
  });
@@ -51,7 +51,7 @@ const cmd = (0, _command.default)({
51
51
  usage: 'vip @mysite.develop config software update nodejs 16',
52
52
  description: 'Update Node.js to v16'
53
53
  }]);
54
- cmd.option('force', 'Auto-confirm update');
54
+ cmd.option('yes', 'Auto-confirm update');
55
55
  cmd.argv(process.argv, async (arg, opt) => {
56
56
  const {
57
57
  app,
@@ -73,7 +73,7 @@ cmd.argv(process.argv, async (arg, opt) => {
73
73
  }
74
74
 
75
75
  const updateOptions = {
76
- force: !!opt.force
76
+ force: !!opt.yes
77
77
  };
78
78
 
79
79
  if (arg.length > 0) {
@@ -66,7 +66,9 @@ cmd.argv(process.argv, async (arg, opt) => {
66
66
  await (0, _devEnvironmentCli.validateDependencies)(slug);
67
67
  debug('Args: ', arg, 'Options: ', opt);
68
68
  const trackingInfo = {
69
- slug
69
+ slug,
70
+ app: opt.app,
71
+ env: opt.env
70
72
  };
71
73
  await (0, _tracker.trackEvent)('dev_env_create_command_execute', trackingInfo);
72
74
 
@@ -29,12 +29,12 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
29
29
  const examples = [{
30
30
  usage: `${_devEnvironment.DEV_ENVIRONMENT_FULL_COMMAND} exec -- wp post list`,
31
31
  description: 'Use dev-environment to run `wp post list`'
32
+ }, {
33
+ usage: `${_devEnvironment.DEV_ENVIRONMENT_FULL_COMMAND} exec --slug my_site -- wp post list --posts_per_page=500`,
34
+ description: 'Use dev-environment "my-site" to run `wp post list --posts_per_page=500`'
32
35
  }, {
33
36
  usage: `${_devEnvironment.DEV_ENVIRONMENT_FULL_COMMAND} exec --slug my_site -- wp shell`,
34
37
  description: 'Use dev-environment "my_site" to run interactive wp shell'
35
- }, {
36
- usage: `${_devEnvironment.DEV_ENVIRONMENT_FULL_COMMAND} exec -- add-site --new-site-slug subsite --new-site-title "New Subsite"`,
37
- description: 'Execute script to add a subsite to multisite dev environment'
38
38
  }];
39
39
  (0, _command.default)({
40
40
  wildcardCommand: true
@@ -81,6 +81,17 @@ const examples = [{
81
81
 
82
82
  const cacheArg = ['wp', 'cache', 'flush'];
83
83
  await (0, _devEnvironmentCore.exec)(slug, cacheArg);
84
+
85
+ try {
86
+ await (0, _devEnvironmentCore.exec)(slug, ['wp', 'cli', 'has-command', 'vip-search']);
87
+ const doIndex = await (0, _devEnvironmentCli.promptForBoolean)('Do you want to index data in ElasticSearch (used by enterprise search)?', true);
88
+
89
+ if (doIndex) {
90
+ await (0, _devEnvironmentCore.exec)(slug, ['wp', 'vip-search', 'index', '--setup', '--network-wide', '--skip-confirm']);
91
+ }
92
+ } catch (err) {// Exception means they don't have vip-search enabled.
93
+ }
94
+
84
95
  const addUserArg = ['wp', 'dev-env-add-admin', '--username=vipgo', '--password=password'];
85
96
  await (0, _devEnvironmentCore.exec)(slug, addUserArg);
86
97
  await (0, _tracker.trackEvent)('dev_env_import_sql_command_success', trackingInfo);
@@ -73,7 +73,11 @@ cmd.argv(process.argv, async (arg, opt) => {
73
73
  multisite: false,
74
74
  title: ''
75
75
  };
76
- const instanceData = await (0, _devEnvironmentCli.promptForArguments)(preselectedOptions, defaultOptions);
76
+ const providedOptions = Object.keys(opt).filter(option => option.length > 1) // Filter out single letter aliases
77
+ .filter(option => !['debug', 'help', 'slug'].includes(option)); // Filter out options that are not related to instance configuration
78
+
79
+ const supressPrompts = providedOptions.length > 0;
80
+ const instanceData = await (0, _devEnvironmentCli.promptForArguments)(preselectedOptions, defaultOptions, supressPrompts);
77
81
  instanceData.siteSlug = slug;
78
82
  await (0, _devEnvironmentCore.updateEnvironment)(instanceData);
79
83
  const message = '\n' + _chalk.default.green('✓') + ' environment updated. Restart environment for changes to take an affect.';
package/dist/lib/api.js CHANGED
@@ -7,8 +7,6 @@ exports.disableGlobalGraphQLErrorHandling = disableGlobalGraphQLErrorHandling;
7
7
  exports.default = API;
8
8
  exports.API_URL = exports.API_HOST = exports.PRODUCTION_API_HOST = void 0;
9
9
 
10
- var _nodeFetch = _interopRequireDefault(require("node-fetch"));
11
-
12
10
  var _core = require("@apollo/client/core");
13
11
 
14
12
  var _core2 = require("@apollo/client/link/core");
@@ -108,9 +106,8 @@ async function API({
108
106
  agent: proxyAgent
109
107
  }
110
108
  });
111
- const apiClient = new _core.ApolloClient({
109
+ return new _core.ApolloClient({
112
110
  link: _core2.ApolloLink.from([withToken, errorLink, authLink, httpLink]),
113
111
  cache: new _core.InMemoryCache()
114
112
  });
115
- return apiClient;
116
113
  }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getUpdateResult = exports.triggerUpdate = exports.promptForUpdate = exports.appQueryFragments = exports.appQuery = void 0;
6
+ exports.formatSoftwareSettings = exports.getUpdateResult = exports.triggerUpdate = exports.promptForUpdate = exports.appQueryFragments = exports.appQuery = void 0;
7
7
 
8
8
  var _enquirer = require("enquirer");
9
9
 
@@ -231,7 +231,9 @@ const _processComponent = async (appTypeId, userProvidedComponent) => {
231
231
  message: 'Component to update',
232
232
  choices
233
233
  });
234
- return await select.run();
234
+ return select.run().catch(() => {
235
+ throw new _userError.default('Command cancelled by user.');
236
+ });
235
237
  };
236
238
 
237
239
  const _processComponentVersion = async (softwareSettings, component, userProvidedVersion) => {
@@ -251,7 +253,9 @@ const _processComponentVersion = async (softwareSettings, component, userProvide
251
253
  message: `Version for ${COMPONENT_NAMES[component]} to upgrade to`,
252
254
  choices: versionChoices
253
255
  });
254
- return await versionSelect.run();
256
+ return versionSelect.run().catch(() => {
257
+ throw new _userError.default('Command cancelled by user.');
258
+ });
255
259
  };
256
260
 
257
261
  const promptForUpdate = async (appTypeId, opts, softwareSettings) => {
@@ -259,7 +263,9 @@ const promptForUpdate = async (appTypeId, opts, softwareSettings) => {
259
263
  const version = await _processComponentVersion(softwareSettings, component, opts.version);
260
264
  const confirm = opts.force || (await new _enquirer.Confirm({
261
265
  message: `Are you sure you want to upgrade ${COMPONENT_NAMES[component]} to ${version}?`
262
- }).run());
266
+ }).run().catch(() => {
267
+ throw new _userError.default('Command cancelled by user.');
268
+ }));
263
269
 
264
270
  if (confirm) {
265
271
  return {
@@ -276,7 +282,7 @@ exports.promptForUpdate = promptForUpdate;
276
282
  const triggerUpdate = async variables => {
277
283
  debug('Triggering update', variables);
278
284
  const api = await (0, _api.default)();
279
- return await api.mutate({
285
+ return api.mutate({
280
286
  mutation: updateSoftwareMutation,
281
287
  variables
282
288
  });
@@ -342,4 +348,30 @@ const getUpdateResult = async (appId, envId) => {
342
348
  };
343
349
  };
344
350
 
345
- exports.getUpdateResult = getUpdateResult;
351
+ exports.getUpdateResult = getUpdateResult;
352
+
353
+ const formatSoftwareSettings = (softwareSetting, includes, format) => {
354
+ let version = softwareSetting.current.version;
355
+
356
+ if (softwareSetting.slug === 'wordpress' && !softwareSetting.pinned) {
357
+ version += ' (managed updates)';
358
+ }
359
+
360
+ const result = {
361
+ name: softwareSetting.name,
362
+ slug: softwareSetting.slug,
363
+ version
364
+ };
365
+
366
+ if (includes.includes('available_versions')) {
367
+ result.available_versions = _optionsForVersion(softwareSetting).map(option => option.value);
368
+
369
+ if (format !== 'json') {
370
+ result.available_versions = result.available_versions.join(',');
371
+ }
372
+ }
373
+
374
+ return result;
375
+ };
376
+
377
+ exports.formatSoftwareSettings = formatSoftwareSettings;
@@ -33,9 +33,11 @@ const DEV_ENVIRONMENT_WORDPRESS_VERSION_TTL = 86400; // once per day
33
33
  exports.DEV_ENVIRONMENT_WORDPRESS_VERSION_TTL = DEV_ENVIRONMENT_WORDPRESS_VERSION_TTL;
34
34
  const DEV_ENVIRONMENT_PHP_VERSIONS = {
35
35
  // eslint-disable-next-line quote-props
36
- '8.1': 'ghcr.io/automattic/vip-container-images/php-fpm-alt:8.1',
37
- '8.0': 'ghcr.io/automattic/vip-container-images/php-fpm-alt:8.0',
38
- // eslint-disable-next-line quote-props -- flow does nit support non-string keys
39
- '7.4': 'ghcr.io/automattic/vip-container-images/php-fpm-alt:7.4'
36
+ '8.2': 'ghcr.io/automattic/vip-container-images/php-fpm:8.2',
37
+ // eslint-disable-next-line quote-props
38
+ '8.1': 'ghcr.io/automattic/vip-container-images/php-fpm:8.1',
39
+ '8.0': 'ghcr.io/automattic/vip-container-images/php-fpm:8.0',
40
+ // eslint-disable-next-line quote-props -- flow does not support non-string keys
41
+ '7.4': 'ghcr.io/automattic/vip-container-images/php-fpm:7.4'
40
42
  };
41
43
  exports.DEV_ENVIRONMENT_PHP_VERSIONS = DEV_ENVIRONMENT_PHP_VERSIONS;
@@ -35,7 +35,7 @@ var _path = _interopRequireDefault(require("path"));
35
35
 
36
36
  var _os = _interopRequireDefault(require("os"));
37
37
 
38
- var exit = _interopRequireWildcard(require("../cli/exit"));
38
+ var _progress = require("../cli/progress");
39
39
 
40
40
  var _tracker = require("../tracker");
41
41
 
@@ -47,10 +47,6 @@ var _devEnvironmentLando = require("./dev-environment-lando");
47
47
 
48
48
  var _userError = _interopRequireDefault(require("../user-error"));
49
49
 
50
- function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
51
-
52
- function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
53
-
54
50
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
55
51
 
56
52
  /**
@@ -131,14 +127,48 @@ const verifyDNSResolution = slug => {
131
127
  });
132
128
  };
133
129
 
130
+ const VALIDATION_STEPS = [{
131
+ id: 'docker',
132
+ name: 'Check for docker installation'
133
+ }, {
134
+ id: 'compose',
135
+ name: 'Check for docker-compose installation'
136
+ }, {
137
+ id: 'access',
138
+ name: 'Check access to docker for current user'
139
+ }, {
140
+ id: 'dns',
141
+ name: 'Check DNS resolution'
142
+ }];
143
+
134
144
  const validateDependencies = async slug => {
145
+ const progressTracker = new _progress.ProgressTracker(VALIDATION_STEPS);
146
+ console.log('Running validation steps...');
147
+ progressTracker.startPrinting();
148
+ progressTracker.stepRunning('docker');
149
+
135
150
  try {
136
151
  await (0, _devEnvironmentLando.validateDockerInstalled)();
137
152
  } catch (exception) {
138
153
  throw new _userError.default(exception.message);
139
154
  }
140
155
 
156
+ progressTracker.stepSuccess('docker');
157
+ progressTracker.stepSuccess('compose');
158
+ progressTracker.print();
159
+
160
+ try {
161
+ await (0, _devEnvironmentLando.validateDockerAccess)();
162
+ } catch (exception) {
163
+ throw new _userError.default(exception.message);
164
+ }
165
+
166
+ progressTracker.stepSuccess('access');
167
+ progressTracker.print();
141
168
  await verifyDNSResolution(slug);
169
+ progressTracker.stepSuccess('dns');
170
+ progressTracker.print();
171
+ progressTracker.stopPrinting();
142
172
  };
143
173
 
144
174
  exports.validateDependencies = validateDependencies;
@@ -213,13 +243,22 @@ function getOptionsFromAppInfo(appInfo) {
213
243
  * Prompt for arguments
214
244
  * @param {InstanceOptions} preselectedOptions - options to be used without prompt
215
245
  * @param {InstanceOptions} defaultOptions - options to be used as default values for prompt
246
+ * @param {boolean} supressPrompts - supress prompts and use default values where needed
216
247
  * @returns {any} instance data
217
248
  */
218
249
 
219
250
 
220
- async function promptForArguments(preselectedOptions, defaultOptions) {
251
+ async function promptForArguments(preselectedOptions, defaultOptions, supressPrompts = false) {
221
252
  debug('Provided preselected', preselectedOptions, 'and default', defaultOptions);
222
- console.log(_devEnvironment.DEV_ENVIRONMENT_PROMPT_INTRO);
253
+
254
+ if (supressPrompts) {
255
+ preselectedOptions = { ...defaultOptions,
256
+ ...preselectedOptions
257
+ };
258
+ } else {
259
+ console.log(_devEnvironment.DEV_ENVIRONMENT_PROMPT_INTRO);
260
+ }
261
+
223
262
  let multisiteText = 'Multisite';
224
263
  let multisiteDefault = _devEnvironment.DEV_ENVIRONMENT_DEFAULTS.multisite;
225
264
 
@@ -248,6 +287,7 @@ async function promptForArguments(preselectedOptions, defaultOptions) {
248
287
  statsd: false,
249
288
  phpmyadmin: false,
250
289
  xdebug: false,
290
+ xdebugConfig: preselectedOptions.xdebugConfig,
251
291
  siteSlug: ''
252
292
  };
253
293
  const promptLabels = {
@@ -299,7 +339,7 @@ async function promptForArguments(preselectedOptions, defaultOptions) {
299
339
  if (service in preselectedOptions) {
300
340
  instanceData[service] = preselectedOptions[service];
301
341
  } else {
302
- instanceData[service] = await promptForBoolean(`Enable ${promptLabels[service] || service}`, instanceData[service]);
342
+ instanceData[service] = await promptForBoolean(`Enable ${promptLabels[service] || service}`, defaultOptions[service]);
303
343
  }
304
344
  }
305
345
  }
@@ -363,6 +403,7 @@ function validateLocalPath(component, providedPath) {
363
403
  }
364
404
 
365
405
  if (missingFiles.length > 0) {
406
+ // eslint-disable-next-line max-len
366
407
  const message = `Provided path "${providedPath}" is missing following files/folders: ${missingFiles.join(', ')}. Learn more: https://docs.wpvip.com/technical-references/vip-codebase/#1-wordpress`;
367
408
  return {
368
409
  result: false,
@@ -420,6 +461,11 @@ async function promptForBoolean(message, initial) {
420
461
 
421
462
  function resolvePhpVersion(version) {
422
463
  debug(`Resolving PHP version '${version}'`);
464
+
465
+ if (typeof version === 'string' && version.startsWith('image:')) {
466
+ return version;
467
+ }
468
+
423
469
  const versions = Object.keys(_devEnvironment.DEV_ENVIRONMENT_PHP_VERSIONS);
424
470
  const images = Object.values(_devEnvironment.DEV_ENVIRONMENT_PHP_VERSIONS); // eslint-disable-next-line eqeqeq -- use loose comparison because commander resolves '8.0' to '8'
425
471
 
@@ -537,15 +583,11 @@ function processBooleanOption(value) {
537
583
  return false;
538
584
  }
539
585
 
540
- if (FALSE_OPTIONS.includes((_value$toLowerCase = value.toLowerCase) === null || _value$toLowerCase === void 0 ? void 0 : _value$toLowerCase.call(value))) {
541
- return false;
542
- }
543
-
544
- return true;
586
+ return !FALSE_OPTIONS.includes((_value$toLowerCase = value.toLowerCase) === null || _value$toLowerCase === void 0 ? void 0 : _value$toLowerCase.call(value));
545
587
  }
546
588
 
547
589
  function addDevEnvConfigurationOptions(command) {
548
- return command.option('wordpress', 'Use a specific WordPress version').option(['u', 'mu-plugins'], 'Use a specific mu-plugins changeset or local directory').option('app-code', 'Use the application code from a local directory or use "demo" for VIP skeleton code').option('statsd', 'Enable statsd component. By default it is disabled', undefined, processBooleanOption).option('phpmyadmin', 'Enable PHPMyAdmin component. By default it is disabled', undefined, processBooleanOption).option('xdebug', 'Enable XDebug. By default it is disabled', undefined, processBooleanOption).option('elasticsearch', 'Explicitly choose Elasticsearch version to use or false to disable it', undefined, value => {
590
+ return command.option('wordpress', 'Use a specific WordPress version').option(['u', 'mu-plugins'], 'Use a specific mu-plugins changeset or local directory').option('app-code', 'Use the application code from a local directory or use "demo" for VIP skeleton code').option('statsd', 'Enable statsd component. By default it is disabled', undefined, processBooleanOption).option('phpmyadmin', 'Enable PHPMyAdmin component. By default it is disabled', undefined, processBooleanOption).option('xdebug', 'Enable XDebug. By default it is disabled', undefined, processBooleanOption).option('xdebug_config', 'Extra configuration to pass to xdebug via XDEBUG_CONFIG environment variable').option('elasticsearch', 'Explicitly choose Elasticsearch version to use or false to disable it', undefined, value => {
549
591
  var _value$toLowerCase2;
550
592
 
551
593
  return FALSE_OPTIONS.includes(value === null || value === void 0 ? void 0 : (_value$toLowerCase2 = value.toLowerCase) === null || _value$toLowerCase2 === void 0 ? void 0 : _value$toLowerCase2.call(value)) ? false : value;
@@ -166,6 +166,11 @@ function preProcessInstanceData(instanceData) {
166
166
 
167
167
  newInstanceData.elasticsearchEnabled = instanceData.elasticsearchEnabled || false;
168
168
  newInstanceData.php = instanceData.php || _devEnvironment.DEV_ENVIRONMENT_PHP_VERSIONS.default;
169
+
170
+ if (newInstanceData.php.startsWith('image:')) {
171
+ newInstanceData.php = newInstanceData.php.slice('image:'.length);
172
+ }
173
+
169
174
  return newInstanceData;
170
175
  }
171
176
 
@@ -258,12 +263,7 @@ async function exec(slug, args, options = {}) {
258
263
  }
259
264
 
260
265
  const command = args.shift();
261
- let commandArgs = [...args];
262
-
263
- if ('add-site' === command) {
264
- commandArgs = [...args.map(argument => argument.replace('--new-site-', '--'))];
265
- }
266
-
266
+ const commandArgs = [...args];
267
267
  await (0, _devEnvironmentLando.landoExec)(instancePath, command, commandArgs, options);
268
268
  }
269
269
 
@@ -606,7 +606,12 @@ async function updateWordPressImage(slug) {
606
606
  }) => tag.trim() === choice.tag.trim()); // Write new data and stage for rebuild
607
607
 
608
608
  envData.wordpress.tag = version.tag;
609
- envData.wordpress.ref = version.ref;
609
+ envData.wordpress.ref = version.ref; // Ensure xdebugConfig is not undefined (needed by .lando.yml template)
610
+
611
+ if (!envData.xdebugConfig) {
612
+ envData.xdebugConfig = '';
613
+ }
614
+
610
615
  await updateEnvironment(envData);
611
616
  return true;
612
617
  }
@@ -10,6 +10,7 @@ exports.landoDestroy = landoDestroy;
10
10
  exports.landoInfo = landoInfo;
11
11
  exports.landoExec = landoExec;
12
12
  exports.validateDockerInstalled = validateDockerInstalled;
13
+ exports.validateDockerAccess = validateDockerAccess;
13
14
 
14
15
  var _debug = _interopRequireDefault(require("debug"));
15
16
 
@@ -27,6 +28,8 @@ var _chalk = _interopRequireDefault(require("chalk"));
27
28
 
28
29
  var _app = _interopRequireDefault(require("lando/lib/app"));
29
30
 
31
+ var _userError = _interopRequireDefault(require("../user-error"));
32
+
30
33
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
31
34
 
32
35
  /**
@@ -283,7 +286,7 @@ async function landoExec(instancePath, toolName, args, options) {
283
286
  const isUp = await isEnvUp(app);
284
287
 
285
288
  if (!isUp) {
286
- throw new Error('environment needs to be started before running wp command');
289
+ throw new _userError.default('Environment needs to be started before running wp command');
287
290
  }
288
291
  }
289
292
 
@@ -359,4 +362,17 @@ async function validateDockerInstalled() {
359
362
  if (lando.engine.composeInstalled === false) {
360
363
  throw Error('docker-compose could not be located! Please follow the following instructions to install it - https://docs.docker.com/compose/install/');
361
364
  }
365
+ }
366
+
367
+ async function validateDockerAccess() {
368
+ const lando = new _lando.default(getLandoConfig());
369
+ await lando.bootstrap();
370
+ const docker = lando.engine.docker;
371
+ lando.log.verbose('Fetching docker info to verify user is in docker group');
372
+
373
+ try {
374
+ await docker.info();
375
+ } catch (error) {
376
+ throw Error('Failed to connect to docker. Please verify that the current user is part of docker group and has access to docker commands.');
377
+ }
362
378
  }
@@ -254,6 +254,28 @@ const checks = {
254
254
  excerpt: "'CREATE TABLE' should be present (case-insensitive)",
255
255
  recommendation: 'Check import settings to include CREATE TABLE statements'
256
256
  },
257
+ alterTable: {
258
+ matcher: /^ALTER TABLE `?([a-z0-9_]*)/i,
259
+ matchHandler: lineNumber => ({
260
+ lineNumber
261
+ }),
262
+ outputFormatter: lineNumberCheckFormatter,
263
+ results: [],
264
+ message: 'ALTER TABLE statement',
265
+ excerpt: "'ALTER TABLE' should not be present (case-insensitive)",
266
+ recommendation: 'Remove these lines and define table structure in the CREATE TABLE statement instead'
267
+ },
268
+ uniqueChecks: {
269
+ matcher: /^SET UNIQUE_CHECKS\s*=\s*0/i,
270
+ matchHandler: lineNumber => ({
271
+ lineNumber
272
+ }),
273
+ outputFormatter: lineNumberCheckFormatter,
274
+ results: [],
275
+ message: 'SET UNIQUE_CHECKS = 0',
276
+ excerpt: "'SET UNIQUE_CHECKS = 0' should not be present",
277
+ recommendation: "Disabling 'UNIQUE_CHECKS' is not allowed. These lines should be removed"
278
+ },
257
279
  siteHomeUrl: {
258
280
  matcher: "'(siteurl|home)',\\s?'(.*?)'",
259
281
  matchHandler: (lineNumber, results) => ({
@@ -386,7 +408,7 @@ const postValidation = async options => {
386
408
  is_import: options.isImport,
387
409
  error: errorSummary
388
410
  });
389
- const errorOutput = [`SQL validation failed due to ${_chalk.default.red(problemsFound)} error(s)`, ''];
411
+ const errorOutput = [];
390
412
  formattedErrors.forEach(error => {
391
413
  errorOutput.push(error.error);
392
414
 
@@ -396,6 +418,7 @@ const postValidation = async options => {
396
418
 
397
419
  errorOutput.push('');
398
420
  });
421
+ errorOutput.push(_chalk.default.bold.red(`SQL validation failed due to ${problemsFound} error(s)`));
399
422
 
400
423
  if (options.isImport) {
401
424
  throw new Error(errorOutput.join('\n'));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "2.19.2",
3
+ "version": "2.20.0",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "2.19.2",
3
+ "version": "2.20.0",
4
4
  "description": "The VIP Javascript library & CLI",
5
5
  "main": "index.js",
6
6
  "bin": {
Binary file