@automattic/vip 2.14.0 → 2.15.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/README.md CHANGED
@@ -26,6 +26,15 @@ By default, we record information about the usage of this tool using an in-house
26
26
 
27
27
  ## Changelog
28
28
 
29
+ ### 2.15.0 (3 Aug 2022)
30
+
31
+ - #1067 FORNO-1244: SQL Import: Increase max import size for launched sites to 1GB
32
+ - #1064 FORNO-1254: SQL Import: Add multisite primary domain validation
33
+ - #1062 [dev-env] Change wizard wording
34
+ - #1065 [dev-env] Update default software versions for PHP and Elasticsearch
35
+ - #1063 [dev-env] Remove redundant healthchecks
36
+ - #1061 [dev-env] Fix duplicate shortcuts
37
+
29
38
  ### 2.14.0 (19 Jul 2022)
30
39
 
31
40
  - #1059 Update engines to show support for npm > 6
@@ -68,8 +68,6 @@ services:
68
68
  command: docker-entrypoint.sh mysqld
69
69
  ports:
70
70
  - ":3306"
71
- healthcheck:
72
- test: 'mysql -uroot --silent --execute "SHOW DATABASES;"'
73
71
  environment:
74
72
  MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 'true'
75
73
  volumes:
@@ -106,8 +104,6 @@ services:
106
104
  discovery.type: 'single-node'
107
105
  ports:
108
106
  - ":9200"
109
- healthcheck:
110
- test: "curl --noproxy '*' -XGET localhost:9200"
111
107
  volumes:
112
108
  - search_data:/usr/share/elasticsearch/data
113
109
  volumes:
@@ -44,7 +44,7 @@ const examples = [{
44
44
  }];
45
45
  (0, _command.default)({
46
46
  requiredArgs: 1
47
- }).option('slug', 'Custom name of the dev environment').option('search-replace', 'Perform Search and Replace on the specified SQL file').option('in-place', 'Search and Replace explicitly on the given input file').option('skip-validate', 'Do not perform file validation.').examples(examples).argv(process.argv, async (unmatchedArgs, opt) => {
47
+ }).option('slug', 'Custom name of the dev environment').option(['r', 'search-replace'], 'Perform Search and Replace on the specified SQL file').option('in-place', 'Search and Replace explicitly on the given input file').option('skip-validate', 'Do not perform file validation.').examples(examples).argv(process.argv, async (unmatchedArgs, opt) => {
48
48
  await (0, _devEnvironmentCli.validateDependencies)();
49
49
  const [fileName] = unmatchedArgs;
50
50
  const {
@@ -249,7 +249,8 @@ async function validateAndGetTableNames({
249
249
  skipValidate,
250
250
  appId,
251
251
  envId,
252
- fileNameToUpload
252
+ fileNameToUpload,
253
+ searchReplace
253
254
  }) {
254
255
  const validations = [_sql.staticSqlValidations, _siteType.siteTypeValidations];
255
256
 
@@ -259,10 +260,10 @@ async function validateAndGetTableNames({
259
260
  }
260
261
 
261
262
  try {
262
- await (0, _lineByLine.fileLineValidations)(appId, envId, fileNameToUpload, validations);
263
+ await (0, _lineByLine.fileLineValidations)(appId, envId, fileNameToUpload, validations, searchReplace);
263
264
  } catch (validateErr) {
264
265
  console.log('');
265
- exit.withError(`${validateErr.message}
266
+ exit.withError(`${validateErr.message}\n
266
267
  If you are confident the file does not contain unsupported statements, you can retry the command with the ${_chalk.default.yellow('--skip-validate')} option.
267
268
  `);
268
269
  } // this can only be called after static validation of the SQL file
@@ -407,7 +408,8 @@ const displayPlaybook = ({
407
408
  skipValidate,
408
409
  appId,
409
410
  envId,
410
- fileNameToUpload
411
+ fileNameToUpload,
412
+ searchReplace
411
413
  }); // display playbook of what will happen during execution
412
414
 
413
415
  displayPlaybook({
@@ -11,9 +11,9 @@ exports.DEV_ENVIRONMENT_FULL_COMMAND = DEV_ENVIRONMENT_FULL_COMMAND;
11
11
  const DEV_ENVIRONMENT_DEFAULTS = {
12
12
  title: 'VIP Dev',
13
13
  multisite: false,
14
- elasticsearchVersion: '7.10.1',
14
+ elasticsearchVersion: '7.17.2',
15
15
  mariadbVersion: '10.3',
16
- phpImage: 'default'
16
+ phpVersion: '8.0'
17
17
  };
18
18
  exports.DEV_ENVIRONMENT_DEFAULTS = DEV_ENVIRONMENT_DEFAULTS;
19
19
  const DEV_ENVIRONMENT_PROMPT_INTRO = 'This is a wizard to help you set up your local dev environment.\n\n' + 'Sensible default values were pre-selected for convenience. ' + 'You may also choose to create multiple environments with different settings using the --slug option.\n\n';
@@ -32,11 +32,10 @@ const DEV_ENVIRONMENT_WORDPRESS_VERSION_TTL = 86400; // once per day
32
32
 
33
33
  exports.DEV_ENVIRONMENT_WORDPRESS_VERSION_TTL = DEV_ENVIRONMENT_WORDPRESS_VERSION_TTL;
34
34
  const DEV_ENVIRONMENT_PHP_VERSIONS = {
35
- default: 'ghcr.io/automattic/vip-container-images/php-fpm:7.4',
36
- // eslint-disable-next-line quote-props -- flow does nit support non-string keys
37
- '7.4': 'ghcr.io/automattic/vip-container-images/php-fpm-alt:7.4',
38
- '8.0': 'ghcr.io/automattic/vip-container-images/php-fpm-alt:8.0',
39
35
  // eslint-disable-next-line quote-props
40
- '8.1': 'ghcr.io/automattic/vip-container-images/php-fpm-alt:8.1'
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'
41
40
  };
42
41
  exports.DEV_ENVIRONMENT_PHP_VERSIONS = DEV_ENVIRONMENT_PHP_VERSIONS;
@@ -248,7 +248,7 @@ async function promptForArguments(preselectedOptions, defaultOptions) {
248
248
  instanceData[component] = result;
249
249
  }
250
250
 
251
- instanceData.enterpriseSearchEnabled = await promptForBoolean('Enable Enterprise Search?', defaultOptions.enterpriseSearchEnabled);
251
+ instanceData.enterpriseSearchEnabled = await promptForBoolean('Enable Elasticsearch (needed by Enterprise Search)?', defaultOptions.enterpriseSearchEnabled);
252
252
 
253
253
  if (instanceData.enterpriseSearchEnabled) {
254
254
  instanceData.statsd = preselectedOptions.statsd || defaultOptions.statsd || false;
@@ -18,7 +18,7 @@ var _fileSize = require("../constants/file-size");
18
18
  */
19
19
  const SQL_IMPORT_FILE_SIZE_LIMIT = 100 * _fileSize.GB_IN_BYTES;
20
20
  exports.SQL_IMPORT_FILE_SIZE_LIMIT = SQL_IMPORT_FILE_SIZE_LIMIT;
21
- const SQL_IMPORT_FILE_SIZE_LIMIT_LAUNCHED = 350 * _fileSize.MB_IN_BYTES;
21
+ const SQL_IMPORT_FILE_SIZE_LIMIT_LAUNCHED = 1 * _fileSize.GB_IN_BYTES;
22
22
  exports.SQL_IMPORT_FILE_SIZE_LIMIT_LAUNCHED = SQL_IMPORT_FILE_SIZE_LIMIT_LAUNCHED;
23
23
 
24
24
  function currentUserCanImportForApp(app) {
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isMultisitePrimaryDomainMapped = isMultisitePrimaryDomainMapped;
7
+ exports.getPrimaryDomain = exports.maybeSearchReplacePrimaryDomain = exports.getPrimaryDomainFromSQL = void 0;
8
+
9
+ var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
10
+
11
+ var _api = _interopRequireDefault(require("../api"));
12
+
13
+ var _tracker = require("../tracker");
14
+
15
+ var exit = _interopRequireWildcard(require("../cli/exit"));
16
+
17
+ 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); }
18
+
19
+ 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; }
20
+
21
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22
+
23
+ /**
24
+ *
25
+ * @format
26
+ */
27
+
28
+ /**
29
+ * External dependencies
30
+ */
31
+
32
+ /**
33
+ * Internal dependencies
34
+ */
35
+
36
+ /**
37
+ * Extracts the domain for site with ID 1 from an INSERT INTO `wp_site` SQL statement
38
+ *
39
+ * @param {array} statements An array of SQL statements
40
+ * @returns {string} The domain
41
+ */
42
+ const getPrimaryDomainFromSQL = statements => {
43
+ var _statements$;
44
+
45
+ if (!statements.length) {
46
+ return '';
47
+ }
48
+
49
+ const SQL_WP_SITE_DOMAINS_REGEX = /\(1,'(.*?)'/s;
50
+ const matches = (_statements$ = statements[0]) === null || _statements$ === void 0 ? void 0 : _statements$.join('').replace(/\s/g, '').match(SQL_WP_SITE_DOMAINS_REGEX);
51
+ return matches ? matches[1] : '';
52
+ };
53
+ /**
54
+ * Apply search-replacements to a domain
55
+ *
56
+ * @param {string} domain The domain to apply replacements to
57
+ * @param {(string|array)} searchReplace The search-replace pairs
58
+ * @returns {string} The processed domain
59
+ */
60
+
61
+
62
+ exports.getPrimaryDomainFromSQL = getPrimaryDomainFromSQL;
63
+
64
+ const maybeSearchReplacePrimaryDomain = function (domain, searchReplace) {
65
+ if (searchReplace) {
66
+ var _primaryDomainReplace;
67
+
68
+ let pairs = searchReplace;
69
+
70
+ if (!Array.isArray(pairs)) {
71
+ pairs = [searchReplace];
72
+ }
73
+
74
+ const domainReplacements = pairs.map(pair => pair.split(','));
75
+ const primaryDomainReplacement = domainReplacements.find(pair => pair[0] === domain);
76
+ return (_primaryDomainReplace = primaryDomainReplacement === null || primaryDomainReplacement === void 0 ? void 0 : primaryDomainReplacement[1]) !== null && _primaryDomainReplace !== void 0 ? _primaryDomainReplace : domain;
77
+ }
78
+
79
+ return domain;
80
+ };
81
+ /**
82
+ * Get the primary domain as it will be imported
83
+ *
84
+ * @param {array} statements An array of SQL statements
85
+ * @param {(string|array)} searchReplace The search-replace pairs
86
+ * @returns {string} The replaced domain, or the domain as found in the SQL dump
87
+ */
88
+
89
+
90
+ exports.maybeSearchReplacePrimaryDomain = maybeSearchReplacePrimaryDomain;
91
+
92
+ const getPrimaryDomain = function (statements, searchReplace) {
93
+ const domainFromSQL = getPrimaryDomainFromSQL(statements);
94
+ return maybeSearchReplacePrimaryDomain(domainFromSQL, searchReplace);
95
+ };
96
+ /**
97
+ * Gets the mapped domains and checks if the primary domain from the provided SQL dump is one of them
98
+ *
99
+ * @param {number} appId The ID of the app in GOOP
100
+ * @param {number} envId The ID of the enviroment in GOOP
101
+ * @param {string} primaryDomain The primary domain found in the provided SQL file
102
+ * @returns {boolean} Whether the primary domain is mapped
103
+ */
104
+
105
+
106
+ exports.getPrimaryDomain = getPrimaryDomain;
107
+
108
+ async function isMultisitePrimaryDomainMapped(appId, envId, primaryDomain) {
109
+ var _res, _res$data, _res$data$app, _environments$, _environments$$domain, _environments$$domain2;
110
+
111
+ const track = _tracker.trackEventWithEnv.bind(null, appId, envId);
112
+
113
+ const api = await (0, _api.default)();
114
+ let res;
115
+
116
+ try {
117
+ res = await api.query({
118
+ query: (0, _graphqlTag.default)`
119
+ query AppMappedDomains($appId: Int, $envId: Int) {
120
+ app(id: $appId) {
121
+ id
122
+ name
123
+ environments(id: $envId) {
124
+ uniqueLabel
125
+ isMultisite
126
+ domains {
127
+ nodes {
128
+ name
129
+ isPrimary
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ `,
136
+ variables: {
137
+ appId,
138
+ envId
139
+ }
140
+ });
141
+ } catch (GraphQlError) {
142
+ await track('import_sql_command_error', {
143
+ error_type: 'GraphQL-MappedDomain-Check-failed',
144
+ gql_err: GraphQlError
145
+ });
146
+ exit.withError(`StartImport call failed: ${GraphQlError}`);
147
+ }
148
+
149
+ if (!Array.isArray((_res = res) === null || _res === void 0 ? void 0 : (_res$data = _res.data) === null || _res$data === void 0 ? void 0 : (_res$data$app = _res$data.app) === null || _res$data$app === void 0 ? void 0 : _res$data$app.environments)) {
150
+ return false;
151
+ }
152
+
153
+ const environments = res.data.app.environments;
154
+
155
+ if (!environments.length) {
156
+ return false;
157
+ }
158
+
159
+ const mappedDomains = (_environments$ = environments[0]) === null || _environments$ === void 0 ? void 0 : (_environments$$domain = _environments$.domains) === null || _environments$$domain === void 0 ? void 0 : (_environments$$domain2 = _environments$$domain.nodes) === null || _environments$$domain2 === void 0 ? void 0 : _environments$$domain2.map(domain => domain.name);
160
+ return mappedDomains.includes(primaryDomain);
161
+ }
@@ -64,7 +64,7 @@ async function getReadInterface(filename) {
64
64
  });
65
65
  }
66
66
 
67
- async function fileLineValidations(appId, envId, fileName, validations) {
67
+ async function fileLineValidations(appId, envId, fileName, validations, searchReplace) {
68
68
  const isImport = true;
69
69
  const readInterface = await getReadInterface(fileName);
70
70
  debug('Validations: ', validations);
@@ -85,7 +85,8 @@ async function fileLineValidations(appId, envId, fileName, validations) {
85
85
  fileName,
86
86
  isImport,
87
87
  appId,
88
- envId
88
+ envId,
89
+ searchReplace
89
90
  });
90
91
  }
91
92
  }));
@@ -13,6 +13,10 @@ var _isMultiSiteSqlDump = require("./is-multi-site-sql-dump");
13
13
 
14
14
  var _isMultiSite = require("./is-multi-site");
15
15
 
16
+ var _isMultisiteDomainMapped = require("./is-multisite-domain-mapped");
17
+
18
+ var _utils = require("./utils");
19
+
16
20
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
21
 
18
22
  /**
@@ -29,9 +33,12 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
29
33
  */
30
34
  const debug = (0, _debug.default)('vip:vip-import-sql');
31
35
  let isMultiSiteSqlDump = false;
36
+ let wpSiteInsertStatement;
37
+ const getWpSiteInsertStatement = (0, _utils.getMultilineStatement)(/INSERT INTO `wp_site`/s);
32
38
  const siteTypeValidations = {
33
39
  execute: line => {
34
40
  const lineIsMultiSite = (0, _isMultiSiteSqlDump.sqlDumpLineIsMultiSite)(line);
41
+ wpSiteInsertStatement = getWpSiteInsertStatement(line);
35
42
 
36
43
  if (lineIsMultiSite) {
37
44
  isMultiSiteSqlDump = true;
@@ -39,9 +46,12 @@ const siteTypeValidations = {
39
46
  },
40
47
  postLineExecutionProcessing: async ({
41
48
  appId,
42
- envId
49
+ envId,
50
+ searchReplace
43
51
  }) => {
44
52
  const isMultiSite = await (0, _isMultiSite.isMultiSiteInSiteMeta)(appId, envId);
53
+ const primaryDomainFromSQL = (0, _isMultisiteDomainMapped.getPrimaryDomain)(wpSiteInsertStatement, searchReplace);
54
+ const isPrimaryDomainMapped = primaryDomainFromSQL && (await (0, _isMultisiteDomainMapped.isMultisitePrimaryDomainMapped)(appId, envId, primaryDomainFromSQL));
45
55
 
46
56
  const track = _tracker.trackEventWithEnv.bind(null, appId, envId);
47
57
 
@@ -61,6 +71,13 @@ const siteTypeValidations = {
61
71
  });
62
72
  throw new Error('You have requested a subsite SQL import but have not provided a subsite compatiable SQL dump.');
63
73
  }
74
+
75
+ if (isMultiSite && !isPrimaryDomainMapped) {
76
+ await track('import_sql_command_error', {
77
+ error_type: 'multisite-import-where-primary-domain-unmapped'
78
+ });
79
+ throw new Error('This import would set the network\'s main site domain to ' + primaryDomainFromSQL + ', however this domain is not mapped to the target environment. Please replace this domain in your ' + 'import file, or map it to the environment.');
80
+ }
64
81
  }
65
82
  };
66
83
  exports.siteTypeValidations = siteTypeValidations;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getMultilineStatement = getMultilineStatement;
7
+
8
+ /**
9
+ * Get SQL statements matching a supplied pattern from a file stream
10
+ *
11
+ * @param {RegExp} statementRegex A RegExp pattern representing the start of the statement to capture
12
+ * @returns {function} A function which processes individual lines to capture the matching statements
13
+ */
14
+ function getMultilineStatement(statementRegex) {
15
+ const matchingStatements = [];
16
+ let isCapturing = false;
17
+ let index = 0;
18
+ /**
19
+ * Processes each line of the file stream and builds an array of statements which start with the supplied pattern
20
+ *
21
+ * @param {string} line A line from the file stream
22
+ * @returns {array} An array of matching statements where each statement is presented as an array of lines
23
+ */
24
+
25
+ return line => {
26
+ const shouldStartCapture = statementRegex.test(line);
27
+ const shouldEndCapture = (shouldStartCapture || isCapturing) && /;$/.test(line);
28
+
29
+ if (shouldStartCapture) {
30
+ isCapturing = true;
31
+ matchingStatements[index] = [];
32
+ }
33
+
34
+ if (isCapturing) {
35
+ matchingStatements[index].push(line);
36
+ }
37
+
38
+ if (shouldEndCapture) {
39
+ isCapturing = false;
40
+ index++;
41
+ }
42
+
43
+ return matchingStatements;
44
+ };
45
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "2.14.0",
3
+ "version": "2.15.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.14.0",
3
+ "version": "2.15.0",
4
4
  "description": "The VIP Javascript library & CLI",
5
5
  "main": "index.js",
6
6
  "bin": {