clever-tools 4.7.1 → 4.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.
- package/bin/clever.js +11 -1
- package/package.json +2 -2
- package/src/clever-client/drains.js +18 -18
- package/src/commands/README.md +1 -0
- package/src/commands/accesslogs/accesslogs.command.js +2 -2
- package/src/commands/activity/activity.command.js +1 -1
- package/src/commands/addon/addon.create.command.js +2 -3
- package/src/commands/addon/addon.delete.command.js +2 -2
- package/src/commands/addon/addon.docs.md +2 -2
- package/src/commands/addon/addon.env.command.js +4 -6
- package/src/commands/config-provider/config-provider.command.js +0 -4
- package/src/commands/config-provider/config-provider.import.command.js +1 -1
- package/src/commands/config-provider/config-provider.list.command.js +8 -19
- package/src/commands/config-provider/config-provider.open.command.js +1 -1
- package/src/commands/console/console.command.js +3 -2
- package/src/commands/curl/curl.command.js +0 -1
- package/src/commands/database/database.backups.command.js +4 -7
- package/src/commands/database/database.backups.download.command.js +5 -7
- package/src/commands/database/database.command.js +0 -4
- package/src/commands/database/database.docs.md +2 -2
- package/src/commands/deploy/deploy.command.js +1 -1
- package/src/commands/deploy/deploy.docs.md +1 -1
- package/src/commands/drain/drain.command.js +14 -9
- package/src/commands/drain/drain.create.command.js +9 -13
- package/src/commands/drain/drain.disable.command.js +7 -7
- package/src/commands/drain/drain.docs.md +6 -0
- package/src/commands/drain/drain.enable.command.js +7 -7
- package/src/commands/drain/drain.get.command.js +12 -8
- package/src/commands/drain/drain.remove.command.js +7 -7
- package/src/commands/emails/emails.open.command.js +1 -4
- package/src/commands/global.commands.js +18 -0
- package/src/commands/global.options.js +7 -7
- package/src/commands/help/help.command.js +0 -3
- package/src/commands/k8s/k8s.command.js +0 -4
- package/src/commands/k8s/k8s.delete.command.js +2 -2
- package/src/commands/k8s/k8s.docs.md +1 -1
- package/src/commands/logs/logs.command.js +4 -5
- package/src/commands/ng/ng.docs.md +0 -1
- package/src/commands/ng/ng.get-config.command.js +3 -14
- package/src/commands/ng/ng.options.js +1 -1
- package/src/commands/notify-email/notify-email.remove.command.js +2 -2
- package/src/commands/oauth-consumers/oauth-consumers.args.js +8 -0
- package/src/commands/oauth-consumers/oauth-consumers.command.js +6 -0
- package/src/commands/oauth-consumers/oauth-consumers.create.command.js +64 -0
- package/src/commands/oauth-consumers/oauth-consumers.delete.command.js +33 -0
- package/src/commands/oauth-consumers/oauth-consumers.docs.md +130 -0
- package/src/commands/oauth-consumers/oauth-consumers.get.command.js +67 -0
- package/src/commands/oauth-consumers/oauth-consumers.list.command.js +34 -0
- package/src/commands/oauth-consumers/oauth-consumers.open.command.js +18 -0
- package/src/commands/oauth-consumers/oauth-consumers.options.js +48 -0
- package/src/commands/oauth-consumers/oauth-consumers.update.command.js +83 -0
- package/src/commands/open/open.command.js +1 -1
- package/src/commands/profile/profile.open.command.js +1 -1
- package/src/commands/restart/restart.docs.md +1 -1
- package/src/commands/ssh-keys/ssh-keys.open.command.js +1 -1
- package/src/commands/webhooks/webhooks.remove.command.js +2 -2
- package/src/lib/cliparse-patched.js +2 -0
- package/src/lib/get-command-info.js +3 -1
- package/src/lib/get-command-info.types.d.ts +4 -0
- package/src/lib/operator-commands.js +11 -25
- package/src/lib/print-items-by-owner.js +38 -0
- package/src/lib/prompts.js +23 -1
- package/src/lib/zod-utils.js +51 -0
- package/src/models/drain.js +17 -0
- package/src/models/ids-resolver.js +38 -20
- package/src/models/ng.js +2 -2
- package/src/models/notification.js +0 -4
- package/src/models/oauth-consumer.js +77 -0
- package/src/models/utils.js +1 -1
package/src/lib/zod-utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {Object} ZodSchemaLike
|
|
3
3
|
* @property {{ type?: string, innerType?: ZodSchemaLike, defaultValue?: unknown, typeName?: string, in?: ZodSchemaLike }} [_def]
|
|
4
|
+
* @property {string[]} [options]
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -37,6 +38,56 @@ export function isRequired(schema) {
|
|
|
37
38
|
return true;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Extracts enum values from a Zod schema.
|
|
43
|
+
* Handles wrapped schemas (default, optional, nullable, pipe).
|
|
44
|
+
* @param {ZodSchemaLike} schema
|
|
45
|
+
* @return {string[] | null}
|
|
46
|
+
*/
|
|
47
|
+
export function getEnumValues(schema) {
|
|
48
|
+
/** @type {ZodSchemaLike | undefined} */
|
|
49
|
+
let current = schema;
|
|
50
|
+
while (current) {
|
|
51
|
+
const schemaType = current._def?.type;
|
|
52
|
+
if (schemaType === 'enum') {
|
|
53
|
+
return current.options ?? null;
|
|
54
|
+
}
|
|
55
|
+
if (schemaType === 'default' || schemaType === 'optional' || schemaType === 'nullable') {
|
|
56
|
+
current = current._def?.innerType;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (schemaType === 'pipe') {
|
|
60
|
+
current = current._def?.in;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Unwraps optional/nullable/default/pipe wrappers to get the inner schema.
|
|
70
|
+
* @param {ZodSchemaLike} schema
|
|
71
|
+
* @return {ZodSchemaLike}
|
|
72
|
+
*/
|
|
73
|
+
export function unwrapSchema(schema) {
|
|
74
|
+
/** @type {ZodSchemaLike | undefined} */
|
|
75
|
+
let current = schema;
|
|
76
|
+
while (current) {
|
|
77
|
+
const schemaType = current._def?.type;
|
|
78
|
+
if (schemaType === 'optional' || schemaType === 'nullable' || schemaType === 'default') {
|
|
79
|
+
current = current._def?.innerType;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (schemaType === 'pipe') {
|
|
83
|
+
current = current._def?.in;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
return /** @type {ZodSchemaLike} */ (current);
|
|
89
|
+
}
|
|
90
|
+
|
|
40
91
|
/**
|
|
41
92
|
* Extracts the default value from a Zod schema.
|
|
42
93
|
* Handles wrapped schemas (optional, nullable, pipe).
|
package/src/models/drain.js
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
import * as Application from './application.js';
|
|
2
|
+
import { resolveAddon } from './ids-resolver.js';
|
|
3
|
+
|
|
4
|
+
export async function resolveDrainResource(alias, appIdOrName, addonIdOrRealId) {
|
|
5
|
+
if (addonIdOrRealId != null && (appIdOrName != null || alias != null)) {
|
|
6
|
+
throw new Error('`--addon` cannot be combined with `--app` or `--alias`');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (addonIdOrRealId != null) {
|
|
10
|
+
const { ownerId, realId } = await resolveAddon(addonIdOrRealId);
|
|
11
|
+
return { ownerId, resourceId: realId };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
15
|
+
return { ownerId, resourceId: appId };
|
|
16
|
+
}
|
|
17
|
+
|
|
1
18
|
export const DRAIN_TYPES = {
|
|
2
19
|
DATADOG: { apiCode: 'DATADOG', cliCode: 'datadog', label: 'Datadog' },
|
|
3
20
|
ELASTICSEARCH: { apiCode: 'ELASTICSEARCH', cliCode: 'elasticsearch', label: 'Elasticsearch' },
|
|
@@ -31,28 +31,17 @@ export async function resolveOwnerId(id) {
|
|
|
31
31
|
return getIdFromCacheOrSummary((ids) => ids.owners[id]);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export async function
|
|
35
|
-
const
|
|
36
|
-
|
|
34
|
+
export async function resolveAddon(addonIdOrRealId) {
|
|
35
|
+
const result = await getIdFromCacheOrSummary((ids) => {
|
|
36
|
+
const addon = ids.addons[addonIdOrRealId];
|
|
37
|
+
if (addon == null) return null;
|
|
38
|
+
const ownerId = ids.owners[addon.realId];
|
|
39
|
+
return { ownerId, addonId: addon.addonId, realId: addon.realId };
|
|
37
40
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return addonId;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
throw new Error(`Add-on ${id} does not exist`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function resolveRealId(id) {
|
|
47
|
-
const realId = await getIdFromCacheOrSummary((ids) => {
|
|
48
|
-
return ids.addons[id] != null ? ids.addons[id].realId : null;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (realId != null) {
|
|
52
|
-
return realId;
|
|
41
|
+
if (result == null) {
|
|
42
|
+
throw new Error(`Add-on ${addonIdOrRealId} does not exist`);
|
|
53
43
|
}
|
|
54
|
-
|
|
55
|
-
throw new Error(`Add-on ${id} does not exist foo`);
|
|
44
|
+
return result;
|
|
56
45
|
}
|
|
57
46
|
|
|
58
47
|
async function getIdFromCacheOrSummary(callback) {
|
|
@@ -94,6 +83,9 @@ async function getIdsFromSummary() {
|
|
|
94
83
|
ids.addons[addon.id] = addonIds;
|
|
95
84
|
ids.addons[addon.realId] = addonIds;
|
|
96
85
|
}
|
|
86
|
+
for (const consumer of owner.consumers) {
|
|
87
|
+
ids.owners[consumer.key] = owner.id;
|
|
88
|
+
}
|
|
97
89
|
}
|
|
98
90
|
|
|
99
91
|
return ids;
|
|
@@ -174,6 +166,32 @@ export async function findAddonsByAddonProvider(provider) {
|
|
|
174
166
|
return candidates;
|
|
175
167
|
}
|
|
176
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Find OAuth consumers by key or name.
|
|
171
|
+
* Fast path: tries the IDs cache first (works for keys).
|
|
172
|
+
* Fallback: fetches the summary and searches by key or name.
|
|
173
|
+
* @param {string} keyOrName
|
|
174
|
+
* @returns {Promise<Array<{ ownerId: string, key: string, name: string }>>}
|
|
175
|
+
*/
|
|
176
|
+
export async function findOauthConsumersByKeyOrName(keyOrName) {
|
|
177
|
+
const ownerIdFromCache = await resolveOwnerId(keyOrName);
|
|
178
|
+
if (ownerIdFromCache != null) {
|
|
179
|
+
return [{ ownerId: ownerIdFromCache, key: keyOrName }];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const summary = await getSummary().then(sendToApi);
|
|
183
|
+
const consumers = [summary.user, ...summary.organisations].flatMap((owner) => {
|
|
184
|
+
return owner.consumers.map((c) => ({ ownerId: owner.id, name: c.name, key: c.key }));
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const matchByKey = consumers.find((c) => c.key === keyOrName);
|
|
188
|
+
if (matchByKey) {
|
|
189
|
+
return [matchByKey];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return consumers.filter((c) => c.name === keyOrName);
|
|
193
|
+
}
|
|
194
|
+
|
|
177
195
|
/**
|
|
178
196
|
* Get the owner ID from an Organisation ID or name
|
|
179
197
|
* @param {object} orgIdOrName The Organisation ID or name
|
package/src/models/ng.js
CHANGED
|
@@ -79,7 +79,7 @@ export async function destroy(ngIdOrLabel, orgaIdOrName) {
|
|
|
79
79
|
* @param {object} peerIdOrLabel The Peer ID or Label
|
|
80
80
|
* @param {object} ngIdOrLabel The Network Group ID or Label
|
|
81
81
|
* @param {object} orgaIdOrName The owner ID or name
|
|
82
|
-
* @returns {Promise<
|
|
82
|
+
* @returns {Promise<string>} The Peer WireGuard configuration
|
|
83
83
|
* @throws {Error} If the Peer is not found
|
|
84
84
|
* @throws {Error} If the Network Group is not found
|
|
85
85
|
* @throws {Error} If the Peer is not in the Network Group
|
|
@@ -113,7 +113,7 @@ export async function getPeerConfig(peerIdOrLabel, ngIdOrLabel, orgaIdOrName) {
|
|
|
113
113
|
networkGroupId: parentNg.id,
|
|
114
114
|
peerId: peer.id,
|
|
115
115
|
}).then(sendToApi);
|
|
116
|
-
Logger.debug(`Received from API:\n${
|
|
116
|
+
Logger.debug(`Received from API:\n${result}`);
|
|
117
117
|
|
|
118
118
|
return result;
|
|
119
119
|
}
|
|
@@ -6,10 +6,6 @@ export function listMetaEvents() {
|
|
|
6
6
|
return ['META_SERVICE_LIFECYCLE', 'META_DEPLOYMENT_RESULT', 'META_SERVICE_MANAGEMENT', 'META_CREDITS'];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function getOrgaIdOrUserId(orgIdOrName) {
|
|
10
|
-
return orgIdOrName == null ? User.getCurrentId() : Organisation.getId(orgIdOrName);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
9
|
export async function getOwnerAndApp(org, useLinkedApp) {
|
|
14
10
|
if (org != null) {
|
|
15
11
|
return { ownerId: await Organisation.getId(org) };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { get as getOauthConsumer } from '@clevercloud/client/esm/api/v2/oauth-consumer.js';
|
|
2
|
+
import { getSummary } from '@clevercloud/client/esm/api/v2/user.js';
|
|
3
|
+
import { promptCheckbox } from '../lib/prompts.js';
|
|
4
|
+
import { styleText } from '../lib/style-text.js';
|
|
5
|
+
import { findOauthConsumersByKeyOrName } from './ids-resolver.js';
|
|
6
|
+
import { sendToApi } from './send-to-api.js';
|
|
7
|
+
|
|
8
|
+
/* eslint-disable camelcase */
|
|
9
|
+
export const OAUTH_RIGHTS = {
|
|
10
|
+
access_organisations: 'access-organisations',
|
|
11
|
+
access_organisations_bills: 'access-organisations-bills',
|
|
12
|
+
access_organisations_consumption_statistics: 'access-organisations-consumption-statistics',
|
|
13
|
+
access_organisations_credit_count: 'access-organisations-credit-count',
|
|
14
|
+
access_personal_information: 'access-personal-information',
|
|
15
|
+
manage_organisations: 'manage-organisations',
|
|
16
|
+
manage_organisations_applications: 'manage-organisations-applications',
|
|
17
|
+
manage_organisations_members: 'manage-organisations-members',
|
|
18
|
+
manage_organisations_services: 'manage-organisations-services',
|
|
19
|
+
manage_personal_information: 'manage-personal-information',
|
|
20
|
+
manage_ssh_keys: 'manage-ssh-keys',
|
|
21
|
+
};
|
|
22
|
+
/* eslint-enable camelcase */
|
|
23
|
+
|
|
24
|
+
export function rightsFromList(requestedRights = []) {
|
|
25
|
+
const hasAll = requestedRights.includes('all');
|
|
26
|
+
return Object.fromEntries(
|
|
27
|
+
Object.entries(OAUTH_RIGHTS).map(([apiName, cliName]) => {
|
|
28
|
+
return [apiName, hasAll || requestedRights.includes(cliName)];
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const READONLY_RIGHTS = ['almighty'];
|
|
34
|
+
|
|
35
|
+
export function removeReadonlyRights(rights) {
|
|
36
|
+
return Object.fromEntries(
|
|
37
|
+
Object.entries(rights).filter(([key]) => {
|
|
38
|
+
return !READONLY_RIGHTS.includes(key);
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function promptRights(existingRights) {
|
|
44
|
+
const choices = Object.entries(OAUTH_RIGHTS).map(([apiName, cliName]) => ({
|
|
45
|
+
name: cliName,
|
|
46
|
+
value: cliName,
|
|
47
|
+
checked: existingRights?.[apiName] ?? false,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
const selected = await promptCheckbox('Select rights', choices);
|
|
51
|
+
|
|
52
|
+
return rightsFromList(selected);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getAllConsumers() {
|
|
56
|
+
const summary = await getSummary().then(sendToApi);
|
|
57
|
+
return [summary.user, ...summary.organisations].flatMap((owner) => {
|
|
58
|
+
return owner.consumers.map((c) => ({ ownerId: owner.id, ownerName: owner.name, ...c }));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function resolveOauthConsumer(keyOrName) {
|
|
63
|
+
const candidates = await findOauthConsumersByKeyOrName(keyOrName);
|
|
64
|
+
|
|
65
|
+
if (candidates.length === 0) {
|
|
66
|
+
throw new Error(`OAuth consumer not found: ${styleText('red', keyOrName)}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (candidates.length > 1) {
|
|
70
|
+
const list = candidates.map((c) => ` - ${c.name} ${styleText('grey', `(${c.key})`)}`).join('\n');
|
|
71
|
+
throw new Error(`Ambiguous name ${styleText('red', keyOrName)}, use the key instead:\n${list}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { ownerId, key } = candidates[0];
|
|
75
|
+
const oauthConsumer = await getOauthConsumer({ id: ownerId, key }).then(sendToApi);
|
|
76
|
+
return { ownerId, ...oauthConsumer };
|
|
77
|
+
}
|
package/src/models/utils.js
CHANGED
|
@@ -26,7 +26,7 @@ export function openBrowser(urlOrPath, message) {
|
|
|
26
26
|
const url = urlOrPath.startsWith('/') ? `${config.CONSOLE_URL}${urlOrPath}` : urlOrPath;
|
|
27
27
|
|
|
28
28
|
Logger.debug(`Opening URL "${url}" in browser`);
|
|
29
|
-
Logger.println(message);
|
|
29
|
+
Logger.println(`🌐 ${message}`);
|
|
30
30
|
|
|
31
31
|
return openPage(url, { wait: false });
|
|
32
32
|
}
|