clever-tools 3.7.0 → 3.8.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.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/bin/clever.js +99 -47
  3. package/package.json +47 -47
  4. package/src/command-options.js +19 -0
  5. package/src/commands/accesslogs.js +114 -34
  6. package/src/commands/activity.js +64 -12
  7. package/src/commands/addon.js +152 -57
  8. package/src/commands/applications.js +83 -1
  9. package/src/commands/cancel-deploy.js +3 -3
  10. package/src/commands/config.js +6 -7
  11. package/src/commands/console.js +16 -4
  12. package/src/commands/delete.js +16 -6
  13. package/src/commands/deploy.js +9 -6
  14. package/src/commands/diag.js +68 -28
  15. package/src/commands/domain.js +13 -13
  16. package/src/commands/drain.js +42 -27
  17. package/src/commands/env.js +53 -24
  18. package/src/commands/logs.js +3 -3
  19. package/src/commands/notify-email.js +33 -18
  20. package/src/commands/open.js +3 -3
  21. package/src/commands/profile.js +29 -9
  22. package/src/commands/published-config.js +26 -15
  23. package/src/commands/restart.js +12 -5
  24. package/src/commands/scale.js +2 -3
  25. package/src/commands/service.js +42 -15
  26. package/src/commands/ssh.js +3 -3
  27. package/src/commands/status.js +72 -46
  28. package/src/commands/stop.js +3 -3
  29. package/src/commands/tcp-redirs.js +54 -18
  30. package/src/commands/webhooks.js +28 -13
  31. package/src/logger.js +9 -6
  32. package/src/models/app_configuration.js +11 -3
  33. package/src/models/application.js +106 -7
  34. package/src/models/exit-strategy-option.js +24 -0
  35. package/src/models/json-array.js +28 -0
  36. package/src/models/log-v4.js +9 -29
  37. package/src/models/log.js +9 -2
  38. package/src/models/namespaces.js +20 -0
  39. package/src/models/organisation.js +0 -20
  40. package/src/models/utils.js +8 -1
  41. package/src/models/accesslogs.js +0 -54
@@ -2,32 +2,68 @@
2
2
 
3
3
  const colors = require('colors/safe');
4
4
 
5
- const AppConfig = require('../models/app_configuration.js');
6
- const Organisation = require('../models/organisation.js');
5
+ const Namespaces = require('../models/namespaces.js');
7
6
  const { sendToApi } = require('../models/send-to-api.js');
8
7
  const Interact = require('../models/interact.js');
9
8
  const Logger = require('../logger.js');
10
9
  const application = require('@clevercloud/client/cjs/api/v2/application.js');
10
+ const Application = require('../models/application.js');
11
11
 
12
12
  async function listNamespaces (params) {
13
- const namespaces = await Organisation.getNamespaces(params);
13
+ const { alias, app: appIdOrName, format } = params.options;
14
+ const { ownerId } = await Application.resolveId(appIdOrName, alias);
14
15
 
15
- Logger.println('Available namespaces: ' + namespaces.map(({ namespace }) => namespace).join(', '));
16
- };
16
+ const namespaces = await Namespaces.getNamespaces(ownerId);
17
+
18
+ namespaces.sort((a, b) => a.namespace.localeCompare(b.namespace));
19
+
20
+ switch (format) {
21
+ case 'json': {
22
+ Logger.printJson(namespaces);
23
+ break;
24
+ }
25
+ case 'human':
26
+ default: {
27
+ Logger.println('Available namespaces:');
28
+ namespaces.forEach(({ namespace }) => {
29
+ switch (namespace) {
30
+ case 'cleverapps':
31
+ Logger.println(`- ${namespace}: for redirections used with 'cleverapps.io' domain`);
32
+ break;
33
+ case 'default':
34
+ Logger.println(`- ${namespace}: for redirections used with custom domains`);
35
+ break;
36
+ default:
37
+ Logger.println(`- ${namespace}`);
38
+ }
39
+ });
40
+ break;
41
+ }
42
+ }
43
+ }
17
44
 
18
45
  async function list (params) {
19
- const { alias } = params.options;
20
- const { ownerId, appId } = await AppConfig.getAppDetails({ alias });
46
+ const { alias, app: appIdOrName, format } = params.options;
47
+ const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
21
48
 
22
49
  const redirs = await application.getTcpRedirs({ id: ownerId, appId }).then(sendToApi);
23
50
 
24
- if (redirs.length === 0) {
25
- Logger.println('No active TCP redirection for this application');
26
- }
27
- else {
28
- Logger.println('Enabled TCP redirections:');
29
- for (const { namespace, port } of redirs) {
30
- Logger.println(port + ' on ' + namespace);
51
+ switch (format) {
52
+ case 'json': {
53
+ Logger.printJson(redirs);
54
+ break;
55
+ }
56
+ case 'human':
57
+ default: {
58
+ if (redirs.length === 0) {
59
+ Logger.println('No active TCP redirection for this application');
60
+ }
61
+ else {
62
+ Logger.println('Enabled TCP redirections:');
63
+ for (const { namespace, port } of redirs) {
64
+ Logger.println(port + ' on ' + namespace);
65
+ }
66
+ }
31
67
  }
32
68
  }
33
69
  }
@@ -46,8 +82,8 @@ async function acceptPayment (result, skipConfirmation) {
46
82
  }
47
83
 
48
84
  async function add (params) {
49
- const { alias, namespace, yes: skipConfirmation } = params.options;
50
- const { ownerId, appId } = await AppConfig.getAppDetails({ alias });
85
+ const { alias, app: appIdOrName, namespace, yes: skipConfirmation } = params.options;
86
+ const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
51
87
 
52
88
  const { port } = await application.addTcpRedir({ id: ownerId, appId }, { namespace }).then(sendToApi).catch((error) => {
53
89
  if (error.status === 402) {
@@ -65,8 +101,8 @@ async function add (params) {
65
101
 
66
102
  async function remove (params) {
67
103
  const [port] = params.args;
68
- const { alias, namespace } = params.options;
69
- const { ownerId, appId } = await AppConfig.getAppDetails({ alias });
104
+ const { alias, app: appIdOrName, namespace } = params.options;
105
+ const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
70
106
 
71
107
  await application.removeTcpRedir({ id: ownerId, appId, sourcePort: port, namespace }).then(sendToApi);
72
108
 
@@ -8,29 +8,44 @@ const { getOwnerAndApp, getOrgaIdOrUserId } = require('../models/notification.js
8
8
  const { getWebhooks, createWebhook, deleteWebhook } = require('@clevercloud/client/cjs/api/v2/notification.js');
9
9
  const { sendToApi } = require('../models/send-to-api.js');
10
10
 
11
- function displayWebhook (hook) {
12
- Logger.println((hook.name && colors.bold(hook.name)) || hook.id);
13
- Logger.println(` id: ${hook.id}`);
14
- Logger.println(` services: ${(hook.scope && hook.scope.join(', ')) || hook.ownerId}`);
15
- Logger.println(` events: ${(hook.events && hook.events.join(', ')) || colors.bold('ALL')}`);
16
- Logger.println(' hooks:');
17
- hook.urls.forEach((url) => Logger.println(` ${url.url} (${url.format})`));
18
- Logger.println();
19
- }
20
-
21
11
  async function list (params) {
22
- const { org, 'list-all': listAll } = params.options;
12
+ const { org, 'list-all': listAll, format } = params.options;
23
13
 
24
14
  // TODO: fix alias option
25
15
  const { ownerId, appId } = await getOwnerAndApp(null, org, !listAll);
26
16
  const hooks = await getWebhooks({ ownerId }).then(sendToApi);
27
17
 
28
- hooks
18
+ const formattedHooks = hooks
29
19
  .filter((hook) => {
30
20
  const emptyScope = !hook.scope || hook.scope.length === 0;
31
21
  return !appId || emptyScope || hook.scope.includes(appId);
32
22
  })
33
- .forEach((hook) => displayWebhook(hook));
23
+ .map((hook) => ({
24
+ id: hook.id,
25
+ name: hook.name,
26
+ ownerId: hook.ownerId,
27
+ services: hook.scope ?? [hook.ownerId],
28
+ events: hook.events ?? ['ALL'],
29
+ urls: hook.urls,
30
+ }));
31
+
32
+ switch (format) {
33
+ case 'json': {
34
+ Logger.printJson(formattedHooks);
35
+ break;
36
+ }
37
+ case 'human':
38
+ default: {
39
+ formattedHooks.forEach((hook) => {
40
+ Logger.println(hook.name ? colors.bold(hook.name) : hook.id);
41
+ Logger.println(` id: ${hook.id}`);
42
+ Logger.println(` services: ${hook.services.join(', ')}`);
43
+ Logger.println(` events: ${hook.events.join(', ')}`);
44
+ Logger.println(' hooks:');
45
+ hook.urls.forEach((url) => Logger.println(` ${url.url} (${url.format})`));
46
+ });
47
+ }
48
+ }
34
49
  }
35
50
 
36
51
  async function add (params) {
package/src/logger.js CHANGED
@@ -28,20 +28,24 @@ function formatLines (prefixLength, lines) {
28
28
  .join('\n');
29
29
  }
30
30
 
31
+ function consoleErrorWithoutColor (line) {
32
+ process.stderr.write(line + '\n');
33
+ }
34
+
31
35
  const Logger = _(['debug', 'info', 'warn', 'error'])
32
36
  .map((severity) => {
33
37
  if (process.env.CLEVER_QUIET || (!process.env.CLEVER_VERBOSE && (severity === 'debug' || severity === 'info'))) {
34
38
  return [severity, _.noop];
35
39
  }
36
- const consoleFn = (severity === 'error') ? console.error : console.log;
40
+ const consoleFn = (severity === 'error') ? consoleErrorWithoutColor : console.log;
37
41
  const { prefix, prefixLength } = getPrefix(severity);
38
42
  return [severity, (err) => {
39
43
  const message = _.get(err, 'message', err);
40
44
  const formattedMsg = formatLines(prefixLength, processApiError(message));
41
45
  if (process.env.CLEVER_VERBOSE && severity === 'error') {
42
- console.error('[STACKTRACE]');
43
- console.error(err);
44
- console.error('[/STACKTRACE]');
46
+ consoleErrorWithoutColor('[STACKTRACE]');
47
+ consoleErrorWithoutColor(err);
48
+ consoleErrorWithoutColor('[/STACKTRACE]');
45
49
  }
46
50
  return consoleFn(`${prefix}${formattedMsg}`);
47
51
  }];
@@ -57,8 +61,7 @@ Logger.printJson = (obj) => {
57
61
  console.log(JSON.stringify(obj, null, 2));
58
62
  };
59
63
 
60
- // No decoration for Logger.printErrorLine
61
- Logger.printErrorLine = console.error;
64
+ Logger.printErrorLine = consoleErrorWithoutColor;
62
65
 
63
66
  // Only exported for testing, shouldn't be used directly
64
67
  Logger.processApiError = processApiError;
@@ -54,13 +54,21 @@ async function addLinkedApplication (appData, alias, ignoreParentConfig) {
54
54
  return persistConfig(currentConfig);
55
55
  };
56
56
 
57
- async function removeLinkedApplication (alias) {
57
+ async function removeLinkedApplication ({ appId, alias }) {
58
58
  const currentConfig = await loadApplicationConf();
59
59
  const newConfig = {
60
60
  ...currentConfig,
61
- apps: currentConfig.apps.filter((appEntry) => appEntry.alias !== alias),
61
+ apps: currentConfig.apps.filter((appEntry) => {
62
+ return appEntry.app_id !== appId && appEntry.alias !== alias;
63
+ }),
62
64
  };
63
- return persistConfig(newConfig);
65
+
66
+ if (currentConfig.apps.length !== newConfig.apps.length) {
67
+ await persistConfig(newConfig);
68
+ return true;
69
+ }
70
+
71
+ return false;
64
72
  };
65
73
 
66
74
  function findApp (config, alias) {
@@ -4,6 +4,7 @@ const _ = require('lodash');
4
4
  const application = require('@clevercloud/client/cjs/api/v2/application.js');
5
5
  const autocomplete = require('cliparse').autocomplete;
6
6
  const product = require('@clevercloud/client/cjs/api/v2/product.js');
7
+ const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js');
7
8
 
8
9
  const AppConfiguration = require('./app_configuration.js');
9
10
  const Interact = require('./interact.js');
@@ -12,6 +13,8 @@ const Organisation = require('./organisation.js');
12
13
  const User = require('./user.js');
13
14
 
14
15
  const { sendToApi } = require('../models/send-to-api.js');
16
+ const AppConfig = require('./app_configuration.js');
17
+ const { resolveOwnerId } = require('./ids-resolver.js');
15
18
 
16
19
  function listAvailableTypes () {
17
20
  return autocomplete.words(['docker', 'elixir', 'go', 'gradle', 'haskell', 'jar', 'maven', 'meteor', 'node', 'php', 'play1', 'play2', 'python', 'ruby', 'rust', 'sbt', 'static-apache', 'war']);
@@ -86,20 +89,58 @@ async function create (name, typeName, region, orgaIdOrName, github, isTask, env
86
89
  return application.create({ id: ownerId }, newApp).then(sendToApi);
87
90
  };
88
91
 
89
- async function deleteApp (addDetails, skipConfirmation) {
90
- Logger.debug('Deleting app: ' + addDetails.name + ' (' + addDetails.appId + ')');
92
+ async function deleteApp (app, skipConfirmation) {
93
+ Logger.debug('Deleting app: ' + app.name + ' (' + app.id + ')');
91
94
 
92
95
  if (!skipConfirmation) {
93
96
  await Interact.confirm(
94
- `Deleting the application ${addDetails.name} can't be undone, please type '${addDetails.name}' to confirm: `,
97
+ `Deleting the application ${app.name} can't be undone, please type '${app.name}' to confirm: `,
95
98
  'No confirmation, aborting application deletion',
96
- [addDetails.name],
99
+ [app.name],
97
100
  );
98
101
  }
99
102
 
100
- return application.remove({ id: addDetails.ownerId, appId: addDetails.appId }).then(sendToApi);
103
+ return application.remove({ id: app.ownerId, appId: app.id }).then(sendToApi);
101
104
  };
102
105
 
106
+ async function getAllApps (ownerId) {
107
+
108
+ const summary = await getSummary().then(sendToApi);
109
+
110
+ const orgaWithApps = await Promise.all(
111
+ summary.organisations
112
+ // If owner ID is present, only keep the matching org
113
+ .filter((org) => ownerId == null || org.id === ownerId)
114
+ .sort((a, b) => a.name.localeCompare(b.name))
115
+ .map(async (org) => {
116
+ const applications = await getApplicationsForOwner(org.id);
117
+ return {
118
+ id: org.id,
119
+ name: org.name,
120
+ applications: applications.sort((a, b) => a.name.localeCompare(b.name)),
121
+ };
122
+ }),
123
+ );
124
+
125
+ return orgaWithApps;
126
+ };
127
+
128
+ async function getApplicationsForOwner (ownerId) {
129
+ const rawApplications = await application.getAll({ id: ownerId }).then(sendToApi);
130
+ return rawApplications.map((app) => {
131
+ return {
132
+ app_id: app.id,
133
+ org_id: ownerId,
134
+ name: app.name,
135
+ zone: app.zone,
136
+ type: app.instance.variant.slug,
137
+ createdAt: new Date(app.creationDate).toISOString(),
138
+ deploy_url: app.deployment.httpUrl,
139
+ git_ssh_url: app.deployment.url,
140
+ };
141
+ });
142
+ }
143
+
103
144
  function getApplicationByName (apps, name) {
104
145
  const filteredApps = apps.filter((app) => app.name === name);
105
146
  if (filteredApps.length === 1) {
@@ -129,6 +170,62 @@ function getFromSelf (appId) {
129
170
  return application.get({ appId }).then(sendToApi);
130
171
  };
131
172
 
173
+ /**
174
+ * @param {{app_id: string}|{app_name: string}} appIdOrName
175
+ * @param {string} alias
176
+ * @return {Promise<{appId: string, ownerId: string}>}
177
+ */
178
+ async function resolveId (appIdOrName, alias) {
179
+ if (appIdOrName != null && alias != null) {
180
+ throw new Error('Only one of the `--app` or `--alias` options can be set at a time');
181
+ }
182
+
183
+ // -- resolve by linked app
184
+
185
+ if (appIdOrName == null) {
186
+ const appDetails = await AppConfig.getAppDetails({ alias });
187
+ return { appId: appDetails.appId, ownerId: appDetails.ownerId };
188
+ }
189
+
190
+ // -- resolve by app id
191
+
192
+ if (appIdOrName.app_id != null) {
193
+ const ownerId = await resolveOwnerId(appIdOrName.app_id);
194
+ if (ownerId != null) {
195
+ return {
196
+ appId: appIdOrName.app_id,
197
+ ownerId,
198
+ };
199
+ }
200
+
201
+ throw new Error('Application not found');
202
+ }
203
+
204
+ // -- resolve by app name
205
+
206
+ const summary = await getSummary({}).then(sendToApi);
207
+
208
+ const candidates = [summary.user, ...summary.organisations]
209
+ .flatMap((owner) => owner.applications.map((app) => ({ app, owner })))
210
+ .filter((candidate) => candidate.app.name === appIdOrName.app_name);
211
+
212
+ if (candidates.length === 0) {
213
+ throw new Error('Application not found');
214
+ }
215
+ if (candidates.length === 1) {
216
+ return {
217
+ appId: candidates[0].app.id,
218
+ ownerId: candidates[0].owner.id,
219
+ };
220
+ }
221
+
222
+ Logger.printErrorLine(`The name '${appIdOrName.app_name}' refers to multiple applications:`);
223
+ candidates.forEach((candidate) => {
224
+ Logger.printErrorLine(`- ${candidate.owner.name}: ${candidate.app.id} (${candidate.app.variantSlug})`);
225
+ });
226
+ throw new Error('Ambiguous application name, use the `--app` option with one of the IDs above');
227
+ }
228
+
132
229
  async function linkRepo (app, orgaIdOrName, alias, ignoreParentConfig) {
133
230
  Logger.debug(`Linking current repository to the app: ${app.app_id || app.app_name}`);
134
231
 
@@ -145,7 +242,7 @@ async function linkRepo (app, orgaIdOrName, alias, ignoreParentConfig) {
145
242
 
146
243
  function unlinkRepo (alias) {
147
244
  Logger.debug(`Unlinking current repository from the app: ${alias}`);
148
- return AppConfiguration.removeLinkedApplication(alias);
245
+ return AppConfiguration.removeLinkedApplication({ alias });
149
246
  };
150
247
 
151
248
  function redeploy (ownerId, appId, commit, withoutCache) {
@@ -214,7 +311,7 @@ async function listDependencies (ownerId, appId, showAll) {
214
311
  const applicationDeps = await application.getAllDependencies({ id: ownerId, appId }).then(sendToApi);
215
312
 
216
313
  if (!showAll) {
217
- return applicationDeps;
314
+ return applicationDeps.map((app) => ({ ...app, isLinked: true }));
218
315
  }
219
316
 
220
317
  const allApps = await application.getAll({ id: ownerId }).then(sendToApi);
@@ -237,9 +334,11 @@ async function unlink (ownerId, appId, dependency) {
237
334
  };
238
335
 
239
336
  module.exports = {
337
+ resolveId,
240
338
  create,
241
339
  deleteApp,
242
340
  get,
341
+ getAllApps,
243
342
  link,
244
343
  linkRepo,
245
344
  listAvailableAliases,
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const colors = require('colors/safe');
4
+ const Logger = require('../logger.js');
5
+
6
+ function get (follow, exitOnDeploy) {
7
+ if (follow) {
8
+ if (exitOnDeploy === 'deploy-start') {
9
+ throw new Error('The `follow` and `exit-on` set to "deploy-start" options are not compatible');
10
+ }
11
+ Logger.println(colors.yellow('The `follow` option is deprecated and will be removed in an upcoming major, use --exit-on set to "never" instead'));
12
+ return 'never';
13
+ }
14
+ return exitOnDeploy;
15
+ }
16
+
17
+ // plotQuietWarning: If in quiet mode and exitStrategy set to never plot a warning to indicate that the command will end
18
+ function plotQuietWarning (exitStrategy, quiet) {
19
+ if (exitStrategy === 'never' && quiet) {
20
+ Logger.println(colors.bold.yellow('The "never" exit-on strategy is not compatible with the "quiet" mode, it will exit once the deployment ends'));
21
+ }
22
+ }
23
+
24
+ module.exports = { get, plotQuietWarning };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Helper to print a real JSON array with starting `[` and ending `]`
3
+ */
4
+ class JsonArray {
5
+ constructor () {
6
+ this._isFirst = true;
7
+ }
8
+
9
+ open () {
10
+ process.stdout.write('[\n');
11
+ }
12
+
13
+ push (log) {
14
+ if (this._isFirst) {
15
+ this._isFirst = false;
16
+ }
17
+ else {
18
+ process.stdout.write(',\n');
19
+ }
20
+ process.stdout.write(` ${JSON.stringify(log)}`);
21
+ }
22
+
23
+ close () {
24
+ process.stdout.write('\n]');
25
+ }
26
+ }
27
+
28
+ module.exports = { JsonArray };
@@ -4,6 +4,8 @@ const { Deferred } = require('./utils.js');
4
4
  const Logger = require('../logger.js');
5
5
  const { waitForDeploymentEnd, waitForDeploymentStart } = require('./deployments.js');
6
6
  const { ApplicationLogStream } = require('@clevercloud/client/cjs/streams/application-logs.js');
7
+ const { JsonArray } = require('./json-array.js');
8
+ const ExitStrategy = require('../models/exit-strategy-option.js');
7
9
 
8
10
  // 2000 logs per 100ms maximum
9
11
  const THROTTLE_ELEMENTS = 2000;
@@ -90,15 +92,20 @@ async function watchDeploymentAndDisplayLogs (options) {
90
92
  commitId,
91
93
  knownDeployments,
92
94
  quiet,
93
- follow,
94
95
  redeployDate,
96
+ exitStrategy,
95
97
  } = options;
96
98
 
99
+ ExitStrategy.plotQuietWarning(exitStrategy, quiet);
97
100
  // If in quiet mode, we only log start/finished deployment messages
98
101
  !quiet && Logger.println('Waiting for deployment to start…');
99
102
  const deployment = await waitForDeploymentStart({ ownerId, appId, deploymentId, commitId, knownDeployments });
100
103
  Logger.println(colors.bold.blue(`Deployment started (${deployment.uuid})`));
101
104
 
105
+ if (exitStrategy === 'deploy-start') {
106
+ return;
107
+ }
108
+
102
109
  const deferred = new Deferred();
103
110
  let logsStream;
104
111
 
@@ -120,7 +127,7 @@ async function watchDeploymentAndDisplayLogs (options) {
120
127
  deferred.promise,
121
128
  ]);
122
129
 
123
- if (!quiet && !follow) {
130
+ if (!quiet && exitStrategy !== 'never') {
124
131
  logsStream.close(quiet ? 'quiet' : 'follow');
125
132
  }
126
133
 
@@ -166,30 +173,3 @@ function isBuildSucessMessage (log) {
166
173
  };
167
174
 
168
175
  module.exports = { displayLogs, watchDeploymentAndDisplayLogs };
169
-
170
- /**
171
- * Helper to print a real JSON array with starting `[` and ending `]`
172
- */
173
- class JsonArray {
174
- constructor () {
175
- this._isFirst = true;
176
- }
177
-
178
- open () {
179
- process.stdout.write('[\n');
180
- }
181
-
182
- push (log) {
183
- if (this._isFirst) {
184
- this._isFirst = false;
185
- }
186
- else {
187
- process.stdout.write(',\n');
188
- }
189
- process.stdout.write(` ${JSON.stringify(log)}`);
190
- }
191
-
192
- close () {
193
- process.stdout.write('\n]');
194
- }
195
- }
package/src/models/log.js CHANGED
@@ -9,6 +9,7 @@ const { getOldLogs } = require('@clevercloud/client/cjs/api/v2/log.js');
9
9
  const { LogsStream } = require('@clevercloud/client/cjs/streams/logs.node.js');
10
10
  const { sendToApi, getHostAndTokens } = require('./send-to-api.js');
11
11
  const { waitForDeploymentEnd, waitForDeploymentStart } = require('./deployments.js');
12
+ const ExitStrategy = require('../models/exit-strategy-option.js');
12
13
 
13
14
  function isCleverMessage (line) {
14
15
  return line._source.syslog_program === '/home/bas/rubydeployer/deployer.rb';
@@ -100,12 +101,18 @@ async function displayLogs ({ appAddonId, until, since, filter, deploymentId })
100
101
  return deferred.promise;
101
102
  }
102
103
 
103
- async function watchDeploymentAndDisplayLogs ({ ownerId, appId, deploymentId, commitId, knownDeployments, quiet, follow }) {
104
+ async function watchDeploymentAndDisplayLogs ({ ownerId, appId, deploymentId, commitId, knownDeployments, quiet, exitStrategy }) {
104
105
 
106
+ ExitStrategy.plotQuietWarning(exitStrategy, quiet);
107
+ // If in quiet mode, we only log start/finished deployment messages
105
108
  Logger.println('Waiting for deployment to start…');
106
109
  const deployment = await waitForDeploymentStart({ ownerId, appId, deploymentId, commitId, knownDeployments });
107
110
  Logger.println(colors.bold.blue(`Deployment started (${deployment.uuid})`));
108
111
 
112
+ if (exitStrategy === 'deploy-start') {
113
+ return;
114
+ }
115
+
109
116
  const deferred = new Deferred();
110
117
  let logsStream;
111
118
 
@@ -127,7 +134,7 @@ async function watchDeploymentAndDisplayLogs ({ ownerId, appId, deploymentId, co
127
134
  deferred.promise,
128
135
  ]);
129
136
 
130
- if (!quiet && !follow) {
137
+ if (!quiet && exitStrategy !== 'never') {
131
138
  logsStream.close();
132
139
  }
133
140
 
@@ -0,0 +1,20 @@
1
+ const Application = require('./application.js');
2
+ const organisation = require('@clevercloud/client/cjs/api/v2/organisation.js');
3
+ const { sendToApi } = require('./send-to-api.js');
4
+ const { autocomplete } = require('cliparse');
5
+
6
+ async function getNamespaces (ownerId) {
7
+ return organisation.getNamespaces({ id: ownerId }).then(sendToApi);
8
+ }
9
+
10
+ async function completeNamespaces () {
11
+ // Sadly we do not have access to current params in complete as of now
12
+ const { ownerId } = await Application.resolveId(null, null);
13
+
14
+ return getNamespaces(ownerId).then(autocomplete.words);
15
+ }
16
+
17
+ module.exports = {
18
+ getNamespaces,
19
+ completeNamespaces,
20
+ };
@@ -1,11 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const _ = require('lodash');
4
- const autocomplete = require('cliparse').autocomplete;
5
4
 
6
- const AppConfig = require('./app_configuration.js');
7
-
8
- const organisation = require('@clevercloud/client/cjs/api/v2/organisation.js');
9
5
  const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js');
10
6
  const { sendToApi } = require('../models/send-to-api.js');
11
7
 
@@ -37,22 +33,6 @@ async function getByName (name) {
37
33
  return filteredOrgs[0];
38
34
  }
39
35
 
40
- async function getNamespaces (params) {
41
- const { alias } = params.options;
42
- const { ownerId } = await AppConfig.getAppDetails({ alias });
43
-
44
- return organisation.getNamespaces({ id: ownerId }).then(sendToApi);
45
- }
46
-
47
- function completeNamespaces () {
48
- // Sadly we do not have access to current params in complete as of now
49
- const params = { options: {} };
50
-
51
- return getNamespaces(params).then(autocomplete.words);
52
- };
53
-
54
36
  module.exports = {
55
37
  getId,
56
- getNamespaces,
57
- completeNamespaces,
58
38
  };
@@ -15,4 +15,11 @@ class Deferred {
15
15
  }
16
16
  }
17
17
 
18
- module.exports = { Deferred };
18
+ function truncateWithEllipsis (length, string) {
19
+ if (string.length > length - 1) {
20
+ return string.substring(0, length - 1) + '…';
21
+ }
22
+ return string;
23
+ }
24
+
25
+ module.exports = { Deferred, truncateWithEllipsis };