@automattic/vip 2.31.0-dev1 → 2.31.1-dev

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,30 @@
1
1
  ## Changelog
2
2
 
3
+ ### 2.31.0
4
+
5
+ - #1397 feature(dev-env): Add ability to sync multisites
6
+ - #1399 chore(dev-deps): Remove stub type definitions
7
+ - #1394 FORNO-1609: Combine all error events into one error event
8
+ - #1345 feat(dev-env): Add Photon
9
+ - #1398 Fix coverage generation
10
+ - #1395 Add types for the other ways of using enquirer
11
+ - #1388 Typescript: refactor `vip-whoami`
12
+ - #1393 refactor: Convert `lib/{app-logs,envvar}` to TypeScript
13
+ - #1392 refactor: Convert `lib/config` to TypeScript
14
+ - #1391 refactor: Convert lib to TypeScript
15
+ - #1390 chore(deps): Update @automattic/vip-search-replace to 1.1.1
16
+ - #1389 Replace 'site' by 'environment' in error msg
17
+ - #1385 refactor: Convert lib/api to TypeScript
18
+ - #1387 chore(deps): Update @automattic/vip-search-replace to 1.1.0
19
+ - #1386 fix: Fix `getAbsolutePath()` and convert `utils.js` to TypeScript
20
+ - #1383 fix(dev-env): Pull uniqueLabel field from backend
21
+ - #1382 test: Reduce noise from tests
22
+ - #1384 fix: Add return type to parseEnvAliasFromArgv()
23
+ - #1381 refactor: Convert lib/cli and dependencies to TypeScript
24
+ - #1377 chore: Configure linting for TS files
25
+ - #1380 chore(dev-deps): Remove jest-environment-jsdom
26
+ - #1378 refactor: Convert analytics and dependencies to TypeScript
27
+
3
28
  ### 2.30.0
4
29
 
5
30
  - #1264 feature(dev-env): Add dev-env-sync-sql command
@@ -222,6 +222,20 @@ services:
222
222
  LANDO_NEEDS_EXEC: 1
223
223
  <% } %>
224
224
 
225
+ <% if ( photon ) { %>
226
+ photon:
227
+ type: compose
228
+ services:
229
+ image: ghcr.io/automattic/vip-container-images/photon:latest
230
+ command: /usr/sbin/php-fpm
231
+ environment:
232
+ LANDO_NO_USER_PERMS: 1
233
+ LANDO_NO_SCRIPTS: 1
234
+ LANDO_NEEDS_EXEC: 1
235
+ volumes:
236
+ - ./uploads:/usr/share/webapps/photon/uploads:ro
237
+ <% } %>
238
+
225
239
  tooling:
226
240
  wp:
227
241
  service: php
@@ -1,6 +1,27 @@
1
+ <% if ( photon ) { %>
2
+
3
+ location ^~ /wp-content/uploads/ {
4
+ expires max;
5
+ log_not_found off;
1
6
  <% if ( mediaRedirectDomain ) { %>
7
+ if (!-f $request_filename) {
8
+ rewrite ^/(.*)$ <%= mediaRedirectDomain %>/$1 redirect;
9
+ }
10
+ <% } %>
11
+
12
+ include fastcgi_params;
13
+ fastcgi_param DOCUMENT_ROOT /usr/share/webapps/photon;
14
+ fastcgi_param SCRIPT_FILENAME /usr/share/webapps/photon/index.php;
15
+ fastcgi_param SCRIPT_NAME /index.php;
16
+
17
+ if ($request_uri ~* \.(gif|jpe?g|png)\?) {
18
+ fastcgi_pass photon:9000;
19
+ }
20
+ }
21
+
22
+ <% } else if ( mediaRedirectDomain ) { %>
2
23
 
3
- location ~* /wp-content/uploads {
24
+ location ^~ /wp-content/uploads {
4
25
  expires max;
5
26
  log_not_found off;
6
27
  try_files $uri @prod_site;
@@ -10,4 +31,4 @@ location @prod_site {
10
31
  rewrite ^/(.*)$ <%= mediaRedirectDomain %>/$1 redirect;
11
32
  }
12
33
 
13
- <% } %>
34
+ <% } %>
Binary file
@@ -29,7 +29,8 @@ const userMap = {
29
29
  elasticsearch: 'elasticsearch',
30
30
  phpmyadmin: 'www-data',
31
31
  mailhog: 'mailhog',
32
- mailpit: 'root'
32
+ mailpit: 'root',
33
+ photon: 'root'
33
34
  };
34
35
  const examples = [{
35
36
  usage: `${_devEnvironment.DEV_ENVIRONMENT_FULL_COMMAND} shell`,
@@ -62,7 +62,8 @@ const appQuery = `
62
62
  const trackerFn = (0, _tracker.makeCommandTracker)('dev_env_sync_sql', {
63
63
  app: app.id,
64
64
  env: env.uniqueLabel,
65
- slug
65
+ slug,
66
+ multisite: env.isMultisite
66
67
  });
67
68
  await trackerFn('execute');
68
69
  const lando = await (0, _devEnvironmentLando.bootstrapLando)();
@@ -74,6 +75,8 @@ const appQuery = `
74
75
  throw new _userError.default('Environment needs to be started first');
75
76
  }
76
77
  const cmd = new _devEnvSyncSql.DevEnvSyncSQLCommand(app, env, slug, trackerFn);
78
+ // TODO: There's a function called handleCLIException for dev-env that handles exceptions but DevEnvSyncSQLCommand has its own implementation.
79
+ // We should probably use handleCLIException instead?
77
80
  await cmd.run();
78
81
  await trackerFn('success');
79
82
  });
File without changes
@@ -64,6 +64,7 @@ cmd.argv(process.argv, async (arg, opt) => {
64
64
  phpmyadmin: currentInstanceData.phpmyadmin,
65
65
  xdebug: currentInstanceData.xdebug,
66
66
  mailpit: (_currentInstanceData$ = currentInstanceData.mailpit) !== null && _currentInstanceData$ !== void 0 ? _currentInstanceData$ : currentInstanceData.mailhog,
67
+ photon: currentInstanceData.photon,
67
68
  mediaRedirectDomain: currentInstanceData.mediaRedirectDomain,
68
69
  multisite: false,
69
70
  title: ''
@@ -46,17 +46,21 @@ const appQuery = `
46
46
  module: 'export-sql',
47
47
  requiredArgs: 0,
48
48
  usage: 'vip export sql'
49
- }).option('output', 'Specify the location where you want to save the export file').examples(examples).argv(process.argv, async (arg, {
49
+ }).option('output', 'Specify the location where you want to save the export file').option('generate-backup', 'Generate a new backup instead of using the available ones').examples(examples).argv(process.argv, async (arg, {
50
50
  app,
51
51
  env,
52
- output
52
+ output,
53
+ generateBackup
53
54
  }) => {
54
55
  const trackerFn = (0, _tracker.makeCommandTracker)('export_sql', {
55
56
  app: app.id,
56
57
  env: env.uniqueLabel
57
58
  });
58
59
  await trackerFn('execute');
59
- const exportCommand = new _exportSql.ExportSQLCommand(app, env, output, trackerFn);
60
+ const exportCommand = new _exportSql.ExportSQLCommand(app, env, {
61
+ outputFile: output,
62
+ generateBackup
63
+ }, trackerFn);
60
64
  await exportCommand.run();
61
65
  await trackerFn('success');
62
66
  });
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DB_BACKUP_JOB_STATUS_QUERY = exports.CREATE_DB_BACKUP_JOB_MUTATION = exports.BackupDBCommand = void 0;
7
+ var _chalk = _interopRequireDefault(require("chalk"));
8
+ var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
9
+ var _api = _interopRequireWildcard(require("../lib/api"));
10
+ var exit = _interopRequireWildcard(require("../lib/cli/exit"));
11
+ var _utils = require("../lib/utils");
12
+ var _progress = require("../lib/cli/progress");
13
+ 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); }
14
+ 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; }
15
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
+ /**
17
+ *
18
+ * @format
19
+ */
20
+
21
+ /**
22
+ * External dependencies
23
+ */
24
+
25
+ /**
26
+ * Internal dependencies
27
+ */
28
+
29
+ const DB_BACKUP_PROGRESS_POLL_INTERVAL = 1000;
30
+ const CREATE_DB_BACKUP_JOB_MUTATION = (0, _graphqlTag.default)`
31
+ mutation TriggerDatabaseBackup($input: AppEnvironmentTriggerDBBackupInput) {
32
+ triggerDatabaseBackup(input: $input) {
33
+ success
34
+ }
35
+ }
36
+ `;
37
+ exports.CREATE_DB_BACKUP_JOB_MUTATION = CREATE_DB_BACKUP_JOB_MUTATION;
38
+ const DB_BACKUP_JOB_STATUS_QUERY = (0, _graphqlTag.default)`
39
+ query AppBackupJobStatus($appId: Int!, $envId: Int!) {
40
+ app(id: $appId) {
41
+ id
42
+ environments(id: $envId) {
43
+ id
44
+ jobs(jobTypes: [db_backup]) {
45
+ id
46
+ type
47
+ completedAt
48
+ createdAt
49
+ inProgressLock
50
+ metadata {
51
+ name
52
+ value
53
+ }
54
+ progress {
55
+ status
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ `;
62
+ exports.DB_BACKUP_JOB_STATUS_QUERY = DB_BACKUP_JOB_STATUS_QUERY;
63
+ async function getBackupJob(appId, envId) {
64
+ const api = await (0, _api.default)();
65
+ const response = await api.query({
66
+ query: DB_BACKUP_JOB_STATUS_QUERY,
67
+ variables: {
68
+ appId,
69
+ envId
70
+ },
71
+ fetchPolicy: 'network-only'
72
+ });
73
+ const {
74
+ data: {
75
+ app: {
76
+ environments
77
+ }
78
+ }
79
+ } = response;
80
+ const job = environments[0].jobs[0];
81
+ return job || null;
82
+ }
83
+ async function createBackupJob(appId, envId) {
84
+ // Disable global error handling so that we can handle errors ourselves
85
+ (0, _api.disableGlobalGraphQLErrorHandling)();
86
+ const api = await (0, _api.default)();
87
+ await api.mutate({
88
+ mutation: CREATE_DB_BACKUP_JOB_MUTATION,
89
+ variables: {
90
+ input: {
91
+ id: appId,
92
+ environmentId: envId
93
+ }
94
+ }
95
+ });
96
+
97
+ // Re-enable global error handling
98
+ (0, _api.enableGlobalGraphQLErrorHandling)();
99
+ }
100
+
101
+ // Library for a possible command in the future: vip backup db @app.env
102
+ class BackupDBCommand {
103
+ steps = {
104
+ PREPARE: 'prepare',
105
+ GENERATE: 'generate'
106
+ };
107
+ constructor(app, env, trackerFn = () => {}) {
108
+ this.app = app;
109
+ this.env = env;
110
+ this.progressTracker = new _progress.ProgressTracker([{
111
+ id: this.steps.PREPARE,
112
+ name: 'Preparing'
113
+ }, {
114
+ id: this.steps.GENERATE,
115
+ name: 'Generating backup'
116
+ }]);
117
+ this.track = trackerFn;
118
+ }
119
+ log(msg) {
120
+ if (this.silent) return;
121
+ console.log(msg);
122
+ }
123
+ isDone(job) {
124
+ return !job.inProgressLock;
125
+ }
126
+
127
+ /**
128
+ * Stops the progress tracker
129
+ *
130
+ * @return {void}
131
+ */
132
+ stopProgressTracker() {
133
+ this.progressTracker.print();
134
+ this.progressTracker.stopPrinting();
135
+ }
136
+ async loadBackupJob() {
137
+ var _this$job, _this$job$metadata$fi, _this$job2, _this$job2$progress, _this$job3;
138
+ this.job = await getBackupJob(this.app.id, this.env.id);
139
+ this.backupName = ((_this$job = this.job) === null || _this$job === void 0 ? void 0 : (_this$job$metadata$fi = _this$job.metadata.find(meta => meta.name === 'backupName')) === null || _this$job$metadata$fi === void 0 ? void 0 : _this$job$metadata$fi.value) || 'Unknown';
140
+ this.jobStatus = (_this$job2 = this.job) === null || _this$job2 === void 0 ? void 0 : (_this$job2$progress = _this$job2.progress) === null || _this$job2$progress === void 0 ? void 0 : _this$job2$progress.status;
141
+ if ((_this$job3 = this.job) !== null && _this$job3 !== void 0 && _this$job3.completedAt) {
142
+ this.jobAge = (new Date() - new Date(this.job.completedAt)) / 1000 / 60;
143
+ } else {
144
+ this.jobAge = undefined;
145
+ }
146
+ return this.job;
147
+ }
148
+ async run(silent = false) {
149
+ var _this$job4;
150
+ this.silent = silent;
151
+ const readMoreMessage = '\nRead more about the limitations around database backups & exports here: https://docs.wpvip.com/technical-references/vip-dashboard/backups/ \n';
152
+ let noticeMessage = `\n${_chalk.default.yellow('NOTICE: ')}`;
153
+ noticeMessage += 'A fresh database backup will be generated only if there hasn\'t one already been created recently, either by our automated system or by a user on your site';
154
+ noticeMessage += readMoreMessage;
155
+ this.log(noticeMessage);
156
+ await this.loadBackupJob();
157
+ if ((_this$job4 = this.job) !== null && _this$job4 !== void 0 && _this$job4.inProgressLock) {
158
+ this.log('Database backup already in progress...');
159
+ } else {
160
+ try {
161
+ this.log('Creating a new database backup...');
162
+ this.progressTracker.stepRunning(this.steps.PREPARE);
163
+ this.progressTracker.startPrinting();
164
+ await createBackupJob(this.app.id, this.env.id);
165
+ } catch (err) {
166
+ var _err$message;
167
+ this.progressTracker.stepFailed(this.steps.PREPARE);
168
+ this.stopProgressTracker();
169
+ if ((_err$message = err.message) !== null && _err$message !== void 0 && _err$message.includes('Database backups limit reached')) {
170
+ await this.track('error', {
171
+ error_type: 'rate_limit_exceeded',
172
+ error_message: `Couldn't create a new database backup job: ${err === null || err === void 0 ? void 0 : err.message}`,
173
+ stack: err === null || err === void 0 ? void 0 : err.stack
174
+ });
175
+ let errMessage = err.message.replace('Database backups limit reached', 'New database backup generation failed because there was already one created recently, either by our automated system or by a user on your site');
176
+ errMessage = errMessage.replace('Retry after', '\nTo create a new backup, you can wait until:');
177
+ errMessage += `\n\nYou can also export the latest backup using the ${_chalk.default.green('vip @app.env export sql')} command`;
178
+ errMessage += readMoreMessage;
179
+ exit.withError(errMessage);
180
+ }
181
+ await this.track('error', {
182
+ error_type: 'db_backup_job_creation_failed',
183
+ error_message: `Database Backup job creation failed: ${err === null || err === void 0 ? void 0 : err.message}`,
184
+ stack: err === null || err === void 0 ? void 0 : err.stack
185
+ });
186
+ exit.withError(`Couldn't create a new database backup job: ${err === null || err === void 0 ? void 0 : err.message}`);
187
+ }
188
+ }
189
+ this.progressTracker.stepSuccess(this.steps.PREPARE);
190
+ this.progressTracker.stepRunning(this.steps.GENERATE);
191
+ try {
192
+ await (0, _utils.pollUntil)(this.loadBackupJob.bind(this), DB_BACKUP_PROGRESS_POLL_INTERVAL, this.isDone.bind(this));
193
+ } catch (err) {
194
+ this.progressTracker.stepFailed(this.steps.GENERATE);
195
+ this.stopProgressTracker();
196
+ await this.track('error', {
197
+ error_type: 'db_backup_job_failed',
198
+ error_message: `Database Backup job failed: ${err === null || err === void 0 ? void 0 : err.message}`,
199
+ stack: err === null || err === void 0 ? void 0 : err.stack
200
+ });
201
+ exit.withError(`Failed to create new database backup: ${err === null || err === void 0 ? void 0 : err.message}`);
202
+ }
203
+ this.progressTracker.stepSuccess(this.steps.GENERATE);
204
+ this.stopProgressTracker();
205
+ await this.loadBackupJob();
206
+ if (this.jobStatus !== 'success') {
207
+ exit.withError('Failed to create a new database backup');
208
+ } else {
209
+ this.log(`New database backup created at ${this.backupName}`);
210
+ }
211
+ }
212
+ }
213
+ exports.BackupDBCommand = BackupDBCommand;
@@ -100,7 +100,9 @@ class DevEnvSyncSQLCommand {
100
100
  * @return {Promise<void>} Promise that resolves when the export is complete
101
101
  */
102
102
  async generateExport() {
103
- const exportCommand = new _exportSql.ExportSQLCommand(this.app, this.env, this.gzFile, this.track);
103
+ const exportCommand = new _exportSql.ExportSQLCommand(this.app, this.env, {
104
+ outputFile: this.gzFile
105
+ }, this.track);
104
106
  await exportCommand.run();
105
107
  }
106
108
 
@@ -135,7 +137,7 @@ class DevEnvSyncSQLCommand {
135
137
  for (const site of networkSites) {
136
138
  if (!site.blogId || site.blogId === 1) continue;
137
139
  const url = site.homeUrl.replace(/https?:\/\//, '');
138
- if (!this.searchReplaceMap[url]) return;
140
+ if (!this.searchReplaceMap[url]) continue;
139
141
  this.searchReplaceMap[url] = `${site.blogId}.${this.landoDomain}`;
140
142
  }
141
143
  }
@@ -165,6 +167,14 @@ class DevEnvSyncSQLCommand {
165
167
  try {
166
168
  await this.generateExport();
167
169
  } catch (err) {
170
+ // this.generateExport probably catches all exceptions, track the event and runs exit.withError() but if things go really wrong
171
+ // and we have no tracking data, we would at least have it logged here.
172
+ // the following will not get executed if this.generateExport() calls exit.withError() on all exception
173
+ await this.track('error', {
174
+ error_type: 'export_sql_backup',
175
+ error_message: err === null || err === void 0 ? void 0 : err.message,
176
+ stack: err === null || err === void 0 ? void 0 : err.stack
177
+ });
168
178
  exit.withError(`Error exporting SQL backup: ${err === null || err === void 0 ? void 0 : err.message}`);
169
179
  }
170
180
  try {
@@ -172,8 +182,10 @@ class DevEnvSyncSQLCommand {
172
182
  await (0, _clientFileUploader.unzipFile)(this.gzFile, this.sqlFile);
173
183
  console.log(`${_chalk.default.green('✓')} Extracted to ${this.sqlFile}`);
174
184
  } catch (err) {
175
- await this.track('archive_extraction_error', {
176
- errorMessage: err.message
185
+ await this.track('error', {
186
+ error_type: 'archive_extraction',
187
+ error_message: err === null || err === void 0 ? void 0 : err.message,
188
+ stack: err === null || err === void 0 ? void 0 : err.stack
177
189
  });
178
190
  exit.withError(`Error extracting the SQL export: ${err.message}`);
179
191
  }
@@ -181,6 +193,11 @@ class DevEnvSyncSQLCommand {
181
193
  console.log('Extracting site urls from the SQL file...');
182
194
  this.siteUrls = await extractSiteUrls(this.sqlFile);
183
195
  } catch (err) {
196
+ await this.track('error', {
197
+ error_type: 'extract_site_urls',
198
+ error_message: err === null || err === void 0 ? void 0 : err.message,
199
+ stack: err === null || err === void 0 ? void 0 : err.stack
200
+ });
184
201
  exit.withError(`Error extracting site URLs: ${err === null || err === void 0 ? void 0 : err.message}`);
185
202
  }
186
203
  console.log('Generating search-replace configuration...');
@@ -193,8 +210,10 @@ class DevEnvSyncSQLCommand {
193
210
  await this.runSearchReplace();
194
211
  console.log(`${_chalk.default.green('✓')} Search-replace operation is complete`);
195
212
  } catch (err) {
196
- await this.track('search_replace_error', {
197
- errorMessage: err === null || err === void 0 ? void 0 : err.message
213
+ await this.track('error', {
214
+ error_type: 'search_replace',
215
+ error_message: err === null || err === void 0 ? void 0 : err.message,
216
+ stack: err === null || err === void 0 ? void 0 : err.stack
198
217
  });
199
218
  exit.withError(`Error replacing domains: ${err === null || err === void 0 ? void 0 : err.message}`);
200
219
  }
@@ -203,8 +222,10 @@ class DevEnvSyncSQLCommand {
203
222
  await this.runImport();
204
223
  console.log(`${_chalk.default.green('✓')} SQL file imported`);
205
224
  } catch (err) {
206
- await this.track('import_error', {
207
- errorMessage: err === null || err === void 0 ? void 0 : err.message
225
+ await this.track('error', {
226
+ error_type: 'import_sql_file',
227
+ error_message: err === null || err === void 0 ? void 0 : err.message,
228
+ stack: err === null || err === void 0 ? void 0 : err.stack
208
229
  });
209
230
  exit.withError(`Error importing SQL file: ${err === null || err === void 0 ? void 0 : err.message}`);
210
231
  }
@@ -13,6 +13,7 @@ var _format = require("../lib/cli/format");
13
13
  var _progress = require("../lib/cli/progress");
14
14
  var exit = _interopRequireWildcard(require("../lib/cli/exit"));
15
15
  var _utils = require("../lib/utils");
16
+ var _backupDb = require("./backup-db");
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); }
17
18
  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; }
18
19
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -185,6 +186,7 @@ async function createExportJob(appId, envId, backupId) {
185
186
  */
186
187
  class ExportSQLCommand {
187
188
  steps = {
189
+ GENERATE: 'generate',
188
190
  PREPARE: 'prepare',
189
191
  CREATE: 'create',
190
192
  DOWNLOAD_LINK: 'downloadLink',
@@ -195,13 +197,14 @@ class ExportSQLCommand {
195
197
  *
196
198
  * @param {any} app The application object
197
199
  * @param {any} env The environment object
198
- * @param {string} outputFile The output file path
200
+ * @param {object} options The optional parameters
199
201
  * @param {Function} trackerFn The progress tracker function
200
202
  */
201
- constructor(app, env, outputFile, trackerFn = () => {}) {
203
+ constructor(app, env, options = {}, trackerFn = () => {}) {
202
204
  this.app = app;
203
205
  this.env = env;
204
- this.outputFile = typeof outputFile === 'string' ? (0, _utils.getAbsolutePath)(outputFile) : null;
206
+ this.outputFile = typeof options.outputFile === 'string' ? (0, _utils.getAbsolutePath)(options.outputFile) : null;
207
+ this.generateBackup = options.generateBackup || false;
205
208
  this.progressTracker = new _progress.ProgressTracker([{
206
209
  id: this.steps.PREPARE,
207
210
  name: 'Preparing'
@@ -309,6 +312,10 @@ class ExportSQLCommand {
309
312
  this.progressTracker.print();
310
313
  this.progressTracker.stopPrinting();
311
314
  }
315
+ async runBackupJob() {
316
+ const cmd = new _backupDb.BackupDBCommand(this.app, this.env);
317
+ await cmd.run(false);
318
+ }
312
319
 
313
320
  /**
314
321
  * Sequentially runs the steps of the export workflow
@@ -322,28 +329,35 @@ class ExportSQLCommand {
322
329
  } catch (err) {
323
330
  await this.track('error', {
324
331
  error_type: 'cannot_write_to_path',
325
- error_message: `Cannot write to the specified path: ${err === null || err === void 0 ? void 0 : err.message}`
332
+ error_message: `Cannot write to the specified path: ${err === null || err === void 0 ? void 0 : err.message}`,
333
+ stack: err === null || err === void 0 ? void 0 : err.stack
326
334
  });
327
335
  exit.withError(`Cannot write to the specified path: ${err === null || err === void 0 ? void 0 : err.message}`);
328
336
  }
329
337
  }
330
- console.log(`Fetching the latest backup for ${this.app.name}`);
338
+ if (this.generateBackup) {
339
+ await this.runBackupJob();
340
+ }
331
341
  const {
332
342
  latestBackup
333
343
  } = await fetchLatestBackupAndJobStatus(this.app.id, this.env.id);
334
- if (!latestBackup) {
335
- await this.track('error', {
336
- error_type: 'no_backup_found',
337
- error_message: 'No backup found for the site'
338
- });
339
- exit.withError(`No backup found for site ${this.app.name}`);
344
+ if (!this.generateBackup) {
345
+ if (!latestBackup) {
346
+ await this.track('error', {
347
+ error_type: 'no_backup_found',
348
+ error_message: 'No backup found for the site'
349
+ });
350
+ exit.withError(`No backup found for site ${this.app.name}`);
351
+ } else {
352
+ console.log(`${(0, _format.getGlyphForStatus)('success')} Latest backup found with timestamp ${latestBackup.createdAt}`);
353
+ }
340
354
  } else {
341
- console.log(`${(0, _format.getGlyphForStatus)('success')} Latest backup found with timestamp ${latestBackup.createdAt}`);
355
+ console.log(`${(0, _format.getGlyphForStatus)('success')} Backup created with timestamp ${latestBackup.createdAt}`);
342
356
  }
343
357
  if (await this.getExportJob()) {
344
358
  console.log(`Attaching to an existing export for the backup with timestamp ${latestBackup.createdAt}`);
345
359
  } else {
346
- console.log(`Creating a new export for the backup with timestamp ${latestBackup.createdAt}`);
360
+ console.log(`Exporting database backup with timestamp ${latestBackup.createdAt}`);
347
361
  try {
348
362
  await createExportJob(this.app.id, this.env.id, latestBackup.id);
349
363
  } catch (err) {
@@ -351,9 +365,16 @@ class ExportSQLCommand {
351
365
  if (err !== null && err !== void 0 && err.message.includes('Backup Copy already in progress')) {
352
366
  await this.track('error', {
353
367
  error_type: 'job_already_running',
354
- error_message: err === null || err === void 0 ? void 0 : err.message
368
+ error_message: err === null || err === void 0 ? void 0 : err.message,
369
+ stack: err === null || err === void 0 ? void 0 : err.stack
355
370
  });
356
371
  exit.withError('There is an export job already running for this environment: ' + `https://dashboard.wpvip.com/apps/${this.app.id}/${this.env.uniqueLabel}/data/database/backups\n` + 'Currently, we allow only one export job at a time, per site. Please try again later.');
372
+ } else {
373
+ await this.track('error', {
374
+ error_type: 'create_export_job',
375
+ error_message: err === null || err === void 0 ? void 0 : err.message,
376
+ stack: err === null || err === void 0 ? void 0 : err.stack
377
+ });
357
378
  }
358
379
  exit.withError(`Error creating export job: ${err === null || err === void 0 ? void 0 : err.message}`);
359
380
  }
@@ -378,7 +399,8 @@ class ExportSQLCommand {
378
399
  this.stopProgressTracker();
379
400
  await this.track('error', {
380
401
  error_type: 'download_failed',
381
- error_message: err === null || err === void 0 ? void 0 : err.message
402
+ error_message: err === null || err === void 0 ? void 0 : err.message,
403
+ stack: err === null || err === void 0 ? void 0 : err.stack
382
404
  });
383
405
  exit.withError(`Error downloading exported file: ${err === null || err === void 0 ? void 0 : err.message}`);
384
406
  }
@@ -307,12 +307,14 @@ async function promptForArguments(preselectedOptions, defaultOptions, suppressPr
307
307
  xdebug: false,
308
308
  xdebugConfig: preselectedOptions.xdebugConfig,
309
309
  siteSlug: '',
310
- mailpit: false
310
+ mailpit: false,
311
+ photon: false
311
312
  };
312
313
  const promptLabels = {
313
314
  xdebug: 'XDebug',
314
315
  phpmyadmin: 'phpMyAdmin',
315
- mailpit: 'Mailpit'
316
+ mailpit: 'Mailpit',
317
+ photon: 'Photon'
316
318
  };
317
319
  if (!instanceData.mediaRedirectDomain && defaultOptions.mediaRedirectDomain) {
318
320
  const mediaRedirectPromptText = `Would you like to redirect to ${defaultOptions.mediaRedirectDomain} for missing media files?`;
@@ -340,7 +342,7 @@ async function promptForArguments(preselectedOptions, defaultOptions, suppressPr
340
342
  } else {
341
343
  instanceData.elasticsearch = await promptForBoolean('Enable Elasticsearch (needed by Enterprise Search)?', !!defaultOptions.elasticsearch);
342
344
  }
343
- for (const service of ['phpmyadmin', 'xdebug', 'mailpit']) {
345
+ for (const service of ['phpmyadmin', 'xdebug', 'mailpit', 'photon']) {
344
346
  if (service in instanceData) {
345
347
  if (service in preselectedOptions) {
346
348
  instanceData[service] = preselectedOptions[service];
@@ -636,7 +638,7 @@ function processVersionOption(value) {
636
638
  }
637
639
  function addDevEnvConfigurationOptions(command) {
638
640
  // We leave the third parameter to undefined on some because the defaults are handled in preProcessInstanceData()
639
- return command.option('wordpress', 'Use a specific WordPress version', undefined, processVersionOption).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('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', 'Enable Elasticsearch (needed by Enterprise Search)', undefined, processBooleanOption).option(['r', 'media-redirect-domain'], 'Domain to redirect for missing media files. This can be used to still have images without the need to import them locally.').option('php', 'Explicitly choose PHP version to use', undefined, processVersionOption).option(['G', 'mailhog'], 'Enable Mailpit. By default it is disabled (deprecated option, please use --mailpit instead)', undefined, processBooleanOption).option(['A', 'mailpit'], 'Enable Mailpit. By default it is disabled', undefined, processBooleanOption);
641
+ return command.option('wordpress', 'Use a specific WordPress version', undefined, processVersionOption).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('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', 'Enable Elasticsearch (needed by Enterprise Search)', undefined, processBooleanOption).option(['r', 'media-redirect-domain'], 'Domain to redirect for missing media files. This can be used to still have images without the need to import them locally.').option('php', 'Explicitly choose PHP version to use', undefined, processVersionOption).option(['G', 'mailhog'], 'Enable Mailpit. By default it is disabled (deprecated option, please use --mailpit instead)', undefined, processBooleanOption).option(['A', 'mailpit'], 'Enable Mailpit. By default it is disabled', undefined, processBooleanOption).option(['H', 'photon'], 'Enable Photon. By default it is disabled', undefined, processBooleanOption);
640
642
  }
641
643
 
642
644
  /**
@@ -94,7 +94,8 @@ async function sanitizeConfiguration(configuration) {
94
94
  phpmyadmin: stringToBooleanIfDefined(configuration.phpmyadmin),
95
95
  xdebug: stringToBooleanIfDefined(configuration.xdebug),
96
96
  mailpit: stringToBooleanIfDefined((_configuration$mailpi = configuration.mailpit) !== null && _configuration$mailpi !== void 0 ? _configuration$mailpi : configuration.mailhog),
97
- 'media-redirect-domain': configuration['media-redirect-domain']
97
+ 'media-redirect-domain': configuration['media-redirect-domain'],
98
+ photon: stringToBooleanIfDefined(configuration.photon)
98
99
  };
99
100
 
100
101
  // Remove undefined values
@@ -119,7 +120,8 @@ function mergeConfigurationFileOptions(preselectedOptions, configurationFileOpti
119
120
  xdebug: configurationFileOptions.xdebug,
120
121
  xdebugConfig: configurationFileOptions['xdebug-config'],
121
122
  mailpit: (_configurationFileOpt = configurationFileOptions.mailpit) !== null && _configurationFileOpt !== void 0 ? _configurationFileOpt : configurationFileOptions.mailhog,
122
- mediaRedirectDomain: configurationFileOptions['media-redirect-domain']
123
+ mediaRedirectDomain: configurationFileOptions['media-redirect-domain'],
124
+ photon: configurationFileOptions.photon
123
125
  };
124
126
  const mergedOptions = {};
125
127
  Object.keys(configurationFileInstanceOptions).forEach(key => {
@@ -146,6 +146,9 @@ function preProcessInstanceData(instanceData) {
146
146
  if (!newInstanceData.phpmyadmin) {
147
147
  newInstanceData.phpmyadmin = false;
148
148
  }
149
+ if (!newInstanceData.photon) {
150
+ newInstanceData.photon = false;
151
+ }
149
152
 
150
153
  // Mailpit migration
151
154
  if (!newInstanceData.mailpit) {