cloudron 6.0.0 → 7.0.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
@@ -1,59 +1,25 @@
1
- 'use strict';
2
-
3
- const assert = require('assert'),
4
- config = require('./config.js'),
5
- ejs = require('ejs'),
6
- { exit, locateManifest } = require('./helper.js'),
7
- { EventSource } = require('eventsource'),
8
- fs = require('fs'),
9
- https = require('https'),
10
- manifestFormat = require('@cloudron/manifest-format'),
11
- os = require('os'),
12
- path = require('path'),
13
- readline = require('./readline.js'),
14
- safe = require('safetydance'),
15
- spawn = require('child_process').spawn,
16
- semver = require('semver'),
17
- superagent = require('@cloudron/superagent'),
18
- Table = require('easy-table'),
19
- tar = require('tar-fs'),
20
- timers = require('timers/promises'),
21
- zlib = require('zlib');
22
-
23
- exports = module.exports = {
24
- list,
25
- login,
26
- logout,
27
- open,
28
- install,
29
- setLocation,
30
- debug,
31
- update,
32
- uninstall,
33
- logs,
34
- exec,
35
- status,
36
- inspect,
37
- pull,
38
- push,
39
- restart,
40
- start,
41
- stop,
42
- repair,
43
- init,
44
- restore,
45
- importApp,
46
- exportApp,
47
- clone,
48
- backupCreate,
49
- backupList,
50
- cancel,
51
-
52
- envGet,
53
- envSet,
54
- envList,
55
- envUnset
56
- };
1
+ import assert from 'assert';
2
+ import { dockerignoreMatcher } from './build-actions.js';
3
+ import * as config from './config.js';
4
+ import ejs from 'ejs';
5
+ import { exit, locateManifest, performOidcLogin } from './helper.js';
6
+ import { EventSource } from 'eventsource';
7
+ import fs from 'fs';
8
+ import https from 'https';
9
+ import manifestFormat from '@cloudron/manifest-format';
10
+ import os from 'os';
11
+ import path from 'path';
12
+ import * as readline from './readline.js';
13
+ import safe from '@cloudron/safetydance';
14
+ import { spawn } from 'child_process';
15
+ import semver from 'semver';
16
+ import stream from 'stream/promises';
17
+ import superagent from '@cloudron/superagent';
18
+ import Table from 'easy-table';
19
+ import tar from 'tar-fs';
20
+ import timers from 'timers/promises';
21
+ import zlib from 'zlib';
22
+ import open from 'open';
57
23
 
58
24
  const NO_APP_FOUND_ERROR_STRING = 'Could not determine app. Use --app to specify the app\n';
59
25
 
@@ -158,7 +124,7 @@ async function selectAppWithRepository(repository, options) {
158
124
  console.log('[%s]\t%s', index, app.fqdn);
159
125
  });
160
126
 
161
- let index = -1;
127
+ let index;
162
128
  while (true) {
163
129
  index = parseInt(await readline.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
164
130
  if (isNaN(index) || index < 0 || index > matchingApps.length-1) console.log('Invalid selection');
@@ -180,7 +146,7 @@ async function getApp(options) {
180
146
  if (!manifestFilePath) throw new Error(NO_APP_FOUND_ERROR_STRING);
181
147
 
182
148
  const sourceDir = path.dirname(manifestFilePath);
183
- const appConfig = config.getAppConfig(sourceDir);
149
+ const appConfig = config.getAppBuildConfig(sourceDir);
184
150
 
185
151
  if (!appConfig.repository) throw new Error(NO_APP_FOUND_ERROR_STRING);
186
152
 
@@ -389,12 +355,24 @@ async function login(adminFqdn, localOptions, cmd) {
389
355
  }
390
356
 
391
357
  if (!token) {
392
- const username = options.username || await readline.question('Username: ', {});
393
- const password = options.password || await readline.question('Password: ', { noEchoBack: true });
358
+ // oidc cli login is possible from 9.1 on
359
+ const request = superagent.get(`https://${adminFqdn}/api/v1/cloudron/status`).ok(() => true);
360
+ if (!rejectUnauthorized) request.disableTLSCerts();
394
361
 
395
- const [error, result] = await safe(authenticate(adminFqdn, username, password, { rejectUnauthorized, askForTotpToken: false }));
396
- if (error) return exit(`Failed to login: ${error.message}`);
397
- token = result;
362
+ const [error, response] = await safe(request);
363
+ if (error) return exit(error);
364
+ if (response.status !== 200) return exit(`Failed to get cloudron status: ${requestError(response)}`);
365
+
366
+ if (semver.gte(response.body.version, '9.1.0')) {
367
+ token = await performOidcLogin(adminFqdn, { rejectUnauthorized });
368
+ } else {
369
+ const username = options.username || await readline.question('Username: ', {});
370
+ const password = options.password || await readline.question('Password: ', { noEchoBack: true });
371
+
372
+ const [authError, result] = await safe(authenticate(adminFqdn, username, password, { rejectUnauthorized, askForTotpToken: false }));
373
+ if (authError) return exit(`Failed to login: ${authError.message}`);
374
+ token = result;
375
+ }
398
376
  }
399
377
 
400
378
  config.setActive(adminFqdn);
@@ -404,6 +382,8 @@ async function login(adminFqdn, localOptions, cmd) {
404
382
  config.set('cloudrons.default', adminFqdn);
405
383
 
406
384
  console.log('Login successful.');
385
+
386
+ process.exit(0);
407
387
  }
408
388
 
409
389
  function logout() {
@@ -413,15 +393,14 @@ function logout() {
413
393
  console.log('Logged out.');
414
394
  }
415
395
 
416
- async function open(localOptions, cmd) {
396
+ async function openApp(localOptions, cmd) {
417
397
  const options = cmd.optsWithGlobals();
418
398
  const [error, app] = await safe(getApp(options));
419
399
  if (error) return exit(error);
420
400
 
421
401
  if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
422
402
 
423
- // pure esm module
424
- (await import('open')).default(`https://${app.fqdn}`);
403
+ open(`https://${app.fqdn}`);
425
404
  }
426
405
 
427
406
  async function list(localOptions, cmd) {
@@ -448,12 +427,12 @@ async function list(localOptions, cmd) {
448
427
  responses.forEach(function (result) {
449
428
  if (result.status !== 'fulfilled') return exit(`Failed to list app: ${requestError(result.value)}`);
450
429
 
451
- const response = result.value;
430
+ const appResponse = result.value;
452
431
 
453
- if (response.status !== 200) return exit(`Failed to list app: ${requestError(response)}`);
454
- response.body.location = response.body.location || response.body.subdomain; // LEGACY support
432
+ if (appResponse.status !== 200) return exit(`Failed to list app: ${requestError(appResponse)}`);
433
+ appResponse.body.location = appResponse.body.location || appResponse.body.subdomain; // LEGACY support
455
434
 
456
- const detailedApp = response.body;
435
+ const detailedApp = appResponse.body;
457
436
 
458
437
  t.cell('Id', detailedApp.id);
459
438
  t.cell('Location', detailedApp.fqdn);
@@ -578,13 +557,32 @@ async function install(localOptions, cmd) {
578
557
  const result = await getManifest(options.appstoreId || '');
579
558
  const { manifest, manifestFilePath } = result;
580
559
 
560
+ let sourceArchiveFilePath = null; // will be set if we need to send a tarball
561
+
581
562
  if (!manifest.dockerImage) { // not a manifest from appstore
582
563
  const sourceDir = path.dirname(manifestFilePath);
583
- const image = options.image || config.getAppConfig(sourceDir).dockerImage;
564
+ const image = options.image || config.getAppBuildConfig(sourceDir).dockerImage;
565
+
566
+ if (!image) {
567
+ console.log('No build detected. This package will be built on the server.');
568
+
569
+ const dockerignoreFilePath = path.join(sourceDir, '.dockerignore');
570
+ const ignoreMatcher = dockerignoreMatcher(dockerignoreFilePath);
571
+
572
+ const tarStream = tar.pack(sourceDir, {
573
+ ignore: function (name) {
574
+ return ignoreMatcher(name.slice(sourceDir.length + 1)); // make name as relative path
575
+ }
576
+ });
584
577
 
585
- if (!image) return exit('No image found, please run `cloudron build` first or specify using --image');
578
+ sourceArchiveFilePath = path.join(os.tmpdir(), `cloudron-source-${Date.now()}.tar`);
579
+ const sourceArchiveStream = fs.createWriteStream(sourceArchiveFilePath);
586
580
 
587
- manifest.dockerImage = image;
581
+ const [tarError] = await safe(stream.pipeline(tarStream, sourceArchiveStream));
582
+ if (tarError) return exit(`Could not create source archive: ${tarError.message}`);
583
+ } else {
584
+ manifest.dockerImage = image;
585
+ }
588
586
  }
589
587
 
590
588
  const location = options.location || await readline.question('Location: ', {});
@@ -689,8 +687,35 @@ async function install(localOptions, cmd) {
689
687
  if (!data.icon) return exit(`Could not read icon: ${iconFilename} message: ${safe.error.message}`);
690
688
  }
691
689
 
692
- const request = createRequest('POST', '/api/v1/apps/install', options);
693
- const response = await request.send(data);
690
+ const request = createRequest('POST', '/api/v1/apps', options);
691
+
692
+ let response;
693
+ if (sourceArchiveFilePath) {
694
+ // Send as multipart form-data with source archive
695
+ request.field('appStoreId', data.appStoreId);
696
+ request.field('manifest', JSON.stringify(data.manifest));
697
+ request.field('location', data.location);
698
+ request.field('subdomain', data.subdomain);
699
+ request.field('domain', data.domain);
700
+ request.field('secondaryDomains', JSON.stringify(data.secondaryDomains));
701
+ request.field('aliasDomains', JSON.stringify(data.aliasDomains));
702
+ request.field('ports', JSON.stringify(data.ports));
703
+ request.field('accessRestriction', JSON.stringify(data.accessRestriction));
704
+ request.field('memoryLimit', String(data.memoryLimit));
705
+ request.field('env', JSON.stringify(data.env));
706
+ if (data.sso !== undefined) request.field('sso', String(data.sso));
707
+ if (data.debugMode) request.field('debugMode', JSON.stringify(data.debugMode));
708
+ if (data.icon) request.field('icon', data.icon);
709
+ request.attach('sourceArchive', sourceArchiveFilePath);
710
+
711
+ response = await request;
712
+
713
+ // Clean up temp file
714
+ safe.fs.unlinkSync(sourceArchiveFilePath);
715
+ } else {
716
+ response = await request.send(data);
717
+ }
718
+
694
719
  if (response.status !== 202) return exit(`Failed to install app: ${requestError(response)}`);
695
720
 
696
721
  const appId = response.body.id;
@@ -766,24 +791,24 @@ async function setLocation(localOptions, cmd) {
766
791
 
767
792
  // port bindings
768
793
  if (options.portBindings) {
769
- let ports;
794
+ let userPorts;
770
795
  // ask the user for port values if the ports are different in the app and the manifest
771
796
  if (typeof options.portBindings === 'string') {
772
- ports = {};
797
+ userPorts = {};
773
798
  options.portBindings.split(',').forEach(function (kv) {
774
799
  const tmp = kv.split('=');
775
800
  if (isNaN(parseInt(tmp[1], 10))) return; // disable the port
776
- ports[tmp[0]] = parseInt(tmp[1], 10);
801
+ userPorts[tmp[0]] = parseInt(tmp[1], 10);
777
802
  });
778
803
  } else {
779
- ports = await queryPortBindings(app, app.manifest);
804
+ userPorts = await queryPortBindings(app, app.manifest);
780
805
  }
781
806
 
782
- for (const port in ports) {
783
- console.log('%s: %s', port, ports[port]);
807
+ for (const port in userPorts) {
808
+ console.log('%s: %s', port, userPorts[port]);
784
809
  }
785
810
 
786
- data.ports = ports;
811
+ data.ports = userPorts;
787
812
  }
788
813
 
789
814
  const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/location`, options);
@@ -803,20 +828,37 @@ async function update(localOptions, cmd) {
803
828
 
804
829
  try {
805
830
  const app = await getApp(options);
806
- if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
807
831
 
808
832
  const result = await getManifest(options.appstoreId || '');
809
833
  const { manifest, manifestFilePath } = result;
810
834
 
811
835
  let data = {};
836
+ let sourceArchiveFilePath = null; // will be set if we need to send a tarball
812
837
 
813
838
  if (!manifest.dockerImage) { // not a manifest from appstore
814
839
  const sourceDir = path.dirname(manifestFilePath);
815
- const image = options.image || config.getAppConfig(sourceDir).dockerImage;
840
+ const image = options.image || config.getAppBuildConfig(sourceDir).dockerImage;
841
+
842
+ if (!image) {
843
+ console.log('No docker image detected. Creating source archive from this folder.');
844
+
845
+ const dockerignoreFilePath = path.join(sourceDir, '.dockerignore');
846
+ const ignoreMatcher = dockerignoreMatcher(dockerignoreFilePath);
847
+
848
+ const tarStream = tar.pack(sourceDir, {
849
+ ignore: function (name) {
850
+ return ignoreMatcher(name.slice(sourceDir.length + 1)); // make name as relative path
851
+ }
852
+ });
816
853
 
817
- if (!image) return exit('No image found, please run `cloudron build` first or use --image');
854
+ sourceArchiveFilePath = path.join(os.tmpdir(), `cloudron-source-${Date.now()}.tar`);
855
+ const sourceArchiveStream = fs.createWriteStream(sourceArchiveFilePath);
818
856
 
819
- manifest.dockerImage = image;
857
+ const [tarError] = await safe(stream.pipeline(tarStream, sourceArchiveStream));
858
+ if (tarError) return exit(`Could not create source archive: ${tarError.message}`);
859
+ } else {
860
+ manifest.dockerImage = image;
861
+ }
820
862
 
821
863
  if (manifest.icon) {
822
864
  let iconFilename = manifest.icon.slice(0, 7) === 'file://' ? manifest.icon.slice(7) : manifest.icon;
@@ -826,24 +868,32 @@ async function update(localOptions, cmd) {
826
868
  }
827
869
  }
828
870
 
829
- let apiPath;
871
+ data = Object.assign(data, {
872
+ appStoreId: options.appstoreId || '', // note case change
873
+ manifest: manifest,
874
+ skipBackup: !options.backup,
875
+ skipNotification: true,
876
+ force: true // permit downgrades
877
+ });
878
+
879
+ const request = createRequest('POST', `/api/v1/apps/${app.id}/update`, options);
830
880
 
831
- if (app.error && (app.error.installationState === 'pending_install')) { // install had failed. call repair to re-install
832
- apiPath = `/api/v1/apps/${app.id}/repair`;
833
- data = Object.assign(data, { manifest });
881
+ let response;
882
+ if (sourceArchiveFilePath) {
883
+ request.field('appStoreId', data.appStoreId);
884
+ request.field('manifest', JSON.stringify(data.manifest));
885
+ request.field('skipBackup', String(data.skipBackup));
886
+ request.field('skipNotification', String(data.skipNotification));
887
+ request.field('force', String(data.force));
888
+ if (data.icon) request.field('icon', data.icon);
889
+ request.attach('sourceArchive', sourceArchiveFilePath);
890
+
891
+ response = await request;
892
+
893
+ safe.fs.unlinkSync(sourceArchiveFilePath);
834
894
  } else {
835
- apiPath = `/api/v1/apps/${app.id}/update`;
836
- data = Object.assign(data, {
837
- appStoreId: options.appstoreId || '', // note case change
838
- manifest: manifest,
839
- skipBackup: !options.backup,
840
- skipNotification: true,
841
- force: true // permit downgrades
842
- });
895
+ response = await request.send(data);
843
896
  }
844
-
845
- const request = createRequest('POST', apiPath, options);
846
- const response = await request.send(data);
847
897
  if (response.status !== 202) return exit(`Failed to update app: ${requestError(response)}`);
848
898
 
849
899
  process.stdout.write('\n => ' + 'Waiting for app to be updated ');
@@ -851,12 +901,11 @@ async function update(localOptions, cmd) {
851
901
  await waitForFinishInstallation(app.id, response.body.taskId, options);
852
902
  console.log('\n\nApp is updated.');
853
903
  } catch (error) {
854
- console.log(error);
855
- exit(`\n\nApp update error: ${error.message}`);
904
+ exit(`\nApp update error: ${error.message}`);
856
905
  }
857
906
  }
858
907
 
859
- async function debug(args, localOptions, cmd) {
908
+ async function debugApp(args, localOptions, cmd) {
860
909
  try {
861
910
  const options = cmd.optsWithGlobals();
862
911
  const app = await getApp(options);
@@ -1319,21 +1368,21 @@ async function clone(localOptions, cmd) {
1319
1368
  }
1320
1369
 
1321
1370
  // taken from docker-modem
1322
- function demuxStream(stream, stdout, stderr) {
1371
+ function demuxStream(inputStream, stdout, stderr) {
1323
1372
  let header = null;
1324
1373
 
1325
- stream.on('readable', function() {
1326
- header = header || stream.read(8);
1374
+ inputStream.on('readable', function() {
1375
+ header = header || inputStream.read(8);
1327
1376
  while (header !== null) {
1328
1377
  const type = header.readUInt8(0);
1329
- const payload = stream.read(header.readUInt32BE(4));
1378
+ const payload = inputStream.read(header.readUInt32BE(4));
1330
1379
  if (payload === null) break;
1331
1380
  if (type == 2) {
1332
1381
  stderr.write(payload);
1333
1382
  } else {
1334
1383
  stdout.write(payload);
1335
1384
  }
1336
- header = stream.read(8);
1385
+ header = inputStream.read(8);
1337
1386
  }
1338
1387
  });
1339
1388
  }
@@ -1344,7 +1393,7 @@ function demuxStream(stream, stdout, stderr) {
1344
1393
  // echo "sauce" | cloudron exec -- bash -c "cat - > /app/data/sauce" - test with binary files. should disable tty
1345
1394
  // cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar xvf - -C /tmp" - must show an error
1346
1395
  // cat ~/tmp/fantome.tar.gz | cloudron exec -- bash -c "tar zxf - -C /tmp" - must extrack ok
1347
- async function exec(args, localOptions, cmd) {
1396
+ async function execApp(args, localOptions, cmd) {
1348
1397
  let stdin = localOptions._stdin || process.stdin; // hack for 'push', 'pull' to reuse this function
1349
1398
  const stdout = localOptions._stdout || process.stdout; // hack for 'push', 'pull' to reuse this function
1350
1399
 
@@ -1378,12 +1427,12 @@ async function exec(args, localOptions, cmd) {
1378
1427
  process.exit(response2.body.exitCode);
1379
1428
  }
1380
1429
 
1381
- const { adminFqdn, token, rejectUnauthorized } = requestOptions(cmd.optsWithGlobals());
1430
+ const { adminFqdn, token: reqToken, rejectUnauthorized } = requestOptions(cmd.optsWithGlobals());
1382
1431
 
1383
1432
  const searchParams = new URLSearchParams({
1384
1433
  rows: stdout.rows || 24,
1385
1434
  columns: stdout.columns || 80,
1386
- access_token: token,
1435
+ access_token: reqToken,
1387
1436
  tty
1388
1437
  });
1389
1438
 
@@ -1448,7 +1497,7 @@ async function exec(args, localOptions, cmd) {
1448
1497
  req.end(); // this makes the request
1449
1498
  }
1450
1499
 
1451
- function push(localDir, remote, localOptions, cmd) {
1500
+ function pushApp(localDir, remote, localOptions, cmd) {
1452
1501
  // deal with paths prefixed with ~
1453
1502
  const local = localDir.replace(/^~(?=$|\/|\\)/, os.homedir());
1454
1503
  const stat = fs.existsSync(path.resolve(local)) ? fs.lstatSync(local) : null;
@@ -1461,7 +1510,7 @@ function push(localDir, remote, localOptions, cmd) {
1461
1510
  return tarzip.stdout;
1462
1511
  };
1463
1512
 
1464
- exec(['tar', 'zxvf', '-', '-C', remote], localOptions, cmd);
1513
+ execApp(['tar', 'zxvf', '-', '-C', remote], localOptions, cmd);
1465
1514
  } else {
1466
1515
  if (local === '-') {
1467
1516
  localOptions._stdin = process.stdin;
@@ -1477,7 +1526,7 @@ function push(localDir, remote, localOptions, cmd) {
1477
1526
  remote = remote + '/' + path.basename(local); // do not use path.join as we want this to be a UNIX path
1478
1527
  }
1479
1528
 
1480
- exec(['bash', '-c', `cat - > "${remote}"`], localOptions, cmd);
1529
+ execApp(['bash', '-c', `cat - > "${remote}"`], localOptions, cmd);
1481
1530
  }
1482
1531
  }
1483
1532
 
@@ -1489,7 +1538,7 @@ function pull(remote, local, localOptions, cmd) {
1489
1538
  unzip.pipe(untar);
1490
1539
  localOptions._stdout = unzip;
1491
1540
 
1492
- exec(['tar', 'zcf', '-', '-C', remote, '.'], localOptions, cmd);
1541
+ execApp(['tar', 'zcf', '-', '-C', remote, '.'], localOptions, cmd);
1493
1542
  } else {
1494
1543
  if (fs.existsSync(local) && fs.lstatSync(local).isDirectory()) {
1495
1544
  local = path.join(local, path.basename(remote));
@@ -1502,7 +1551,7 @@ function pull(remote, local, localOptions, cmd) {
1502
1551
 
1503
1552
  localOptions._stdout.on('error', function (error) { exit('Error pulling', error); });
1504
1553
 
1505
- exec(['cat', remote], localOptions, cmd);
1554
+ execApp(['cat', remote], localOptions, cmd);
1506
1555
  }
1507
1556
  }
1508
1557
 
@@ -1513,10 +1562,10 @@ function init(localOptions, cmd) {
1513
1562
 
1514
1563
  const manifestTemplateFilename = options.appstore ? 'CloudronManifest.appstore.json.ejs' : 'CloudronManifest.json.ejs';
1515
1564
 
1516
- const manifestTemplate = fs.readFileSync(path.join(__dirname, 'templates/', manifestTemplateFilename), 'utf8');
1517
- const dockerfileTemplate = fs.readFileSync(path.join(__dirname, 'templates/', 'Dockerfile.ejs'), 'utf8');
1518
- const dockerignoreTemplate = fs.readFileSync(path.join(__dirname, 'templates/', 'dockerignore.ejs'), 'utf8');
1519
- const startShTemplate = fs.readFileSync(path.join(__dirname, 'templates/', 'start.sh.ejs'), 'utf8');
1565
+ const manifestTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', manifestTemplateFilename), 'utf8');
1566
+ const dockerfileTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'Dockerfile.ejs'), 'utf8');
1567
+ const dockerignoreTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'dockerignore.ejs'), 'utf8');
1568
+ const startShTemplate = fs.readFileSync(path.join(import.meta.dirname, 'templates/', 'start.sh.ejs'), 'utf8');
1520
1569
 
1521
1570
  const data = {
1522
1571
  version: '0.1.0',
@@ -1549,7 +1598,7 @@ function init(localOptions, cmd) {
1549
1598
  fs.chmodSync('start.sh', 0o0775);
1550
1599
  }
1551
1600
 
1552
- fs.copyFileSync(path.join(__dirname, 'templates/', 'logo.png'), 'logo.png');
1601
+ fs.copyFileSync(path.join(import.meta.dirname, 'templates/', 'logo.png'), 'logo.png');
1553
1602
 
1554
1603
  if (options.appstore) {
1555
1604
  if (!fs.existsSync('.gitignore')) fs.writeFileSync('.gitignore', 'node_modules/\n', 'utf8');
@@ -1658,3 +1707,38 @@ async function envGet(envName, localOptions, cmd) {
1658
1707
  exit(error);
1659
1708
  }
1660
1709
  }
1710
+
1711
+ export default {
1712
+ list,
1713
+ login,
1714
+ logout,
1715
+ open: openApp,
1716
+ install,
1717
+ setLocation,
1718
+ debug: debugApp,
1719
+ update,
1720
+ uninstall,
1721
+ logs,
1722
+ exec: execApp,
1723
+ status,
1724
+ inspect,
1725
+ pull,
1726
+ push: pushApp,
1727
+ restart,
1728
+ start,
1729
+ stop,
1730
+ repair,
1731
+ init,
1732
+ restore,
1733
+ importApp,
1734
+ exportApp,
1735
+ clone,
1736
+ backupCreate,
1737
+ backupList,
1738
+ cancel,
1739
+
1740
+ envGet,
1741
+ envSet,
1742
+ envList,
1743
+ envUnset
1744
+ };
@@ -1,32 +1,14 @@
1
- /* jshint node:true */
2
-
3
- 'use strict';
4
-
5
- const assert = require('assert'),
6
- config = require('./config.js'),
7
- execSync = require('child_process').execSync,
8
- fs = require('fs'),
9
- { exit, locateManifest } = require('./helper.js'),
10
- manifestFormat = require('@cloudron/manifest-format'),
11
- path = require('path'),
12
- readline = require('./readline.js'),
13
- safe = require('safetydance'),
14
- superagent = require('@cloudron/superagent'),
15
- Table = require('easy-table');
16
-
17
- exports = module.exports = {
18
- login,
19
- logout,
20
- info,
21
- listVersions,
22
- submit,
23
- upload,
24
- revoke,
25
- approve,
26
- verifyManifest,
27
-
28
- notify
29
- };
1
+ import assert from 'assert';
2
+ import * as config from './config.js';
3
+ import { execSync } from 'child_process';
4
+ import fs from 'fs';
5
+ import { exit, locateManifest, parseChangelog } from './helper.js';
6
+ import manifestFormat from '@cloudron/manifest-format';
7
+ import path from 'path';
8
+ import * as readline from './readline.js';
9
+ import safe from '@cloudron/safetydance';
10
+ import superagent from '@cloudron/superagent';
11
+ import Table from 'easy-table';
30
12
 
31
13
  const NO_MANIFEST_FOUND_ERROR_STRING = 'No CloudronManifest.json found';
32
14
 
@@ -156,29 +138,6 @@ async function listVersions(localOptions, cmd) {
156
138
  console.log(t.toString());
157
139
  }
158
140
 
159
- function parseChangelog(file, version) {
160
- let changelog = '';
161
- const data = safe.fs.readFileSync(file, 'utf8');
162
- if (!data) return null;
163
- const lines = data.split('\n');
164
-
165
- version = version.replace(/-.*/, ''); // remove any prerelease
166
-
167
- let i;
168
- for (i = 0; i < lines.length; i++) {
169
- if (lines[i] === '[' + version + ']') break;
170
- }
171
-
172
- for (i = i + 1; i < lines.length; i++) {
173
- if (lines[i] === '') continue;
174
- if (lines[i][0] === '[') break;
175
-
176
- changelog += lines[i] + '\n';
177
- }
178
-
179
- return changelog;
180
- }
181
-
182
141
  async function addVersion(manifest, baseDir, options) {
183
142
  assert.strictEqual(typeof manifest, 'object');
184
143
  assert.strictEqual(typeof baseDir, 'string');
@@ -210,8 +169,7 @@ async function addVersion(manifest, baseDir, options) {
210
169
  }
211
170
 
212
171
  if (manifest.changelog.slice(0, 7) === 'file://') {
213
- let changelogPath = manifest.changelog.slice(7);
214
- changelogPath = path.isAbsolute(changelogPath) ? changelogPath : path.join(baseDir, changelogPath);
172
+ const changelogPath = path.isAbsolute(manifest.changelog.slice(7)) ? manifest.changelog.slice(7) : path.join(baseDir, manifest.changelog.slice(7));
215
173
  manifest.changelog = parseChangelog(changelogPath, manifest.version);
216
174
  if (!manifest.changelog) throw new Error('Bad changelog format or missing changelog for this version');
217
175
  }
@@ -277,7 +235,7 @@ async function verifyManifest(localOptions, cmd) {
277
235
  const manifest = result.manifest;
278
236
 
279
237
  const sourceDir = path.dirname(manifestFilePath);
280
- const appConfig = config.getAppConfig(sourceDir);
238
+ const appConfig = config.getAppBuildConfig(sourceDir);
281
239
 
282
240
  // image can be passed in options for buildbot
283
241
  if (options.image) {
@@ -324,7 +282,7 @@ async function upload(localOptions, cmd) {
324
282
  const manifest = result.manifest;
325
283
 
326
284
  const sourceDir = path.dirname(manifestFilePath);
327
- const appConfig = config.getAppConfig(sourceDir);
285
+ const appConfig = config.getAppBuildConfig(sourceDir);
328
286
 
329
287
  // image can be passed in options for buildbot
330
288
  if (options.image) {
@@ -467,11 +425,11 @@ async function notify() {
467
425
  if (result.error) return exit(new Error(`Invalid CloudronManifest.json: ${result.error.message}`));
468
426
  const { manifest } = result;
469
427
 
470
- let postContent = null;
428
+ let postContent;
471
429
  if (manifest.changelog.slice(0, 7) === 'file://') {
472
430
  const baseDir = path.dirname(manifestFilePath);
473
- let changelogPath = manifest.changelog.slice(7);
474
- changelogPath = path.isAbsolute(changelogPath) ? changelogPath : path.join(baseDir, changelogPath);
431
+ const manifestChangelogPath = manifest.changelog.slice(7);
432
+ const changelogPath = path.isAbsolute(manifestChangelogPath) ? manifestChangelogPath : path.join(baseDir, manifestChangelogPath);
475
433
  const changelog = parseChangelog(changelogPath, manifest.version);
476
434
  if (!changelog) return exit('Bad changelog format or missing changelog for this version');
477
435
  postContent = `[${manifest.version}]\n${changelog}\n`;
@@ -511,3 +469,17 @@ async function notify() {
511
469
  if (postResponse.status !== 200) return exit(`Unable to create changelog post: ${requestError(postResponse)}`);
512
470
  console.log('Posted to forum');
513
471
  }
472
+
473
+ export default {
474
+ login,
475
+ logout,
476
+ info,
477
+ listVersions,
478
+ submit,
479
+ upload,
480
+ revoke,
481
+ approve,
482
+ verifyManifest,
483
+
484
+ notify
485
+ };