@contentstack/cli-cm-export-to-csv 1.10.1 → 1.11.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 +1 -1
- package/bin/dev.cmd +2 -0
- package/bin/dev.js +7 -0
- package/bin/run.cmd +2 -0
- package/bin/run.js +7 -0
- package/lib/base-command.d.ts +40 -0
- package/lib/base-command.js +48 -0
- package/lib/commands/cm/export-to-csv.d.ts +52 -0
- package/lib/commands/cm/export-to-csv.js +581 -0
- package/lib/config/index.d.ts +9 -0
- package/lib/config/index.js +11 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +19 -0
- package/lib/messages/index.d.ts +56 -0
- package/lib/messages/index.js +73 -0
- package/lib/types/index.d.ts +591 -0
- package/lib/types/index.js +8 -0
- package/lib/utils/api-client.d.ts +81 -0
- package/lib/utils/api-client.js +523 -0
- package/lib/utils/csv-writer.d.ts +24 -0
- package/lib/utils/csv-writer.js +70 -0
- package/lib/utils/data-transform.d.ts +66 -0
- package/lib/utils/data-transform.js +348 -0
- package/lib/utils/error-handler.d.ts +35 -0
- package/lib/utils/error-handler.js +111 -0
- package/lib/utils/index.d.ts +10 -0
- package/lib/utils/index.js +73 -0
- package/lib/utils/interactive.d.ts +41 -0
- package/lib/utils/interactive.js +336 -0
- package/lib/utils/teams-export.d.ts +24 -0
- package/lib/utils/teams-export.js +173 -0
- package/oclif.manifest.json +171 -0
- package/package.json +41 -28
- package/src/commands/cm/export-to-csv.js +0 -523
- package/src/util/client.js +0 -0
- package/src/util/config.js +0 -13
- package/src/util/index.js +0 -1313
package/src/util/index.js
DELETED
|
@@ -1,1313 +0,0 @@
|
|
|
1
|
-
const os = require('os');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const mkdirp = require('mkdirp');
|
|
4
|
-
const find = require('lodash/find');
|
|
5
|
-
const cloneDeep = require('lodash/cloneDeep');
|
|
6
|
-
const omit = require('lodash/omit');
|
|
7
|
-
const flat = require('lodash/flatten');
|
|
8
|
-
const fastcsv = require('fast-csv');
|
|
9
|
-
const inquirer = require('inquirer');
|
|
10
|
-
const debug = require('debug')('export-to-csv');
|
|
11
|
-
const checkboxPlus = require('inquirer-checkbox-plus-prompt');
|
|
12
|
-
const config = require('./config.js');
|
|
13
|
-
const {
|
|
14
|
-
cliux,
|
|
15
|
-
configHandler,
|
|
16
|
-
HttpClient,
|
|
17
|
-
messageHandler,
|
|
18
|
-
managementSDKClient,
|
|
19
|
-
ContentstackClient,
|
|
20
|
-
} = require('@contentstack/cli-utilities');
|
|
21
|
-
|
|
22
|
-
const directory = './data';
|
|
23
|
-
const delimeter = os.platform() === 'win32' ? '\\' : '/';
|
|
24
|
-
|
|
25
|
-
// Register checkbox-plus here.
|
|
26
|
-
inquirer.registerPrompt('checkbox-plus', checkboxPlus);
|
|
27
|
-
|
|
28
|
-
function chooseOrganization(managementAPIClient, action) {
|
|
29
|
-
return new Promise(async (resolve, reject) => {
|
|
30
|
-
try {
|
|
31
|
-
let organizations;
|
|
32
|
-
if (action === config.exportUsers || action === config.exportTeams || action === 'teams') {
|
|
33
|
-
organizations = await getOrganizationsWhereUserIsAdmin(managementAPIClient);
|
|
34
|
-
} else {
|
|
35
|
-
organizations = await getOrganizations(managementAPIClient);
|
|
36
|
-
}
|
|
37
|
-
let orgList = Object.keys(organizations);
|
|
38
|
-
orgList.push(config.cancelString);
|
|
39
|
-
let _chooseOrganization = [
|
|
40
|
-
{
|
|
41
|
-
type: 'list',
|
|
42
|
-
name: 'chosenOrg',
|
|
43
|
-
message: 'Choose an Organization',
|
|
44
|
-
choices: orgList,
|
|
45
|
-
loop: false,
|
|
46
|
-
},
|
|
47
|
-
];
|
|
48
|
-
inquirer
|
|
49
|
-
.prompt(_chooseOrganization)
|
|
50
|
-
.then(({ chosenOrg }) => {
|
|
51
|
-
if (chosenOrg === config.cancelString) exitProgram();
|
|
52
|
-
resolve({ name: chosenOrg, uid: organizations[chosenOrg] });
|
|
53
|
-
})
|
|
54
|
-
.catch(reject);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
reject(error);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function getOrganizations(managementAPIClient) {
|
|
62
|
-
try {
|
|
63
|
-
return await getOrganizationList(managementAPIClient, { skip: 0, page: 1, limit: 100 }, []);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function getOrganizationList(managementAPIClient, params, result = []) {
|
|
70
|
-
let organizations;
|
|
71
|
-
const configOrgUid = configHandler.get('oauthOrgUid');
|
|
72
|
-
|
|
73
|
-
if (configOrgUid) {
|
|
74
|
-
organizations = await managementAPIClient.organization(configOrgUid).fetch();
|
|
75
|
-
result = result.concat([organizations]);
|
|
76
|
-
} else {
|
|
77
|
-
organizations = await managementAPIClient.organization().fetchAll({ limit: 100 });
|
|
78
|
-
result = result.concat(organizations.items);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!organizations.items || (organizations.items && organizations.items.length < params.limit)) {
|
|
82
|
-
const orgMap = {};
|
|
83
|
-
for (const org of result) {
|
|
84
|
-
orgMap[org.name] = org.uid;
|
|
85
|
-
}
|
|
86
|
-
return orgMap;
|
|
87
|
-
} else {
|
|
88
|
-
params.skip = params.page * params.limit;
|
|
89
|
-
params.page++;
|
|
90
|
-
await wait(200);
|
|
91
|
-
return getOrganizationList(managementAPIClient, params, result);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function getOrganizationsWhereUserIsAdmin(managementAPIClient) {
|
|
96
|
-
try {
|
|
97
|
-
let result = {};
|
|
98
|
-
const configOrgUid = configHandler.get('oauthOrgUid');
|
|
99
|
-
|
|
100
|
-
if (configOrgUid) {
|
|
101
|
-
const response = await managementAPIClient.organization(configOrgUid).fetch();
|
|
102
|
-
result[response.name] = response.uid;
|
|
103
|
-
} else {
|
|
104
|
-
const response = await managementAPIClient.getUser({ include_orgs_roles: true });
|
|
105
|
-
const organizations = response.organizations.filter((org) => {
|
|
106
|
-
if (org.org_roles) {
|
|
107
|
-
const org_role = org.org_roles.shift();
|
|
108
|
-
return org_role.admin;
|
|
109
|
-
}
|
|
110
|
-
return org.is_owner === true;
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
organizations.forEach((org) => {
|
|
114
|
-
result[org.name] = org.uid;
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return result;
|
|
119
|
-
} catch (error) {
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function chooseStack(managementAPIClient, orgUid, stackApiKey) {
|
|
125
|
-
return new Promise(async (resolve, reject) => {
|
|
126
|
-
try {
|
|
127
|
-
let stacks = await getStacks(managementAPIClient, orgUid);
|
|
128
|
-
|
|
129
|
-
if (stackApiKey) {
|
|
130
|
-
const stackName = Object.keys(stacks).find((key) => stacks[key] === stackApiKey);
|
|
131
|
-
|
|
132
|
-
if (stackName) {
|
|
133
|
-
resolve({ name: stackName, apiKey: stackApiKey });
|
|
134
|
-
} else {
|
|
135
|
-
throw new Error('Could not find stack');
|
|
136
|
-
}
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
let stackList = Object.keys(stacks);
|
|
141
|
-
stackList.push(config.cancelString);
|
|
142
|
-
let _chooseStack = [
|
|
143
|
-
{
|
|
144
|
-
type: 'list',
|
|
145
|
-
name: 'chosenStack',
|
|
146
|
-
message: 'Choose a Stack',
|
|
147
|
-
choices: stackList,
|
|
148
|
-
},
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
inquirer
|
|
152
|
-
.prompt(_chooseStack)
|
|
153
|
-
.then(({ chosenStack }) => {
|
|
154
|
-
if (chosenStack === config.cancelString) exitProgram();
|
|
155
|
-
resolve({ name: chosenStack, apiKey: stacks[chosenStack] });
|
|
156
|
-
})
|
|
157
|
-
.catch(reject);
|
|
158
|
-
} catch (error) {
|
|
159
|
-
reject(error);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function chooseBranch(branchList) {
|
|
165
|
-
try {
|
|
166
|
-
const branchesArray = branchList.map((branch) => branch.uid);
|
|
167
|
-
|
|
168
|
-
let _chooseBranch = [
|
|
169
|
-
{
|
|
170
|
-
type: 'list',
|
|
171
|
-
name: 'branch',
|
|
172
|
-
message: 'Choose a Branch',
|
|
173
|
-
choices: branchesArray,
|
|
174
|
-
},
|
|
175
|
-
];
|
|
176
|
-
return await inquirer.prompt(_chooseBranch);
|
|
177
|
-
} catch (err) {
|
|
178
|
-
cliux.error(err);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function getStacks(managementAPIClient, orgUid) {
|
|
183
|
-
return new Promise((resolve, reject) => {
|
|
184
|
-
let result = {};
|
|
185
|
-
managementAPIClient
|
|
186
|
-
.stack({ organization_uid: orgUid })
|
|
187
|
-
.query({ query: {} })
|
|
188
|
-
.find()
|
|
189
|
-
.then((stacks) => {
|
|
190
|
-
stacks.items.forEach((stack) => {
|
|
191
|
-
result[stack.name] = stack.api_key;
|
|
192
|
-
});
|
|
193
|
-
resolve(result);
|
|
194
|
-
})
|
|
195
|
-
.catch((error) => {
|
|
196
|
-
reject(error);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function chooseContentType(stackAPIClient, skip) {
|
|
202
|
-
return new Promise(async (resolve, reject) => {
|
|
203
|
-
let contentTypes = await getContentTypes(stackAPIClient, skip);
|
|
204
|
-
let contentTypesList = Object.values(contentTypes);
|
|
205
|
-
let _chooseContentType = [
|
|
206
|
-
{
|
|
207
|
-
type: 'checkbox',
|
|
208
|
-
message: 'Choose Content Type (Press Space to select the content types) ',
|
|
209
|
-
choices: contentTypesList,
|
|
210
|
-
name: 'chosenContentTypes',
|
|
211
|
-
loop: false,
|
|
212
|
-
},
|
|
213
|
-
];
|
|
214
|
-
|
|
215
|
-
inquirer
|
|
216
|
-
.prompt(_chooseContentType)
|
|
217
|
-
.then(({ chosenContentTypes }) => resolve(chosenContentTypes))
|
|
218
|
-
.catch(reject);
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function chooseInMemContentTypes(contentTypesList) {
|
|
223
|
-
return new Promise((resolve, reject) => {
|
|
224
|
-
let _chooseContentType = [
|
|
225
|
-
{
|
|
226
|
-
type: 'checkbox-plus',
|
|
227
|
-
message: 'Choose Content Type (Press Space to select the content types)',
|
|
228
|
-
choices: contentTypesList,
|
|
229
|
-
name: 'chosenContentTypes',
|
|
230
|
-
loop: false,
|
|
231
|
-
highlight: true,
|
|
232
|
-
searchable: true,
|
|
233
|
-
source: (_, input) => {
|
|
234
|
-
input = input || '';
|
|
235
|
-
const inputArray = input.split(' ');
|
|
236
|
-
return new Promise((resolveSource) => {
|
|
237
|
-
const contentTypes = contentTypesList.filter((contentType) => {
|
|
238
|
-
let shouldInclude = true;
|
|
239
|
-
inputArray.forEach((inputChunk) => {
|
|
240
|
-
// if any term to filter by doesn't exist, exclude
|
|
241
|
-
if (!contentType.toLowerCase().includes(inputChunk.toLowerCase())) {
|
|
242
|
-
shouldInclude = false;
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
return shouldInclude;
|
|
246
|
-
});
|
|
247
|
-
resolveSource(contentTypes);
|
|
248
|
-
});
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
];
|
|
252
|
-
inquirer
|
|
253
|
-
.prompt(_chooseContentType)
|
|
254
|
-
.then(({ chosenContentTypes }) => {
|
|
255
|
-
if (chosenContentTypes.length === 0) {
|
|
256
|
-
reject('Please select atleast one content type.');
|
|
257
|
-
}
|
|
258
|
-
resolve(chosenContentTypes);
|
|
259
|
-
})
|
|
260
|
-
.catch(reject);
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function getContentTypes(stackAPIClient, skip) {
|
|
265
|
-
return new Promise((resolve, reject) => {
|
|
266
|
-
let result = {};
|
|
267
|
-
stackAPIClient
|
|
268
|
-
.contentType()
|
|
269
|
-
.query({ skip: skip * 100, include_branch: true })
|
|
270
|
-
.find()
|
|
271
|
-
.then((contentTypes) => {
|
|
272
|
-
contentTypes.items.forEach((contentType) => {
|
|
273
|
-
result[contentType.title] = contentType.uid;
|
|
274
|
-
});
|
|
275
|
-
resolve(result);
|
|
276
|
-
})
|
|
277
|
-
.catch((error) => {
|
|
278
|
-
reject(error);
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function chooseLanguage(stackAPIClient) {
|
|
284
|
-
return new Promise(async (resolve, reject) => {
|
|
285
|
-
let languages = await getLanguages(stackAPIClient);
|
|
286
|
-
let languagesList = Object.keys(languages);
|
|
287
|
-
languagesList.push(config.cancelString);
|
|
288
|
-
|
|
289
|
-
let _chooseLanguage = [
|
|
290
|
-
{
|
|
291
|
-
type: 'list',
|
|
292
|
-
message: 'Choose Language',
|
|
293
|
-
choices: languagesList,
|
|
294
|
-
name: 'chosenLanguage',
|
|
295
|
-
},
|
|
296
|
-
];
|
|
297
|
-
|
|
298
|
-
inquirer
|
|
299
|
-
.prompt(_chooseLanguage)
|
|
300
|
-
.then(({ chosenLanguage }) => {
|
|
301
|
-
if (chosenLanguage === config.cancelString) exitProgram();
|
|
302
|
-
resolve({ name: chosenLanguage, code: languages[chosenLanguage] });
|
|
303
|
-
})
|
|
304
|
-
.catch(reject);
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function getLanguages(stackAPIClient) {
|
|
309
|
-
return new Promise((resolve, reject) => {
|
|
310
|
-
let result = {};
|
|
311
|
-
stackAPIClient
|
|
312
|
-
.locale()
|
|
313
|
-
.query()
|
|
314
|
-
.find()
|
|
315
|
-
.then((languages) => {
|
|
316
|
-
languages.items.forEach((language) => {
|
|
317
|
-
result[language.name] = language.code;
|
|
318
|
-
});
|
|
319
|
-
resolve(result);
|
|
320
|
-
})
|
|
321
|
-
.catch((error) => reject(error));
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function getEntries(stackAPIClient, contentType, language, skip, limit) {
|
|
326
|
-
return new Promise((resolve, reject) => {
|
|
327
|
-
stackAPIClient
|
|
328
|
-
.contentType(contentType)
|
|
329
|
-
.entry()
|
|
330
|
-
.query({
|
|
331
|
-
include_publish_details: true,
|
|
332
|
-
locale: language,
|
|
333
|
-
skip: skip * 100,
|
|
334
|
-
limit: limit,
|
|
335
|
-
include_workflow: true,
|
|
336
|
-
})
|
|
337
|
-
.find()
|
|
338
|
-
.then((entries) => resolve(entries))
|
|
339
|
-
.catch((error) => reject(error));
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function getEntriesCount(stackAPIClient, contentType, language) {
|
|
344
|
-
return new Promise((resolve, reject) => {
|
|
345
|
-
stackAPIClient
|
|
346
|
-
.contentType(contentType)
|
|
347
|
-
.entry()
|
|
348
|
-
.query({ include_publish_details: true, locale: language })
|
|
349
|
-
.count()
|
|
350
|
-
.then((entriesData) => resolve(entriesData.entries))
|
|
351
|
-
.catch((error) => reject(formatError(error)));
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function getEnvironments(stackAPIClient) {
|
|
356
|
-
let result = {};
|
|
357
|
-
return stackAPIClient
|
|
358
|
-
.environment()
|
|
359
|
-
.query()
|
|
360
|
-
.find()
|
|
361
|
-
.then((environments) => {
|
|
362
|
-
environments.items.forEach((env) => {
|
|
363
|
-
result[env['uid']] = env['name'];
|
|
364
|
-
});
|
|
365
|
-
return result;
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function getContentTypeCount(stackAPIClient) {
|
|
370
|
-
return new Promise((resolve, reject) => {
|
|
371
|
-
stackAPIClient
|
|
372
|
-
.contentType()
|
|
373
|
-
.query()
|
|
374
|
-
.count()
|
|
375
|
-
.then((contentTypes) => resolve(contentTypes.content_types))
|
|
376
|
-
.catch((error) => reject(error));
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function exitProgram() {
|
|
381
|
-
debug('Exiting...');
|
|
382
|
-
// eslint-disable-next-line no-undef
|
|
383
|
-
process.exit();
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function sanitizeData(flatData) {
|
|
387
|
-
// sanitize against CSV Injections
|
|
388
|
-
const CSVRegex = /^[\\+\\=@\\-]/;
|
|
389
|
-
for (key in flatData) {
|
|
390
|
-
if (typeof flatData[key] === 'string' && flatData[key].match(CSVRegex)) {
|
|
391
|
-
flatData[key] = flatData[key].replace(/\"/g, "\"\"");
|
|
392
|
-
flatData[key] = `"'${flatData[key]}"`;
|
|
393
|
-
} else if (typeof flatData[key] === 'object') {
|
|
394
|
-
// convert any objects or arrays to string
|
|
395
|
-
// to store this data correctly in csv
|
|
396
|
-
flatData[key] = JSON.stringify(flatData[key]);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return flatData;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function cleanEntries(entries, language, environments, contentTypeUid) {
|
|
403
|
-
const filteredEntries = entries.filter((entry) => {
|
|
404
|
-
return entry['locale'] === language;
|
|
405
|
-
});
|
|
406
|
-
return filteredEntries.map((entry) => {
|
|
407
|
-
let workflow = '';
|
|
408
|
-
const envArr = [];
|
|
409
|
-
if (entry?.publish_details?.length) {
|
|
410
|
-
entry.publish_details.forEach((env) => {
|
|
411
|
-
envArr.push(JSON.stringify([environments[env['environment']], env['locale'], env['time']]));
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
delete entry.publish_details;
|
|
416
|
-
delete entry.setWorkflowStage;
|
|
417
|
-
if ('_workflow' in entry) {
|
|
418
|
-
if (entry._workflow?.name) {
|
|
419
|
-
workflow = entry['_workflow']['name'];
|
|
420
|
-
delete entry['_workflow'];
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
entry = flatten(entry);
|
|
424
|
-
entry = sanitizeData(entry);
|
|
425
|
-
entry['publish_details'] = envArr;
|
|
426
|
-
entry['_workflow'] = workflow;
|
|
427
|
-
entry['ACL'] = JSON.stringify({}); // setting ACL to empty obj
|
|
428
|
-
entry['content_type_uid'] = contentTypeUid; // content_type_uid is being returned as 'uid' from the sdk for some reason
|
|
429
|
-
|
|
430
|
-
// entry['url'] might also be wrong
|
|
431
|
-
delete entry.stackHeaders;
|
|
432
|
-
delete entry.update;
|
|
433
|
-
delete entry.delete;
|
|
434
|
-
delete entry.fetch;
|
|
435
|
-
delete entry.publish;
|
|
436
|
-
delete entry.unpublish;
|
|
437
|
-
delete entry.import;
|
|
438
|
-
delete entry.publishRequest;
|
|
439
|
-
return entry;
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function getDateTime() {
|
|
444
|
-
let date = new Date();
|
|
445
|
-
let dateTime = date.toLocaleString().split(',');
|
|
446
|
-
dateTime[0] = dateTime[0].split('/').join('-');
|
|
447
|
-
dateTime[1] = dateTime[1].trim(); // trim the space before time
|
|
448
|
-
dateTime[1] = dateTime[1].split(' ').join('');
|
|
449
|
-
return dateTime.join('_');
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function write(command, entries, fileName, message, delimiter, headers) {
|
|
453
|
-
// eslint-disable-next-line no-undef
|
|
454
|
-
if (process.cwd().split(delimeter).pop() !== 'data' && !fs.existsSync(directory)) {
|
|
455
|
-
mkdirp.sync(directory);
|
|
456
|
-
}
|
|
457
|
-
// eslint-disable-next-line no-undef
|
|
458
|
-
if (process.cwd().split(delimeter).pop() !== 'data') {
|
|
459
|
-
// eslint-disable-next-line no-undef
|
|
460
|
-
process.chdir(directory);
|
|
461
|
-
}
|
|
462
|
-
// eslint-disable-next-line no-undef
|
|
463
|
-
cliux.print(`Writing ${message} to file: "${process.cwd()}${delimeter}${fileName}"`);
|
|
464
|
-
if (headers?.length) fastcsv.writeToPath(fileName, entries, { headers, delimiter });
|
|
465
|
-
else fastcsv.writeToPath(fileName, entries, { headers: true, delimiter });
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function startupQuestions() {
|
|
469
|
-
return new Promise((resolve, reject) => {
|
|
470
|
-
let actions = [
|
|
471
|
-
{
|
|
472
|
-
type: 'list',
|
|
473
|
-
name: 'action',
|
|
474
|
-
message: 'Choose Action',
|
|
475
|
-
choices: [config.exportEntries, config.exportUsers, config.exportTeams, config.exportTaxonomies, 'Exit'],
|
|
476
|
-
},
|
|
477
|
-
];
|
|
478
|
-
inquirer
|
|
479
|
-
.prompt(actions)
|
|
480
|
-
.then((answers) => {
|
|
481
|
-
if (answers.action === 'Exit') exitProgram();
|
|
482
|
-
resolve(answers.action);
|
|
483
|
-
})
|
|
484
|
-
.catch(reject);
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function chooseFallbackOptions(stackAPIClient) {
|
|
489
|
-
return new Promise(async (resolve, reject) => {
|
|
490
|
-
try {
|
|
491
|
-
const questions = [
|
|
492
|
-
{
|
|
493
|
-
type: 'confirm',
|
|
494
|
-
name: 'includeFallback',
|
|
495
|
-
message: 'Include fallback locale data when exporting taxonomies?',
|
|
496
|
-
default: false,
|
|
497
|
-
},
|
|
498
|
-
];
|
|
499
|
-
|
|
500
|
-
const { includeFallback } = await inquirer.prompt(questions);
|
|
501
|
-
|
|
502
|
-
let fallbackLocale = null;
|
|
503
|
-
|
|
504
|
-
if (includeFallback) {
|
|
505
|
-
// Get available languages for fallback locale selection
|
|
506
|
-
const languages = await getLanguages(stackAPIClient);
|
|
507
|
-
const languagesList = Object.keys(languages);
|
|
508
|
-
|
|
509
|
-
const fallbackQuestion = [
|
|
510
|
-
{
|
|
511
|
-
type: 'list',
|
|
512
|
-
name: 'selectedFallbackLocale',
|
|
513
|
-
message: 'Choose fallback locale',
|
|
514
|
-
choices: languagesList,
|
|
515
|
-
},
|
|
516
|
-
];
|
|
517
|
-
|
|
518
|
-
const { selectedFallbackLocale } = await inquirer.prompt(fallbackQuestion);
|
|
519
|
-
fallbackLocale = languages[selectedFallbackLocale];
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
resolve({
|
|
523
|
-
includeFallback,
|
|
524
|
-
fallbackLocale,
|
|
525
|
-
});
|
|
526
|
-
} catch (error) {
|
|
527
|
-
reject(error);
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function getOrgUsers(managementAPIClient, orgUid) {
|
|
533
|
-
return new Promise((resolve, reject) => {
|
|
534
|
-
managementAPIClient
|
|
535
|
-
.getUser({ include_orgs_roles: true })
|
|
536
|
-
.then(async (response) => {
|
|
537
|
-
let organization = response.organizations.filter((org) => org.uid === orgUid).pop();
|
|
538
|
-
if (!organization) return reject(new Error('Org UID not found.'));
|
|
539
|
-
if (organization.is_owner === true) {
|
|
540
|
-
return managementAPIClient
|
|
541
|
-
.organization(organization.uid)
|
|
542
|
-
.getInvitations()
|
|
543
|
-
.then((data) => {
|
|
544
|
-
resolve({ items: data.items });
|
|
545
|
-
})
|
|
546
|
-
.catch(reject);
|
|
547
|
-
}
|
|
548
|
-
if (!organization.getInvitations && !find(organization.org_roles, 'admin')) {
|
|
549
|
-
return reject(new Error(config.adminError));
|
|
550
|
-
}
|
|
551
|
-
try {
|
|
552
|
-
const users = await getUsers(managementAPIClient, organization, { skip: 0, page: 1, limit: 100 });
|
|
553
|
-
return resolve({ items: users });
|
|
554
|
-
} catch (error) {
|
|
555
|
-
return reject(error);
|
|
556
|
-
}
|
|
557
|
-
})
|
|
558
|
-
.catch((error) => reject(error));
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
async function getUsers(managementAPIClient, organization, params, result = []) {
|
|
563
|
-
try {
|
|
564
|
-
const users = await managementAPIClient.organization(organization.uid).getInvitations(params);
|
|
565
|
-
if (!users.items || (users.items && !users.items.length)) {
|
|
566
|
-
return result;
|
|
567
|
-
} else {
|
|
568
|
-
result = result.concat(users.items);
|
|
569
|
-
params.skip = params.page * params.limit;
|
|
570
|
-
params.page++;
|
|
571
|
-
await wait(200);
|
|
572
|
-
return getUsers(managementAPIClient, organization, params, result);
|
|
573
|
-
}
|
|
574
|
-
} catch (error) {}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function getMappedUsers(users) {
|
|
578
|
-
let mappedUsers = {};
|
|
579
|
-
users.items.forEach((user) => {
|
|
580
|
-
mappedUsers[user.user_uid] = user.email;
|
|
581
|
-
});
|
|
582
|
-
mappedUsers['System'] = 'System';
|
|
583
|
-
return mappedUsers;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function getMappedRoles(roles) {
|
|
587
|
-
let mappedRoles = {};
|
|
588
|
-
roles.items.forEach((role) => {
|
|
589
|
-
mappedRoles[role.uid] = role.name;
|
|
590
|
-
});
|
|
591
|
-
return mappedRoles;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function getOrgRoles(managementAPIClient, orgUid, ecsv) {
|
|
595
|
-
return new Promise((resolve, reject) => {
|
|
596
|
-
managementAPIClient
|
|
597
|
-
.getUser({ include_orgs_roles: true })
|
|
598
|
-
.then((response) => {
|
|
599
|
-
let organization = response.organizations.filter((org) => org.uid === orgUid).pop();
|
|
600
|
-
if (organization.is_owner === true) {
|
|
601
|
-
return managementAPIClient
|
|
602
|
-
.organization(organization.uid)
|
|
603
|
-
.roles()
|
|
604
|
-
.then((roles) => {
|
|
605
|
-
resolve({ items: roles.items });
|
|
606
|
-
})
|
|
607
|
-
.catch(reject);
|
|
608
|
-
}
|
|
609
|
-
if (!organization.roles && !find(organization.org_roles, 'admin')) {
|
|
610
|
-
return reject(new Error(config.adminError));
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
managementAPIClient
|
|
614
|
-
.organization(organization.uid)
|
|
615
|
-
.roles()
|
|
616
|
-
.then((roles) => {
|
|
617
|
-
resolve({ items: roles.items });
|
|
618
|
-
})
|
|
619
|
-
.catch(reject);
|
|
620
|
-
})
|
|
621
|
-
.catch((error) => reject(error));
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function determineUserOrgRole(user, roles) {
|
|
626
|
-
let roleName = 'No Role';
|
|
627
|
-
let roleUid = user.org_roles || [];
|
|
628
|
-
if (roleUid.length > 0) {
|
|
629
|
-
roleUid = roleUid.shift();
|
|
630
|
-
roleName = roles[roleUid];
|
|
631
|
-
}
|
|
632
|
-
if (user.is_owner) {
|
|
633
|
-
roleName = 'Owner';
|
|
634
|
-
}
|
|
635
|
-
return roleName;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function cleanOrgUsers(orgUsers, mappedUsers, mappedRoles) {
|
|
639
|
-
const userList = [];
|
|
640
|
-
orgUsers.items.forEach((user) => {
|
|
641
|
-
let invitedBy;
|
|
642
|
-
let formattedUser = {};
|
|
643
|
-
try {
|
|
644
|
-
invitedBy = mappedUsers[user['invited_by']];
|
|
645
|
-
} catch (error) {
|
|
646
|
-
invitedBy = 'System';
|
|
647
|
-
}
|
|
648
|
-
formattedUser['Email'] = user['email'];
|
|
649
|
-
formattedUser['User UID'] = user['user_uid'];
|
|
650
|
-
formattedUser['Organization Role'] = determineUserOrgRole(user, mappedRoles);
|
|
651
|
-
formattedUser['Status'] = user['status'];
|
|
652
|
-
formattedUser['Invited By'] = invitedBy;
|
|
653
|
-
formattedUser['Created Time'] = getFormattedDate(user['created_at']);
|
|
654
|
-
formattedUser['Updated Time'] = getFormattedDate(user['updated_at']);
|
|
655
|
-
userList.push(formattedUser);
|
|
656
|
-
});
|
|
657
|
-
return userList;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function kebabize(str) {
|
|
661
|
-
return str
|
|
662
|
-
.split(' ')
|
|
663
|
-
.map((word) => word.toLowerCase())
|
|
664
|
-
.join('-');
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function getFormattedDate(date) {
|
|
668
|
-
if (!(date instanceof Date)) {
|
|
669
|
-
date = new Date(date);
|
|
670
|
-
}
|
|
671
|
-
const year = date.getFullYear();
|
|
672
|
-
const month = (1 + date.getMonth()).toString().padStart(2, '0');
|
|
673
|
-
const day = date.getDate().toString().padStart(2, '0');
|
|
674
|
-
return month + '/' + day + '/' + year;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// https://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects
|
|
678
|
-
function flatten(data) {
|
|
679
|
-
let result = {};
|
|
680
|
-
function recurse(cur, prop) {
|
|
681
|
-
if (Object(cur) !== cur) {
|
|
682
|
-
result[prop] = cur;
|
|
683
|
-
} else if (Array.isArray(cur)) {
|
|
684
|
-
let i, l;
|
|
685
|
-
for (i = 0, l = cur.length; i < l; i++) recurse(cur[i], prop + '[' + i + ']');
|
|
686
|
-
if (l == 0) result[prop] = [];
|
|
687
|
-
} else {
|
|
688
|
-
let isEmpty = true;
|
|
689
|
-
for (let p in cur) {
|
|
690
|
-
isEmpty = false;
|
|
691
|
-
recurse(cur[p], prop ? prop + '.' + p : p);
|
|
692
|
-
}
|
|
693
|
-
if (isEmpty && prop) result[prop] = {};
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
recurse(data, '');
|
|
697
|
-
return result;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
function formatError(error) {
|
|
701
|
-
try {
|
|
702
|
-
if (typeof error === 'string') {
|
|
703
|
-
error = JSON.parse(error);
|
|
704
|
-
} else {
|
|
705
|
-
error = JSON.parse(error.message);
|
|
706
|
-
}
|
|
707
|
-
} catch (e) {}
|
|
708
|
-
let message = error.errorMessage || error.error_message || error.message || error;
|
|
709
|
-
if (error.errors && Object.keys(error.errors).length > 0) {
|
|
710
|
-
Object.keys(error.errors).forEach((e) => {
|
|
711
|
-
let entity = e;
|
|
712
|
-
switch (e) {
|
|
713
|
-
case 'authorization':
|
|
714
|
-
entity = 'Management Token';
|
|
715
|
-
break;
|
|
716
|
-
case 'api_key':
|
|
717
|
-
entity = 'Stack API key';
|
|
718
|
-
break;
|
|
719
|
-
case 'uid':
|
|
720
|
-
entity = 'Content Type';
|
|
721
|
-
break;
|
|
722
|
-
case 'access_token':
|
|
723
|
-
entity = 'Delivery Token';
|
|
724
|
-
break;
|
|
725
|
-
}
|
|
726
|
-
message += ' ' + [entity, error.errors[e]].join(' ');
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
return message;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
function wait(time) {
|
|
733
|
-
return new Promise((res) => {
|
|
734
|
-
setTimeout(res, time);
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
function handleErrorMsg(err) {
|
|
739
|
-
cliux.print(`Error: ${(err?.errorMessage || err?.message) ?? messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, {
|
|
740
|
-
color: 'red',
|
|
741
|
-
});
|
|
742
|
-
process.exit(1);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* This function does the sdk calls to get all the teams in org
|
|
747
|
-
* @param {object} managementAPIClient
|
|
748
|
-
* @param {object} org
|
|
749
|
-
* @param {object} queryParam
|
|
750
|
-
* @returns
|
|
751
|
-
*/
|
|
752
|
-
async function getAllTeams(managementAPIClient, org, queryParam = {}) {
|
|
753
|
-
try {
|
|
754
|
-
return await managementAPIClient.organization(org.uid).teams().fetchAll(queryParam);
|
|
755
|
-
} catch (error) {
|
|
756
|
-
handleErrorMsg(error);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
* This function is used to handle the pagination and call the sdk
|
|
762
|
-
* @param {object} managementAPIClient
|
|
763
|
-
* @param {object} org
|
|
764
|
-
*/
|
|
765
|
-
async function exportOrgTeams(managementAPIClient, org) {
|
|
766
|
-
let allTeamsInOrg = [];
|
|
767
|
-
let skip = 0;
|
|
768
|
-
let limit = config?.limit || 100;
|
|
769
|
-
do {
|
|
770
|
-
const data = await getAllTeams(managementAPIClient, org, {
|
|
771
|
-
skip: skip,
|
|
772
|
-
limit: limit,
|
|
773
|
-
includeUserDetails: true,
|
|
774
|
-
});
|
|
775
|
-
skip += limit;
|
|
776
|
-
allTeamsInOrg.push(...data.items);
|
|
777
|
-
if (skip >= data?.count) break;
|
|
778
|
-
} while (1);
|
|
779
|
-
allTeamsInOrg = await cleanTeamsData(allTeamsInOrg, managementAPIClient, org);
|
|
780
|
-
return allTeamsInOrg;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
/**
|
|
784
|
-
* This function will get all the org level roles
|
|
785
|
-
* @param {object} managementAPIClient
|
|
786
|
-
* @param {object} org
|
|
787
|
-
*/
|
|
788
|
-
async function getOrgRolesForTeams(managementAPIClient, org) {
|
|
789
|
-
let roleMap = {}; // for org level there are two roles only admin and member
|
|
790
|
-
|
|
791
|
-
// SDK call to get the role UIDs
|
|
792
|
-
await managementAPIClient
|
|
793
|
-
.organization(org.uid)
|
|
794
|
-
.roles()
|
|
795
|
-
.then((roles) => {
|
|
796
|
-
roles.items.forEach((item) => {
|
|
797
|
-
if (item.name === 'member' || item.name === 'admin') {
|
|
798
|
-
roleMap[item.name] = item.uid;
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
})
|
|
802
|
-
.catch((err) => {
|
|
803
|
-
handleErrorMsg(err);
|
|
804
|
-
});
|
|
805
|
-
return roleMap;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
/**
|
|
809
|
-
* Removes the unnecessary fields from the objects in the data and assign org level roles to the team based on role uid
|
|
810
|
-
* @param {array} data
|
|
811
|
-
* @param {object} managementAPIClient
|
|
812
|
-
* @param {object} org
|
|
813
|
-
*/
|
|
814
|
-
async function cleanTeamsData(data, managementAPIClient, org) {
|
|
815
|
-
const roleMap = await getOrgRolesForTeams(managementAPIClient, org);
|
|
816
|
-
const fieldToBeDeleted = [
|
|
817
|
-
'_id',
|
|
818
|
-
'createdAt',
|
|
819
|
-
'createdBy',
|
|
820
|
-
'updatedAt',
|
|
821
|
-
'updatedBy',
|
|
822
|
-
'__v',
|
|
823
|
-
'createdByUserName',
|
|
824
|
-
'updatedByUserName',
|
|
825
|
-
'organizationUid',
|
|
826
|
-
'urlPath',
|
|
827
|
-
'update',
|
|
828
|
-
'delete',
|
|
829
|
-
'fetch',
|
|
830
|
-
'stackRoleMappings',
|
|
831
|
-
'teamUsers',
|
|
832
|
-
];
|
|
833
|
-
if (data?.length) {
|
|
834
|
-
return data.map((team) => {
|
|
835
|
-
team = omit(team, fieldToBeDeleted);
|
|
836
|
-
|
|
837
|
-
team.organizationRole = team.organizationRole === roleMap['member'] ? 'member' : 'admin';
|
|
838
|
-
|
|
839
|
-
if (!team.hasOwnProperty('description')) {
|
|
840
|
-
team.description = '';
|
|
841
|
-
}
|
|
842
|
-
team.Total_Members = team?.users?.length || 0;
|
|
843
|
-
|
|
844
|
-
return team;
|
|
845
|
-
});
|
|
846
|
-
} else {
|
|
847
|
-
return [];
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
/**
|
|
852
|
-
* This function is used to call all the other teams function to export the required files
|
|
853
|
-
* @param {object} managementAPIClient
|
|
854
|
-
* @param {object} organization
|
|
855
|
-
* @param {string} teamUid
|
|
856
|
-
* @param {character} delimiter
|
|
857
|
-
*/
|
|
858
|
-
async function exportTeams(managementAPIClient, organization, teamUid, delimiter) {
|
|
859
|
-
cliux.print(
|
|
860
|
-
`info: Exporting the ${
|
|
861
|
-
teamUid && organization?.name
|
|
862
|
-
? `team with uid ${teamUid} in Organisation ${organization?.name} `
|
|
863
|
-
: `teams of Organisation ` + organization?.name
|
|
864
|
-
}`,
|
|
865
|
-
{ color: 'blue' },
|
|
866
|
-
);
|
|
867
|
-
const allTeamsData = await exportOrgTeams(managementAPIClient, organization);
|
|
868
|
-
if (!allTeamsData?.length) {
|
|
869
|
-
cliux.print(
|
|
870
|
-
`info: The organization ${organization?.name} does not have any teams associated with it. Please verify and provide the correct organization name.`,
|
|
871
|
-
);
|
|
872
|
-
} else {
|
|
873
|
-
const modifiedTeam = cloneDeep(allTeamsData);
|
|
874
|
-
modifiedTeam.forEach((team) => {
|
|
875
|
-
delete team['users'];
|
|
876
|
-
delete team['stackRoleMapping'];
|
|
877
|
-
});
|
|
878
|
-
const fileName = `${kebabize(organization.name.replace(config.organizationNameRegex, ''))}_teams_export.csv`;
|
|
879
|
-
write(this, modifiedTeam, fileName, ' organization Team details', delimiter);
|
|
880
|
-
// exporting teams user data or a single team user data
|
|
881
|
-
cliux.print(
|
|
882
|
-
`info: Exporting the teams user data for ${teamUid ? `team ` + teamUid : `organisation ` + organization?.name}`,
|
|
883
|
-
{ color: 'blue' },
|
|
884
|
-
);
|
|
885
|
-
await getTeamsDetail(allTeamsData, organization, teamUid, delimiter);
|
|
886
|
-
cliux.print(
|
|
887
|
-
`info: Exporting the stack role details for ${
|
|
888
|
-
teamUid ? `team ` + teamUid : `organisation ` + organization?.name
|
|
889
|
-
}`,
|
|
890
|
-
{ color: 'blue' },
|
|
891
|
-
);
|
|
892
|
-
// Exporting the stack Role data for all the teams or exporting stack role data for a single team
|
|
893
|
-
await exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
/**
|
|
898
|
-
* This function is used to get individual team user details and write to file
|
|
899
|
-
* @param {array} allTeamsData
|
|
900
|
-
* @param {object} organization
|
|
901
|
-
* @param {string} teamUid optional
|
|
902
|
-
* @param {character} delimiter
|
|
903
|
-
*/
|
|
904
|
-
async function getTeamsDetail(allTeamsData, organization, teamUid, delimiter) {
|
|
905
|
-
if (!teamUid) {
|
|
906
|
-
const userData = await getTeamsUserDetails(allTeamsData);
|
|
907
|
-
const fileName = `${kebabize(
|
|
908
|
-
organization.name.replace(config.organizationNameRegex, ''),
|
|
909
|
-
)}_team_User_Details_export.csv`;
|
|
910
|
-
|
|
911
|
-
write(this, userData, fileName, 'Team User details', delimiter);
|
|
912
|
-
} else {
|
|
913
|
-
const team = allTeamsData.filter((team) => team.uid === teamUid)[0];
|
|
914
|
-
team.users.forEach((user) => {
|
|
915
|
-
user['team-name'] = team.name;
|
|
916
|
-
user['team-uid'] = team.uid;
|
|
917
|
-
delete user['active'];
|
|
918
|
-
delete user['orgInvitationStatus'];
|
|
919
|
-
});
|
|
920
|
-
const fileName = `${kebabize(
|
|
921
|
-
organization.name.replace(config.organizationNameRegex, ''),
|
|
922
|
-
)}_team_${teamUid}_User_Details_export.csv`;
|
|
923
|
-
|
|
924
|
-
write(this, team.users, fileName, 'Team User details', delimiter);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* This will export the role mappings of the team, for which stack the team has which role
|
|
930
|
-
* @param {object} managementAPIClient
|
|
931
|
-
* @param {array} allTeamsData Data for all the teams in the stack
|
|
932
|
-
* @param {string} teamUid for a particular team who's data we want
|
|
933
|
-
* @param {character} delimiter
|
|
934
|
-
*/
|
|
935
|
-
async function exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter) {
|
|
936
|
-
let stackRoleWithTeamData = [];
|
|
937
|
-
let flag = false;
|
|
938
|
-
const stackNotAdmin = [];
|
|
939
|
-
if (teamUid) {
|
|
940
|
-
const team = find(allTeamsData, function (teamObject) {
|
|
941
|
-
return teamObject?.uid === teamUid;
|
|
942
|
-
});
|
|
943
|
-
for (const stack of team?.stackRoleMapping) {
|
|
944
|
-
const roleData = await mapRoleWithTeams(managementAPIClient, stack, team?.name, team?.uid);
|
|
945
|
-
stackRoleWithTeamData.push(...roleData);
|
|
946
|
-
if (roleData[0]['Stack Name'] === '') {
|
|
947
|
-
flag = true;
|
|
948
|
-
stackNotAdmin.push(stack.stackApiKey);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
} else {
|
|
952
|
-
for (const team of allTeamsData ?? []) {
|
|
953
|
-
for (const stack of team?.stackRoleMapping ?? []) {
|
|
954
|
-
const roleData = await mapRoleWithTeams(managementAPIClient, stack, team?.name, team?.uid);
|
|
955
|
-
stackRoleWithTeamData.push(...roleData);
|
|
956
|
-
if (roleData[0]['Stack Name'] === '') {
|
|
957
|
-
flag = true;
|
|
958
|
-
stackNotAdmin.push(stack.stackApiKey);
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
if (stackNotAdmin?.length) {
|
|
964
|
-
cliux.print(
|
|
965
|
-
`warning: Admin access denied to the following stacks using the provided API keys. Please get in touch with the stack owner to request access.`,
|
|
966
|
-
{ color: 'yellow' },
|
|
967
|
-
);
|
|
968
|
-
cliux.print(`${stackNotAdmin.join(' , ')}`, { color: 'yellow' });
|
|
969
|
-
}
|
|
970
|
-
if (flag) {
|
|
971
|
-
let export_stack_role = [
|
|
972
|
-
{
|
|
973
|
-
type: 'list',
|
|
974
|
-
name: 'chooseExport',
|
|
975
|
-
message: `Access denied: Please confirm if you still want to continue exporting the data without the { Stack Name, Stack Uid, Role Name } fields.`,
|
|
976
|
-
choices: ['yes', 'no'],
|
|
977
|
-
loop: false,
|
|
978
|
-
},
|
|
979
|
-
];
|
|
980
|
-
try {
|
|
981
|
-
const exportStackRole = await inquirer.prompt(export_stack_role);
|
|
982
|
-
if (exportStackRole.chooseExport === 'no') {
|
|
983
|
-
process.exit(1);
|
|
984
|
-
}
|
|
985
|
-
} catch (error) {
|
|
986
|
-
cliux.print(error, { color: 'red' });
|
|
987
|
-
process.exit(1);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
const fileName = `${kebabize('Stack_Role_Mapping'.replace(config.organizationNameRegex, ''))}${
|
|
992
|
-
teamUid ? `_${teamUid}` : ''
|
|
993
|
-
}.csv`;
|
|
994
|
-
|
|
995
|
-
write(this, stackRoleWithTeamData, fileName, 'Team Stack Role details', delimiter);
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
/**
|
|
999
|
-
* Mapping the team stacks with the stack role and returning and array of object
|
|
1000
|
-
* @param {object} managementAPIClient
|
|
1001
|
-
* @param {array} stackRoleMapping
|
|
1002
|
-
* @param {string} teamName
|
|
1003
|
-
* @param {string} teamUid
|
|
1004
|
-
*/
|
|
1005
|
-
async function mapRoleWithTeams(managementAPIClient, stackRoleMapping, teamName, teamUid) {
|
|
1006
|
-
const roles = await getRoleData(managementAPIClient, stackRoleMapping.stackApiKey);
|
|
1007
|
-
const stackRole = {};
|
|
1008
|
-
roles?.items?.forEach((role) => {
|
|
1009
|
-
if (!stackRole.hasOwnProperty(role?.uid)) {
|
|
1010
|
-
stackRole[role?.uid] = role?.name;
|
|
1011
|
-
stackRole[role?.stack?.api_key] = { name: role?.stack?.name, uid: role?.stack?.uid };
|
|
1012
|
-
}
|
|
1013
|
-
});
|
|
1014
|
-
const stackRoleMapOfTeam = stackRoleMapping?.roles.map((role) => {
|
|
1015
|
-
return {
|
|
1016
|
-
'Team Name': teamName,
|
|
1017
|
-
'Team Uid': teamUid,
|
|
1018
|
-
'Stack Name': stackRole[stackRoleMapping?.stackApiKey]?.name || '',
|
|
1019
|
-
'Stack Uid': stackRole[stackRoleMapping?.stackApiKey]?.uid || '',
|
|
1020
|
-
'Role Name': stackRole[role] || '',
|
|
1021
|
-
'Role Uid': role || '',
|
|
1022
|
-
};
|
|
1023
|
-
});
|
|
1024
|
-
return stackRoleMapOfTeam;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
/**
|
|
1028
|
-
* Making sdk call to get all the roles in the given stack
|
|
1029
|
-
* @param {object} managementAPIClient
|
|
1030
|
-
* @param {string} stackApiKey
|
|
1031
|
-
*/
|
|
1032
|
-
async function getRoleData(managementAPIClient, stackApiKey) {
|
|
1033
|
-
try {
|
|
1034
|
-
return await managementAPIClient.stack({ api_key: stackApiKey }).role().fetchAll();
|
|
1035
|
-
} catch (error) {
|
|
1036
|
-
return {};
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* Here in the users array we are adding the team-name and team-uid to individual users and returning an array of object of user details only
|
|
1042
|
-
* @param {array} teams
|
|
1043
|
-
*/
|
|
1044
|
-
async function getTeamsUserDetails(teams) {
|
|
1045
|
-
const allTeamUsers = [];
|
|
1046
|
-
teams.forEach((team) => {
|
|
1047
|
-
if (team?.users?.length) {
|
|
1048
|
-
team.users.forEach((user) => {
|
|
1049
|
-
user['team-name'] = team.name;
|
|
1050
|
-
user['team-uid'] = team.uid;
|
|
1051
|
-
delete user['active'];
|
|
1052
|
-
delete user['orgInvitationStatus'];
|
|
1053
|
-
allTeamUsers.push(user);
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1056
|
-
});
|
|
1057
|
-
return allTeamUsers;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
/**
|
|
1061
|
-
* fetch all taxonomies in the provided stack
|
|
1062
|
-
* @param {object} payload
|
|
1063
|
-
* @param {number} skip
|
|
1064
|
-
* @param {array} taxonomies
|
|
1065
|
-
* @returns
|
|
1066
|
-
*/
|
|
1067
|
-
async function getAllTaxonomies(payload, skip = 0, taxonomies = []) {
|
|
1068
|
-
payload['type'] = 'taxonomies';
|
|
1069
|
-
const { items, count } = await taxonomySDKHandler(payload, skip);
|
|
1070
|
-
if (items) {
|
|
1071
|
-
skip += payload.limit;
|
|
1072
|
-
taxonomies.push(...items);
|
|
1073
|
-
if (skip >= count) {
|
|
1074
|
-
return taxonomies;
|
|
1075
|
-
} else {
|
|
1076
|
-
return getAllTaxonomies(payload, skip, taxonomies);
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
return taxonomies;
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
/**
|
|
1083
|
-
* fetch taxonomy related terms
|
|
1084
|
-
* @param {object} payload
|
|
1085
|
-
* @param {number} skip
|
|
1086
|
-
* @param {number} limit
|
|
1087
|
-
* @param {array} terms
|
|
1088
|
-
* @returns
|
|
1089
|
-
*/
|
|
1090
|
-
async function getAllTermsOfTaxonomy(payload, skip = 0, terms = []) {
|
|
1091
|
-
payload['type'] = 'terms';
|
|
1092
|
-
const { items, count } = await taxonomySDKHandler(payload, skip);
|
|
1093
|
-
if (items) {
|
|
1094
|
-
skip += payload.limit;
|
|
1095
|
-
terms.push(...items);
|
|
1096
|
-
if (skip >= count) {
|
|
1097
|
-
return terms;
|
|
1098
|
-
} else {
|
|
1099
|
-
return getAllTermsOfTaxonomy(payload, skip, terms);
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
return terms;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* Verify the existence of a taxonomy. Obtain its details if it exists and return
|
|
1107
|
-
* @param {object} payload
|
|
1108
|
-
* @param {string} taxonomyUID
|
|
1109
|
-
* @returns
|
|
1110
|
-
*/
|
|
1111
|
-
async function getTaxonomy(payload) {
|
|
1112
|
-
payload['type'] = 'taxonomy';
|
|
1113
|
-
const resp = await taxonomySDKHandler(payload);
|
|
1114
|
-
return resp;
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
/**
|
|
1118
|
-
* taxonomy & term sdk handler
|
|
1119
|
-
* @async
|
|
1120
|
-
* @method
|
|
1121
|
-
* @param payload
|
|
1122
|
-
* @param skip
|
|
1123
|
-
* @param limit
|
|
1124
|
-
* @returns {*} Promise<any>
|
|
1125
|
-
*/
|
|
1126
|
-
async function taxonomySDKHandler(payload, skip) {
|
|
1127
|
-
const { stackAPIClient, taxonomyUID, type, format, locale, branch, include_fallback, fallback_locale } = payload;
|
|
1128
|
-
|
|
1129
|
-
const queryParams = { include_count: true, limit: payload.limit };
|
|
1130
|
-
if (skip >= 0) queryParams['skip'] = skip || 0;
|
|
1131
|
-
|
|
1132
|
-
// Add locale and branch parameters if provided
|
|
1133
|
-
if (locale) queryParams['locale'] = locale;
|
|
1134
|
-
if (branch) queryParams['branch'] = branch;
|
|
1135
|
-
if (include_fallback !== undefined) queryParams['include_fallback'] = include_fallback;
|
|
1136
|
-
if (fallback_locale) queryParams['fallback_locale'] = fallback_locale;
|
|
1137
|
-
|
|
1138
|
-
switch (type) {
|
|
1139
|
-
case 'taxonomies':
|
|
1140
|
-
return await stackAPIClient
|
|
1141
|
-
.taxonomy()
|
|
1142
|
-
.query(queryParams)
|
|
1143
|
-
.find()
|
|
1144
|
-
.then((data) => data)
|
|
1145
|
-
.catch((err) => handleTaxonomyErrorMsg(err));
|
|
1146
|
-
case 'taxonomy':
|
|
1147
|
-
return await stackAPIClient
|
|
1148
|
-
.taxonomy(taxonomyUID)
|
|
1149
|
-
.fetch()
|
|
1150
|
-
.then((data) => data)
|
|
1151
|
-
.catch((err) => handleTaxonomyErrorMsg(err));
|
|
1152
|
-
case 'terms':
|
|
1153
|
-
queryParams['depth'] = 0;
|
|
1154
|
-
return await stackAPIClient
|
|
1155
|
-
.taxonomy(taxonomyUID)
|
|
1156
|
-
.terms()
|
|
1157
|
-
.query(queryParams)
|
|
1158
|
-
.find()
|
|
1159
|
-
.then((data) => data)
|
|
1160
|
-
.catch((err) => handleTaxonomyErrorMsg(err));
|
|
1161
|
-
case 'export-taxonomies':
|
|
1162
|
-
const exportParams = { format };
|
|
1163
|
-
if (locale) exportParams.locale = locale;
|
|
1164
|
-
if (branch) exportParams.branch = branch;
|
|
1165
|
-
if (include_fallback !== undefined) exportParams.include_fallback = include_fallback;
|
|
1166
|
-
if (fallback_locale) exportParams.fallback_locale = fallback_locale;
|
|
1167
|
-
|
|
1168
|
-
return await stackAPIClient
|
|
1169
|
-
.taxonomy(taxonomyUID)
|
|
1170
|
-
.export(exportParams)
|
|
1171
|
-
.then((data) => data)
|
|
1172
|
-
.catch((err) => handleTaxonomyErrorMsg(err));
|
|
1173
|
-
default:
|
|
1174
|
-
handleTaxonomyErrorMsg({ errorMessage: 'Invalid module!' });
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
/**
|
|
1179
|
-
* Change taxonomies data in required CSV headers format
|
|
1180
|
-
* @param {array} taxonomies
|
|
1181
|
-
* @returns
|
|
1182
|
-
*/
|
|
1183
|
-
function formatTaxonomiesData(taxonomies) {
|
|
1184
|
-
if (taxonomies?.length) {
|
|
1185
|
-
const formattedTaxonomies = taxonomies.map((taxonomy) => {
|
|
1186
|
-
return sanitizeData({
|
|
1187
|
-
'Taxonomy UID': taxonomy.uid,
|
|
1188
|
-
Name: taxonomy.name,
|
|
1189
|
-
Description: taxonomy.description,
|
|
1190
|
-
});
|
|
1191
|
-
});
|
|
1192
|
-
return formattedTaxonomies;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
/**
|
|
1197
|
-
* Modify the linked taxonomy data's terms in required CSV headers format
|
|
1198
|
-
* @param {array} terms
|
|
1199
|
-
* @param {string} taxonomyUID
|
|
1200
|
-
* @returns
|
|
1201
|
-
*/
|
|
1202
|
-
function formatTermsOfTaxonomyData(terms, taxonomyUID) {
|
|
1203
|
-
if (terms?.length) {
|
|
1204
|
-
const formattedTerms = terms.map((term) => {
|
|
1205
|
-
return sanitizeData({
|
|
1206
|
-
'Taxonomy UID': taxonomyUID,
|
|
1207
|
-
UID: term.uid,
|
|
1208
|
-
Name: term.name,
|
|
1209
|
-
'Parent UID': term.parent_uid,
|
|
1210
|
-
Depth: term.depth,
|
|
1211
|
-
});
|
|
1212
|
-
});
|
|
1213
|
-
return formattedTerms;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
function handleTaxonomyErrorMsg(err) {
|
|
1218
|
-
if (err?.errorMessage || err?.message) {
|
|
1219
|
-
const errorMsg = err?.errorMessage || err?.errors?.taxonomy || err?.errors?.term || err?.message;
|
|
1220
|
-
cliux.print(`Error: ${errorMsg}`, { color: 'red' });
|
|
1221
|
-
} else {
|
|
1222
|
-
console.log(err);
|
|
1223
|
-
cliux.print(`Error: ${messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, { color: 'red' });
|
|
1224
|
-
}
|
|
1225
|
-
process.exit(1);
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
/**
|
|
1229
|
-
* Generate a CSV file that can be imported for use with the migration script.
|
|
1230
|
-
* @param {*} payload api request payload
|
|
1231
|
-
* @param {*} taxonomies taxonomies data
|
|
1232
|
-
* @returns
|
|
1233
|
-
*/
|
|
1234
|
-
async function createImportableCSV(payload, taxonomies) {
|
|
1235
|
-
let taxonomiesData = [];
|
|
1236
|
-
let headers = [];
|
|
1237
|
-
payload['type'] = 'export-taxonomies';
|
|
1238
|
-
payload['format'] = 'csv';
|
|
1239
|
-
for (const taxonomy of taxonomies) {
|
|
1240
|
-
if (taxonomy?.uid) {
|
|
1241
|
-
payload['taxonomyUID'] = taxonomy?.uid;
|
|
1242
|
-
const data = await taxonomySDKHandler(payload);
|
|
1243
|
-
const taxonomies = await csvParse(data, headers);
|
|
1244
|
-
taxonomiesData.push(...taxonomies);
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
return { taxonomiesData, headers };
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
/**
|
|
1252
|
-
* Parse the CSV data and segregate the headers from the actual data.
|
|
1253
|
-
* @param {*} data taxonomy csv data with headers
|
|
1254
|
-
* @param {*} headers list of csv headers
|
|
1255
|
-
* @returns taxonomy data without headers
|
|
1256
|
-
*/
|
|
1257
|
-
const csvParse = (data, headers) => {
|
|
1258
|
-
return new Promise((resolve, reject) => {
|
|
1259
|
-
const taxonomies = [];
|
|
1260
|
-
const stream = fastcsv.parseStream(fastcsv.parse());
|
|
1261
|
-
stream.write(data);
|
|
1262
|
-
stream.end();
|
|
1263
|
-
stream
|
|
1264
|
-
.on('data', (data) => {
|
|
1265
|
-
taxonomies.push(data);
|
|
1266
|
-
})
|
|
1267
|
-
.on('error', (err) => reject(err))
|
|
1268
|
-
.on('end', () => {
|
|
1269
|
-
taxonomies[0]?.forEach((header) => {
|
|
1270
|
-
if (!headers.includes(header)) headers.push(header);
|
|
1271
|
-
});
|
|
1272
|
-
resolve(taxonomies.splice(1));
|
|
1273
|
-
});
|
|
1274
|
-
});
|
|
1275
|
-
};
|
|
1276
|
-
|
|
1277
|
-
module.exports = {
|
|
1278
|
-
chooseOrganization: chooseOrganization,
|
|
1279
|
-
chooseStack: chooseStack,
|
|
1280
|
-
chooseBranch: chooseBranch,
|
|
1281
|
-
chooseContentType: chooseContentType,
|
|
1282
|
-
chooseLanguage: chooseLanguage,
|
|
1283
|
-
chooseFallbackOptions: chooseFallbackOptions,
|
|
1284
|
-
getEntries: getEntries,
|
|
1285
|
-
getEnvironments: getEnvironments,
|
|
1286
|
-
cleanEntries: cleanEntries,
|
|
1287
|
-
write: write,
|
|
1288
|
-
startupQuestions: startupQuestions,
|
|
1289
|
-
getDateTime: getDateTime,
|
|
1290
|
-
getOrgUsers: getOrgUsers,
|
|
1291
|
-
getOrgRoles: getOrgRoles,
|
|
1292
|
-
getMappedUsers: getMappedUsers,
|
|
1293
|
-
getMappedRoles: getMappedRoles,
|
|
1294
|
-
cleanOrgUsers: cleanOrgUsers,
|
|
1295
|
-
determineUserOrgRole: determineUserOrgRole,
|
|
1296
|
-
getOrganizationsWhereUserIsAdmin: getOrganizationsWhereUserIsAdmin,
|
|
1297
|
-
kebabize: kebabize,
|
|
1298
|
-
flatten: flatten,
|
|
1299
|
-
getContentTypeCount: getContentTypeCount,
|
|
1300
|
-
getContentTypes: getContentTypes,
|
|
1301
|
-
chooseInMemContentTypes: chooseInMemContentTypes,
|
|
1302
|
-
getEntriesCount: getEntriesCount,
|
|
1303
|
-
formatError: formatError,
|
|
1304
|
-
exportOrgTeams: exportOrgTeams,
|
|
1305
|
-
exportTeams: exportTeams,
|
|
1306
|
-
getAllTaxonomies,
|
|
1307
|
-
getAllTermsOfTaxonomy,
|
|
1308
|
-
formatTaxonomiesData,
|
|
1309
|
-
formatTermsOfTaxonomyData,
|
|
1310
|
-
getTaxonomy,
|
|
1311
|
-
getStacks,
|
|
1312
|
-
createImportableCSV,
|
|
1313
|
-
};
|