cloudron 5.5.0 → 5.6.1

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const program = require('commander'),
5
+ const { program } = require('commander'),
6
6
  appstoreActions = require('../src/appstore-actions.js');
7
7
 
8
8
  program.version(require('../package.json').version);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudron",
3
- "version": "5.5.0",
3
+ "version": "5.6.1",
4
4
  "license": "MIT",
5
5
  "description": "Cloudron Commandline Tool",
6
6
  "main": "main.js",
@@ -10,40 +10,39 @@
10
10
  "url": "https://git.cloudron.io/cloudron/cloudron-cli.git"
11
11
  },
12
12
  "scripts": {
13
- "test": "./node_modules/.bin/mocha test/test.js"
13
+ "test": "mocha test/test.js"
14
14
  },
15
15
  "bin": {
16
- "cloudron": "./bin/cloudron"
16
+ "cloudron": "bin/cloudron"
17
17
  },
18
18
  "author": "Cloudron Developers <support@cloudron.io>",
19
19
  "dependencies": {
20
- "async": "^3.2.4",
21
- "cloudron-manifestformat": "^5.22.1",
22
- "commander": "^9.5.0",
23
- "debug": "^4.3.4",
24
- "delay": "^5.0.0",
20
+ "async": "^3.2.5",
21
+ "cloudron-manifestformat": "^5.24.0",
22
+ "commander": "^12.1.0",
23
+ "debug": "^4.3.5",
25
24
  "easy-table": "^1.2.0",
26
- "ejs": "^3.1.9",
25
+ "ejs": "^3.1.10",
27
26
  "eventsource": "^2.0.2",
28
- "micromatch": "^4.0.5",
27
+ "micromatch": "^4.0.7",
29
28
  "once": "^1.4.0",
30
29
  "open": "^8.4.0",
31
30
  "progress": "^2.0.3",
32
31
  "progress-stream": "^2.0.0",
33
32
  "readline-sync": "^1.4.10",
34
- "safetydance": "^2.2.0",
35
- "split": "^1.0.1",
36
- "superagent": "^8.0.9",
33
+ "safetydance": "^2.4.0",
34
+ "split2": "^4.2.0",
35
+ "superagent": "^9.0.2",
37
36
  "tar-fs": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz",
38
37
  "underscore": "^1.13.6"
39
38
  },
40
39
  "engines": {
41
- "node": ">= 14.x.x"
40
+ "node": ">= 18.x.x"
42
41
  },
43
42
  "devDependencies": {
44
43
  "expect.js": "^0.3.1",
45
44
  "memorystream": "^0.3.1",
46
- "mocha": "^10.2.0",
47
- "rimraf": "^3.0.2"
45
+ "mocha": "^10.4.0",
46
+ "rimraf": "^5.0.7"
48
47
  }
49
48
  }
package/src/actions.js CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  const assert = require('assert'),
4
4
  config = require('./config.js'),
5
- delay = require('delay'),
6
5
  ejs = require('ejs'),
7
6
  { exit, locateManifest } = require('./helper.js'),
8
7
  EventSource = require('eventsource'),
@@ -17,10 +16,12 @@ const assert = require('assert'),
17
16
  readlineSync = require('readline-sync'),
18
17
  safe = require('safetydance'),
19
18
  spawn = require('child_process').spawn,
20
- split = require('split'),
19
+ semver = require('semver'),
20
+ split = require('split2'),
21
21
  superagent = require('superagent'),
22
22
  Table = require('easy-table'),
23
23
  tar = require('tar-fs'),
24
+ timers = require('timers/promises'),
24
25
  zlib = require('zlib'),
25
26
  _ = require('underscore');
26
27
 
@@ -147,7 +148,7 @@ async function selectAppWithRepository(repository, options) {
147
148
  const response = await createRequest('GET', '/api/v1/apps', options);
148
149
  if (response.statusCode !== 200) throw new Error(`Failed to install app: ${requestError(response)}`);
149
150
 
150
- let matchingApps = response.body.apps.filter(function (app) {
151
+ const matchingApps = response.body.apps.filter(function (app) {
151
152
  return !app.appStoreId && app.manifest.dockerImage.startsWith(repository); // never select apps from the store
152
153
  });
153
154
 
@@ -162,7 +163,6 @@ async function selectAppWithRepository(repository, options) {
162
163
  });
163
164
 
164
165
  let index = -1;
165
- // eslint-disable-next-line no-constant-condition
166
166
  while (true) {
167
167
  index = parseInt(readlineSync.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
168
168
  if (isNaN(index) || index < 0 || index > matchingApps.length-1) console.log('Invalid selection');
@@ -216,9 +216,8 @@ async function waitForHealthy(appId, options) {
216
216
 
217
217
  process.stdout.write('\n => ' + 'Wait for health check ');
218
218
 
219
- // eslint-disable-next-line no-constant-condition
220
219
  while (true) {
221
- await delay(1000);
220
+ await timers.setTimeout(1000);
222
221
  const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
223
222
  if (response.statusCode !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
224
223
 
@@ -241,7 +240,6 @@ async function waitForTask(taskId, options) {
241
240
 
242
241
  let currentMessage = '';
243
242
 
244
- // eslint-disable-next-line no-constant-condition
245
243
  while (true) {
246
244
  const response = await createRequest('GET', `/api/v1/tasks/${taskId}`, options);
247
245
  if (response.statusCode !== 200) throw new Error(`Failed to get task: ${requestError(response)}`);
@@ -272,7 +270,7 @@ async function waitForTask(taskId, options) {
272
270
 
273
271
  currentMessage = message;
274
272
 
275
- await delay(1000);
273
+ await timers.setTimeout(1000);
276
274
  }
277
275
  }
278
276
 
@@ -523,8 +521,13 @@ function parseDebugCommand(cmd) {
523
521
  }
524
522
 
525
523
  async function downloadManifest(appstoreId) {
526
- const parts = appstoreId.split('@');
527
- const url = config.appStoreOrigin() + '/api/v1/apps/' + parts[0] + (parts[1] ? '/versions/' + parts[1] : '');
524
+ const [ id, version ] = appstoreId.split('@');
525
+
526
+ if (!manifestFormat.isId(id)) throw new Error('Invalid appstore ID');
527
+ if (version && !semver.valid(version)) throw new Error('Invalid appstore version, not a semver');
528
+
529
+ // encodeURIComponent required to protect against passing junk in the URL (e.g appstoreId=org.wordpress.cloudronapp?foo=bar)
530
+ const url = config.appStoreOrigin() + '/api/v1/apps/' + id + (version ? '/versions/' + version : '');
528
531
 
529
532
  const [error, response] = await safe(superagent.get(url).ok(() => true));
530
533
  if (error) throw new Error(`Failed to list apps from appstore: ${error.message}`);
@@ -598,7 +601,7 @@ async function install(localOptions, cmd) {
598
601
 
599
602
  for (const binding in secondaryDomains) console.log(`Secondary domain ${binding}: ${secondaryDomains[binding].subdomain}.${secondaryDomains[binding].domain}`);
600
603
 
601
- let aliasDomains = [];
604
+ const aliasDomains = [];
602
605
  if (options.aliasDomains) {
603
606
  for (const aliasDomain of options.aliasDomains.split(',')) {
604
607
  aliasDomains.push(await selectDomain(aliasDomain, options));
@@ -612,7 +615,7 @@ async function install(localOptions, cmd) {
612
615
  if (typeof options.portBindings === 'string') {
613
616
  portBindings = { };
614
617
  options.portBindings.split(',').forEach(function (kv) {
615
- let tmp = kv.split('=');
618
+ const tmp = kv.split('=');
616
619
  if (isNaN(parseInt(tmp[1], 10))) return; // disable the port
617
620
  portBindings[tmp[0]] = parseInt(tmp[1], 10);
618
621
  });
@@ -621,7 +624,7 @@ async function install(localOptions, cmd) {
621
624
  }
622
625
  } else { // just put in defaults
623
626
  const allPorts = _.extend({}, manifest.tcpPorts, manifest.udpPorts);
624
- for (let portName in allPorts) {
627
+ for (const portName in allPorts) {
625
628
  portBindings[portName] = allPorts[portName].defaultValue;
626
629
  }
627
630
  }
@@ -682,7 +685,7 @@ async function configure(localOptions, cmd) {
682
685
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
683
686
 
684
687
  if (!options.location) options.location = readlineSync.question(`Enter new location (default: ${app.fqdn}): `, { });
685
- let location = options.location || app.subdomain;
688
+ const location = options.location || app.subdomain;
686
689
 
687
690
  const domainObject = await selectDomain(location, options);
688
691
 
@@ -744,7 +747,7 @@ async function configure(localOptions, cmd) {
744
747
  portBindings = queryPortBindings(app, app.manifest);
745
748
  }
746
749
 
747
- for (let binding in portBindings) {
750
+ for (const binding in portBindings) {
748
751
  console.log('%s: %s', binding, portBindings[binding]);
749
752
  }
750
753
 
@@ -964,7 +967,7 @@ async function logs(localOptions, cmd) {
964
967
  }
965
968
 
966
969
  if (tail) {
967
- const url = `${apiPath}?access_token=${token}&lines=10`;
970
+ const url = `${apiPath}?access_token=${token}&lines=10&format=json`;
968
971
  var es = new EventSource(url, { rejectUnauthorized }); // not sure why this is needed
969
972
 
970
973
  es.on('message', function (e) { // e { type, data, lastEventId }. lastEventId is the timestamp
@@ -977,16 +980,21 @@ async function logs(localOptions, cmd) {
977
980
  exit(error);
978
981
  });
979
982
  } else {
980
- const url = `${apiPath}?access_token=${token}&lines=${lines}`;
983
+ const url = `${apiPath}?access_token=${token}&lines=${lines}&format=json`;
981
984
 
982
985
  const req = superagent.get(url, { rejectUnauthorized });
983
986
  req.on('response', function (response) {
984
987
  if (response.statusCode !== 200) return exit(`Failed to get logs: ${requestError(response)}`);
985
988
  });
986
- req.pipe(split(JSON.parse))
989
+ req.on('error', (error) => exit(`Pipe error: ${error.message}`));
990
+
991
+ const jsonStream = split((line) => JSON.parse(line));
992
+ jsonStream
987
993
  .on('data', logPrinter)
988
- .on('error', process.exit)
994
+ .on('error', (error) => exit(`JSON parse error: ${error.message}`))
989
995
  .on('end', process.exit);
996
+
997
+ req.pipe(jsonStream);
990
998
  }
991
999
  }
992
1000
 
@@ -1013,7 +1021,7 @@ async function inspect(localOptions, cmd) {
1013
1021
  const response = await createRequest('GET', '/api/v1/apps', options);
1014
1022
  if (response.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
1015
1023
 
1016
- let apps = [];
1024
+ const apps = [];
1017
1025
 
1018
1026
  for (const app of response.body.apps) {
1019
1027
  const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
@@ -1172,7 +1180,8 @@ async function importApp(localOptions, cmd) {
1172
1180
  let data = {};
1173
1181
 
1174
1182
  if (!options.inPlace) {
1175
- let { remotePath, backupFormat, backupConfig, backupPath, backupKey } = options;
1183
+ let { remotePath, backupFormat, backupConfig } = options;
1184
+ const { backupPath, backupKey } = options;
1176
1185
  let backupFolder;
1177
1186
 
1178
1187
  if (backupPath) {
@@ -1224,7 +1233,7 @@ async function exportApp(localOptions, cmd) {
1224
1233
  const app = await getApp(options);
1225
1234
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
1226
1235
 
1227
- let data = {};
1236
+ const data = {};
1228
1237
 
1229
1238
  if (!options.snapshot) return exit('--snapshot is required');
1230
1239
 
@@ -1302,7 +1311,7 @@ function demuxStream(stream, stdout, stderr) {
1302
1311
  // cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar zxf - -C /tmp" - must extrack ok
1303
1312
  async function exec(args, localOptions, cmd) {
1304
1313
  let stdin = localOptions._stdin || process.stdin; // hack for 'push', 'pull' to reuse this function
1305
- let stdout = localOptions._stdout || process.stdout; // hack for 'push', 'pull' to reuse this function
1314
+ const stdout = localOptions._stdout || process.stdout; // hack for 'push', 'pull' to reuse this function
1306
1315
 
1307
1316
  const options = cmd.optsWithGlobals();
1308
1317
  let tty = !!options.tty;
@@ -1551,9 +1560,9 @@ async function envSet(envVars, localOptions, cmd) {
1551
1560
  const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
1552
1561
  if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1553
1562
 
1554
- let env = response.body.env;
1563
+ const env = response.body.env;
1555
1564
  envVars.forEach(envVar => {
1556
- let m = envVar.match(/(.*?)=(.*)/);
1565
+ const m = envVar.match(/(.*?)=(.*)/);
1557
1566
  if (!m) return exit(`Expecting KEY=VALUE pattern. Got ${envVar}`);
1558
1567
  env[m[1]] = m[2];
1559
1568
  });
@@ -1573,7 +1582,7 @@ async function envUnset(envNames, localOptions, cmd) {
1573
1582
  const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
1574
1583
  if (response.statusCode !== 200) return exit(`Failed to get app: ${requestError(response)}`);
1575
1584
 
1576
- let env = response.body.env;
1585
+ const env = response.body.env;
1577
1586
  envNames.forEach(name => delete env[name]);
1578
1587
 
1579
1588
  await setEnv(app, env, options);