@contentstack/cli-cm-export-to-csv 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -7
- package/src/commands/cm/export-to-csv.js +206 -61
- package/src/util/config.js +3 -1
- package/src/util/index.js +249 -26
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentstack/cli-cm-export-to-csv",
|
|
3
3
|
"description": "Export entities to csv",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.1",
|
|
5
5
|
"author": "Abhinav Gupta @abhinav-from-contentstack",
|
|
6
6
|
"bugs": "https://github.com/contentstack/cli/issues",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@contentstack/cli-command": "~1.2.
|
|
9
|
-
"@contentstack/cli-utilities": "~1.5.
|
|
8
|
+
"@contentstack/cli-command": "~1.2.16",
|
|
9
|
+
"@contentstack/cli-utilities": "~1.5.7",
|
|
10
10
|
"chalk": "^4.1.0",
|
|
11
11
|
"fast-csv": "^4.3.6",
|
|
12
12
|
"inquirer": "8.2.4",
|
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@oclif/test": "^2.2.10",
|
|
18
|
-
"chai": "^4.
|
|
18
|
+
"@types/chai": "^4.3.6",
|
|
19
|
+
"@types/mocha": "^10.0.1",
|
|
20
|
+
"chai": "^4.3.8",
|
|
19
21
|
"debug": "^4.3.1",
|
|
22
|
+
"dotenv": "^16.3.1",
|
|
20
23
|
"eslint": "^7.32.0",
|
|
21
24
|
"eslint-config-oclif": "^4.0.0",
|
|
22
25
|
"globby": "^10.0.2",
|
|
23
|
-
"mocha": "^10.
|
|
26
|
+
"mocha": "^10.2.0",
|
|
24
27
|
"nyc": "^15.1.0",
|
|
25
28
|
"oclif": "^3.8.1"
|
|
26
29
|
},
|
|
@@ -44,7 +47,8 @@
|
|
|
44
47
|
"postpack": "rm -f oclif.manifest.json",
|
|
45
48
|
"prepack": "oclif manifest && oclif readme",
|
|
46
49
|
"test": "nyc mocha --forbid-only \"test/**/*.test.js\"",
|
|
47
|
-
"test:unit": "
|
|
50
|
+
"test:unit": "mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.js\" \"test/util/common-utils.test.js\"",
|
|
51
|
+
"test:unit:report": "nyc --extension .js mocha --forbid-only \"test/unit/**/*.test.js\" \"test/util/common-utils.test.js\"",
|
|
48
52
|
"version": "oclif readme && git add README.md",
|
|
49
53
|
"clean": "rm -rf ./node_modules tsconfig.build.tsbuildinfo"
|
|
50
54
|
},
|
|
@@ -61,4 +65,4 @@
|
|
|
61
65
|
}
|
|
62
66
|
},
|
|
63
67
|
"repository": "https://github.com/contentstack/cli"
|
|
64
|
-
}
|
|
68
|
+
}
|
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
isAuthenticated,
|
|
7
7
|
cliux,
|
|
8
8
|
doesBranchExist,
|
|
9
|
+
isManagementTokenValid
|
|
9
10
|
} = require('@contentstack/cli-utilities');
|
|
10
11
|
const util = require('../../util');
|
|
11
12
|
const config = require('../../util/config');
|
|
@@ -15,8 +16,8 @@ class ExportToCsvCommand extends Command {
|
|
|
15
16
|
action: flags.string({
|
|
16
17
|
required: false,
|
|
17
18
|
multiple: false,
|
|
18
|
-
options: ['entries', 'users', 'teams'],
|
|
19
|
-
description: `Option to export data (entries, users, teams)`,
|
|
19
|
+
options: ['entries', 'users', 'teams', 'taxonomies'],
|
|
20
|
+
description: `Option to export data (entries, users, teams, taxonomies)`,
|
|
20
21
|
}),
|
|
21
22
|
alias: flags.string({
|
|
22
23
|
char: 'a',
|
|
@@ -61,7 +62,14 @@ class ExportToCsvCommand extends Command {
|
|
|
61
62
|
}),
|
|
62
63
|
"team-uid": flags.string({
|
|
63
64
|
description: 'Uid of the team whose user data and stack roles are required'
|
|
64
|
-
})
|
|
65
|
+
}),
|
|
66
|
+
'taxonomy-uid': flags.string({
|
|
67
|
+
description: 'Provide the taxonomy UID of the related terms you want to export',
|
|
68
|
+
}),
|
|
69
|
+
delimiter: flags.string({
|
|
70
|
+
description: '[optional] Provide a delimiter to separate individual data fields within the CSV file.',
|
|
71
|
+
default: ',',
|
|
72
|
+
}),
|
|
65
73
|
};
|
|
66
74
|
|
|
67
75
|
async run() {
|
|
@@ -78,7 +86,9 @@ class ExportToCsvCommand extends Command {
|
|
|
78
86
|
'content-type': contentTypesFlag,
|
|
79
87
|
alias: managementTokenAlias,
|
|
80
88
|
branch: branchUid,
|
|
81
|
-
"team-uid": teamUid
|
|
89
|
+
"team-uid": teamUid,
|
|
90
|
+
'taxonomy-uid': taxonomyUID,
|
|
91
|
+
delimiter
|
|
82
92
|
},
|
|
83
93
|
} = await this.parse(ExportToCsvCommand);
|
|
84
94
|
|
|
@@ -106,61 +116,17 @@ class ExportToCsvCommand extends Command {
|
|
|
106
116
|
let stackAPIClient;
|
|
107
117
|
let language;
|
|
108
118
|
let contentTypes = [];
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
host: this.cmaHost,
|
|
115
|
-
management_token: listOfTokens[managementTokenAlias].token,
|
|
116
|
-
});
|
|
117
|
-
stack = {
|
|
118
|
-
name: stackName || managementTokenAlias,
|
|
119
|
-
apiKey: listOfTokens[managementTokenAlias].apiKey,
|
|
120
|
-
token: listOfTokens[managementTokenAlias].token,
|
|
121
|
-
};
|
|
122
|
-
} else if (managementTokenAlias) {
|
|
123
|
-
this.error('Provided management token alias not found in your config.!');
|
|
119
|
+
|
|
120
|
+
if (managementTokenAlias) {
|
|
121
|
+
const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName);
|
|
122
|
+
managementAPIClient = apiClient;
|
|
123
|
+
stack = stackDetails;
|
|
124
124
|
} else {
|
|
125
|
-
|
|
126
|
-
if (org) {
|
|
127
|
-
organization = { uid: org };
|
|
128
|
-
} else {
|
|
129
|
-
organization = await util.chooseOrganization(managementAPIClient); // prompt for organization
|
|
130
|
-
}
|
|
131
|
-
if (!stackAPIKey) {
|
|
132
|
-
stack = await util.chooseStack(managementAPIClient, organization.uid); // prompt for stack
|
|
133
|
-
} else {
|
|
134
|
-
stack = await util.chooseStack(managementAPIClient, organization.uid, stackAPIKey);
|
|
135
|
-
}
|
|
125
|
+
stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org);
|
|
136
126
|
}
|
|
137
127
|
|
|
138
128
|
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
139
|
-
|
|
140
|
-
if (branchUid) {
|
|
141
|
-
try {
|
|
142
|
-
const branchExists = await doesBranchExist(stackAPIClient, branchUid);
|
|
143
|
-
if (branchExists?.errorCode) {
|
|
144
|
-
throw new Error(branchExists.errorMessage);
|
|
145
|
-
}
|
|
146
|
-
stack.branch_uid = branchUid;
|
|
147
|
-
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
148
|
-
} catch (error) {
|
|
149
|
-
if (error.message || error.errorMessage) {
|
|
150
|
-
cliux.error(util.formatError(error));
|
|
151
|
-
this.exit();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
stackBranches = await this.getStackBranches(stackAPIClient);
|
|
156
|
-
if (stackBranches === undefined) {
|
|
157
|
-
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
158
|
-
} else {
|
|
159
|
-
const { branch } = await util.chooseBranch(stackBranches);
|
|
160
|
-
stack.branch_uid = branch;
|
|
161
|
-
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
129
|
+
await this.checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, managementAPIClient);
|
|
164
130
|
|
|
165
131
|
const contentTypeCount = await util.getContentTypeCount(stackAPIClient);
|
|
166
132
|
|
|
@@ -219,7 +185,7 @@ class ExportToCsvCommand extends Command {
|
|
|
219
185
|
flatEntries = flatEntries.concat(flatEntriesResult);
|
|
220
186
|
}
|
|
221
187
|
let fileName = `${stackName ? stackName : stack.name}_${contentType}_${language.code}_entries_export.csv`;
|
|
222
|
-
util.write(this, flatEntries, fileName, 'entries'); // write to file
|
|
188
|
+
util.write(this, flatEntries, fileName, 'entries', delimiter); // write to file
|
|
223
189
|
}
|
|
224
190
|
} catch (error) {
|
|
225
191
|
cliux.error(util.formatError(error));
|
|
@@ -246,7 +212,7 @@ class ExportToCsvCommand extends Command {
|
|
|
246
212
|
(orgName ? orgName : organization.name).replace(config.organizationNameRegex, ''),
|
|
247
213
|
)}_users_export.csv`;
|
|
248
214
|
|
|
249
|
-
util.write(this, listOfUsers, fileName, 'organization details');
|
|
215
|
+
util.write(this, listOfUsers, fileName, 'organization details', delimiter);
|
|
250
216
|
} catch (error) {
|
|
251
217
|
if (error.message || error.errorMessage) {
|
|
252
218
|
cliux.error(util.formatError(error));
|
|
@@ -264,14 +230,30 @@ class ExportToCsvCommand extends Command {
|
|
|
264
230
|
organization = await util.chooseOrganization(managementAPIClient, action); // prompt for organization
|
|
265
231
|
}
|
|
266
232
|
|
|
267
|
-
await util.exportTeams(managementAPIClient,organization,teamUid);
|
|
233
|
+
await util.exportTeams(managementAPIClient,organization,teamUid, delimiter);
|
|
268
234
|
} catch (error) {
|
|
269
235
|
if (error.message || error.errorMessage) {
|
|
270
236
|
cliux.error(util.formatError(error));
|
|
271
237
|
}
|
|
272
238
|
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case config.exportTaxonomies:
|
|
242
|
+
case 'taxonomies': {
|
|
243
|
+
let stack;
|
|
244
|
+
let stackAPIClient;
|
|
245
|
+
if (managementTokenAlias) {
|
|
246
|
+
const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName);
|
|
247
|
+
managementAPIClient = apiClient;
|
|
248
|
+
stack = stackDetails;
|
|
249
|
+
} else {
|
|
250
|
+
stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
254
|
+
await this.createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxonomyUID, delimiter);
|
|
255
|
+
break;
|
|
273
256
|
}
|
|
274
|
-
break;
|
|
275
257
|
}
|
|
276
258
|
} catch (error) {
|
|
277
259
|
if (error.message || error.errorMessage) {
|
|
@@ -287,8 +269,8 @@ class ExportToCsvCommand extends Command {
|
|
|
287
269
|
getStackClient(managementAPIClient, stack) {
|
|
288
270
|
const stackInit = {
|
|
289
271
|
api_key: stack.apiKey,
|
|
290
|
-
branch_uid: stack.branch_uid,
|
|
291
272
|
};
|
|
273
|
+
if (stack?.branch_uid) stackInit['branch_uid'] = stack.branch_uid;
|
|
292
274
|
if (stack.token) {
|
|
293
275
|
return managementAPIClient.stack({
|
|
294
276
|
...stackInit,
|
|
@@ -306,9 +288,163 @@ class ExportToCsvCommand extends Command {
|
|
|
306
288
|
.then(({ items }) => (items !== undefined ? items : []))
|
|
307
289
|
.catch((_err) => {});
|
|
308
290
|
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* check whether branch enabled org or not and update branch details
|
|
294
|
+
* @param {string} branchUid
|
|
295
|
+
* @param {object} stack
|
|
296
|
+
* @param {*} stackAPIClient
|
|
297
|
+
* @param {*} managementAPIClient
|
|
298
|
+
*/
|
|
299
|
+
async checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, managementAPIClient) {
|
|
300
|
+
if (branchUid) {
|
|
301
|
+
try {
|
|
302
|
+
const branchExists = await doesBranchExist(stackAPIClient, branchUid);
|
|
303
|
+
if (branchExists?.errorCode) {
|
|
304
|
+
throw new Error(branchExists.errorMessage);
|
|
305
|
+
}
|
|
306
|
+
stack.branch_uid = branchUid;
|
|
307
|
+
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (error?.message || error?.errorMessage) {
|
|
310
|
+
cliux.error(util.formatError(error));
|
|
311
|
+
this.exit();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
const stackBranches = await this.getStackBranches(stackAPIClient);
|
|
316
|
+
if (stackBranches === undefined) {
|
|
317
|
+
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
318
|
+
} else {
|
|
319
|
+
const { branch } = await util.chooseBranch(stackBranches);
|
|
320
|
+
stack.branch_uid = branch;
|
|
321
|
+
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* fetch stack details from alias token
|
|
328
|
+
* @param {string} managementTokenAlias
|
|
329
|
+
* @param {string} stackName
|
|
330
|
+
* @returns
|
|
331
|
+
*/
|
|
332
|
+
async getAliasDetails(managementTokenAlias, stackName) {
|
|
333
|
+
let apiClient, stackDetails;
|
|
334
|
+
const listOfTokens = configHandler.get('tokens');
|
|
335
|
+
if (managementTokenAlias && listOfTokens[managementTokenAlias]) {
|
|
336
|
+
const checkManagementTokenValidity = await isManagementTokenValid((listOfTokens[managementTokenAlias].apiKey) ,listOfTokens[managementTokenAlias].token);
|
|
337
|
+
if(!checkManagementTokenValidity?.valid) {
|
|
338
|
+
throw checkManagementTokenValidity.hasOwnProperty('valid')?(`error: Management token or stack API key is invalid. ${checkManagementTokenValidity?.message||""}`):checkManagementTokenValidity?.message||"";
|
|
339
|
+
}
|
|
340
|
+
apiClient = await managementSDKClient({
|
|
341
|
+
host: this.cmaHost,
|
|
342
|
+
management_token: listOfTokens[managementTokenAlias].token,
|
|
343
|
+
});
|
|
344
|
+
stackDetails = {
|
|
345
|
+
name: stackName || managementTokenAlias,
|
|
346
|
+
apiKey: listOfTokens[managementTokenAlias].apiKey,
|
|
347
|
+
token: listOfTokens[managementTokenAlias].token,
|
|
348
|
+
};
|
|
349
|
+
} else if (managementTokenAlias) {
|
|
350
|
+
this.error('Provided management token alias not found in your config.!');
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
apiClient,
|
|
354
|
+
stackDetails,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* fetch stack details on basis of the selected org and stack
|
|
360
|
+
* @param {*} managementAPIClient
|
|
361
|
+
* @param {string} stackAPIKey
|
|
362
|
+
* @param {string} org
|
|
363
|
+
* @returns
|
|
364
|
+
*/
|
|
365
|
+
async getStackDetails(managementAPIClient, stackAPIKey, org) {
|
|
366
|
+
let organization, stackDetails;
|
|
367
|
+
|
|
368
|
+
if (!isAuthenticated()) {
|
|
369
|
+
this.error(config.CLI_EXPORT_CSV_ENTRIES_ERROR, {
|
|
370
|
+
exit: 2,
|
|
371
|
+
suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'],
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (org) {
|
|
376
|
+
organization = { uid: org };
|
|
377
|
+
} else {
|
|
378
|
+
organization = await util.chooseOrganization(managementAPIClient); // prompt for organization
|
|
379
|
+
}
|
|
380
|
+
if (!stackAPIKey) {
|
|
381
|
+
stackDetails = await util.chooseStack(managementAPIClient, organization.uid); // prompt for stack
|
|
382
|
+
} else {
|
|
383
|
+
stackDetails = await util.chooseStack(managementAPIClient, organization.uid, stackAPIKey);
|
|
384
|
+
}
|
|
385
|
+
return stackDetails;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Create a taxonomies csv file for stack and a terms csv file for associated taxonomies
|
|
390
|
+
* @param {string} stackName
|
|
391
|
+
* @param {object} stack
|
|
392
|
+
* @param {string} taxUID
|
|
393
|
+
*/
|
|
394
|
+
async createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxUID, delimiter) {
|
|
395
|
+
//TODO: Temp variable to export taxonomies in importable format will replaced with flag once decided
|
|
396
|
+
const importableCSV = true;
|
|
397
|
+
const payload = {
|
|
398
|
+
stackAPIClient,
|
|
399
|
+
type: '',
|
|
400
|
+
limit: config.limit || 100,
|
|
401
|
+
};
|
|
402
|
+
//check whether the taxonomy is valid or not
|
|
403
|
+
let taxonomies = [];
|
|
404
|
+
if (taxUID) {
|
|
405
|
+
payload['taxonomyUID'] = taxUID;
|
|
406
|
+
const taxonomy = await util.getTaxonomy(payload);
|
|
407
|
+
taxonomies.push(taxonomy);
|
|
408
|
+
} else {
|
|
409
|
+
taxonomies = await util.getAllTaxonomies(payload);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!importableCSV) {
|
|
413
|
+
const formattedTaxonomiesData = util.formatTaxonomiesData(taxonomies);
|
|
414
|
+
if (formattedTaxonomiesData?.length) {
|
|
415
|
+
const fileName = `${stackName ? stackName : stack.name}_taxonomies.csv`;
|
|
416
|
+
util.write(this, formattedTaxonomiesData, fileName, 'taxonomies', delimiter);
|
|
417
|
+
} else {
|
|
418
|
+
cliux.print('info: No taxonomies found! Please provide a valid stack.', { color: 'blue' });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
for (let index = 0; index < taxonomies?.length; index++) {
|
|
422
|
+
const taxonomy = taxonomies[index];
|
|
423
|
+
const taxonomyUID = taxonomy?.uid;
|
|
424
|
+
if (taxonomyUID) {
|
|
425
|
+
payload['taxonomyUID'] = taxonomyUID;
|
|
426
|
+
const terms = await util.getAllTermsOfTaxonomy(payload);
|
|
427
|
+
const formattedTermsData = util.formatTermsOfTaxonomyData(terms, taxonomyUID);
|
|
428
|
+
const taxonomyName = taxonomy?.name ?? '';
|
|
429
|
+
const termFileName = `${stackName ?? stack.name}_${taxonomyName}_${taxonomyUID}_terms.csv`;
|
|
430
|
+
if (formattedTermsData?.length) {
|
|
431
|
+
util.write(this, formattedTermsData, termFileName, 'terms', delimiter);
|
|
432
|
+
} else {
|
|
433
|
+
cliux.print(`info: No terms found for the taxonomy UID - '${taxonomyUID}'!`, { color: 'blue' });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
const fileName = `${stackName ?? stack.name}_taxonomies.csv`;
|
|
439
|
+
const { taxonomiesData, headers } = await util.createImportableCSV(payload, taxonomies);
|
|
440
|
+
if (taxonomiesData?.length) {
|
|
441
|
+
util.write(this, taxonomiesData, fileName, 'taxonomies',delimiter, headers);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
309
445
|
}
|
|
310
446
|
|
|
311
|
-
ExportToCsvCommand.description = `Export entries or organization users to csv using this command`;
|
|
447
|
+
ExportToCsvCommand.description = `Export entries, taxonomies, terms or organization users to csv using this command`;
|
|
312
448
|
|
|
313
449
|
ExportToCsvCommand.examples = [
|
|
314
450
|
'csdx cm:export-to-csv',
|
|
@@ -339,6 +475,15 @@ ExportToCsvCommand.examples = [
|
|
|
339
475
|
'',
|
|
340
476
|
'Exporting Organizations Teams to CSV with org-uid and team uid',
|
|
341
477
|
'csdx cm:export-to-csv --action <teams> --org <org-uid> --team-uid <team-uid> --org-name <org-name>',
|
|
478
|
+
'',
|
|
479
|
+
'Exporting taxonomies and related terms to a .CSV file with the provided taxonomy UID',
|
|
480
|
+
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias> --taxonomy-uid <taxonomy-uid>',
|
|
481
|
+
'',
|
|
482
|
+
'Exporting taxonomies and respective terms to a .CSV file',
|
|
483
|
+
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias>',
|
|
484
|
+
'',
|
|
485
|
+
'Exporting taxonomies and respective terms to a .CSV file with a delimiter',
|
|
486
|
+
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias> --delimiter <delimiter>',
|
|
342
487
|
];
|
|
343
488
|
|
|
344
489
|
module.exports = ExportToCsvCommand;
|
package/src/util/config.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
+
limit:100,
|
|
2
3
|
cancelString: 'Cancel and Exit',
|
|
3
4
|
exportEntries: 'Export entries to a .CSV file',
|
|
4
5
|
exportUsers: "Export organization user's data to a .CSV file",
|
|
5
6
|
exportTeams: "Export organization team's data to a .csv file",
|
|
7
|
+
exportTaxonomies: 'Export taxonomies to a .CSV file',
|
|
6
8
|
adminError: "Unable to export data. Make sure you're an admin or owner of this organization",
|
|
7
9
|
organizationNameRegex: /\'/,
|
|
8
10
|
CLI_EXPORT_CSV_LOGIN_FAILED: "You need to login to execute this command. See: auth:login --help",
|
|
9
11
|
CLI_EXPORT_CSV_ENTRIES_ERROR: "You need to either login or provide a management token to execute this command",
|
|
10
|
-
CLI_EXPORT_CSV_API_FAILED: 'Something went wrong
|
|
12
|
+
CLI_EXPORT_CSV_API_FAILED: 'Something went wrong! Please try again'
|
|
11
13
|
};
|
package/src/util/index.js
CHANGED
|
@@ -4,12 +4,21 @@ const mkdirp = require('mkdirp');
|
|
|
4
4
|
const find = require('lodash/find');
|
|
5
5
|
const cloneDeep = require('lodash/cloneDeep');
|
|
6
6
|
const omit = require('lodash/omit');
|
|
7
|
+
const flat = require('lodash/flatten');
|
|
7
8
|
const fastcsv = require('fast-csv');
|
|
8
9
|
const inquirer = require('inquirer');
|
|
9
10
|
const debug = require('debug')('export-to-csv');
|
|
10
11
|
const checkboxPlus = require('inquirer-checkbox-plus-prompt');
|
|
11
12
|
const config = require('./config.js');
|
|
12
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
cliux,
|
|
15
|
+
configHandler,
|
|
16
|
+
HttpClient,
|
|
17
|
+
messageHandler,
|
|
18
|
+
managementSDKClient,
|
|
19
|
+
ContentstackClient,
|
|
20
|
+
} = require('@contentstack/cli-utilities');
|
|
21
|
+
|
|
13
22
|
const directory = './data';
|
|
14
23
|
const delimeter = os.platform() === 'win32' ? '\\' : '/';
|
|
15
24
|
|
|
@@ -155,9 +164,7 @@ function chooseStack(managementAPIClient, orgUid, stackApiKey) {
|
|
|
155
164
|
|
|
156
165
|
async function chooseBranch(branchList) {
|
|
157
166
|
try {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const branchesArray = branches.map((branch) => branch.uid);
|
|
167
|
+
const branchesArray = branchList.map((branch) => branch.uid);
|
|
161
168
|
|
|
162
169
|
let _chooseBranch = [
|
|
163
170
|
{
|
|
@@ -377,20 +384,20 @@ function exitProgram() {
|
|
|
377
384
|
process.exit();
|
|
378
385
|
}
|
|
379
386
|
|
|
380
|
-
function
|
|
387
|
+
function sanitizeData(flatData) {
|
|
381
388
|
// sanitize against CSV Injections
|
|
382
389
|
const CSVRegex = /^[\\+\\=@\\-]/;
|
|
383
|
-
for (key in
|
|
384
|
-
if (typeof
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} else if (typeof
|
|
390
|
+
for (key in flatData) {
|
|
391
|
+
if (typeof flatData[key] === 'string' && flatData[key].match(CSVRegex)) {
|
|
392
|
+
flatData[key] = flatData[key].replace(/\"/g, "\"\"");
|
|
393
|
+
flatData[key] = `"'${flatData[key]}"`;
|
|
394
|
+
} else if (typeof flatData[key] === 'object') {
|
|
388
395
|
// convert any objects or arrays to string
|
|
389
396
|
// to store this data correctly in csv
|
|
390
|
-
|
|
397
|
+
flatData[key] = JSON.stringify(flatData[key]);
|
|
391
398
|
}
|
|
392
399
|
}
|
|
393
|
-
return
|
|
400
|
+
return flatData;
|
|
394
401
|
}
|
|
395
402
|
|
|
396
403
|
function cleanEntries(entries, language, environments, contentTypeUid) {
|
|
@@ -400,7 +407,7 @@ function cleanEntries(entries, language, environments, contentTypeUid) {
|
|
|
400
407
|
return filteredEntries.map((entry) => {
|
|
401
408
|
let workflow = '';
|
|
402
409
|
const envArr = [];
|
|
403
|
-
if (entry
|
|
410
|
+
if (entry?.publish_details?.length) {
|
|
404
411
|
entry.publish_details.forEach((env) => {
|
|
405
412
|
envArr.push(JSON.stringify([environments[env['environment']], env['locale'], env['time']]));
|
|
406
413
|
});
|
|
@@ -415,7 +422,7 @@ function cleanEntries(entries, language, environments, contentTypeUid) {
|
|
|
415
422
|
}
|
|
416
423
|
}
|
|
417
424
|
entry = flatten(entry);
|
|
418
|
-
entry =
|
|
425
|
+
entry = sanitizeData(entry);
|
|
419
426
|
entry['publish_details'] = envArr;
|
|
420
427
|
entry['_workflow'] = workflow;
|
|
421
428
|
entry['ACL'] = JSON.stringify({}); // setting ACL to empty obj
|
|
@@ -443,7 +450,7 @@ function getDateTime() {
|
|
|
443
450
|
return dateTime.join('_');
|
|
444
451
|
}
|
|
445
452
|
|
|
446
|
-
function write(command, entries, fileName, message) {
|
|
453
|
+
function write(command, entries, fileName, message, delimiter, headers) {
|
|
447
454
|
// eslint-disable-next-line no-undef
|
|
448
455
|
if (process.cwd().split(delimeter).pop() !== 'data' && !fs.existsSync(directory)) {
|
|
449
456
|
mkdirp.sync(directory);
|
|
@@ -455,7 +462,8 @@ function write(command, entries, fileName, message) {
|
|
|
455
462
|
}
|
|
456
463
|
// eslint-disable-next-line no-undef
|
|
457
464
|
cliux.print(`Writing ${message} to file: ${process.cwd()}${delimeter}${fileName}`);
|
|
458
|
-
fastcsv.writeToPath(fileName, entries, { headers
|
|
465
|
+
if (headers?.length) fastcsv.writeToPath(fileName, entries, { headers, delimiter });
|
|
466
|
+
else fastcsv.writeToPath(fileName, entries, { headers: true, delimiter });
|
|
459
467
|
}
|
|
460
468
|
|
|
461
469
|
function startupQuestions() {
|
|
@@ -465,7 +473,7 @@ function startupQuestions() {
|
|
|
465
473
|
type: 'list',
|
|
466
474
|
name: 'action',
|
|
467
475
|
message: 'Choose Action',
|
|
468
|
-
choices: [config.exportEntries, config.exportUsers, config.exportTeams, 'Exit'],
|
|
476
|
+
choices: [config.exportEntries, config.exportUsers, config.exportTeams, config.exportTaxonomies, 'Exit'],
|
|
469
477
|
},
|
|
470
478
|
];
|
|
471
479
|
inquirer
|
|
@@ -780,7 +788,7 @@ async function cleanTeamsData(data, managementAPIClient, org) {
|
|
|
780
788
|
}
|
|
781
789
|
}
|
|
782
790
|
|
|
783
|
-
async function exportTeams(managementAPIClient, organization, teamUid) {
|
|
791
|
+
async function exportTeams(managementAPIClient, organization, teamUid, delimiter) {
|
|
784
792
|
cliux.print(
|
|
785
793
|
`info: Exporting the ${
|
|
786
794
|
teamUid && organization?.name
|
|
@@ -799,13 +807,13 @@ async function exportTeams(managementAPIClient, organization, teamUid) {
|
|
|
799
807
|
delete team['stackRoleMapping'];
|
|
800
808
|
});
|
|
801
809
|
const fileName = `${kebabize(organization.name.replace(config.organizationNameRegex, ''))}_teams_export.csv`;
|
|
802
|
-
write(this, modifiedTeam, fileName, ' organization Team details');
|
|
810
|
+
write(this, modifiedTeam, fileName, ' organization Team details', delimiter);
|
|
803
811
|
// exporting teams user data or a single team user data
|
|
804
812
|
cliux.print(
|
|
805
813
|
`info: Exporting the teams user data for ${teamUid ? `team ` + teamUid : `organisation ` + organization?.name}`,
|
|
806
814
|
{ color: 'blue' },
|
|
807
815
|
);
|
|
808
|
-
await getTeamsDetail(allTeamsData, organization, teamUid);
|
|
816
|
+
await getTeamsDetail(allTeamsData, organization, teamUid, delimiter);
|
|
809
817
|
cliux.print(
|
|
810
818
|
`info: Exporting the stack role details for ${
|
|
811
819
|
teamUid ? `team ` + teamUid : `organisation ` + organization?.name
|
|
@@ -813,18 +821,18 @@ async function exportTeams(managementAPIClient, organization, teamUid) {
|
|
|
813
821
|
{ color: 'blue' },
|
|
814
822
|
);
|
|
815
823
|
// Exporting the stack Role data for all the teams or exporting stack role data for a single team
|
|
816
|
-
await exportRoleMappings(managementAPIClient, allTeamsData, teamUid);
|
|
824
|
+
await exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter);
|
|
817
825
|
}
|
|
818
826
|
}
|
|
819
827
|
|
|
820
|
-
async function getTeamsDetail(allTeamsData, organization, teamUid) {
|
|
828
|
+
async function getTeamsDetail(allTeamsData, organization, teamUid, delimiter) {
|
|
821
829
|
if (!teamUid) {
|
|
822
830
|
const userData = await getTeamsUserDetails(allTeamsData);
|
|
823
831
|
const fileName = `${kebabize(
|
|
824
832
|
organization.name.replace(config.organizationNameRegex, ''),
|
|
825
833
|
)}_team_User_Details_export.csv`;
|
|
826
834
|
|
|
827
|
-
write(this, userData, fileName, 'Team User details');
|
|
835
|
+
write(this, userData, fileName, 'Team User details', delimiter);
|
|
828
836
|
} else {
|
|
829
837
|
const team = allTeamsData.filter((team) => team.uid === teamUid)[0];
|
|
830
838
|
|
|
@@ -839,11 +847,11 @@ async function getTeamsDetail(allTeamsData, organization, teamUid) {
|
|
|
839
847
|
organization.name.replace(config.organizationNameRegex, ''),
|
|
840
848
|
)}_team_${teamUid}_User_Details_export.csv`;
|
|
841
849
|
|
|
842
|
-
write(this, team.users, fileName, 'Team User details');
|
|
850
|
+
write(this, team.users, fileName, 'Team User details', delimiter);
|
|
843
851
|
}
|
|
844
852
|
}
|
|
845
853
|
|
|
846
|
-
async function exportRoleMappings(managementAPIClient, allTeamsData, teamUid) {
|
|
854
|
+
async function exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter) {
|
|
847
855
|
let stackRoleWithTeamData = [];
|
|
848
856
|
let flag = false;
|
|
849
857
|
const stackNotAdmin = [];
|
|
@@ -900,7 +908,7 @@ async function exportRoleMappings(managementAPIClient, allTeamsData, teamUid) {
|
|
|
900
908
|
teamUid ? `_${teamUid}` : ''
|
|
901
909
|
}.csv`;
|
|
902
910
|
|
|
903
|
-
write(this, stackRoleWithTeamData, fileName, 'Team Stack Role details');
|
|
911
|
+
write(this, stackRoleWithTeamData, fileName, 'Team Stack Role details', delimiter);
|
|
904
912
|
}
|
|
905
913
|
|
|
906
914
|
async function mapRoleWithTeams(managementAPIClient, stackRoleMapping, teamName, teamUid) {
|
|
@@ -949,6 +957,214 @@ async function getTeamsUserDetails(teamsObject) {
|
|
|
949
957
|
return allTeamUsers;
|
|
950
958
|
}
|
|
951
959
|
|
|
960
|
+
/**
|
|
961
|
+
* fetch all taxonomies in the provided stack
|
|
962
|
+
* @param {object} payload
|
|
963
|
+
* @param {number} skip
|
|
964
|
+
* @param {array} taxonomies
|
|
965
|
+
* @returns
|
|
966
|
+
*/
|
|
967
|
+
async function getAllTaxonomies(payload, skip = 0, taxonomies = []) {
|
|
968
|
+
payload['type'] = 'taxonomies';
|
|
969
|
+
const { items, count } = await taxonomySDKHandler(payload, skip);
|
|
970
|
+
if (items) {
|
|
971
|
+
skip += payload.limit;
|
|
972
|
+
taxonomies.push(...items);
|
|
973
|
+
if (skip >= count) {
|
|
974
|
+
return taxonomies;
|
|
975
|
+
} else {
|
|
976
|
+
return getAllTaxonomies(payload, skip, taxonomies);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return taxonomies;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* fetch taxonomy related terms
|
|
984
|
+
* @param {object} payload
|
|
985
|
+
* @param {number} skip
|
|
986
|
+
* @param {number} limit
|
|
987
|
+
* @param {array} terms
|
|
988
|
+
* @returns
|
|
989
|
+
*/
|
|
990
|
+
async function getAllTermsOfTaxonomy(payload, skip = 0, terms = []) {
|
|
991
|
+
payload['type'] = 'terms';
|
|
992
|
+
const { items, count } = await taxonomySDKHandler(payload, skip);
|
|
993
|
+
if (items) {
|
|
994
|
+
skip += payload.limit;
|
|
995
|
+
terms.push(...items);
|
|
996
|
+
if (skip >= count) {
|
|
997
|
+
return terms;
|
|
998
|
+
} else {
|
|
999
|
+
return getAllTermsOfTaxonomy(payload, skip, terms);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return terms;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Verify the existence of a taxonomy. Obtain its details if it exists and return
|
|
1007
|
+
* @param {object} payload
|
|
1008
|
+
* @param {string} taxonomyUID
|
|
1009
|
+
* @returns
|
|
1010
|
+
*/
|
|
1011
|
+
async function getTaxonomy(payload) {
|
|
1012
|
+
payload['type'] = 'taxonomy';
|
|
1013
|
+
const resp = await taxonomySDKHandler(payload);
|
|
1014
|
+
return resp;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* taxonomy & term sdk handler
|
|
1019
|
+
* @async
|
|
1020
|
+
* @method
|
|
1021
|
+
* @param payload
|
|
1022
|
+
* @param skip
|
|
1023
|
+
* @param limit
|
|
1024
|
+
* @returns {*} Promise<any>
|
|
1025
|
+
*/
|
|
1026
|
+
async function taxonomySDKHandler(payload, skip) {
|
|
1027
|
+
const { stackAPIClient, taxonomyUID, type } = payload;
|
|
1028
|
+
|
|
1029
|
+
const queryParams = { include_count: true, limit: payload.limit };
|
|
1030
|
+
if (skip >= 0) queryParams['skip'] = skip || 0;
|
|
1031
|
+
|
|
1032
|
+
switch (type) {
|
|
1033
|
+
case 'taxonomies':
|
|
1034
|
+
return await stackAPIClient
|
|
1035
|
+
.taxonomy()
|
|
1036
|
+
.query(queryParams)
|
|
1037
|
+
.find()
|
|
1038
|
+
.then((data) => data)
|
|
1039
|
+
.catch((err) => handleErrorMsg(err));
|
|
1040
|
+
case 'taxonomy':
|
|
1041
|
+
return await stackAPIClient
|
|
1042
|
+
.taxonomy(taxonomyUID)
|
|
1043
|
+
.fetch()
|
|
1044
|
+
.then((data) => data)
|
|
1045
|
+
.catch((err) => handleErrorMsg(err));
|
|
1046
|
+
case 'terms':
|
|
1047
|
+
queryParams['depth'] = 0;
|
|
1048
|
+
return await stackAPIClient
|
|
1049
|
+
.taxonomy(taxonomyUID)
|
|
1050
|
+
.terms()
|
|
1051
|
+
.query(queryParams)
|
|
1052
|
+
.find()
|
|
1053
|
+
.then((data) => data)
|
|
1054
|
+
.catch((err) => handleErrorMsg(err));
|
|
1055
|
+
default:
|
|
1056
|
+
handleErrorMsg({ errorMessage: 'Invalid module!' });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Change taxonomies data in required CSV headers format
|
|
1062
|
+
* @param {array} taxonomies
|
|
1063
|
+
* @returns
|
|
1064
|
+
*/
|
|
1065
|
+
function formatTaxonomiesData(taxonomies) {
|
|
1066
|
+
if (taxonomies?.length) {
|
|
1067
|
+
const formattedTaxonomies = taxonomies.map((taxonomy) => {
|
|
1068
|
+
return sanitizeData({
|
|
1069
|
+
'Taxonomy UID': taxonomy.uid,
|
|
1070
|
+
Name: taxonomy.name,
|
|
1071
|
+
Description: taxonomy.description,
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
return formattedTaxonomies;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Modify the linked taxonomy data's terms in required CSV headers format
|
|
1080
|
+
* @param {array} terms
|
|
1081
|
+
* @param {string} taxonomyUID
|
|
1082
|
+
* @returns
|
|
1083
|
+
*/
|
|
1084
|
+
function formatTermsOfTaxonomyData(terms, taxonomyUID) {
|
|
1085
|
+
if (terms?.length) {
|
|
1086
|
+
const formattedTerms = terms.map((term) => {
|
|
1087
|
+
return sanitizeData({
|
|
1088
|
+
'Taxonomy UID': taxonomyUID,
|
|
1089
|
+
UID: term.uid,
|
|
1090
|
+
Name: term.name,
|
|
1091
|
+
'Parent UID': term.parent_uid,
|
|
1092
|
+
Depth: term.depth,
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
return formattedTerms;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function handleErrorMsg(err) {
|
|
1100
|
+
if (err?.errorMessage) {
|
|
1101
|
+
cliux.print(`Error: ${err.errorMessage}`, { color: 'red' });
|
|
1102
|
+
} else if (err?.message) {
|
|
1103
|
+
const errorMsg = err?.errors?.taxonomy || err?.errors?.term || err?.message;
|
|
1104
|
+
cliux.print(`Error: ${errorMsg}`, { color: 'red' });
|
|
1105
|
+
} else {
|
|
1106
|
+
console.log(err);
|
|
1107
|
+
cliux.print(`Error: ${messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, { color: 'red' });
|
|
1108
|
+
}
|
|
1109
|
+
process.exit(1);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* create an importable CSV file, to utilize with the migration script.
|
|
1114
|
+
* @param {*} payload api request payload
|
|
1115
|
+
* @param {*} taxonomies taxonomies data
|
|
1116
|
+
* @returns
|
|
1117
|
+
*/
|
|
1118
|
+
async function createImportableCSV(payload, taxonomies) {
|
|
1119
|
+
let taxonomiesData = [];
|
|
1120
|
+
let headers = ['Taxonomy Name','Taxonomy UID','Taxonomy Description'];
|
|
1121
|
+
for (let index = 0; index < taxonomies?.length; index++) {
|
|
1122
|
+
const taxonomy = taxonomies[index];
|
|
1123
|
+
const taxonomyUID = taxonomy?.uid;
|
|
1124
|
+
if (taxonomyUID) {
|
|
1125
|
+
const sanitizedTaxonomy = sanitizeData({
|
|
1126
|
+
'Taxonomy Name': taxonomy?.name,
|
|
1127
|
+
'Taxonomy UID': taxonomyUID,
|
|
1128
|
+
'Taxonomy Description': taxonomy?.description,
|
|
1129
|
+
});
|
|
1130
|
+
taxonomiesData.push(sanitizedTaxonomy);
|
|
1131
|
+
payload['taxonomyUID'] = taxonomyUID;
|
|
1132
|
+
const terms = await getAllTermsOfTaxonomy(payload);
|
|
1133
|
+
//fetch all parent terms
|
|
1134
|
+
const parentTerms = terms.filter((term) => term?.parent_uid === null);
|
|
1135
|
+
const termsData = getParentAndChildTerms(parentTerms, terms, headers);
|
|
1136
|
+
taxonomiesData.push(...termsData)
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
return {taxonomiesData, headers};
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Get the parent and child terms, then arrange them hierarchically in a CSV file.
|
|
1145
|
+
* @param {*} parentTerms list of parent terms
|
|
1146
|
+
* @param {*} terms respective terms of taxonomies
|
|
1147
|
+
* @param {*} headers list of csv headers include taxonomy and terms column
|
|
1148
|
+
* @param {*} termsData parent and child terms
|
|
1149
|
+
*/
|
|
1150
|
+
function getParentAndChildTerms(parentTerms, terms, headers, termsData=[]) {
|
|
1151
|
+
for (let i = 0; i < parentTerms?.length; i++) {
|
|
1152
|
+
const parentTerm = parentTerms[i];
|
|
1153
|
+
const levelUID = `Term Level${parentTerm.depth} UID`;
|
|
1154
|
+
const levelName = `Term Level${parentTerm.depth} Name`;
|
|
1155
|
+
if (headers.indexOf(levelName) === -1) headers.push(levelName);
|
|
1156
|
+
if (headers.indexOf(levelUID) === -1) headers.push(levelUID);
|
|
1157
|
+
const sanitizedTermData = sanitizeData({ [levelName]: parentTerm.name, [levelUID]: parentTerm.uid });
|
|
1158
|
+
termsData.push(sanitizedTermData);
|
|
1159
|
+
//fetch all sibling terms
|
|
1160
|
+
const newParents = terms.filter((term) => term.parent_uid === parentTerm.uid);
|
|
1161
|
+
if (newParents?.length) {
|
|
1162
|
+
getParentAndChildTerms(newParents, terms, headers, termsData);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return termsData;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
952
1168
|
module.exports = {
|
|
953
1169
|
chooseOrganization: chooseOrganization,
|
|
954
1170
|
chooseStack: chooseStack,
|
|
@@ -977,4 +1193,11 @@ module.exports = {
|
|
|
977
1193
|
formatError: formatError,
|
|
978
1194
|
exportOrgTeams: exportOrgTeams,
|
|
979
1195
|
exportTeams: exportTeams,
|
|
1196
|
+
getAllTaxonomies,
|
|
1197
|
+
getAllTermsOfTaxonomy,
|
|
1198
|
+
formatTaxonomiesData,
|
|
1199
|
+
formatTermsOfTaxonomyData,
|
|
1200
|
+
getTaxonomy,
|
|
1201
|
+
getStacks,
|
|
1202
|
+
createImportableCSV,
|
|
980
1203
|
};
|