clever-tools 3.12.0 → 3.13.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.
- package/bin/clever.js +280 -6
- package/package.json +1 -1
- package/src/clever-client/auth-bridge.js +1 -1
- package/src/clever-client/operators.js +121 -0
- package/src/commands/addon.js +12 -7
- package/src/commands/cancel-deploy.js +3 -4
- package/src/commands/config.js +16 -20
- package/src/commands/console.js +4 -10
- package/src/commands/create.js +76 -9
- package/src/commands/delete.js +11 -10
- package/src/commands/deploy.js +35 -21
- package/src/commands/emails.js +174 -0
- package/src/commands/keycloak.js +138 -0
- package/src/commands/link.js +3 -1
- package/src/commands/makeDefault.js +2 -1
- package/src/commands/matomo.js +85 -0
- package/src/commands/metabase.js +112 -0
- package/src/commands/open.js +2 -5
- package/src/commands/otoroshi.js +138 -0
- package/src/commands/profile.js +2 -4
- package/src/commands/restart.js +1 -1
- package/src/commands/ssh-keys.js +159 -0
- package/src/commands/stop.js +1 -1
- package/src/commands/tcp-redirs.js +3 -3
- package/src/commands/tokens.js +16 -7
- package/src/commands/unlink.js +2 -1
- package/src/experimental-features.js +16 -15
- package/src/lib/operator-commands.js +281 -0
- package/src/lib/prompts.js +30 -0
- package/src/lib/slugify.js +10 -0
- package/src/models/addon.js +5 -2
- package/src/models/app_configuration.js +12 -9
- package/src/models/application.js +24 -12
- package/src/models/application_configuration.js +72 -72
- package/src/models/git.js +29 -1
- package/src/models/ids-resolver.js +37 -1
- package/src/models/interact.js +0 -24
- package/src/models/log-v4.js +17 -5
- package/src/models/operator.js +48 -0
- package/src/models/utils.js +21 -0
- package/src/parsers.js +14 -0
- package/src/prompt-password.js +0 -10
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import cliparse from 'cliparse';
|
|
2
|
-
import colors from 'colors/safe.js';
|
|
3
|
-
|
|
4
2
|
import { Logger } from '../logger.js';
|
|
3
|
+
import dedent from 'dedent';
|
|
5
4
|
|
|
6
5
|
const CONFIG_KEYS = [
|
|
7
6
|
{ id: 'name', name: 'name', displayName: 'Name', kind: 'string' },
|
|
@@ -10,31 +9,43 @@ const CONFIG_KEYS = [
|
|
|
10
9
|
{ id: 'sticky-sessions', name: 'stickySessions', displayName: 'Sticky sessions', kind: 'bool' },
|
|
11
10
|
{ id: 'cancel-on-push', name: 'cancelOnPush', displayName: 'Cancel current deployment on push', kind: 'bool' },
|
|
12
11
|
{ id: 'force-https', name: 'forceHttps', displayName: 'Force redirection of HTTP to HTTPS', kind: 'force-https' },
|
|
12
|
+
{ id: 'task', name: 'instanceLifetime', displayName: 'Deploy an application as a Clever Task', kind: 'task' },
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
-
export function listAvailableIds () {
|
|
16
|
-
|
|
15
|
+
export function listAvailableIds (asText = false) {
|
|
16
|
+
const ids = CONFIG_KEYS.map((config) => config.id);
|
|
17
|
+
if (asText) {
|
|
18
|
+
return new Intl
|
|
19
|
+
.ListFormat('en', { style: 'short', type: 'disjunction' })
|
|
20
|
+
.format(ids);
|
|
21
|
+
}
|
|
22
|
+
return ids;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export function getById (id) {
|
|
20
26
|
const config = CONFIG_KEYS.find((config) => config.id === id);
|
|
21
|
-
if (config
|
|
22
|
-
|
|
23
|
-
Logger.error(`Available configuration names are: ${listAvailableIds().join(', ')}.`);
|
|
27
|
+
if (config != null) {
|
|
28
|
+
return config;
|
|
24
29
|
}
|
|
25
|
-
|
|
30
|
+
throw new Error(dedent`
|
|
31
|
+
Invalid configuration name: ${id}.
|
|
32
|
+
Available configuration names: ${listAvailableIds(true)}.
|
|
33
|
+
`);
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
function
|
|
36
|
+
export function formatValue (config, value) {
|
|
29
37
|
switch (config.kind) {
|
|
30
38
|
case 'bool': {
|
|
31
|
-
return
|
|
39
|
+
return value;
|
|
32
40
|
}
|
|
33
41
|
case 'inverted-bool': {
|
|
34
|
-
return
|
|
42
|
+
return !value;
|
|
35
43
|
}
|
|
36
44
|
case 'force-https': {
|
|
37
|
-
return value
|
|
45
|
+
return value === 'ENABLED';
|
|
46
|
+
}
|
|
47
|
+
case 'task': {
|
|
48
|
+
return value === 'TASK';
|
|
38
49
|
}
|
|
39
50
|
default: {
|
|
40
51
|
return String(value);
|
|
@@ -44,14 +55,26 @@ function display (config, value) {
|
|
|
44
55
|
|
|
45
56
|
export function parse (config, value) {
|
|
46
57
|
switch (config.kind) {
|
|
47
|
-
case 'bool':
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
case '
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
case 'bool':
|
|
59
|
+
case 'inverted-bool':
|
|
60
|
+
case 'force-https':
|
|
61
|
+
case 'task': {
|
|
62
|
+
if (value !== 'true' && value !== 'false') {
|
|
63
|
+
throw new Error('Invalid configuration value, it must be a boolean (true or false)');
|
|
64
|
+
}
|
|
65
|
+
if (config.kind === 'bool') {
|
|
66
|
+
return (value === 'true');
|
|
67
|
+
}
|
|
68
|
+
if (config.kind === 'inverted-bool') {
|
|
69
|
+
return (value === 'false');
|
|
70
|
+
}
|
|
71
|
+
if (config.kind === 'force-https') {
|
|
72
|
+
return (value === 'true') ? 'ENABLED' : 'DISABLED';
|
|
73
|
+
}
|
|
74
|
+
if (config.kind === 'task') {
|
|
75
|
+
return (value === 'false') ? 'REGULAR' : 'TASK';
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
55
78
|
}
|
|
56
79
|
default: {
|
|
57
80
|
return value;
|
|
@@ -69,7 +92,8 @@ function getConfigOptions (config) {
|
|
|
69
92
|
switch (config.kind) {
|
|
70
93
|
case 'bool':
|
|
71
94
|
case 'inverted-bool':
|
|
72
|
-
case 'force-https':
|
|
95
|
+
case 'force-https':
|
|
96
|
+
case 'task': {
|
|
73
97
|
return [
|
|
74
98
|
cliparse.flag(`enable-${config.id}`, { description: `Enable ${config.id}` }),
|
|
75
99
|
cliparse.flag(`disable-${config.id}`, { description: `Disable ${config.id}` }),
|
|
@@ -92,69 +116,45 @@ export function parseOptions (options) {
|
|
|
92
116
|
|
|
93
117
|
function parseConfigOption (config, options) {
|
|
94
118
|
switch (config.kind) {
|
|
95
|
-
case 'bool':
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Logger.warn(`${config.id} is both enabled and disabled, ignoring`);
|
|
100
|
-
}
|
|
101
|
-
else if (enable || disable) {
|
|
102
|
-
return [config.name, enable];
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
case 'inverted-bool': {
|
|
107
|
-
const disable = options[`enable-${config.id}`];
|
|
108
|
-
const enable = options[`disable-${config.id}`];
|
|
109
|
-
if (enable && disable) {
|
|
110
|
-
Logger.warn(`${config.id} is both enabled and disabled, ignoring`);
|
|
111
|
-
}
|
|
112
|
-
else if (enable || disable) {
|
|
113
|
-
return [config.name, enable];
|
|
114
|
-
}
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
case 'force-https': {
|
|
119
|
+
case 'bool':
|
|
120
|
+
case 'inverted-bool':
|
|
121
|
+
case 'force-https':
|
|
122
|
+
case 'task': {
|
|
118
123
|
const enable = options[`enable-${config.id}`];
|
|
119
124
|
const disable = options[`disable-${config.id}`];
|
|
120
125
|
if (enable && disable) {
|
|
121
|
-
|
|
126
|
+
throw new Error(`You cannot use both --enable-${config.id} and --disable-${config.id} at the same time`);
|
|
122
127
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
if (enable || disable) {
|
|
129
|
+
if (config.kind === 'bool') {
|
|
130
|
+
return [config.name, enable];
|
|
131
|
+
}
|
|
132
|
+
if (config.kind === 'inverted-bool') {
|
|
133
|
+
return [config.name, disable];
|
|
134
|
+
}
|
|
135
|
+
if (config.kind === 'force-https' || config.kind === 'task') {
|
|
136
|
+
return [config.name, parse(config, String(enable))];
|
|
137
|
+
}
|
|
126
138
|
}
|
|
127
|
-
return
|
|
139
|
+
return;
|
|
128
140
|
}
|
|
129
141
|
default: {
|
|
130
|
-
|
|
131
|
-
return [config.name, options[config.id]];
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
142
|
+
return [config.name, options[config.id]];
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
|
|
138
|
-
function
|
|
139
|
-
if (app[config.name] != null) {
|
|
140
|
-
Logger.println(`${config.displayName}: ${colors.bold(display(config, app[config.name]))}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function printById (app, id) {
|
|
147
|
+
export function printValue (app, id) {
|
|
145
148
|
const config = getById(id);
|
|
146
|
-
|
|
147
|
-
printConfig(app, config);
|
|
148
|
-
}
|
|
149
|
+
Logger.println(formatValue(config, app[config.name]));
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
export function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
152
|
+
export function printAllValues (app) {
|
|
153
|
+
console.table(
|
|
154
|
+
Object.fromEntries(
|
|
155
|
+
CONFIG_KEYS.map((config) => {
|
|
156
|
+
return [config.id, formatValue(config, app[config.name])];
|
|
157
|
+
}),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
160
|
}
|
package/src/models/git.js
CHANGED
|
@@ -5,7 +5,7 @@ import _ from 'lodash';
|
|
|
5
5
|
import git from 'isomorphic-git';
|
|
6
6
|
import * as http from './isomorphic-http-with-agent.js';
|
|
7
7
|
import cliparse from 'cliparse';
|
|
8
|
-
import slugify from 'slugify';
|
|
8
|
+
import { slugify } from '../lib/slugify.js';
|
|
9
9
|
import { findPath } from './fs-utils.js';
|
|
10
10
|
import { loadOAuthConf } from './configuration.js';
|
|
11
11
|
|
|
@@ -130,3 +130,31 @@ export async function isShallow () {
|
|
|
130
130
|
return false;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if the current directory is a git repository
|
|
136
|
+
* @returns {Promise<boolean>}
|
|
137
|
+
*/
|
|
138
|
+
export async function isInsideGitRepo () {
|
|
139
|
+
return getRepo()
|
|
140
|
+
.then(() => true)
|
|
141
|
+
.catch(() => false);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if the current git working directory is clean
|
|
146
|
+
* @returns {Promise<boolean>}
|
|
147
|
+
*/
|
|
148
|
+
export async function isGitWorkingDirectoryClean () {
|
|
149
|
+
const repo = await getRepo();
|
|
150
|
+
const status = await git.statusMatrix({ ...repo });
|
|
151
|
+
const isStatusEmpty = status
|
|
152
|
+
.filter(([filepath, head, workdir]) => {
|
|
153
|
+
// WARNING: isomorphic-git does not support global gitignore so we filter hidden files and dirs to reduce the amount of false positives
|
|
154
|
+
const isHidden = filepath.startsWith('.');
|
|
155
|
+
const isCleverJson = filepath === '.clever.json';
|
|
156
|
+
return (!isHidden || isCleverJson) && head !== workdir;
|
|
157
|
+
})
|
|
158
|
+
.length === 0;
|
|
159
|
+
return isStatusEmpty;
|
|
160
|
+
}
|
|
@@ -110,7 +110,7 @@ async function getIdsFromSummary () {
|
|
|
110
110
|
* @param {{ orga_name?: string, orga_id?: string }} ownerNameOrId
|
|
111
111
|
* @throws {Error} if no add-on is found
|
|
112
112
|
* @throws {Error} if several add-ons are found
|
|
113
|
-
* @returns {Object} The
|
|
113
|
+
* @returns {Object} The name, IDs and owner ID of the add-on { name, addonId, realId, ownerId }
|
|
114
114
|
*/
|
|
115
115
|
export async function findAddonsByNameOrId (addonIdOrRealIdOrName, ownerNameOrId) {
|
|
116
116
|
const summary = await getSummary().then(sendToApi);
|
|
@@ -128,7 +128,9 @@ export async function findAddonsByNameOrId (addonIdOrRealIdOrName, ownerNameOrId
|
|
|
128
128
|
return matchOwner && matchAddon;
|
|
129
129
|
})
|
|
130
130
|
.map(({ addon, owner }) => ({
|
|
131
|
+
name: addon.name,
|
|
131
132
|
addonId: addon.id,
|
|
133
|
+
realId: addon.realId,
|
|
132
134
|
ownerId: owner.id,
|
|
133
135
|
}));
|
|
134
136
|
|
|
@@ -139,3 +141,37 @@ export async function findAddonsByNameOrId (addonIdOrRealIdOrName, ownerNameOrId
|
|
|
139
141
|
|
|
140
142
|
return candidates;
|
|
141
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get the IDs and owners of found add-ons from a name, ID or real ID
|
|
147
|
+
* @param {string} addonIdOrRealIdOrName
|
|
148
|
+
* @throws {Error} if no add-on is found
|
|
149
|
+
* @throws {Error} if several add-ons are found
|
|
150
|
+
* @returns {Object} The name, IDs and owner ID of the add-on { name, addonId, realId, ownerId }
|
|
151
|
+
*/
|
|
152
|
+
export async function findAddonsByAddonProvider (provider) {
|
|
153
|
+
const summary = await getSummary().then(sendToApi);
|
|
154
|
+
|
|
155
|
+
Logger.debug(`Searching for ${provider} add-ons in ${summary.user.id} and ${summary.organisations.map((org) => org.id).join(', ')}`);
|
|
156
|
+
const candidates = [summary.user, ...summary.organisations]
|
|
157
|
+
.flatMap((owner) => {
|
|
158
|
+
return owner.addons
|
|
159
|
+
.filter((addon) => addon.providerId === provider)
|
|
160
|
+
.map((addon) => {
|
|
161
|
+
return {
|
|
162
|
+
name: addon.name,
|
|
163
|
+
addonId: addon.id,
|
|
164
|
+
realId: addon.realId,
|
|
165
|
+
ownerId: owner.id,
|
|
166
|
+
ownerName: owner.name,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
Logger.debug(`Found ${candidates.length} candidate(s) for provider ${provider}:`);
|
|
172
|
+
for (const candidate of candidates) {
|
|
173
|
+
Logger.debug(` - ${candidate.addonId} (${candidate.ownerId})`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return candidates;
|
|
177
|
+
}
|
package/src/models/interact.js
CHANGED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import readline from 'node:readline';
|
|
2
|
-
|
|
3
|
-
function ask (question) {
|
|
4
|
-
|
|
5
|
-
const rl = readline.createInterface({
|
|
6
|
-
input: process.stdin,
|
|
7
|
-
output: process.stdout,
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
return new Promise((resolve) => {
|
|
11
|
-
rl.question(question, (answer) => {
|
|
12
|
-
rl.close();
|
|
13
|
-
resolve(answer);
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function confirm (question, rejectionMessage, expectedAnswers = ['yes', 'y']) {
|
|
19
|
-
const answer = await ask(question);
|
|
20
|
-
if (!expectedAnswers.includes(answer)) {
|
|
21
|
-
throw new Error(rejectionMessage);
|
|
22
|
-
}
|
|
23
|
-
return true;
|
|
24
|
-
}
|
package/src/models/log-v4.js
CHANGED
|
@@ -6,6 +6,10 @@ import { waitForDeploymentEnd, waitForDeploymentStart } from './deployments.js';
|
|
|
6
6
|
import { ApplicationLogStream } from '@clevercloud/client/esm/streams/application-logs.js';
|
|
7
7
|
import { JsonArray } from './json-array.js';
|
|
8
8
|
import * as ExitStrategy from '../models/exit-strategy-option.js';
|
|
9
|
+
import { getBest } from './domain.js';
|
|
10
|
+
import { conf } from './configuration.js';
|
|
11
|
+
|
|
12
|
+
const RESET_COLOR = '\x1B[0m';
|
|
9
13
|
|
|
10
14
|
// 2000 logs per 100ms maximum
|
|
11
15
|
const THROTTLE_ELEMENTS = 2000;
|
|
@@ -65,6 +69,7 @@ export async function displayLogs (params) {
|
|
|
65
69
|
return;
|
|
66
70
|
case 'human':
|
|
67
71
|
default:
|
|
72
|
+
if (log.message === RESET_COLOR) return;
|
|
68
73
|
Logger.println(formatLogLine(log));
|
|
69
74
|
}
|
|
70
75
|
});
|
|
@@ -98,9 +103,11 @@ export async function watchDeploymentAndDisplayLogs (options) {
|
|
|
98
103
|
|
|
99
104
|
ExitStrategy.plotQuietWarning(exitStrategy, quiet);
|
|
100
105
|
// If in quiet mode, we only log start/finished deployment messages
|
|
101
|
-
!quiet
|
|
106
|
+
if (!quiet) {
|
|
107
|
+
Logger.println(` ${colors.blue('→ Waiting for deployment to start…')}`);
|
|
108
|
+
}
|
|
102
109
|
const deployment = await waitForDeploymentStart({ ownerId, appId, deploymentId, commitId, knownDeployments });
|
|
103
|
-
Logger.println(colors.
|
|
110
|
+
Logger.println(` ${colors.green(`✓ Deployment started ${colors.grey(`(${deployment.uuid})`)}`)}`);
|
|
104
111
|
|
|
105
112
|
if (exitStrategy === 'deploy-start') {
|
|
106
113
|
return;
|
|
@@ -119,7 +126,9 @@ export async function watchDeploymentAndDisplayLogs (options) {
|
|
|
119
126
|
logsStream = await displayLogs({ ownerId, appId, deploymentId: deployment.uuid, since: redeployDate, deferred });
|
|
120
127
|
}
|
|
121
128
|
|
|
122
|
-
!quiet
|
|
129
|
+
if (!quiet) {
|
|
130
|
+
Logger.println(` ${colors.blue('→ Waiting for application logs…')}`);
|
|
131
|
+
}
|
|
123
132
|
|
|
124
133
|
// Wait for deployment end (or an error thrown by logs with the deferred)
|
|
125
134
|
const deploymentEnded = await Promise.race([
|
|
@@ -132,7 +141,10 @@ export async function watchDeploymentAndDisplayLogs (options) {
|
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
if (deploymentEnded.state === 'OK') {
|
|
135
|
-
|
|
144
|
+
const favouriteDomain = await getBest(appId, ownerId);
|
|
145
|
+
Logger.println('');
|
|
146
|
+
Logger.println(`${colors.bold.green('✓ Access your application:')} ${colors.underline.bold(`https://${favouriteDomain.fqdn}`)}`);
|
|
147
|
+
Logger.println(`${colors.bold.blue('→ Manage your application:')} ${colors.underline.bold(`${conf.GOTO_URL}/${appId}`)}`);
|
|
136
148
|
}
|
|
137
149
|
else if (deploymentEnded.state === 'CANCELLED') {
|
|
138
150
|
throw new Error('Deployment was cancelled. Please check the activity');
|
|
@@ -153,7 +165,7 @@ function formatLogLine (log) {
|
|
|
153
165
|
else if (isBuildSucessMessage(log)) {
|
|
154
166
|
return `${date.toISOString()}: ${colors.bold.blue(message)}`;
|
|
155
167
|
}
|
|
156
|
-
return `${date.toISOString()}: ${message}`;
|
|
168
|
+
return `${date.toISOString()}: ${message}${RESET_COLOR}`;
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
function isCleverMessage (log) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import dedent from 'dedent';
|
|
3
|
+
|
|
4
|
+
import { sendToApi } from './send-to-api.js';
|
|
5
|
+
import { findAddonsByNameOrId } from './ids-resolver.js';
|
|
6
|
+
import { getOperator } from '../clever-client/operators.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the details of an operator from its name or ID
|
|
10
|
+
* @param {string} provider The operator's provider
|
|
11
|
+
* @param {object|string} operatorIdOrName The operator's ID or name
|
|
12
|
+
* @returns {Promise<object>} The operator's details
|
|
13
|
+
* @throws {Error} If the operator provider is unknown
|
|
14
|
+
*/
|
|
15
|
+
export async function getDetails (provider, operatorIdOrName) {
|
|
16
|
+
const realId = await getSingleRealId(operatorIdOrName);
|
|
17
|
+
return getOperator({ provider, realId }).then(sendToApi);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the real ID of an operator from its name or ID
|
|
22
|
+
* @param {object|string} operatorIdOrName The operator's ID or name
|
|
23
|
+
* @returns {Promise<string>} The operator's real ID
|
|
24
|
+
* @throws {Error} If the operator is not found
|
|
25
|
+
* @throws {Error} If the operator name is ambiguous
|
|
26
|
+
*/
|
|
27
|
+
export async function getSingleRealId (operatorIdOrName) {
|
|
28
|
+
|
|
29
|
+
if (operatorIdOrName.operator_id != null) {
|
|
30
|
+
return operatorIdOrName.operator_id;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const name = operatorIdOrName.addon_name ?? operatorIdOrName.addon_id;
|
|
34
|
+
const operators = await findAddonsByNameOrId(name);
|
|
35
|
+
|
|
36
|
+
if (operators.length === 0) {
|
|
37
|
+
throw new Error(`Could not find ${colors.red(name)}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (operators.length > 1) {
|
|
41
|
+
throw new Error(dedent`
|
|
42
|
+
Ambiguous name ${colors.red(name)}, use the real ID instead:
|
|
43
|
+
${colors.grey(operators.map((otoroshi) => `- ${otoroshi.name} (${otoroshi.realId})`).join('\n'))}
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return operators[0].realId;
|
|
48
|
+
}
|
package/src/models/utils.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { Logger } from '../logger.js';
|
|
2
|
+
import { conf } from './configuration.js';
|
|
3
|
+
import openPage from 'open';
|
|
4
|
+
|
|
1
5
|
// Inspirations:
|
|
2
6
|
// https://github.com/sindresorhus/p-defer/blob/master/index.js
|
|
3
7
|
// https://github.com/ljharb/promise-deferred/blob/master/index.js
|
|
@@ -13,6 +17,23 @@ export class Deferred {
|
|
|
13
17
|
}
|
|
14
18
|
}
|
|
15
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Open an absolute URL or a console path in the default browser
|
|
22
|
+
* @param {string} urlOrPath The URL to open
|
|
23
|
+
* @param {string} message The message to display before opening the URL
|
|
24
|
+
* @returns {Promise<void>} A promise that resolves when the URL is opened
|
|
25
|
+
*/
|
|
26
|
+
export function openBrowser (urlOrPath, message) {
|
|
27
|
+
const url = urlOrPath.startsWith('/')
|
|
28
|
+
? `${conf.CONSOLE_URL}${urlOrPath}`
|
|
29
|
+
: urlOrPath;
|
|
30
|
+
|
|
31
|
+
Logger.debug(`Opening URL "${url}" in browser`);
|
|
32
|
+
Logger.println(message);
|
|
33
|
+
|
|
34
|
+
return openPage(url, { wait: false });
|
|
35
|
+
}
|
|
36
|
+
|
|
16
37
|
export function truncateWithEllipsis (length, string) {
|
|
17
38
|
if (string.length > length - 1) {
|
|
18
39
|
return string.substring(0, length - 1) + '…';
|
package/src/parsers.js
CHANGED
|
@@ -69,6 +69,16 @@ export function futureDateOrDuration (dateString) {
|
|
|
69
69
|
return duration;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// This simple regex is enough for our use cases
|
|
73
|
+
const emailRegex = /^\S+@\S+\.\S+$/g;
|
|
74
|
+
|
|
75
|
+
export function email (string) {
|
|
76
|
+
if (string.match(emailRegex)) {
|
|
77
|
+
return cliparse.parsers.success(string);
|
|
78
|
+
}
|
|
79
|
+
return cliparse.parsers.error('Invalid email');
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
const appIdRegex = /^app_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
73
83
|
|
|
74
84
|
export function appIdOrName (string) {
|
|
@@ -88,11 +98,15 @@ export function orgaIdOrName (string) {
|
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
const addonIdRegex = /^addon_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
101
|
+
const operatorIdRegex = /^(keycloak|otoroshi|matomo|metabase)_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
91
102
|
|
|
92
103
|
export function addonIdOrName (string) {
|
|
93
104
|
if (string.match(addonIdRegex)) {
|
|
94
105
|
return cliparse.parsers.success({ addon_id: string });
|
|
95
106
|
}
|
|
107
|
+
if (string.match(operatorIdRegex)) {
|
|
108
|
+
return cliparse.parsers.success({ operator_id: string });
|
|
109
|
+
}
|
|
96
110
|
return cliparse.parsers.success({ addon_name: string });
|
|
97
111
|
}
|
|
98
112
|
|
package/src/prompt-password.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { password } from '@inquirer/prompts';
|
|
2
|
-
|
|
3
|
-
export function promptPassword (message) {
|
|
4
|
-
return password({ message, mask: true }).catch((error) => {
|
|
5
|
-
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
6
|
-
process.exit(1);
|
|
7
|
-
}
|
|
8
|
-
throw error;
|
|
9
|
-
});
|
|
10
|
-
}
|