@j-256/ccam 0.1.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/LICENSE +21 -0
- package/README.md +209 -0
- package/dist/auth/browser-login.d.ts +14 -0
- package/dist/auth/browser-login.js +72 -0
- package/dist/auth/manual-login.d.ts +10 -0
- package/dist/auth/manual-login.js +33 -0
- package/dist/auth/paths.d.ts +4 -0
- package/dist/auth/paths.js +15 -0
- package/dist/auth/profile-resolver.d.ts +26 -0
- package/dist/auth/profile-resolver.js +42 -0
- package/dist/auth/profile-store.d.ts +38 -0
- package/dist/auth/profile-store.js +125 -0
- package/dist/auth/prompt.d.ts +9 -0
- package/dist/auth/prompt.js +70 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.js +4 -0
- package/dist/client-factory.d.ts +6 -0
- package/dist/client-factory.js +40 -0
- package/dist/commands/auth.d.ts +77 -0
- package/dist/commands/auth.js +387 -0
- package/dist/commands/client.d.ts +3 -0
- package/dist/commands/client.js +365 -0
- package/dist/commands/instance.d.ts +11 -0
- package/dist/commands/instance.js +128 -0
- package/dist/commands/org-config.d.ts +3 -0
- package/dist/commands/org-config.js +31 -0
- package/dist/commands/org.d.ts +11 -0
- package/dist/commands/org.js +234 -0
- package/dist/commands/permission.d.ts +3 -0
- package/dist/commands/permission.js +60 -0
- package/dist/commands/realm.d.ts +3 -0
- package/dist/commands/realm.js +58 -0
- package/dist/commands/role.d.ts +3 -0
- package/dist/commands/role.js +77 -0
- package/dist/commands/service-type.d.ts +3 -0
- package/dist/commands/service-type.js +57 -0
- package/dist/commands/user.d.ts +14 -0
- package/dist/commands/user.js +573 -0
- package/dist/error-handler.d.ts +2 -0
- package/dist/error-handler.js +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/output/csv.d.ts +3 -0
- package/dist/output/csv.js +57 -0
- package/dist/output/default-columns.d.ts +2 -0
- package/dist/output/default-columns.js +16 -0
- package/dist/output/detect.d.ts +4 -0
- package/dist/output/detect.js +6 -0
- package/dist/output/index.d.ts +15 -0
- package/dist/output/index.js +34 -0
- package/dist/output/json.d.ts +2 -0
- package/dist/output/json.js +10 -0
- package/dist/output/shared.d.ts +13 -0
- package/dist/output/shared.js +41 -0
- package/dist/output/table.d.ts +2 -0
- package/dist/output/table.js +72 -0
- package/dist/output/types.d.ts +2 -0
- package/dist/output/types.js +2 -0
- package/dist/output/yaml-fmt.d.ts +2 -0
- package/dist/output/yaml-fmt.js +11 -0
- package/dist/program.d.ts +3 -0
- package/dist/program.js +37 -0
- package/dist/shared.d.ts +46 -0
- package/dist/shared.js +96 -0
- package/dist/tui/App.d.ts +7 -0
- package/dist/tui/App.js +30 -0
- package/dist/tui/components/AuditTab.d.ts +8 -0
- package/dist/tui/components/AuditTab.js +80 -0
- package/dist/tui/components/FooterBar.d.ts +17 -0
- package/dist/tui/components/FooterBar.js +23 -0
- package/dist/tui/components/FullScreenLayout.d.ts +6 -0
- package/dist/tui/components/FullScreenLayout.js +11 -0
- package/dist/tui/components/HeaderBar.d.ts +5 -0
- package/dist/tui/components/HeaderBar.js +10 -0
- package/dist/tui/components/InfoTab.d.ts +8 -0
- package/dist/tui/components/InfoTab.js +70 -0
- package/dist/tui/components/ResourcePicker.d.ts +10 -0
- package/dist/tui/components/ResourcePicker.js +36 -0
- package/dist/tui/components/SubResourceTab.d.ts +7 -0
- package/dist/tui/components/SubResourceTab.js +193 -0
- package/dist/tui/components/TabBar.d.ts +11 -0
- package/dist/tui/components/TabBar.js +13 -0
- package/dist/tui/components/Table.d.ts +32 -0
- package/dist/tui/components/Table.js +175 -0
- package/dist/tui/context/client.d.ts +7 -0
- package/dist/tui/context/client.js +14 -0
- package/dist/tui/context/navigation.d.ts +14 -0
- package/dist/tui/context/navigation.js +25 -0
- package/dist/tui/context/terminal-size.d.ts +9 -0
- package/dist/tui/context/terminal-size.js +26 -0
- package/dist/tui/format.d.ts +20 -0
- package/dist/tui/format.js +57 -0
- package/dist/tui/hooks/use-audit-log.d.ts +12 -0
- package/dist/tui/hooks/use-audit-log.js +71 -0
- package/dist/tui/hooks/use-local-collection.d.ts +8 -0
- package/dist/tui/hooks/use-local-collection.js +30 -0
- package/dist/tui/hooks/use-paginated-resource.d.ts +23 -0
- package/dist/tui/hooks/use-paginated-resource.js +115 -0
- package/dist/tui/hooks/use-resource-detail.d.ts +7 -0
- package/dist/tui/hooks/use-resource-detail.js +30 -0
- package/dist/tui/hooks/use-scroll-window.d.ts +7 -0
- package/dist/tui/hooks/use-scroll-window.js +29 -0
- package/dist/tui/index.d.ts +2 -0
- package/dist/tui/index.js +22 -0
- package/dist/tui/navigation.d.ts +11 -0
- package/dist/tui/navigation.js +29 -0
- package/dist/tui/resource-configs/api-clients.d.ts +3 -0
- package/dist/tui/resource-configs/api-clients.js +118 -0
- package/dist/tui/resource-configs/index.d.ts +14 -0
- package/dist/tui/resource-configs/index.js +28 -0
- package/dist/tui/resource-configs/instances.d.ts +3 -0
- package/dist/tui/resource-configs/instances.js +24 -0
- package/dist/tui/resource-configs/org-configuration.d.ts +3 -0
- package/dist/tui/resource-configs/org-configuration.js +28 -0
- package/dist/tui/resource-configs/organizations.d.ts +3 -0
- package/dist/tui/resource-configs/organizations.js +104 -0
- package/dist/tui/resource-configs/permissions.d.ts +3 -0
- package/dist/tui/resource-configs/permissions.js +25 -0
- package/dist/tui/resource-configs/realms.d.ts +3 -0
- package/dist/tui/resource-configs/realms.js +36 -0
- package/dist/tui/resource-configs/roles.d.ts +3 -0
- package/dist/tui/resource-configs/roles.js +56 -0
- package/dist/tui/resource-configs/service-types.d.ts +3 -0
- package/dist/tui/resource-configs/service-types.js +24 -0
- package/dist/tui/resource-configs/users.d.ts +3 -0
- package/dist/tui/resource-configs/users.js +126 -0
- package/dist/tui/types.d.ts +99 -0
- package/dist/tui/types.js +23 -0
- package/dist/tui/views/ResourceDetailView.d.ts +7 -0
- package/dist/tui/views/ResourceDetailView.js +123 -0
- package/dist/tui/views/ResourceListView.d.ts +6 -0
- package/dist/tui/views/ResourceListView.js +140 -0
- package/dist/tui/views/ViewRouter.d.ts +2 -0
- package/dist/tui/views/ViewRouter.js +60 -0
- package/package.json +62 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { addGlobalOptions, parseExpand, resolveGlobalOptions, writePageInfoIfTable } from '../shared.js';
|
|
2
|
+
import { resolveProfile } from '../auth/profile-resolver.js';
|
|
3
|
+
import { createClientFromResolved } from '../client-factory.js';
|
|
4
|
+
import { handleError } from '../error-handler.js';
|
|
5
|
+
import { renderOutput, resolveFormat } from '../output/index.js';
|
|
6
|
+
import { DEFAULT_COLUMNS } from '../output/default-columns.js';
|
|
7
|
+
const USER_GET_EXPAND = ['organizations', 'roles', 'organizations,roles'];
|
|
8
|
+
const USER_GET_BY_LOGIN_EXPAND = ['organizations'];
|
|
9
|
+
export function selectUserFinder(filters) {
|
|
10
|
+
// Check for specific combinations
|
|
11
|
+
if (filters.org && filters.role) {
|
|
12
|
+
return 'findByOrgAndRole';
|
|
13
|
+
}
|
|
14
|
+
if (filters.orgRealmAccess) {
|
|
15
|
+
return 'findByOrgRealmAccess';
|
|
16
|
+
}
|
|
17
|
+
if (filters.role) {
|
|
18
|
+
return 'findByRole';
|
|
19
|
+
}
|
|
20
|
+
if (filters.org) {
|
|
21
|
+
return filters.all ? 'findAllByOrg' : 'findByOrg';
|
|
22
|
+
}
|
|
23
|
+
if (filters.login) {
|
|
24
|
+
return 'findByLogin';
|
|
25
|
+
}
|
|
26
|
+
return 'list';
|
|
27
|
+
}
|
|
28
|
+
async function listUsers(options) {
|
|
29
|
+
try {
|
|
30
|
+
const resolved = resolveGlobalOptions(options);
|
|
31
|
+
// Validate modifiedAfter requires role
|
|
32
|
+
if (options.modifiedAfter && !options.role) {
|
|
33
|
+
process.stderr.write('Error: --modified-after requires --role\n');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
// Validate --all requires --org
|
|
37
|
+
if (options.all && !options.org) {
|
|
38
|
+
process.stderr.write('Error: --all requires --org\n');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const profileResolved = await resolveProfile({
|
|
42
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
43
|
+
});
|
|
44
|
+
const client = await createClientFromResolved(profileResolved);
|
|
45
|
+
const filters = {
|
|
46
|
+
login: options.login,
|
|
47
|
+
org: options.org,
|
|
48
|
+
role: options.role,
|
|
49
|
+
orgRealmAccess: options.orgRealmAccess,
|
|
50
|
+
modifiedAfter: options.modifiedAfter,
|
|
51
|
+
all: options.all,
|
|
52
|
+
};
|
|
53
|
+
const finder = selectUserFinder(filters);
|
|
54
|
+
let result;
|
|
55
|
+
switch (finder) {
|
|
56
|
+
case 'list':
|
|
57
|
+
result = await client.users.list({
|
|
58
|
+
page: resolved.page,
|
|
59
|
+
size: resolved.size,
|
|
60
|
+
sort: resolved.sort,
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
case 'findByLogin':
|
|
64
|
+
result = await client.users.getByLogin(filters.login);
|
|
65
|
+
break;
|
|
66
|
+
case 'findByOrg':
|
|
67
|
+
result = await client.users.search.findByOrg({
|
|
68
|
+
organization: filters.org,
|
|
69
|
+
page: resolved.page,
|
|
70
|
+
size: resolved.size,
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
case 'findAllByOrg':
|
|
74
|
+
result = await client.users.search.findAllByOrg({
|
|
75
|
+
organization: filters.org,
|
|
76
|
+
page: resolved.page,
|
|
77
|
+
size: resolved.size,
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case 'findByRole':
|
|
81
|
+
result = await client.users.search.findByRole({
|
|
82
|
+
role: filters.role,
|
|
83
|
+
modifiedAfter: filters.modifiedAfter,
|
|
84
|
+
page: resolved.page,
|
|
85
|
+
size: resolved.size,
|
|
86
|
+
});
|
|
87
|
+
break;
|
|
88
|
+
case 'findByOrgAndRole':
|
|
89
|
+
result = await client.users.search.findByOrgAndRole({
|
|
90
|
+
organization: filters.org,
|
|
91
|
+
role: filters.role,
|
|
92
|
+
page: resolved.page,
|
|
93
|
+
size: resolved.size,
|
|
94
|
+
});
|
|
95
|
+
break;
|
|
96
|
+
case 'findByOrgRealmAccess':
|
|
97
|
+
result = await client.users.search.findByOrgRealmAccess({
|
|
98
|
+
organization: filters.orgRealmAccess,
|
|
99
|
+
page: resolved.page,
|
|
100
|
+
size: resolved.size,
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
105
|
+
const data = result && typeof result === 'object' && 'content' in result ? result.content : result;
|
|
106
|
+
renderOutput(data, { format, fields: resolved.fields, defaultFields: DEFAULT_COLUMNS.user });
|
|
107
|
+
writePageInfoIfTable(format, result);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
handleError(err);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function getUser(loginOrId, options) {
|
|
114
|
+
try {
|
|
115
|
+
const resolved = resolveGlobalOptions(options);
|
|
116
|
+
const profileResolved = await resolveProfile({
|
|
117
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
118
|
+
});
|
|
119
|
+
const client = await createClientFromResolved(profileResolved);
|
|
120
|
+
let result;
|
|
121
|
+
if (options.id) {
|
|
122
|
+
const expand = parseExpand(options.expand, USER_GET_EXPAND);
|
|
123
|
+
// Branch on the literal expand value so TypeScript can pick the right overload.
|
|
124
|
+
if (expand === 'organizations') {
|
|
125
|
+
result = await client.users.get(loginOrId, { expand });
|
|
126
|
+
}
|
|
127
|
+
else if (expand === 'roles') {
|
|
128
|
+
result = await client.users.get(loginOrId, { expand });
|
|
129
|
+
}
|
|
130
|
+
else if (expand === 'organizations,roles') {
|
|
131
|
+
result = await client.users.get(loginOrId, { expand });
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
result = await client.users.get(loginOrId);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const expand = parseExpand(options.expand, USER_GET_BY_LOGIN_EXPAND);
|
|
139
|
+
result = expand ? await client.users.getByLogin(loginOrId, { expand }) : await client.users.getByLogin(loginOrId);
|
|
140
|
+
}
|
|
141
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
142
|
+
renderOutput(result, { format, fields: resolved.fields, defaultFields: DEFAULT_COLUMNS.userDetail });
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
handleError(err);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function currentUser(options) {
|
|
149
|
+
try {
|
|
150
|
+
const resolved = resolveGlobalOptions(options);
|
|
151
|
+
const profileResolved = await resolveProfile({
|
|
152
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
153
|
+
});
|
|
154
|
+
const client = await createClientFromResolved(profileResolved);
|
|
155
|
+
const expand = parseExpand(options.expand, USER_GET_EXPAND);
|
|
156
|
+
// Branch on the literal expand value so TypeScript can pick the right overload.
|
|
157
|
+
let result;
|
|
158
|
+
if (expand === 'organizations') {
|
|
159
|
+
result = await client.users.current({ expand });
|
|
160
|
+
}
|
|
161
|
+
else if (expand === 'roles') {
|
|
162
|
+
result = await client.users.current({ expand });
|
|
163
|
+
}
|
|
164
|
+
else if (expand === 'organizations,roles') {
|
|
165
|
+
result = await client.users.current({ expand });
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
result = await client.users.current();
|
|
169
|
+
}
|
|
170
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
171
|
+
renderOutput(result, { format, fields: resolved.fields, defaultFields: DEFAULT_COLUMNS.userDetail });
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
handleError(err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function auditUser(login, options) {
|
|
178
|
+
try {
|
|
179
|
+
const resolved = resolveGlobalOptions(options);
|
|
180
|
+
const profileResolved = await resolveProfile({
|
|
181
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
182
|
+
});
|
|
183
|
+
const client = await createClientFromResolved(profileResolved);
|
|
184
|
+
// Resolve login to ID first
|
|
185
|
+
const user = await client.users.getByLogin(login);
|
|
186
|
+
const querySize = options.querySize !== undefined ? parseInt(options.querySize, 10) : undefined;
|
|
187
|
+
const result = await client.users.auditLogs(user.id, querySize !== undefined ? { querySize } : undefined);
|
|
188
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
189
|
+
renderOutput(result.content, { format, fields: resolved.fields, defaultFields: DEFAULT_COLUMNS.auditLog });
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
handleError(err);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function userRoles(login, options) {
|
|
196
|
+
try {
|
|
197
|
+
const resolved = resolveGlobalOptions(options);
|
|
198
|
+
const profileResolved = await resolveProfile({
|
|
199
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
200
|
+
});
|
|
201
|
+
const client = await createClientFromResolved(profileResolved);
|
|
202
|
+
const user = await client.users.getByLogin(login);
|
|
203
|
+
const result = await client.users.roles(user.id);
|
|
204
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
205
|
+
renderOutput(result.content, { format, fields: resolved.fields, defaultFields: DEFAULT_COLUMNS.role });
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
handleError(err);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function userInstances(login, options) {
|
|
212
|
+
try {
|
|
213
|
+
const resolved = resolveGlobalOptions(options);
|
|
214
|
+
const profileResolved = await resolveProfile({
|
|
215
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
216
|
+
});
|
|
217
|
+
const client = await createClientFromResolved(profileResolved);
|
|
218
|
+
const user = await client.users.getByLogin(login);
|
|
219
|
+
const result = await client.users.instances(user.id);
|
|
220
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
221
|
+
renderOutput(result.content, { format, fields: resolved.fields, defaultFields: DEFAULT_COLUMNS.instance });
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
handleError(err);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function userAssignedRealms(login, options) {
|
|
228
|
+
try {
|
|
229
|
+
const resolved = resolveGlobalOptions(options);
|
|
230
|
+
const profileResolved = await resolveProfile({
|
|
231
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
232
|
+
});
|
|
233
|
+
const client = await createClientFromResolved(profileResolved);
|
|
234
|
+
const user = await client.users.getByLogin(login);
|
|
235
|
+
const result = await client.users.assignedRealms(user.id);
|
|
236
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
237
|
+
renderOutput(result.content, { format, fields: resolved.fields, defaultFields: DEFAULT_COLUMNS.realm });
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
handleError(err);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function userAssignedInstances(login, options) {
|
|
244
|
+
try {
|
|
245
|
+
const resolved = resolveGlobalOptions(options);
|
|
246
|
+
const profileResolved = await resolveProfile({
|
|
247
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
248
|
+
});
|
|
249
|
+
const client = await createClientFromResolved(profileResolved);
|
|
250
|
+
const user = await client.users.getByLogin(login);
|
|
251
|
+
const result = await client.users.assignedInstances(user.id);
|
|
252
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
253
|
+
renderOutput(result.content, { format, fields: resolved.fields, defaultFields: DEFAULT_COLUMNS.instance });
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
handleError(err);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function createUser(options) {
|
|
260
|
+
try {
|
|
261
|
+
const resolved = resolveGlobalOptions(options);
|
|
262
|
+
const profileResolved = await resolveProfile({
|
|
263
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
264
|
+
});
|
|
265
|
+
const client = await createClientFromResolved(profileResolved);
|
|
266
|
+
const data = {
|
|
267
|
+
mail: options.mail,
|
|
268
|
+
firstName: options.firstName,
|
|
269
|
+
lastName: options.lastName,
|
|
270
|
+
primaryOrganization: options.primaryOrg,
|
|
271
|
+
};
|
|
272
|
+
if (options.displayName !== undefined)
|
|
273
|
+
data.displayName = options.displayName;
|
|
274
|
+
if (options.roles)
|
|
275
|
+
data.roles = options.roles.split(',');
|
|
276
|
+
if (options.organizations)
|
|
277
|
+
data.organizations = options.organizations.split(',');
|
|
278
|
+
const result = await client.users.create(data);
|
|
279
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
280
|
+
renderOutput(result, {
|
|
281
|
+
format,
|
|
282
|
+
fields: resolved.fields,
|
|
283
|
+
defaultFields: DEFAULT_COLUMNS.userDetail,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
handleError(err);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function updateUser(loginOrId, options) {
|
|
291
|
+
try {
|
|
292
|
+
const resolved = resolveGlobalOptions(options);
|
|
293
|
+
const profileResolved = await resolveProfile({
|
|
294
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
295
|
+
});
|
|
296
|
+
const client = await createClientFromResolved(profileResolved);
|
|
297
|
+
const userId = options.id ? loginOrId : (await client.users.getByLogin(loginOrId)).id;
|
|
298
|
+
const data = {};
|
|
299
|
+
if (options.mail !== undefined)
|
|
300
|
+
data.mail = options.mail;
|
|
301
|
+
if (options.firstName !== undefined)
|
|
302
|
+
data.firstName = options.firstName;
|
|
303
|
+
if (options.lastName !== undefined)
|
|
304
|
+
data.lastName = options.lastName;
|
|
305
|
+
if (options.displayName !== undefined)
|
|
306
|
+
data.displayName = options.displayName;
|
|
307
|
+
if (options.primaryOrg !== undefined)
|
|
308
|
+
data.primaryOrganization = options.primaryOrg;
|
|
309
|
+
if (options.roles)
|
|
310
|
+
data.roles = options.roles.split(',');
|
|
311
|
+
if (options.organizations)
|
|
312
|
+
data.organizations = options.organizations.split(',');
|
|
313
|
+
const result = await client.users.update(userId, data);
|
|
314
|
+
const format = resolveFormat(resolved.format, process.stdout.isTTY);
|
|
315
|
+
renderOutput(result, {
|
|
316
|
+
format,
|
|
317
|
+
fields: resolved.fields,
|
|
318
|
+
defaultFields: DEFAULT_COLUMNS.userDetail,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
handleError(err);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function deleteUser(loginOrId, options) {
|
|
326
|
+
try {
|
|
327
|
+
const resolved = resolveGlobalOptions(options);
|
|
328
|
+
const profileResolved = await resolveProfile({
|
|
329
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
330
|
+
});
|
|
331
|
+
const client = await createClientFromResolved(profileResolved);
|
|
332
|
+
const userId = options.id ? loginOrId : (await client.users.getByLogin(loginOrId)).id;
|
|
333
|
+
await client.users.delete(userId);
|
|
334
|
+
process.stderr.write(`Deleted user ${userId}\n`);
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
handleError(err);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function resetUser(login, options) {
|
|
341
|
+
try {
|
|
342
|
+
const resolved = resolveGlobalOptions(options);
|
|
343
|
+
const profileResolved = await resolveProfile({
|
|
344
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
345
|
+
});
|
|
346
|
+
const client = await createClientFromResolved(profileResolved);
|
|
347
|
+
const user = await client.users.getByLogin(login);
|
|
348
|
+
const opts = options.supportTicket ? { supportTicketId: options.supportTicket } : undefined;
|
|
349
|
+
await client.users.reset(user.id, opts);
|
|
350
|
+
process.stderr.write(`Reset password for user ${login}\n`);
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
handleError(err);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async function disableUser(login, options) {
|
|
357
|
+
try {
|
|
358
|
+
const resolved = resolveGlobalOptions(options);
|
|
359
|
+
const profileResolved = await resolveProfile({
|
|
360
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
361
|
+
});
|
|
362
|
+
const client = await createClientFromResolved(profileResolved);
|
|
363
|
+
const user = await client.users.getByLogin(login);
|
|
364
|
+
const opts = options.supportTicket ? { supportTicketId: options.supportTicket } : undefined;
|
|
365
|
+
await client.users.disable(user.id, opts);
|
|
366
|
+
process.stderr.write(`Disabled user ${login}\n`);
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
handleError(err);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function revokeUserVerifier(login, verifierId, options) {
|
|
373
|
+
try {
|
|
374
|
+
const resolved = resolveGlobalOptions(options);
|
|
375
|
+
const profileResolved = await resolveProfile({
|
|
376
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
377
|
+
});
|
|
378
|
+
const client = await createClientFromResolved(profileResolved);
|
|
379
|
+
const user = await client.users.getByLogin(login);
|
|
380
|
+
await client.users.revokeVerifier(user.id, verifierId);
|
|
381
|
+
process.stderr.write(`Revoked verifier ${verifierId} for user ${login}\n`);
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
handleError(err);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
async function grantUserRole(loginOrId, roleId, options) {
|
|
388
|
+
try {
|
|
389
|
+
const resolved = resolveGlobalOptions(options);
|
|
390
|
+
const profileResolved = await resolveProfile({
|
|
391
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
392
|
+
});
|
|
393
|
+
const client = await createClientFromResolved(profileResolved);
|
|
394
|
+
const userId = options.id ? loginOrId : (await client.users.getByLogin(loginOrId)).id;
|
|
395
|
+
const tenants = options.tenants
|
|
396
|
+
? options.tenants.split(',').map((t) => t.trim()).filter((t) => t.length > 0)
|
|
397
|
+
: undefined;
|
|
398
|
+
const opts = tenants !== undefined ? { tenants } : undefined;
|
|
399
|
+
const result = await client.users.grantRole(userId, roleId, opts);
|
|
400
|
+
const subject = loginOrId;
|
|
401
|
+
if (result.changed) {
|
|
402
|
+
process.stderr.write(`Granted role ${roleId} to user ${subject}\n`);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
process.stderr.write(`User ${subject} already has role ${roleId} (no changes)\n`);
|
|
406
|
+
}
|
|
407
|
+
if (result.roleScope !== 'GLOBAL' && (tenants === undefined || tenants.length === 0)) {
|
|
408
|
+
process.stderr.write(`Warning: role ${roleId} has scope ${result.roleScope}; it will be inert until tenants are set\n`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
handleError(err);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async function revokeUserRole(loginOrId, roleId, options) {
|
|
416
|
+
try {
|
|
417
|
+
const resolved = resolveGlobalOptions(options);
|
|
418
|
+
const profileResolved = await resolveProfile({
|
|
419
|
+
flags: { profile: options.profile, host: resolved.host },
|
|
420
|
+
});
|
|
421
|
+
const client = await createClientFromResolved(profileResolved);
|
|
422
|
+
const userId = options.id ? loginOrId : (await client.users.getByLogin(loginOrId)).id;
|
|
423
|
+
const result = await client.users.revokeRole(userId, roleId);
|
|
424
|
+
const subject = loginOrId;
|
|
425
|
+
if (result.changed) {
|
|
426
|
+
process.stderr.write(`Revoked role ${roleId} from user ${subject}\n`);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
process.stderr.write(`User ${subject} does not have role ${roleId} (no changes)\n`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
catch (err) {
|
|
433
|
+
handleError(err);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
export function registerUserCommands(program) {
|
|
437
|
+
const user = program
|
|
438
|
+
.command('user')
|
|
439
|
+
.description('Manage users');
|
|
440
|
+
// user list
|
|
441
|
+
const list = user
|
|
442
|
+
.command('list')
|
|
443
|
+
.description('List users with optional filters');
|
|
444
|
+
addGlobalOptions(list)
|
|
445
|
+
.option('--login <email>', 'Find user by login (email address)')
|
|
446
|
+
.option('--org <id>', 'Filter users by organization ID')
|
|
447
|
+
.option('--role <id>', 'Filter users by role ID')
|
|
448
|
+
.option('--org-realm-access <id>', 'Find users with realm access in organization')
|
|
449
|
+
.option('--modified-after <date>', 'Filter by modification date (requires --role)')
|
|
450
|
+
.option('--all', 'Include deleted users (requires --org)')
|
|
451
|
+
.action(listUsers);
|
|
452
|
+
// user get
|
|
453
|
+
const get = user
|
|
454
|
+
.command('get')
|
|
455
|
+
.argument('<login>', 'User login (email) or ID')
|
|
456
|
+
.description('Get a specific user');
|
|
457
|
+
addGlobalOptions(get)
|
|
458
|
+
.option('--id', 'Treat argument as user ID instead of login')
|
|
459
|
+
.option('--expand <fields>', 'Expand related resources (organizations, roles, organizations,roles)')
|
|
460
|
+
.action(getUser);
|
|
461
|
+
// user current
|
|
462
|
+
const current = user
|
|
463
|
+
.command('current')
|
|
464
|
+
.description('Get the current authenticated user (requires user-context token)');
|
|
465
|
+
addGlobalOptions(current)
|
|
466
|
+
.option('--expand <fields>', 'Expand related resources (organizations, roles, organizations,roles)')
|
|
467
|
+
.action(currentUser);
|
|
468
|
+
// user audit
|
|
469
|
+
const audit = user
|
|
470
|
+
.command('audit')
|
|
471
|
+
.argument('<login>', 'User login (email)')
|
|
472
|
+
.description('Get audit log for a user');
|
|
473
|
+
addGlobalOptions(audit)
|
|
474
|
+
.option('--query-size <n>', 'Limit audit log query window size')
|
|
475
|
+
.action(auditUser);
|
|
476
|
+
// user roles
|
|
477
|
+
const roles = user
|
|
478
|
+
.command('roles')
|
|
479
|
+
.argument('<login>', 'User login (email)')
|
|
480
|
+
.description('List roles assigned to a user');
|
|
481
|
+
addGlobalOptions(roles).action(userRoles);
|
|
482
|
+
// user instances
|
|
483
|
+
const instances = user
|
|
484
|
+
.command('instances')
|
|
485
|
+
.argument('<login>', 'User login (email)')
|
|
486
|
+
.description('List instances accessible to a user');
|
|
487
|
+
addGlobalOptions(instances).action(userInstances);
|
|
488
|
+
// user assigned-realms
|
|
489
|
+
const assignedRealms = user
|
|
490
|
+
.command('assigned-realms')
|
|
491
|
+
.argument('<login>', 'User login (email)')
|
|
492
|
+
.description('List realms assigned to a user via role-tenant filter');
|
|
493
|
+
addGlobalOptions(assignedRealms).action(userAssignedRealms);
|
|
494
|
+
// user assigned-instances
|
|
495
|
+
const assignedInstances = user
|
|
496
|
+
.command('assigned-instances')
|
|
497
|
+
.argument('<login>', 'User login (email)')
|
|
498
|
+
.description('List instances assigned to a user via role-tenant filter');
|
|
499
|
+
addGlobalOptions(assignedInstances).action(userAssignedInstances);
|
|
500
|
+
// user create
|
|
501
|
+
const create = user.command('create').description('Create a new user');
|
|
502
|
+
addGlobalOptions(create)
|
|
503
|
+
.requiredOption('--mail <email>', 'Email address')
|
|
504
|
+
.requiredOption('--first-name <name>', 'First name')
|
|
505
|
+
.requiredOption('--last-name <name>', 'Last name')
|
|
506
|
+
.requiredOption('--primary-org <id>', 'Primary organization ID')
|
|
507
|
+
.option('--display-name <name>', 'Display name')
|
|
508
|
+
.option('--roles <roles>', 'Comma-separated role IDs')
|
|
509
|
+
.option('--organizations <orgs>', 'Comma-separated organization IDs')
|
|
510
|
+
.action(createUser);
|
|
511
|
+
// user update
|
|
512
|
+
const update = user
|
|
513
|
+
.command('update')
|
|
514
|
+
.argument('<login-or-id>', 'User login (email) or ID')
|
|
515
|
+
.description('Update a user');
|
|
516
|
+
addGlobalOptions(update)
|
|
517
|
+
.option('--id', 'Treat argument as user ID instead of login')
|
|
518
|
+
.option('--mail <email>', 'Email address')
|
|
519
|
+
.option('--first-name <name>', 'First name')
|
|
520
|
+
.option('--last-name <name>', 'Last name')
|
|
521
|
+
.option('--display-name <name>', 'Display name')
|
|
522
|
+
.option('--primary-org <id>', 'Primary organization ID')
|
|
523
|
+
.option('--roles <roles>', 'Comma-separated role IDs')
|
|
524
|
+
.option('--organizations <orgs>', 'Comma-separated organization IDs')
|
|
525
|
+
.action(updateUser);
|
|
526
|
+
// user delete
|
|
527
|
+
const del = user
|
|
528
|
+
.command('delete')
|
|
529
|
+
.argument('<login-or-id>', 'User login (email) or ID')
|
|
530
|
+
.description('Delete a user');
|
|
531
|
+
addGlobalOptions(del)
|
|
532
|
+
.option('--id', 'Treat argument as user ID instead of login')
|
|
533
|
+
.action(deleteUser);
|
|
534
|
+
// user reset
|
|
535
|
+
const reset = user
|
|
536
|
+
.command('reset')
|
|
537
|
+
.argument('<login>', 'User login (email)')
|
|
538
|
+
.description('Reset a user password');
|
|
539
|
+
addGlobalOptions(reset).option('--support-ticket <id>', 'Support ticket ID').action(resetUser);
|
|
540
|
+
// user disable
|
|
541
|
+
const disable = user
|
|
542
|
+
.command('disable')
|
|
543
|
+
.argument('<login>', 'User login (email)')
|
|
544
|
+
.description('Disable (deactivate) a user');
|
|
545
|
+
addGlobalOptions(disable).option('--support-ticket <id>', 'Support ticket ID').action(disableUser);
|
|
546
|
+
// user revoke-verifier
|
|
547
|
+
const revokeVerifier = user
|
|
548
|
+
.command('revoke-verifier')
|
|
549
|
+
.argument('<login>', 'User login (email)')
|
|
550
|
+
.argument('<verifier-id>', 'Verifier ID to revoke')
|
|
551
|
+
.description('Revoke an MFA verifier for a user');
|
|
552
|
+
addGlobalOptions(revokeVerifier).action(revokeUserVerifier);
|
|
553
|
+
// user grant-role
|
|
554
|
+
const grantRole = user
|
|
555
|
+
.command('grant-role')
|
|
556
|
+
.argument('<login-or-id>', 'User login (email) or ID')
|
|
557
|
+
.argument('<role-id>', 'Role ID to grant (e.g. "ccdx-sbx-user")')
|
|
558
|
+
.description('Grant a role to a user (idempotent; read-modify-write on the user resource)');
|
|
559
|
+
addGlobalOptions(grantRole)
|
|
560
|
+
.option('--id', 'Treat first argument as user ID instead of login')
|
|
561
|
+
.option('--tenants <csv>', 'Comma-separated tenants for scoped roles; union-merged into roleTenantFilter')
|
|
562
|
+
.action(grantUserRole);
|
|
563
|
+
// user revoke-role
|
|
564
|
+
const revokeRole = user
|
|
565
|
+
.command('revoke-role')
|
|
566
|
+
.argument('<login-or-id>', 'User login (email) or ID')
|
|
567
|
+
.argument('<role-id>', 'Role ID to revoke')
|
|
568
|
+
.description('Revoke a role from a user (idempotent; server auto-strips any tenant-filter entry)');
|
|
569
|
+
addGlobalOptions(revokeRole)
|
|
570
|
+
.option('--id', 'Treat first argument as user ID instead of login')
|
|
571
|
+
.action(revokeUserRole);
|
|
572
|
+
}
|
|
573
|
+
//# sourceMappingURL=user.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { CcamError, CcamAuthError, CcamNotFoundError } from 'ccam-sdk';
|
|
3
|
+
export function handleError(err) {
|
|
4
|
+
if (err instanceof CcamAuthError) {
|
|
5
|
+
process.stderr.write(chalk.red(`Error: ${err.message}`) + '\n');
|
|
6
|
+
process.stderr.write(chalk.yellow('Run `ccam auth login` to re-authenticate.') + '\n');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
if (err instanceof CcamNotFoundError) {
|
|
10
|
+
process.stderr.write(chalk.red(`Error: ${err.message}`) + '\n');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (err instanceof CcamError) {
|
|
14
|
+
let message = chalk.red(`Error: ${err.message}`);
|
|
15
|
+
if (err.code) {
|
|
16
|
+
message += chalk.dim(` (${err.code})`);
|
|
17
|
+
}
|
|
18
|
+
process.stderr.write(message + '\n');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (err instanceof Error) {
|
|
22
|
+
process.stderr.write(chalk.red(`Error: ${err.message}`) + '\n');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
process.stderr.write(chalk.red(`Error: ${String(err)}`) + '\n');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=error-handler.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { extractColumns, getNestedValue } from './shared.js';
|
|
2
|
+
export function formatCsv(data, fields) {
|
|
3
|
+
return formatDelimited(data, ',', fields, true);
|
|
4
|
+
}
|
|
5
|
+
export function formatTsv(data, fields) {
|
|
6
|
+
return formatDelimited(data, '\t', fields, false);
|
|
7
|
+
}
|
|
8
|
+
function formatDelimited(data, delimiter, fields, quote = false) {
|
|
9
|
+
let items;
|
|
10
|
+
if (!Array.isArray(data)) {
|
|
11
|
+
items = [data];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
items = data;
|
|
15
|
+
}
|
|
16
|
+
if (items.length === 0) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
// Determine columns
|
|
20
|
+
const columns = fields || extractColumns(items);
|
|
21
|
+
if (columns.length === 0) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
// Build header row
|
|
25
|
+
const header = columns.map((col) => (quote ? escapeCsv(col) : col)).join(delimiter);
|
|
26
|
+
// Build data rows
|
|
27
|
+
const rows = items.map((item) => {
|
|
28
|
+
return columns
|
|
29
|
+
.map((col) => {
|
|
30
|
+
const value = getNestedValue(item, col);
|
|
31
|
+
const stringValue = formatValue(value);
|
|
32
|
+
return quote ? escapeCsv(stringValue) : stringValue;
|
|
33
|
+
})
|
|
34
|
+
.join(delimiter);
|
|
35
|
+
});
|
|
36
|
+
return [header, ...rows].join('\n');
|
|
37
|
+
}
|
|
38
|
+
function formatValue(value) {
|
|
39
|
+
if (value === null || value === undefined) {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return value.join(';');
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === 'object') {
|
|
46
|
+
return JSON.stringify(value);
|
|
47
|
+
}
|
|
48
|
+
return String(value);
|
|
49
|
+
}
|
|
50
|
+
function escapeCsv(value) {
|
|
51
|
+
// Quote if contains comma, double quote, or newline
|
|
52
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
53
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=csv.js.map
|