cloudron 5.12.0 → 5.14.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/src/actions.js CHANGED
@@ -4,25 +4,22 @@ const assert = require('assert'),
4
4
  config = require('./config.js'),
5
5
  ejs = require('ejs'),
6
6
  { exit, locateManifest } = require('./helper.js'),
7
- EventSource = require('eventsource'),
7
+ { EventSource } = require('eventsource'),
8
8
  fs = require('fs'),
9
9
  https = require('https'),
10
+ LineStream = require('./line-stream.js'),
10
11
  manifestFormat = require('cloudron-manifestformat'),
11
12
  os = require('os'),
12
13
  path = require('path'),
13
- ProgressBar = require('progress'),
14
- ProgressStream = require('progress-stream'),
15
- readlineSync = require('readline-sync'),
14
+ readline = require('./readline.js'),
16
15
  safe = require('safetydance'),
17
16
  spawn = require('child_process').spawn,
18
17
  semver = require('semver'),
19
- split = require('split2'),
20
- superagent = require('superagent'),
18
+ superagent = require('./superagent.js'),
21
19
  Table = require('easy-table'),
22
20
  tar = require('tar-fs'),
23
21
  timers = require('timers/promises'),
24
- zlib = require('zlib'),
25
- _ = require('underscore');
22
+ zlib = require('zlib');
26
23
 
27
24
  exports = module.exports = {
28
25
  list,
@@ -82,7 +79,7 @@ function createRequest(method, apiPath, options) {
82
79
  let url = `https://${adminFqdn}${apiPath}`;
83
80
  if (url.includes('?')) url += '&'; else url += '?';
84
81
  url += `access_token=${token}`;
85
- const request = superagent(method, url);
82
+ const request = superagent.request(method, url);
86
83
  if (!rejectUnauthorized) request.disableTLSCerts();
87
84
  request.retry(3);
88
85
  request.ok(() => true);
@@ -91,9 +88,9 @@ function createRequest(method, apiPath, options) {
91
88
 
92
89
  // error for the request module
93
90
  function requestError(response) {
94
- if (response.statusCode === 401) return 'Invalid token. Use cloudron login again.';
91
+ if (response.status === 401) return 'Invalid token. Use cloudron login again.';
95
92
 
96
- return `${response.statusCode} message: ${response.body.message || JSON.stringify(response.body)}`; // body is sometimes just a string like in 401
93
+ return `${response.status} message: ${response.body.message || JSON.stringify(response.body)}`; // body is sometimes just a string like in 401
97
94
  }
98
95
 
99
96
  async function selectDomain(location, options) {
@@ -103,7 +100,7 @@ async function selectDomain(location, options) {
103
100
  const { adminFqdn } = requestOptions(options);
104
101
 
105
102
  const response = await createRequest('GET', '/api/v1/domains', options);
106
- if (response.statusCode !== 200) throw new Error(`Failed to list domains: ${requestError(response)}`);
103
+ if (response.status !== 200) throw new Error(`Failed to list domains: ${requestError(response)}`);
107
104
 
108
105
  const domains = response.body.domains;
109
106
 
@@ -137,7 +134,7 @@ async function stopActiveTask(app, options) {
137
134
  console.log(`Stopping app's current active task ${app.taskId}`);
138
135
 
139
136
  const response = await createRequest('POST', `/api/v1/tasks/${app.taskId}/stop`, options);
140
- if (response.statusCode !== 204) throw `Failed to stop active task: ${requestError(response)}`;
137
+ if (response.status !== 204) throw `Failed to stop active task: ${requestError(response)}`;
141
138
  }
142
139
 
143
140
  async function selectAppWithRepository(repository, options) {
@@ -145,7 +142,7 @@ async function selectAppWithRepository(repository, options) {
145
142
  assert.strictEqual(typeof options, 'object');
146
143
 
147
144
  const response = await createRequest('GET', '/api/v1/apps', options);
148
- if (response.statusCode !== 200) throw new Error(`Failed to install app: ${requestError(response)}`);
145
+ if (response.status !== 200) throw new Error(`Failed to install app: ${requestError(response)}`);
149
146
 
150
147
  const matchingApps = response.body.apps.filter(function (app) {
151
148
  return !app.appStoreId && app.manifest.dockerImage.startsWith(repository); // never select apps from the store
@@ -163,7 +160,7 @@ async function selectAppWithRepository(repository, options) {
163
160
 
164
161
  let index = -1;
165
162
  while (true) {
166
- index = parseInt(readlineSync.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
163
+ index = parseInt(await readline.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
167
164
  if (isNaN(index) || index < 0 || index > matchingApps.length-1) console.log('Invalid selection');
168
165
  else break;
169
166
  }
@@ -193,12 +190,12 @@ async function getApp(options) {
193
190
  return result;
194
191
  } else if (app.match(/.{8}-.{4}-.{4}-.{4}-.{8}/)) { // it is an id
195
192
  const response = await createRequest('GET', `/api/v1/apps/${app}`, options);
196
- if (response.statusCode !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
193
+ if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
197
194
 
198
195
  return response.body;
199
196
  } else { // it is a location
200
197
  const response = await createRequest('GET', '/api/v1/apps', options);
201
- if (response.statusCode !== 200) throw new Error(`Failed to get apps: ${requestError(response)}`);
198
+ if (response.status !== 200) throw new Error(`Failed to get apps: ${requestError(response)}`);
202
199
 
203
200
  const match = response.body.apps.filter(function (m) { return m.subdomain === app || m.location === app || m.fqdn === app; });
204
201
  if (match.length == 0) throw new Error(`App at location ${app} not found`);
@@ -218,7 +215,7 @@ async function waitForHealthy(appId, options) {
218
215
  while (true) {
219
216
  await timers.setTimeout(1000);
220
217
  const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
221
- if (response.statusCode !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
218
+ if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
222
219
 
223
220
  // do not check installation state here. it can be pending_backup etc (this is a bug in box code)
224
221
  if (response.body.health === 'healthy') return;
@@ -241,7 +238,7 @@ async function waitForTask(taskId, options) {
241
238
 
242
239
  while (true) {
243
240
  const response = await createRequest('GET', `/api/v1/tasks/${taskId}`, options);
244
- if (response.statusCode !== 200) throw new Error(`Failed to get task: ${requestError(response)}`);
241
+ if (response.status !== 200) throw new Error(`Failed to get task: ${requestError(response)}`);
245
242
 
246
243
  if (!response.body.active) return response.body;
247
244
 
@@ -275,7 +272,7 @@ async function waitForFinishInstallation(appId, taskId, options) {
275
272
  await waitForTask(taskId, options);
276
273
 
277
274
  const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
278
- if (response.statusCode !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
275
+ if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
279
276
 
280
277
  if (response.body.installationState !== 'installed') throw new Error(`Installation failed: ${response.body.error ? response.body.error.message : ''}`);
281
278
 
@@ -292,7 +289,7 @@ async function waitForFinishBackup(appId, taskId, options) {
292
289
  if (result.error) throw new Error(`Backup failed: ${result.error.message}`);
293
290
 
294
291
  const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
295
- if (response.statusCode !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
292
+ if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
296
293
  }
297
294
 
298
295
  async function stopApp(app, options) {
@@ -300,7 +297,7 @@ async function stopApp(app, options) {
300
297
  assert.strictEqual(typeof options, 'object');
301
298
 
302
299
  const response = await createRequest('POST', `/api/v1/apps/${app.id}/stop`, options);
303
- if (response.statusCode !== 202) throw `Failed to stop app: ${requestError(response)}`;
300
+ if (response.status !== 202) throw `Failed to stop app: ${requestError(response)}`;
304
301
 
305
302
  await waitForTask(response.body.taskId, options);
306
303
  }
@@ -310,7 +307,7 @@ async function startApp(app, options) {
310
307
  assert.strictEqual(typeof options, 'object');
311
308
 
312
309
  const response = await createRequest('POST', `/api/v1/apps/${app.id}/start`, options);
313
- if (response.statusCode !== 202) throw `Failed to start app: ${requestError(response)}`;
310
+ if (response.status !== 202) throw `Failed to start app: ${requestError(response)}`;
314
311
 
315
312
  await waitForTask(response.body.taskId, options);
316
313
  }
@@ -320,7 +317,7 @@ async function restartApp(app, options) {
320
317
  assert.strictEqual(typeof options, 'object');
321
318
 
322
319
  const response = await createRequest('POST', `/api/v1/apps/${app.id}/restart`, options);
323
- if (response.statusCode !== 202) throw `Failed to restart app: ${requestError(response)}`;
320
+ if (response.status !== 202) throw `Failed to restart app: ${requestError(response)}`;
324
321
 
325
322
  await waitForTask(response.body.taskId, options);
326
323
  }
@@ -334,7 +331,7 @@ async function authenticate(adminFqdn, username, password, options) {
334
331
  let totpToken;
335
332
 
336
333
  const { rejectUnauthorized, askForTotpToken } = options;
337
- if (askForTotpToken) totpToken = readlineSync.question('2FA Token: ', {});
334
+ if (askForTotpToken) totpToken = await readline.question('2FA Token: ', {});
338
335
 
339
336
  const request = superagent.post(`https://${adminFqdn}/api/v1/auth/login`)
340
337
  .timeout(60000)
@@ -354,13 +351,13 @@ async function authenticate(adminFqdn, username, password, options) {
354
351
  }
355
352
  }
356
353
 
357
- if (response.statusCode !== 200) throw new Error(`Login failed: Status code: ${requestError(response)}`);
354
+ if (response.status !== 200) throw new Error(`Login failed: Status code: ${requestError(response)}`);
358
355
 
359
356
  return response.body.accessToken;
360
357
  }
361
358
 
362
359
  async function login(adminFqdn, localOptions, cmd) {
363
- if (!adminFqdn) adminFqdn = readlineSync.question('Cloudron Domain (e.g. my.example.com): ', {});
360
+ if (!adminFqdn) adminFqdn = await readline.question('Cloudron Domain (e.g. my.example.com): ', {});
364
361
  if (!adminFqdn) return exit('');
365
362
 
366
363
  if (adminFqdn.indexOf('https://') === 0) adminFqdn = adminFqdn.slice('https://'.length);
@@ -389,8 +386,8 @@ async function login(adminFqdn, localOptions, cmd) {
389
386
  }
390
387
 
391
388
  if (!token) {
392
- const username = options.username || readlineSync.question('Username: ', {});
393
- const password = options.password || readlineSync.question('Password: ', { noEchoBack: true });
389
+ const username = options.username || await readline.question('Username: ', {});
390
+ const password = options.password || await readline.question('Password: ', { noEchoBack: true });
394
391
 
395
392
  const [error, result] = await safe(authenticate(adminFqdn, username, password, { rejectUnauthorized, askForTotpToken: false }));
396
393
  if (error) return exit(`Failed to login: ${error.message}`);
@@ -428,7 +425,7 @@ async function list(localOptions, cmd) {
428
425
  const options = cmd.optsWithGlobals();
429
426
  const [error, response] = await safe(createRequest('GET', '/api/v1/apps', options));
430
427
  if (error) return exit(error);
431
- if (response.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
428
+ if (response.status !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
432
429
 
433
430
  let apps = response.body.apps;
434
431
 
@@ -450,7 +447,7 @@ async function list(localOptions, cmd) {
450
447
 
451
448
  const response = result.value;
452
449
 
453
- if (response.statusCode !== 200) return exit(`Failed to list app: ${requestError(response)}`);
450
+ if (response.status !== 200) return exit(`Failed to list app: ${requestError(response)}`);
454
451
  response.body.location = response.body.location || response.body.subdomain; // LEGACY support
455
452
 
456
453
  const detailedApp = response.body;
@@ -458,7 +455,7 @@ async function list(localOptions, cmd) {
458
455
  t.cell('Id', detailedApp.id);
459
456
  t.cell('Location', detailedApp.fqdn);
460
457
  t.cell('Manifest Id', (detailedApp.manifest.id || 'customapp') + '@' + detailedApp.manifest.version);
461
- var prettyState;
458
+ let prettyState;
462
459
  if (detailedApp.installationState === 'installed') {
463
460
  prettyState = (detailedApp.debugMode ? 'debug' : detailedApp.runState);
464
461
  } else if (detailedApp.installationState === 'error') {
@@ -481,18 +478,19 @@ async function querySecondaryDomains(app, manifest, options) {
481
478
 
482
479
  for (const env in manifest.httpPorts) {
483
480
  const defaultDomain = (app && app.secondaryDomains && app.secondaryDomains[env]) ? app.secondaryDomains[env] : (manifest.httpPorts[env].defaultValue || '');
484
- const input = readlineSync.question(`${manifest.httpPorts[env].description} (default: "${defaultDomain}"): `, {});
481
+ const input = await readline.question(`${manifest.httpPorts[env].description} (default: "${defaultDomain}"): `, {});
485
482
  secondaryDomains[env] = await selectDomain(input, options);
486
483
  }
487
484
  return secondaryDomains;
488
485
  }
489
486
 
490
- function queryPortBindings(app, manifest) {
487
+ async function queryPortBindings(app, manifest) {
491
488
  const portBindings = {};
492
- const allPorts = _.extend({}, manifest.tcpPorts, manifest.udpPorts);
489
+ const allPorts = Object.assign({}, manifest.tcpPorts, manifest.udpPorts);
490
+
493
491
  for (const env in allPorts) {
494
492
  const defaultPort = (app && app.portBindings && app.portBindings[env]) ? app.portBindings[env] : (allPorts[env].defaultValue || '');
495
- const port = readlineSync.question(allPorts[env].description + ' (default ' + env + '=' + defaultPort + '. "x" to disable): ', {});
493
+ const port = await readline.question(allPorts[env].description + ' (default ' + env + '=' + defaultPort + '. "x" to disable): ', {});
496
494
  if (port === '') {
497
495
  portBindings[env] = defaultPort;
498
496
  } else if (isNaN(parseInt(port, 10))) {
@@ -515,7 +513,7 @@ async function downloadManifest(appstoreId) {
515
513
 
516
514
  const [error, response] = await safe(superagent.get(url).ok(() => true));
517
515
  if (error) throw new Error(`Failed to list apps from appstore: ${error.message}`);
518
- if (response.statusCode !== 200) throw new Error(`Failed to get app info from store: ${requestError(response)}`);
516
+ if (response.status !== 200) throw new Error(`Failed to get app info from store: ${requestError(response)}`);
519
517
 
520
518
  return { manifest: response.body.manifest, manifestFilePath: null /* manifest file path */ };
521
519
  }
@@ -559,7 +557,7 @@ async function install(localOptions, cmd) {
559
557
  manifest.dockerImage = image;
560
558
  }
561
559
 
562
- const location = options.location || readlineSync.question('Location: ', {});
560
+ const location = options.location || await readline.question('Location: ', {});
563
561
  if (!location) return exit('');
564
562
 
565
563
  const domainObject = await selectDomain(location, options);
@@ -604,10 +602,10 @@ async function install(localOptions, cmd) {
604
602
  ports[tmp[0]] = parseInt(tmp[1], 10);
605
603
  });
606
604
  } else {
607
- ports = queryPortBindings(null /* existing app */, manifest);
605
+ ports = await queryPortBindings(null /* existing app */, manifest);
608
606
  }
609
607
  } else { // just put in defaults
610
- const allPorts = _.extend({}, manifest.tcpPorts, manifest.udpPorts);
608
+ const allPorts = Object.assign({}, manifest.tcpPorts, manifest.udpPorts);
611
609
  for (const portName in allPorts) {
612
610
  ports[portName] = allPorts[portName].defaultValue;
613
611
  }
@@ -649,7 +647,7 @@ async function install(localOptions, cmd) {
649
647
 
650
648
  const request = createRequest('POST', '/api/v1/apps/install', options);
651
649
  const response = await request.send(data);
652
- if (response.statusCode !== 202) return exit(`Failed to install app: ${requestError(response)}`);
650
+ if (response.status !== 202) return exit(`Failed to install app: ${requestError(response)}`);
653
651
 
654
652
  const appId = response.body.id;
655
653
 
@@ -669,7 +667,7 @@ async function setLocation(localOptions, cmd) {
669
667
  const app = await getApp(options);
670
668
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
671
669
 
672
- if (!options.location) options.location = readlineSync.question(`Enter new location (default: ${app.fqdn}): `, { });
670
+ if (!options.location) options.location = await readline.question(`Enter new location (default: ${app.fqdn}): `, { });
673
671
  const location = options.location || app.subdomain;
674
672
 
675
673
  const domainObject = await selectDomain(location, options);
@@ -734,7 +732,7 @@ async function setLocation(localOptions, cmd) {
734
732
  ports[tmp[0]] = parseInt(tmp[1], 10);
735
733
  });
736
734
  } else {
737
- ports = queryPortBindings(app, app.manifest);
735
+ ports = await queryPortBindings(app, app.manifest);
738
736
  }
739
737
 
740
738
  for (const port in ports) {
@@ -746,7 +744,7 @@ async function setLocation(localOptions, cmd) {
746
744
 
747
745
  const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/location`, options);
748
746
  const response = await request.send(data);
749
- if (response.statusCode !== 202) return exit(`Failed to configure app: ${requestError(response)}`);
747
+ if (response.status !== 202) return exit(`Failed to configure app: ${requestError(response)}`);
750
748
 
751
749
  await waitForTask(response.body.taskId, options);
752
750
  await waitForHealthy(app.id, options);
@@ -788,10 +786,10 @@ async function update(localOptions, cmd) {
788
786
 
789
787
  if (app.error && (app.error.installationState === 'pending_install')) { // install had failed. call repair to re-install
790
788
  apiPath = `/api/v1/apps/${app.id}/repair`;
791
- data = _.extend(data, { manifest });
789
+ data = Object.assign(data, { manifest });
792
790
  } else {
793
791
  apiPath = `/api/v1/apps/${app.id}/update`;
794
- data = _.extend(data, {
792
+ data = Object.assign(data, {
795
793
  appStoreId: options.appstoreId || '', // note case change
796
794
  manifest: manifest,
797
795
  skipBackup: !options.backup,
@@ -802,7 +800,7 @@ async function update(localOptions, cmd) {
802
800
 
803
801
  const request = createRequest('POST', apiPath, options);
804
802
  const response = await request.send(data);
805
- if (response.statusCode !== 202) return exit(`Failed to update app: ${requestError(response)}`);
803
+ if (response.status !== 202) return exit(`Failed to update app: ${requestError(response)}`);
806
804
 
807
805
  process.stdout.write('\n => ' + 'Waiting for app to be updated ');
808
806
 
@@ -831,7 +829,7 @@ async function debug(args, localOptions, cmd) {
831
829
 
832
830
  const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/debug_mode`, options);
833
831
  const response = await request.send(data);
834
- if (response.statusCode !== 202) return exit(`Failed to set debug mode: ${requestError(response)}`);
832
+ if (response.status !== 202) return exit(`Failed to set debug mode: ${requestError(response)}`);
835
833
 
836
834
  await waitForTask(response.body.taskId, options);
837
835
 
@@ -845,7 +843,7 @@ async function debug(args, localOptions, cmd) {
845
843
 
846
844
  const request2 = createRequest('POST', `/api/v1/apps/${app.id}/configure/memory_limit`, options);
847
845
  const response2 = await request2.send({ memoryLimit });
848
- if (response2.statusCode !== 202) return exit(`Failed to set memory limit: ${requestError(response2)}`);
846
+ if (response2.status !== 202) return exit(`Failed to set memory limit: ${requestError(response2)}`);
849
847
 
850
848
  await waitForTask(response2.body.taskId, options);
851
849
  console.log('\n\nDone');
@@ -865,7 +863,7 @@ async function repair(localOptions, cmd) {
865
863
 
866
864
  const request = createRequest('POST', `/api/v1/apps/${app.id}/repair`, options);
867
865
  const response = await request.send(data);
868
- if (response.statusCode !== 202) return exit(`Failed to set repair mode: ${requestError(response)}`);
866
+ if (response.status !== 202) return exit(`Failed to set repair mode: ${requestError(response)}`);
869
867
 
870
868
  process.stdout.write('\n => ' + 'Waiting for app to be repaired ');
871
869
 
@@ -898,13 +896,13 @@ async function uninstall(localOptions, cmd) {
898
896
  await stopActiveTask(app, options);
899
897
 
900
898
  const response = await createRequest('POST', `/api/v1/apps/${app.id}/uninstall`, options);
901
- if (response.statusCode !== 202) return exit(`Failed to uninstall app: ${requestError(response)}`);
899
+ if (response.status !== 202) return exit(`Failed to uninstall app: ${requestError(response)}`);
902
900
 
903
901
  process.stdout.write('\n => ' + 'Waiting for app to be uninstalled ');
904
902
 
905
903
  await waitForTask(response.body.taskId, options);
906
904
  const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
907
- if (response2.statusCode === 404) {
905
+ if (response2.status === 404) {
908
906
  console.log('\n\nApp %s successfully uninstalled.', app.fqdn);
909
907
  } else if (response2.body.installationState === 'error') {
910
908
  console.log('\n\nApp uninstallation failed.\n');
@@ -954,13 +952,13 @@ async function logs(localOptions, cmd) {
954
952
 
955
953
  if (tail) {
956
954
  const url = `${apiPath}?access_token=${token}&lines=10&format=json`;
957
- var es = new EventSource(url, { rejectUnauthorized }); // not sure why this is needed
955
+ const es = new EventSource(url, { rejectUnauthorized }); // not sure why this is needed
958
956
 
959
- es.on('message', function (e) { // e { type, data, lastEventId }. lastEventId is the timestamp
957
+ es.addEventListener('message', function (e) { // e { type, data, lastEventId }. lastEventId is the timestamp
960
958
  logPrinter(JSON.parse(e.data));
961
959
  });
962
960
 
963
- es.on('error', function (error) {
961
+ es.addEventListener('error', function (error) {
964
962
  if (error.status === 401) return exit('Please login first');
965
963
  if (error.status === 412) exit('Logs currently not available.');
966
964
  exit(error);
@@ -970,17 +968,17 @@ async function logs(localOptions, cmd) {
970
968
 
971
969
  const req = superagent.get(url, { rejectUnauthorized });
972
970
  req.on('response', function (response) {
973
- if (response.statusCode !== 200) return exit(`Failed to get logs: ${requestError(response)}`);
971
+ if (response.status !== 200) return exit(`Failed to get logs: ${requestError(response)}`);
974
972
  });
975
973
  req.on('error', (error) => exit(`Pipe error: ${error.message}`));
976
974
 
977
- const jsonStream = split((line) => JSON.parse(line));
978
- jsonStream
979
- .on('data', logPrinter)
975
+ const lineStream = new LineStream();
976
+ lineStream
977
+ .on('line', (line) => { logPrinter(JSON.parse(line)); } )
980
978
  .on('error', (error) => exit(`JSON parse error: ${error.message}`))
981
979
  .on('end', process.exit);
982
980
 
983
- req.pipe(jsonStream);
981
+ req.pipe(lineStream);
984
982
  }
985
983
  }
986
984
 
@@ -1005,13 +1003,13 @@ async function inspect(localOptions, cmd) {
1005
1003
  try {
1006
1004
  const options = cmd.optsWithGlobals();
1007
1005
  const response = await createRequest('GET', '/api/v1/apps', options);
1008
- if (response.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
1006
+ if (response.status !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
1009
1007
 
1010
1008
  const apps = [];
1011
1009
 
1012
1010
  for (const app of response.body.apps) {
1013
1011
  const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
1014
- if (response2.statusCode !== 200) return exit(`Failed to list app: ${requestError(response2)}`);
1012
+ if (response2.status !== 200) return exit(`Failed to list app: ${requestError(response2)}`);
1015
1013
  response2.body.location = response2.body.location || response2.body.subdomain; // LEGACY support
1016
1014
  apps.push(response2.body);
1017
1015
  }
@@ -1072,7 +1070,7 @@ async function backupCreate(localOptions, cmd) {
1072
1070
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1073
1071
 
1074
1072
  const response = await createRequest('POST', `/api/v1/apps/${app.id}/backup`, options);
1075
- if (response.statusCode !== 202) return exit(`Failed to start backup: ${requestError(response)}`);
1073
+ if (response.status !== 202) return exit(`Failed to start backup: ${requestError(response)}`);
1076
1074
 
1077
1075
  // FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
1078
1076
  await waitForFinishBackup(app.id, response.body.taskId, options);
@@ -1089,7 +1087,7 @@ async function backupList(localOptions, cmd) {
1089
1087
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1090
1088
 
1091
1089
  const response = await createRequest('GET', `/api/v1/apps/${app.id}/backups`, options);
1092
- if (response.statusCode !== 200) return exit(`Failed to list backups: ${requestError(response)}`);
1090
+ if (response.status !== 200) return exit(`Failed to list backups: ${requestError(response)}`);
1093
1091
 
1094
1092
  if (options.raw) return console.log(JSON.stringify(response.body.backups, null, 4));
1095
1093
 
@@ -1100,7 +1098,7 @@ async function backupList(localOptions, cmd) {
1100
1098
  return backup;
1101
1099
  }).sort(function (a, b) { return b.creationTime - a.creationTime; });
1102
1100
 
1103
- var t = new Table();
1101
+ const t = new Table();
1104
1102
 
1105
1103
  response.body.backups.forEach(function (backup) {
1106
1104
  t.cell('Id', backup.id);
@@ -1119,7 +1117,7 @@ async function backupList(localOptions, cmd) {
1119
1117
 
1120
1118
  async function getLastBackup(app, options) {
1121
1119
  const response = await createRequest('GET', `/api/v1/apps/${app.id}/backups`, options);
1122
- if (response.statusCode !== 200) throw new Error(`Failed to list backups: ${requestError(response)}`);
1120
+ if (response.status !== 200) throw new Error(`Failed to list backups: ${requestError(response)}`);
1123
1121
  if (response.body.backups.length === 0) throw new Error('No backups');
1124
1122
 
1125
1123
  response.body.backups = response.body.backups.map(function (backup) {
@@ -1145,7 +1143,7 @@ async function restore(localOptions, cmd) {
1145
1143
 
1146
1144
  const request = createRequest('POST', `/api/v1/apps/${app.id}/restore`, options);
1147
1145
  const response = await request.send({ backupId });
1148
- if (response.statusCode !== 202) return exit(`Failed to restore app: ${requestError(response)}`);
1146
+ if (response.status !== 202) return exit(`Failed to restore app: ${requestError(response)}`);
1149
1147
 
1150
1148
  // FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
1151
1149
  await waitForFinishInstallation(app.id, response.body.taskId, options);
@@ -1204,7 +1202,7 @@ async function importApp(localOptions, cmd) {
1204
1202
  }
1205
1203
 
1206
1204
  const response = await createRequest('POST', `/api/v1/apps/${app.id}/import`, options);
1207
- if (response.statusCode !== 202) return exit(`Failed to import app: ${requestError(response)}`);
1205
+ if (response.status !== 202) return exit(`Failed to import app: ${requestError(response)}`);
1208
1206
 
1209
1207
  await waitForFinishInstallation(app.id, response.body.taskId, options);
1210
1208
  console.log('\n\nApp is restored');
@@ -1225,7 +1223,7 @@ async function exportApp(localOptions, cmd) {
1225
1223
 
1226
1224
  const request = createRequest('POST', `/api/v1/apps/${app.id}/export`, options);
1227
1225
  const response = await request.send(data);
1228
- if (response.statusCode !== 202) return exit(`Failed to export app: ${requestError(response)}`);
1226
+ if (response.status !== 202) return exit(`Failed to export app: ${requestError(response)}`);
1229
1227
 
1230
1228
  await waitForFinishInstallation(app.id, response.body.taskId, options);
1231
1229
  console.log('\n\nApp is exported');
@@ -1242,9 +1240,9 @@ async function clone(localOptions, cmd) {
1242
1240
 
1243
1241
  if (!options.backup) return exit('Use --backup to specify the backup id');
1244
1242
 
1245
- const location = options.location || readlineSync.question('Cloned app location: ', {});
1243
+ const location = options.location || await readline.question('Cloned app location: ', {});
1246
1244
  const secondaryDomains = await querySecondaryDomains(app, app.manifest, options);
1247
- const ports = queryPortBindings(app, app.manifest);
1245
+ const ports = await queryPortBindings(app, app.manifest);
1248
1246
  const backupId = options.backup;
1249
1247
 
1250
1248
  const domainObject = await selectDomain(location, options);
@@ -1258,7 +1256,7 @@ async function clone(localOptions, cmd) {
1258
1256
  };
1259
1257
  const request = createRequest('POST', `/api/v1/apps/${app.id}/clone`, options);
1260
1258
  const response = await request.send(data);
1261
- if (response.statusCode !== 201) return exit(`Failed to list apps: ${requestError(response)}`);
1259
+ if (response.status !== 201) return exit(`Failed to list apps: ${requestError(response)}`);
1262
1260
 
1263
1261
  // FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
1264
1262
  console.log('App cloned as id ' + response.body.id);
@@ -1319,12 +1317,12 @@ async function exec(args, localOptions, cmd) {
1319
1317
 
1320
1318
  const request = createRequest('POST', `/api/v1/apps/${app.id}/exec`, options);
1321
1319
  const response = await request.send({ cmd: args, tty, lang });
1322
- if (response.statusCode !== 200) return exit(`Failed to create exec: ${requestError(response)}`);
1320
+ if (response.status !== 200) return exit(`Failed to create exec: ${requestError(response)}`);
1323
1321
  const execId = response.body.id;
1324
1322
 
1325
1323
  async function exitWithCode() {
1326
1324
  const response2 = await createRequest('GET', `/api/v1/apps/${app.id}/exec/${execId}`, options);
1327
- if (response2.statusCode !== 200) return exit(`Failed to get exec code: ${requestError(response2)}`);
1325
+ if (response2.status !== 200) return exit(`Failed to get exec code: ${requestError(response2)}`);
1328
1326
 
1329
1327
  process.exit(response2.body.exitCode);
1330
1328
  }
@@ -1417,19 +1415,7 @@ function push(localDir, remote, localOptions, cmd) {
1417
1415
  if (local === '-') {
1418
1416
  localOptions._stdin = process.stdin;
1419
1417
  } else if (stat) {
1420
- const progress = new ProgressStream({ length: stat.size, time: 1000 });
1421
-
1422
- localOptions._stdin = progress;
1423
- fs.createReadStream(local).pipe(progress);
1424
-
1425
- const bar = new ProgressBar('Uploading [:bar] :percent: :etas', {
1426
- complete: '=',
1427
- incomplete: ' ',
1428
- width: 100,
1429
- total: stat.size
1430
- });
1431
-
1432
- progress.on('progress', function (p) { bar.update(p.percentage / 100); /* bar.tick(p.transferred - bar.curr); */ });
1418
+ localOptions._stdin = fs.createReadStream(local);
1433
1419
  } else {
1434
1420
  exit('local file ' + local + ' does not exist');
1435
1421
  }
@@ -1533,7 +1519,7 @@ async function setEnv(app, env, options) {
1533
1519
 
1534
1520
  const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/env`, options);
1535
1521
  const response = await request.send({ env });
1536
- if (response.statusCode !== 202) return exit(`Failed to set env: ${requestError(response)}`);
1522
+ if (response.status !== 202) return exit(`Failed to set env: ${requestError(response)}`);
1537
1523
 
1538
1524
  await waitForTask(response.body.taskId, options);
1539
1525
  console.log('\n');
@@ -1546,7 +1532,7 @@ async function envSet(envVars, localOptions, cmd) {
1546
1532
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1547
1533
 
1548
1534
  const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
1549
- if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1535
+ if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1550
1536
 
1551
1537
  const env = response.body.env;
1552
1538
  envVars.forEach(envVar => {
@@ -1568,7 +1554,7 @@ async function envUnset(envNames, localOptions, cmd) {
1568
1554
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1569
1555
 
1570
1556
  const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
1571
- if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1557
+ if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1572
1558
 
1573
1559
  const env = response.body.env;
1574
1560
  envNames.forEach(name => delete env[name]);
@@ -1586,7 +1572,7 @@ async function envList(localOptions, cmd) {
1586
1572
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1587
1573
 
1588
1574
  const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
1589
- if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1575
+ if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1590
1576
 
1591
1577
  const t = new Table();
1592
1578
 
@@ -1614,7 +1600,7 @@ async function envGet(envName, localOptions, cmd) {
1614
1600
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1615
1601
 
1616
1602
  const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
1617
- if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1603
+ if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1618
1604
 
1619
1605
  console.log(response.body.env[envName]);
1620
1606
  } catch (error) {