@automattic/vip 2.33.0-dev1 → 2.33.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.
@@ -14,6 +14,6 @@ var _tracker = require("../lib/tracker");
14
14
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
15
  void (0, _command.default)({
16
16
  usage: 'vip backup'
17
- }).command('db', 'Trigger a new backup for your database').example('vip backup sql @mysite.develop', 'Trigger a new backup for your database of the @mysite.develop environment').argv(process.argv, async () => {
17
+ }).command('db', 'Trigger a new backup for your database').example('vip backup db @mysite.develop', 'Trigger a new backup for your database of the @mysite.develop environment').argv(process.argv, async () => {
18
18
  await (0, _tracker.trackEvent)('vip_backup_command_execute');
19
19
  });
@@ -10,9 +10,7 @@
10
10
  */
11
11
  "use strict";
12
12
 
13
- var _chalk = _interopRequireDefault(require("chalk"));
14
13
  var _debug = _interopRequireDefault(require("debug"));
15
- var _child_process = require("child_process");
16
14
  var _tracker = require("../lib/tracker");
17
15
  var _command = _interopRequireDefault(require("../lib/cli/command"));
18
16
  var _devEnvironmentCore = require("../lib/dev-environment/dev-environment-core");
@@ -24,9 +22,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
24
22
  * Internal dependencies
25
23
  */
26
24
  const debug = (0, _debug.default)('@automattic/vip:bin:dev-environment');
27
-
28
- // PowerShell command for Windows Docker patch
29
- const dockerWindowsPathCmd = 'wsl -d docker-desktop bash -c "sysctl -w vm.max_map_count=262144"';
30
25
  const examples = [{
31
26
  usage: `${_devEnvironment.DEV_ENVIRONMENT_FULL_COMMAND} start`,
32
27
  description: 'Starts a local dev environment'
@@ -48,20 +43,6 @@ const examples = [{
48
43
  skipWpVersionsCheck: !!opt.skipWpVersionsCheck
49
44
  };
50
45
  try {
51
- if (process.platform === 'win32') {
52
- debug('Windows platform detected. Applying Docker patch...');
53
- (0, _child_process.exec)(dockerWindowsPathCmd, {
54
- shell: 'powershell.exe'
55
- }, (error, stdout) => {
56
- if (error) {
57
- debug(error);
58
- console.log(`${_chalk.default.red('✕')} There was an error while applying the Windows Docker patch.`);
59
- } else {
60
- debug(stdout);
61
- console.log(`${_chalk.default.green('✓')} Docker patch for Windows applied.`);
62
- }
63
- });
64
- }
65
46
  await (0, _devEnvironmentCore.startEnvironment)(lando, slug, options);
66
47
  const processingTime = Math.ceil((new Date() - startProcessing) / 1000); // in seconds
67
48
  const successTrackingInfo = {
@@ -79,6 +79,9 @@ const appQuery = `
79
79
  const cmd = new _devEnvSyncSql.DevEnvSyncSQLCommand(app, env, slug, trackerFn);
80
80
  // TODO: There's a function called handleCLIException for dev-env that handles exceptions but DevEnvSyncSQLCommand has its own implementation.
81
81
  // We should probably use handleCLIException instead?
82
- await cmd.run();
82
+ const didCommandRun = await cmd.run();
83
+ if (!didCommandRun) {
84
+ console.log('Command canceled by user.');
85
+ }
83
86
  await trackerFn('success');
84
87
  });
@@ -172,7 +172,7 @@ class BackupDBCommand {
172
172
  stack: error.stack
173
173
  });
174
174
  const errMessage = `A new database backup was not generated because a recently generated backup already exists.
175
- If you would like to run the same command, you can retry on or after ${(0, _format.formatDuration)(new Date(), new Date(retryAfter))}
175
+ If you would like to run the same command, you can retry in ${(0, _format.formatDuration)(new Date(), new Date(retryAfter))}
176
176
  Alternatively, you can export the latest existing database backup by running: ${_chalk.default.green('vip @app.env export sql')}, right away.
177
177
  Learn more about limitations around generating database backups: https://docs.wpvip.com/technical-references/vip-dashboard/backups/#0-limitations
178
178
  `;
@@ -24,6 +24,7 @@ var _utils = require("../lib/utils");
24
24
  var _lineByLine = require("../lib/validations/line-by-line");
25
25
  var exit = _interopRequireWildcard(require("../lib/cli/exit"));
26
26
  var _devEnvImportSql = require("./dev-env-import-sql");
27
+ var _backupStorageAvailability = require("../lib/backup-storage-availability/backup-storage-availability");
27
28
  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); }
28
29
  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; }
29
30
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -92,16 +93,19 @@ class DevEnvSyncSQLCommand {
92
93
  get gzFile() {
93
94
  return `${this.tmpDir}/sql-export.sql.gz`;
94
95
  }
96
+ async confirmEnoughStorage(job) {
97
+ const storageAvailability = _backupStorageAvailability.BackupStorageAvailability.createFromDbCopyJob(job);
98
+ return await storageAvailability.validateAndPromptDiskSpaceWarningForDevEnvBackupImport();
99
+ }
95
100
 
96
101
  /**
97
102
  * Runs the SQL export command to generate the SQL export from
98
103
  * the latest backup
99
- *
100
- * @return {Promise<void>} Promise that resolves when the export is complete
101
104
  */
102
105
  async generateExport() {
103
106
  const exportCommand = new _exportSql.ExportSQLCommand(this.app, this.env, {
104
- outputFile: this.gzFile
107
+ outputFile: this.gzFile,
108
+ confirmEnoughStorageHook: this.confirmEnoughStorage.bind(this)
105
109
  }, this.track);
106
110
  await exportCommand.run();
107
111
  }
@@ -161,7 +165,7 @@ class DevEnvSyncSQLCommand {
161
165
  * Sequentially runs the commands to export, search-replace, and import the SQL file
162
166
  * to the local environment
163
167
  *
164
- * @return {Promise<void>} Promise that resolves when the commands are complete
168
+ * @return {Promise<void>} Promise that resolves to true when the commands are complete. It will return false if the user did not continue during validation prompts.
165
169
  */
166
170
  async run() {
167
171
  try {
@@ -221,6 +225,7 @@ class DevEnvSyncSQLCommand {
221
225
  console.log('Importing the SQL file...');
222
226
  await this.runImport();
223
227
  console.log(`${_chalk.default.green('✓')} SQL file imported`);
228
+ return true;
224
229
  } catch (err) {
225
230
  await this.track('error', {
226
231
  error_type: 'import_sql_file',
@@ -15,6 +15,7 @@ var _progress = require("../lib/cli/progress");
15
15
  var exit = _interopRequireWildcard(require("../lib/cli/exit"));
16
16
  var _utils = require("../lib/utils");
17
17
  var _backupDb = require("./backup-db");
18
+ var _backupStorageAvailability = require("../lib/backup-storage-availability/backup-storage-availability");
18
19
  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); }
19
20
  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 }; }
@@ -190,6 +191,7 @@ class ExportSQLCommand {
190
191
  PREPARE: 'prepare',
191
192
  CREATE: 'create',
192
193
  DOWNLOAD_LINK: 'downloadLink',
194
+ CONFIRM_ENOUGH_STORAGE: 'confirmEnoughStorage',
193
195
  DOWNLOAD: 'download'
194
196
  };
195
197
  /**
@@ -204,6 +206,7 @@ class ExportSQLCommand {
204
206
  this.app = app;
205
207
  this.env = env;
206
208
  this.outputFile = typeof options.outputFile === 'string' ? (0, _utils.getAbsolutePath)(options.outputFile) : null;
209
+ this.confirmEnoughStorageHook = options.confirmEnoughStorageHook;
207
210
  this.generateBackup = options.generateBackup || false;
208
211
  this.progressTracker = new _progress.ProgressTracker([{
209
212
  id: this.steps.PREPARE,
@@ -211,6 +214,9 @@ class ExportSQLCommand {
211
214
  }, {
212
215
  id: this.steps.CREATE,
213
216
  name: 'Creating backup copy'
217
+ }, {
218
+ id: this.steps.CONFIRM_ENOUGH_STORAGE,
219
+ name: "Checking if there's enough storage"
214
220
  }, {
215
221
  id: this.steps.DOWNLOAD_LINK,
216
222
  name: 'Requesting download link'
@@ -320,11 +326,17 @@ class ExportSQLCommand {
320
326
  this.log(noticeMessage);
321
327
  await cmd.run(false);
322
328
  }
329
+ async confirmEnoughStorage(job) {
330
+ if (this.confirmEnoughStorageHook) {
331
+ return await this.confirmEnoughStorageHook(job);
332
+ }
333
+ const storageAvailability = _backupStorageAvailability.BackupStorageAvailability.createFromDbCopyJob(job);
334
+ return await storageAvailability.validateAndPromptDiskSpaceWarningForBackupImport();
335
+ }
323
336
 
324
337
  /**
325
338
  * Sequentially runs the steps of the export workflow
326
339
  *
327
- * @return {Promise} A promise which resolves to void
328
340
  */
329
341
  async run() {
330
342
  if (this.outputFile) {
@@ -389,6 +401,16 @@ class ExportSQLCommand {
389
401
  this.progressTracker.stepSuccess(this.steps.PREPARE);
390
402
  await (0, _utils.pollUntil)(this.getExportJob.bind(this), EXPORT_SQL_PROGRESS_POLL_INTERVAL, this.isCreated.bind(this));
391
403
  this.progressTracker.stepSuccess(this.steps.CREATE);
404
+ const storageConfirmed = await this.progressTracker.handleContinuePrompt(async () => {
405
+ return await this.confirmEnoughStorage(await this.getExportJob());
406
+ }, 3);
407
+ if (storageConfirmed) {
408
+ this.progressTracker.stepSuccess(this.steps.CONFIRM_ENOUGH_STORAGE);
409
+ } else {
410
+ this.progressTracker.stepFailed(this.steps.CONFIRM_ENOUGH_STORAGE);
411
+ this.stopProgressTracker();
412
+ exit.withError('Command canceled by user.');
413
+ }
392
414
  const url = await generateDownloadLink(this.app.id, this.env.id, latestBackup.id);
393
415
  this.progressTracker.stepSuccess(this.steps.DOWNLOAD_LINK);
394
416
 
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BackupStorageAvailability = void 0;
7
+ var _shelljs = require("shelljs");
8
+ var _path = _interopRequireDefault(require("path"));
9
+ var _xdgBasedir = _interopRequireDefault(require("xdg-basedir"));
10
+ var _os = _interopRequireDefault(require("os"));
11
+ var _checkDiskSpace = _interopRequireDefault(require("check-disk-space"));
12
+ var _enquirer = require("enquirer");
13
+ var _format = require("../cli/format");
14
+ var _dockerMachineNotFoundError = require("./docker-machine-not-found-error");
15
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
+ const oneGiBInBytes = 1024 * 1024 * 1024;
17
+ class BackupStorageAvailability {
18
+ constructor(archiveSize) {
19
+ this.archiveSize = archiveSize;
20
+ }
21
+ static createFromDbCopyJob(job) {
22
+ var _job$metadata;
23
+ const bytesWrittenMeta = (_job$metadata = job.metadata) === null || _job$metadata === void 0 ? void 0 : _job$metadata.find(meta => (meta === null || meta === void 0 ? void 0 : meta.name) === 'bytesWritten');
24
+ if (!(bytesWrittenMeta !== null && bytesWrittenMeta !== void 0 && bytesWrittenMeta.value)) {
25
+ throw new Error('Meta not found');
26
+ }
27
+ return new BackupStorageAvailability(Number(bytesWrittenMeta.value));
28
+ }
29
+ getDockerStorageKiBRaw() {
30
+ return (0, _shelljs.exec)(`docker run --rm alpine df -k`, {
31
+ silent: true
32
+ }).grep(/\/dev\/vda1/).head({
33
+ '-n': 1
34
+ }).replace(/\s+/g, ' ').split(' ')[3];
35
+ }
36
+ getDockerStorageAvailable() {
37
+ const kiBLeft = this.getDockerStorageKiBRaw();
38
+ if (!kiBLeft || Number.isNaN(Number(kiBLeft))) {
39
+ throw new _dockerMachineNotFoundError.DockerMachineNotFoundError();
40
+ }
41
+ return Number(kiBLeft) * 1024;
42
+ }
43
+ bytesToHuman(bytes) {
44
+ return (0, _format.formatMetricBytes)(bytes);
45
+ }
46
+ async getStorageAvailableInVipPath() {
47
+ const vipDir = _path.default.join(_xdgBasedir.default.data ?? _os.default.tmpdir(), 'vip');
48
+ const diskSpace = await (0, _checkDiskSpace.default)(vipDir);
49
+ return diskSpace.free;
50
+ }
51
+ getReserveSpace() {
52
+ return oneGiBInBytes;
53
+ }
54
+ getSqlSize() {
55
+ // We estimated that it'd be about 3.5x the archive size.
56
+ return this.archiveSize * 3.5;
57
+ }
58
+ getArchiveSize() {
59
+ return this.archiveSize;
60
+ }
61
+ getStorageRequiredInMainMachine() {
62
+ return this.getArchiveSize() + this.getSqlSize() + this.getReserveSpace();
63
+ }
64
+ getStorageRequiredInDockerMachine() {
65
+ return this.getSqlSize() + this.getReserveSpace();
66
+ }
67
+ async isStorageAvailableInMainMachine() {
68
+ return (await this.getStorageAvailableInVipPath()) > this.getStorageRequiredInMainMachine();
69
+ }
70
+ isStorageAvailableInDockerMachine() {
71
+ return this.getDockerStorageAvailable() > this.getStorageRequiredInDockerMachine();
72
+ }
73
+
74
+ // eslint-disable-next-line id-length
75
+ async validateAndPromptDiskSpaceWarningForBackupImport() {
76
+ const isStorageAvailable = (await this.getStorageAvailableInVipPath()) > this.getArchiveSize();
77
+ if (!isStorageAvailable) {
78
+ const storageRequired = this.getArchiveSize();
79
+ const confirmPrompt = new _enquirer.Confirm({
80
+ message: `We recommend that you have at least ${this.bytesToHuman(storageRequired)} of free space in your machine to download this database backup. Do you still want to continue with downloading the database backup?`
81
+ });
82
+ return await confirmPrompt.run();
83
+ }
84
+ return true;
85
+ }
86
+
87
+ // eslint-disable-next-line id-length
88
+ async validateAndPromptDiskSpaceWarningForDevEnvBackupImport() {
89
+ let storageAvailableInMainMachinePrompted = false;
90
+ if (!(await this.isStorageAvailableInMainMachine())) {
91
+ const storageRequired = this.getStorageRequiredInMainMachine();
92
+ const storageAvailableInVipPath = this.bytesToHuman(await this.getStorageAvailableInVipPath());
93
+ const confirmPrompt = new _enquirer.Confirm({
94
+ message: `We recommend that you have at least ${this.bytesToHuman(storageRequired)} of free space in your machine to import this database backup. We estimate that you currently have ${storageAvailableInVipPath} of space in your machine.
95
+ Do you still want to continue with importing the database backup?
96
+ `
97
+ });
98
+ storageAvailableInMainMachinePrompted = await confirmPrompt.run();
99
+ if (!storageAvailableInMainMachinePrompted) {
100
+ return false;
101
+ }
102
+ }
103
+ try {
104
+ if (!this.isStorageAvailableInDockerMachine()) {
105
+ const storageRequired = this.getStorageRequiredInDockerMachine();
106
+ const storageAvailableInDockerMachine = this.bytesToHuman(this.getDockerStorageAvailable());
107
+ const confirmPrompt = new _enquirer.Confirm({
108
+ message: `We recommend that you have at least ${this.bytesToHuman(storageRequired)} of free space in your Docker machine to import this database backup. We estimate that you currently have ${storageAvailableInDockerMachine} of space in your machine.
109
+ Do you still want to continue with importing the database backup?`
110
+ });
111
+ return await confirmPrompt.run();
112
+ }
113
+ } catch (error) {
114
+ if (error instanceof _dockerMachineNotFoundError.DockerMachineNotFoundError) {
115
+ // skip storage available check
116
+ return true;
117
+ }
118
+ throw error;
119
+ }
120
+ return true;
121
+ }
122
+ }
123
+ exports.BackupStorageAvailability = BackupStorageAvailability;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DockerMachineNotFoundError = void 0;
7
+ class DockerMachineNotFoundError extends Error {
8
+ constructor() {
9
+ super('Docker machine not found');
10
+ }
11
+ }
12
+ exports.DockerMachineNotFoundError = DockerMachineNotFoundError;
@@ -9,6 +9,7 @@ exports.formatBytes = void 0;
9
9
  exports.formatData = formatData;
10
10
  exports.formatDuration = formatDuration;
11
11
  exports.formatEnvironment = formatEnvironment;
12
+ exports.formatMetricBytes = void 0;
12
13
  exports.formatSearchReplaceValues = formatSearchReplaceValues;
13
14
  exports.getGlyphForStatus = getGlyphForStatus;
14
15
  exports.isJson = isJson;
@@ -186,34 +187,44 @@ function formatSearchReplaceValues(values, message) {
186
187
  }
187
188
 
188
189
  // Format bytes into kilobytes, megabytes, etc based on the size
189
- const formatBytes = (bytes, decimals = 2) => {
190
+ // for historical reasons, this uses KB instead of KiB, MB instead of MiB and so on.
191
+ const formatBytes = (bytes, decimals = 2, bytesMultiplier = 1024, sizes = ['bytes', 'KB', 'MB', 'GB', 'TB']) => {
190
192
  if (0 === bytes) {
191
193
  return '0 Bytes';
192
194
  }
193
- const bytesMultiplier = 1024;
194
195
  const dm = decimals < 0 ? 0 : decimals;
195
- const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB'];
196
196
  const idx = Math.floor(Math.log(bytes) / Math.log(bytesMultiplier));
197
197
  return `${parseFloat((bytes / Math.pow(bytesMultiplier, idx)).toFixed(dm))} ${sizes[idx]}`;
198
198
  };
199
199
 
200
200
  /**
201
+ * Format bytes in powers of 1000, based on the size
202
+ * This is how it's displayed on Macs
203
+ */
204
+ exports.formatBytes = formatBytes;
205
+ const formatMetricBytes = (bytes, decimals = 2) => {
206
+ return formatBytes(bytes, decimals, 1000);
207
+ };
208
+
209
+ /*
201
210
  * Get the duration between two dates
202
211
  *
203
212
  * @param {Date} from The start date
204
213
  * @param {Date} to The end date
205
214
  * @returns {string} The duration between the two dates
206
215
  */
207
- exports.formatBytes = formatBytes;
216
+ exports.formatMetricBytes = formatMetricBytes;
208
217
  function formatDuration(from, to) {
209
218
  const millisecondsPerSecond = 1000;
210
219
  const millisecondsPerMinute = 60 * millisecondsPerSecond;
211
220
  const millisecondsPerHour = 60 * millisecondsPerMinute;
212
221
  const millisecondsPerDay = 24 * millisecondsPerHour;
213
- const duration = Math.abs(from.getTime() - to.getTime());
222
+ const duration = to.getTime() - from.getTime();
223
+ if (duration < 1000) return '0 second';
214
224
  const days = Math.floor(duration / millisecondsPerDay);
215
225
  const hours = Math.floor(duration % millisecondsPerDay / millisecondsPerHour);
216
226
  const minutes = Math.floor(duration % millisecondsPerHour / millisecondsPerMinute);
227
+ const seconds = Math.floor(duration % millisecondsPerMinute / millisecondsPerSecond);
217
228
  let durationString = '';
218
229
  if (days > 0) {
219
230
  durationString += `${days} day${days > 1 ? 's' : ''} `;
@@ -224,5 +235,8 @@ function formatDuration(from, to) {
224
235
  if (minutes > 0) {
225
236
  durationString += `${minutes} minute${minutes > 1 ? 's' : ''} `;
226
237
  }
238
+ if (seconds > 0) {
239
+ durationString += `${seconds} second${seconds > 1 ? 's' : ''}`;
240
+ }
227
241
  return durationString.trim();
228
242
  }
@@ -3,9 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.ProgressTracker = void 0;
6
+ exports.StepStatus = exports.ProgressTracker = void 0;
7
7
  var _singleLineLog = require("single-line-log");
8
8
  var _format = require("../../lib/cli/format");
9
+ var _os = _interopRequireDefault(require("os"));
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
11
  // @format
10
12
 
11
13
  /**
@@ -17,7 +19,17 @@ var _format = require("../../lib/cli/format");
17
19
  */
18
20
 
19
21
  const PRINT_INTERVAL = process.env.DEBUG ? 5000 : 200; // How often the report is printed. Mainly affects the "spinner" animation.
20
- const COMPLETED_STEP_SLUGS = ['success', 'skipped'];
22
+ let StepStatus = /*#__PURE__*/function (StepStatus) {
23
+ StepStatus["PENDING"] = "pending";
24
+ StepStatus["RUNNING"] = "running";
25
+ StepStatus["SUCCESS"] = "success";
26
+ StepStatus["FAILED"] = "failed";
27
+ StepStatus["UNKNOWN"] = "unknown";
28
+ StepStatus["SKIPPED"] = "skipped";
29
+ return StepStatus;
30
+ }({});
31
+ exports.StepStatus = StepStatus;
32
+ const COMPLETED_STEP_SLUGS = [StepStatus.SUCCESS, StepStatus.SKIPPED];
21
33
  class ProgressTracker {
22
34
  // Track the state of each step
23
35
 
@@ -27,6 +39,14 @@ class ProgressTracker {
27
39
 
28
40
  // This gets printed after the step status
29
41
 
42
+ /**
43
+ * This determines from which step should we display the steps
44
+ *
45
+ * Useful when you want to display a prompt.
46
+ *
47
+ * And we don't want to repeatedly display the steps that has finished.
48
+ */
49
+ displayFromStep = 0;
30
50
  constructor(steps) {
31
51
  this.runningSprite = new _format.RunningSprite();
32
52
  this.hasFailure = false;
@@ -48,7 +68,7 @@ class ProgressTracker {
48
68
  map.set(id, {
49
69
  id,
50
70
  name,
51
- status: status || 'pending'
71
+ status: status || StepStatus.PENDING
52
72
  });
53
73
  return map;
54
74
  }, new Map());
@@ -90,7 +110,7 @@ class ProgressTracker {
90
110
  }) => status === 'pending');
91
111
  if (firstPendingStepIndex !== -1) {
92
112
  // "Promote" the first "pending" to "running"
93
- formattedSteps[firstPendingStepIndex].status = 'running';
113
+ formattedSteps[firstPendingStepIndex].status = StepStatus.RUNNING;
94
114
  }
95
115
  }
96
116
  this.stepsFromServer = this.mapSteps(formattedSteps);
@@ -114,16 +134,16 @@ class ProgressTracker {
114
134
  }) => status === 'running');
115
135
  }
116
136
  stepRunning(stepId) {
117
- this.setStatusForStepId(stepId, 'running');
137
+ this.setStatusForStepId(stepId, StepStatus.RUNNING);
118
138
  }
119
139
  stepFailed(stepId) {
120
- this.setStatusForStepId(stepId, 'failed');
140
+ this.setStatusForStepId(stepId, StepStatus.FAILED);
121
141
  }
122
142
  stepSkipped(stepId) {
123
- this.setStatusForStepId(stepId, 'skipped');
143
+ this.setStatusForStepId(stepId, StepStatus.SKIPPED);
124
144
  }
125
145
  stepSuccess(stepId) {
126
- this.setStatusForStepId(stepId, 'success');
146
+ this.setStatusForStepId(stepId, StepStatus.SUCCESS);
127
147
  // The stepSuccess helper automatically sets the next step to "running"
128
148
  const nextStep = this.getNextStep();
129
149
  if (nextStep) {
@@ -163,6 +183,32 @@ class ProgressTracker {
163
183
  clearInterval(this.printInterval);
164
184
  }
165
185
  }
186
+ async handleContinuePrompt(prompt) {
187
+ this.print();
188
+ this.stopPrinting();
189
+ const returnValue = await prompt();
190
+ this.displayFromStep = [...this.getSteps().values()].findIndex(step => step.status === StepStatus.RUNNING);
191
+ let hasPrintedOnce = false;
192
+ const printingStartedPromise = new Promise(resolve => {
193
+ this.startPrinting(() => {
194
+ if (hasPrintedOnce) {
195
+ return;
196
+ }
197
+
198
+ // this is so that we leave some room for the progress tracker to refresh
199
+ // without this, any prompt, or any text in between will get overwritten by the progress tracker
200
+ let linesToSkip = '';
201
+ for (let iteration = 0; iteration < this.stepsFromCaller.size; iteration++) {
202
+ linesToSkip += _os.default.EOL;
203
+ }
204
+ process.stdout.write(linesToSkip);
205
+ hasPrintedOnce = true;
206
+ resolve();
207
+ });
208
+ });
209
+ await printingStartedPromise;
210
+ return returnValue;
211
+ }
166
212
  print({
167
213
  clearAfter = false
168
214
  } = {}) {
@@ -177,7 +223,10 @@ class ProgressTracker {
177
223
  percentage,
178
224
  status,
179
225
  progress
180
- }) => {
226
+ }, stepNumber) => {
227
+ if (stepNumber < this.displayFromStep) {
228
+ return accumulator;
229
+ }
181
230
  const statusIcon = (0, _format.getGlyphForStatus)(status, this.runningSprite);
182
231
  let suffix = '';
183
232
  if (id === 'upload') {