@contentstack/cli-cm-branches 1.5.1 → 1.6.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/README.md +11 -4
- package/lib/branch/diff-handler.js +5 -0
- package/lib/commands/cm/branches/diff.js +8 -2
- package/lib/interfaces/index.js +3 -0
- package/lib/utils/branch-diff-utility.js +78 -37
- package/lib/utils/csv-utility.js +307 -0
- package/oclif.manifest.json +12 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ $ npm install -g @contentstack/cli-cm-branches
|
|
|
37
37
|
$ csdx COMMAND
|
|
38
38
|
running command...
|
|
39
39
|
$ csdx (--version)
|
|
40
|
-
@contentstack/cli-cm-branches/1.
|
|
40
|
+
@contentstack/cli-cm-branches/1.6.0 linux-x64 node-v22.19.0
|
|
41
41
|
$ csdx --help [COMMAND]
|
|
42
42
|
USAGE
|
|
43
43
|
$ csdx COMMAND
|
|
@@ -51,7 +51,7 @@ USAGE
|
|
|
51
51
|
* [`csdx cm:branches`](#csdx-cmbranches)
|
|
52
52
|
* [`csdx cm:branches:create`](#csdx-cmbranchescreate)
|
|
53
53
|
* [`csdx cm:branches:delete [-uid <value>] [-k <value>]`](#csdx-cmbranchesdelete--uid-value--k-value)
|
|
54
|
-
* [`csdx cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>]`](#csdx-cmbranchesdiff---base-branch-value---compare-branch-value--k-value--module-value)
|
|
54
|
+
* [`csdx cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>] [--format <value>] [--csv-path <value>]`](#csdx-cmbranchesdiff---base-branch-value---compare-branch-value--k-value--module-value---format-value---csv-path-value)
|
|
55
55
|
* [`csdx cm:branches:merge [-k <value>][--compare-branch <value>] [--no-revert] [--export-summary-path <value>] [--use-merge-summary <value>] [--comment <value>] [--base-branch <value>]`](#csdx-cmbranchesmerge--k-value--compare-branch-value---no-revert---export-summary-path-value---use-merge-summary-value---comment-value---base-branch-value)
|
|
56
56
|
|
|
57
57
|
## `csdx cm:branches`
|
|
@@ -136,18 +136,21 @@ EXAMPLES
|
|
|
136
136
|
|
|
137
137
|
_See code: [src/commands/cm/branches/delete.ts](https://github.com/contentstack/cli/blob/main/packages/contentstack-export/src/commands/cm/branches/delete.ts)_
|
|
138
138
|
|
|
139
|
-
## `csdx cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>]`
|
|
139
|
+
## `csdx cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>] [--format <value>] [--csv-path <value>]`
|
|
140
140
|
|
|
141
141
|
Differences between two branches
|
|
142
142
|
|
|
143
143
|
```
|
|
144
144
|
USAGE
|
|
145
|
-
$ csdx cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>]
|
|
145
|
+
$ csdx cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>] [--format
|
|
146
|
+
<value>] [--csv-path <value>]
|
|
146
147
|
|
|
147
148
|
FLAGS
|
|
148
149
|
-k, --stack-api-key=<value> [optional] Provide the stack API key to show the difference between branches.
|
|
149
150
|
--base-branch=<value> [optional] Base branch (Target branch).
|
|
150
151
|
--compare-branch=<value> [optional] Compare branch (Source branch).
|
|
152
|
+
--csv-path=<value> [optional] Custom path for CSV output file. If not provided, will use the current
|
|
153
|
+
working directory.
|
|
151
154
|
--format=<option> [default: compact-text] [default: compact-text] [optional] Type of flags to show the
|
|
152
155
|
difference between two branches. <options: compact-text, detailed-text>
|
|
153
156
|
<options: compact-text|detailed-text>
|
|
@@ -181,6 +184,10 @@ EXAMPLES
|
|
|
181
184
|
$ csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --compare-branch "develop" --module "content-types"
|
|
182
185
|
|
|
183
186
|
$ csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --compare-branch "develop" --module "content-types" --format "detailed-text"
|
|
187
|
+
|
|
188
|
+
$ csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --compare-branch "develop" --module "content-types" --format "compact-text"
|
|
189
|
+
|
|
190
|
+
$ csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --compare-branch "develop" --module "content-types" --format "detailed-text" --csv-path "./reports/diff-report.csv"
|
|
184
191
|
```
|
|
185
192
|
|
|
186
193
|
_See code: [src/commands/cm/branches/diff.ts](https://github.com/contentstack/cli/blob/main/packages/contentstack-export/src/commands/cm/branches/diff.ts)_
|
|
@@ -7,6 +7,7 @@ const cli_utilities_1 = require("@contentstack/cli-utilities");
|
|
|
7
7
|
const utils_1 = require("../utils");
|
|
8
8
|
const interactive_1 = require("../utils/interactive");
|
|
9
9
|
const branch_diff_utility_1 = require("../utils/branch-diff-utility");
|
|
10
|
+
const csv_utility_1 = require("../utils/csv-utility");
|
|
10
11
|
class BranchDiffHandler {
|
|
11
12
|
constructor(params) {
|
|
12
13
|
this.options = params;
|
|
@@ -40,6 +41,9 @@ class BranchDiffHandler {
|
|
|
40
41
|
if (!this.options.module) {
|
|
41
42
|
this.options.module = await (0, interactive_1.selectModule)();
|
|
42
43
|
}
|
|
44
|
+
if (this.options.format === 'detailed-text' && !this.options.csvPath) {
|
|
45
|
+
this.options.csvPath = process.cwd();
|
|
46
|
+
}
|
|
43
47
|
if (baseBranch) {
|
|
44
48
|
cli_utilities_1.cliux.print(`\nBase branch: ${baseBranch}`, { color: 'grey' });
|
|
45
49
|
}
|
|
@@ -109,6 +113,7 @@ class BranchDiffHandler {
|
|
|
109
113
|
const verboseRes = await (0, branch_diff_utility_1.parseVerbose)(branchDiffData, payload);
|
|
110
114
|
cli_utilities_1.cliux.loaderV2('', spinner);
|
|
111
115
|
(0, branch_diff_utility_1.printVerboseTextView)(verboseRes);
|
|
116
|
+
(0, csv_utility_1.exportCSVReport)(payload.module, verboseRes, this.options.csvPath);
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
119
|
}
|
|
@@ -14,7 +14,8 @@ class BranchDiffCommand extends cli_command_1.Command {
|
|
|
14
14
|
compareBranch: branchDiffFlags['compare-branch'],
|
|
15
15
|
module: branchDiffFlags.module,
|
|
16
16
|
format: branchDiffFlags.format,
|
|
17
|
-
|
|
17
|
+
csvPath: branchDiffFlags['csv-path'],
|
|
18
|
+
host: this.cmaHost,
|
|
18
19
|
};
|
|
19
20
|
if (!(0, cli_utilities_1.isAuthenticated)()) {
|
|
20
21
|
const err = { errorMessage: 'You are not logged in. Please login with command $ csdx auth:login' };
|
|
@@ -43,8 +44,10 @@ BranchDiffCommand.examples = [
|
|
|
43
44
|
'csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --module "content-types"',
|
|
44
45
|
'csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --compare-branch "develop" --module "content-types"',
|
|
45
46
|
'csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --compare-branch "develop" --module "content-types" --format "detailed-text"',
|
|
47
|
+
'csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --compare-branch "develop" --module "content-types" --format "compact-text"',
|
|
48
|
+
'csdx cm:branches:diff --stack-api-key "bltxxxxxxxx" --base-branch "main" --compare-branch "develop" --module "content-types" --format "detailed-text" --csv-path "./reports/diff-report.csv"',
|
|
46
49
|
];
|
|
47
|
-
BranchDiffCommand.usage = 'cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>]';
|
|
50
|
+
BranchDiffCommand.usage = 'cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>] [--format <value>] [--csv-path <value>]';
|
|
48
51
|
BranchDiffCommand.flags = {
|
|
49
52
|
'base-branch': cli_utilities_1.flags.string({
|
|
50
53
|
description: '[optional] Base branch (Target branch).',
|
|
@@ -66,5 +69,8 @@ BranchDiffCommand.flags = {
|
|
|
66
69
|
options: ['compact-text', 'detailed-text'],
|
|
67
70
|
description: '[default: compact-text] [optional] Type of flags to show the difference between two branches. <options: compact-text, detailed-text>',
|
|
68
71
|
}),
|
|
72
|
+
'csv-path': cli_utilities_1.flags.string({
|
|
73
|
+
description: '[optional] Custom path for CSV output file. If not provided, will use the current working directory.',
|
|
74
|
+
}),
|
|
69
75
|
};
|
|
70
76
|
BranchDiffCommand.aliases = []; // Note: alternative usage if any
|
package/lib/interfaces/index.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CSV_HEADER = exports.FIELD_TYPES = void 0;
|
|
4
|
+
exports.FIELD_TYPES = ['modified', 'added', 'deleted'];
|
|
5
|
+
exports.CSV_HEADER = 'Sr No,Content Type Name,Field Name,Field Path,Operation,Source Branch Value,Target Branch Value\n';
|
|
@@ -12,6 +12,7 @@ const find_1 = tslib_1.__importDefault(require("lodash/find"));
|
|
|
12
12
|
const cli_utilities_1 = require("@contentstack/cli-utilities");
|
|
13
13
|
const isArray_1 = tslib_1.__importDefault(require("lodash/isArray"));
|
|
14
14
|
const just_diff_1 = require("just-diff");
|
|
15
|
+
const csv_utility_1 = require("./csv-utility");
|
|
15
16
|
const config_1 = tslib_1.__importDefault(require("../config"));
|
|
16
17
|
/**
|
|
17
18
|
* Fetch differences between two branches
|
|
@@ -229,6 +230,7 @@ async function parseVerbose(branchesDiffData, payload) {
|
|
|
229
230
|
added: added,
|
|
230
231
|
deleted: deleted,
|
|
231
232
|
};
|
|
233
|
+
verboseRes.csvData = (0, csv_utility_1.generateCSVDataFromVerbose)(verboseRes);
|
|
232
234
|
return verboseRes;
|
|
233
235
|
}
|
|
234
236
|
exports.parseVerbose = parseVerbose;
|
|
@@ -270,13 +272,13 @@ exports.prepareBranchVerboseRes = prepareBranchVerboseRes;
|
|
|
270
272
|
* @param params
|
|
271
273
|
*/
|
|
272
274
|
async function baseAndCompareBranchDiff(params) {
|
|
273
|
-
const { baseBranchFieldExists, compareBranchFieldExists
|
|
275
|
+
const { baseBranchFieldExists, compareBranchFieldExists } = params;
|
|
274
276
|
if (baseBranchFieldExists && compareBranchFieldExists) {
|
|
275
277
|
await prepareModifiedDiff(params);
|
|
276
278
|
}
|
|
277
279
|
else if (baseBranchFieldExists && !compareBranchFieldExists) {
|
|
278
280
|
let displayName = baseBranchFieldExists === null || baseBranchFieldExists === void 0 ? void 0 : baseBranchFieldExists.display_name;
|
|
279
|
-
let path = baseBranchFieldExists === null || baseBranchFieldExists === void 0 ? void 0 : baseBranchFieldExists.uid;
|
|
281
|
+
let path = (baseBranchFieldExists === null || baseBranchFieldExists === void 0 ? void 0 : baseBranchFieldExists.path) || (baseBranchFieldExists === null || baseBranchFieldExists === void 0 ? void 0 : baseBranchFieldExists.uid);
|
|
280
282
|
let field = baseBranchFieldExists === null || baseBranchFieldExists === void 0 ? void 0 : baseBranchFieldExists.data_type;
|
|
281
283
|
if (baseBranchFieldExists.path === 'description') {
|
|
282
284
|
displayName = 'Description';
|
|
@@ -292,7 +294,7 @@ async function baseAndCompareBranchDiff(params) {
|
|
|
292
294
|
}
|
|
293
295
|
else if (!baseBranchFieldExists && compareBranchFieldExists) {
|
|
294
296
|
let displayName = compareBranchFieldExists === null || compareBranchFieldExists === void 0 ? void 0 : compareBranchFieldExists.display_name;
|
|
295
|
-
let path = compareBranchFieldExists === null || compareBranchFieldExists === void 0 ? void 0 : compareBranchFieldExists.uid;
|
|
297
|
+
let path = (compareBranchFieldExists === null || compareBranchFieldExists === void 0 ? void 0 : compareBranchFieldExists.path) || (compareBranchFieldExists === null || compareBranchFieldExists === void 0 ? void 0 : compareBranchFieldExists.uid);
|
|
296
298
|
let field = compareBranchFieldExists === null || compareBranchFieldExists === void 0 ? void 0 : compareBranchFieldExists.data_type;
|
|
297
299
|
if (compareBranchFieldExists.path === 'description') {
|
|
298
300
|
displayName = 'Description';
|
|
@@ -313,59 +315,72 @@ async function prepareModifiedDiff(params) {
|
|
|
313
315
|
compareBranchFieldExists.path === 'title' ||
|
|
314
316
|
compareBranchFieldExists.path === 'options.singleton') {
|
|
315
317
|
let displayName;
|
|
318
|
+
let changeDetails = '';
|
|
316
319
|
if (baseBranchFieldExists.path === 'options.singleton') {
|
|
317
320
|
if (compareBranchFieldExists.value) {
|
|
318
321
|
displayName = 'Single';
|
|
322
|
+
changeDetails = `Changed from Multiple to Single`;
|
|
319
323
|
}
|
|
320
324
|
else {
|
|
321
325
|
displayName = 'Multiple';
|
|
326
|
+
changeDetails = `Changed from Single to Multiple`;
|
|
322
327
|
}
|
|
323
328
|
}
|
|
324
329
|
else if (baseBranchFieldExists.path === 'description') {
|
|
325
330
|
displayName = 'Description';
|
|
331
|
+
const oldDesc = baseBranchFieldExists.value || 'undefined';
|
|
332
|
+
const newDesc = compareBranchFieldExists.value || 'undefined';
|
|
333
|
+
changeDetails = `Changed from "${oldDesc}" to "${newDesc}"`;
|
|
326
334
|
}
|
|
327
335
|
else if (baseBranchFieldExists.path === 'title') {
|
|
328
336
|
displayName = 'Display Name';
|
|
337
|
+
const oldTitle = baseBranchFieldExists.value || 'undefined';
|
|
338
|
+
const newTitle = compareBranchFieldExists.value || 'undefined';
|
|
339
|
+
changeDetails = `Changed from "${oldTitle}" to "${newTitle}"`;
|
|
329
340
|
}
|
|
330
341
|
params.listOfModifiedFields.push({
|
|
331
342
|
path: '',
|
|
332
343
|
displayName: displayName,
|
|
333
344
|
uid: baseBranchFieldExists.path,
|
|
334
345
|
field: 'changed',
|
|
346
|
+
changeDetails,
|
|
347
|
+
oldValue: baseBranchFieldExists.value,
|
|
348
|
+
newValue: compareBranchFieldExists.value,
|
|
335
349
|
});
|
|
336
350
|
}
|
|
337
351
|
else {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
352
|
+
const fieldDisplayName = (0, csv_utility_1.getFieldDisplayName)(compareBranchFieldExists);
|
|
353
|
+
const { modified, deleted, added } = await deepDiff(baseBranchFieldExists, compareBranchFieldExists);
|
|
354
|
+
for (let field of Object.values(added)) {
|
|
355
|
+
if (field) {
|
|
356
|
+
params.listOfAddedFields.push({
|
|
357
|
+
path: field['path'],
|
|
358
|
+
displayName: (0, csv_utility_1.getFieldDisplayName)(field),
|
|
359
|
+
uid: field['uid'],
|
|
360
|
+
field: field['fieldType'] || field['data_type'] || 'field',
|
|
361
|
+
});
|
|
349
362
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
363
|
+
}
|
|
364
|
+
for (let field of Object.values(deleted)) {
|
|
365
|
+
if (field) {
|
|
366
|
+
params.listOfDeletedFields.push({
|
|
367
|
+
path: field['path'],
|
|
368
|
+
displayName: (0, csv_utility_1.getFieldDisplayName)(field),
|
|
369
|
+
uid: field['uid'],
|
|
370
|
+
field: field['fieldType'] || field['data_type'] || 'field',
|
|
371
|
+
});
|
|
359
372
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
373
|
+
}
|
|
374
|
+
for (let field of Object.values(modified)) {
|
|
375
|
+
if (field) {
|
|
376
|
+
params.listOfModifiedFields.push({
|
|
377
|
+
path: field['path'],
|
|
378
|
+
displayName: field['displayName'] || field['display_name'] || fieldDisplayName,
|
|
379
|
+
uid: field['uid'] || (compareBranchFieldExists === null || compareBranchFieldExists === void 0 ? void 0 : compareBranchFieldExists.uid),
|
|
380
|
+
field: `${field['fieldType'] || field['data_type'] || (compareBranchFieldExists === null || compareBranchFieldExists === void 0 ? void 0 : compareBranchFieldExists.data_type) || 'field'} field`,
|
|
381
|
+
propertyChanges: field['propertyChanges'],
|
|
382
|
+
changeCount: field['changeCount'],
|
|
383
|
+
});
|
|
369
384
|
}
|
|
370
385
|
}
|
|
371
386
|
}
|
|
@@ -449,7 +464,14 @@ async function deepDiff(baseObj, compareObj) {
|
|
|
449
464
|
const { schema: compareSchema, path: comparePath } = compareObj, restCompareObj = tslib_1.__rest(compareObj, ["schema", "path"]);
|
|
450
465
|
const currentPath = buildPath(path, baseObj['uid']);
|
|
451
466
|
if (restBaseObj['uid'] === restCompareObj['uid']) {
|
|
452
|
-
prepareModifiedField({
|
|
467
|
+
prepareModifiedField({
|
|
468
|
+
restBaseObj,
|
|
469
|
+
restCompareObj,
|
|
470
|
+
currentPath,
|
|
471
|
+
changes,
|
|
472
|
+
fullFieldContext: baseObj,
|
|
473
|
+
parentContext: baseObj,
|
|
474
|
+
});
|
|
453
475
|
}
|
|
454
476
|
//case1:- base & compare schema both exists
|
|
455
477
|
if ((baseSchema === null || baseSchema === void 0 ? void 0 : baseSchema.length) && (compareSchema === null || compareSchema === void 0 ? void 0 : compareSchema.length) && (0, isArray_1.default)(baseSchema) && (0, isArray_1.default)(compareSchema)) {
|
|
@@ -498,6 +520,8 @@ function prepareAddedField(params) {
|
|
|
498
520
|
uid: compareField['uid'],
|
|
499
521
|
displayName: compareField['display_name'],
|
|
500
522
|
fieldType: compareField['data_type'],
|
|
523
|
+
oldValue: undefined,
|
|
524
|
+
newValue: compareField,
|
|
501
525
|
};
|
|
502
526
|
changes.added[path] = obj;
|
|
503
527
|
}
|
|
@@ -515,14 +539,31 @@ function prepareDeletedField(params) {
|
|
|
515
539
|
}
|
|
516
540
|
}
|
|
517
541
|
function prepareModifiedField(params) {
|
|
518
|
-
const { restBaseObj, restCompareObj, currentPath, changes } = params;
|
|
542
|
+
const { restBaseObj, restCompareObj, currentPath, changes, fullFieldContext } = params;
|
|
519
543
|
const differences = (0, just_diff_1.diff)(restBaseObj, restCompareObj);
|
|
520
544
|
if (differences.length) {
|
|
521
545
|
const modifiedField = {
|
|
522
546
|
path: currentPath,
|
|
523
|
-
uid: restCompareObj['uid'],
|
|
524
|
-
displayName: restCompareObj
|
|
525
|
-
fieldType: restCompareObj['data_type'],
|
|
547
|
+
uid: fullFieldContext['uid'] || restCompareObj['uid'],
|
|
548
|
+
displayName: (0, csv_utility_1.getFieldDisplayName)(fullFieldContext) || (0, csv_utility_1.getFieldDisplayName)(restCompareObj) || 'Field',
|
|
549
|
+
fieldType: restCompareObj['data_type'] || 'field',
|
|
550
|
+
propertyChanges: differences.map((diff) => {
|
|
551
|
+
let oldValue = 'from' in diff ? diff.from : undefined;
|
|
552
|
+
let newValue = diff.value;
|
|
553
|
+
if (!('from' in diff) && fullFieldContext && diff.path && diff.path.length > 0) {
|
|
554
|
+
const contextValue = (0, csv_utility_1.extractValueFromPath)(fullFieldContext, diff.path);
|
|
555
|
+
if (contextValue !== undefined) {
|
|
556
|
+
oldValue = contextValue;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
property: diff.path.join('.'),
|
|
561
|
+
changeType: diff.op === 'add' ? 'added' : diff.op === 'remove' ? 'deleted' : 'modified',
|
|
562
|
+
oldValue: oldValue,
|
|
563
|
+
newValue: newValue,
|
|
564
|
+
};
|
|
565
|
+
}),
|
|
566
|
+
changeCount: differences.length,
|
|
526
567
|
};
|
|
527
568
|
if (!changes.modified[currentPath])
|
|
528
569
|
changes.modified[currentPath] = modifiedField;
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exportCSVReport = exports.generateCSVDataFromVerbose = exports.generateFieldPath = exports.extractValueFromPath = exports.formatValue = exports.getFieldDisplayName = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const cli_utilities_1 = require("@contentstack/cli-utilities");
|
|
7
|
+
const interfaces_1 = require("../interfaces");
|
|
8
|
+
/**
|
|
9
|
+
* Get display name for a field with special handling for system fields
|
|
10
|
+
* @param field - The field object
|
|
11
|
+
* @returns {string} Display name for the field
|
|
12
|
+
*/
|
|
13
|
+
function getFieldDisplayName(field) {
|
|
14
|
+
let fieldName = (field === null || field === void 0 ? void 0 : field.displayName) || (field === null || field === void 0 ? void 0 : field.display_name) || (field === null || field === void 0 ? void 0 : field.title) || (field === null || field === void 0 ? void 0 : field.uid);
|
|
15
|
+
// Special handling for field_rules
|
|
16
|
+
if (!fieldName && (field === null || field === void 0 ? void 0 : field.path) === 'field_rules') {
|
|
17
|
+
fieldName = 'Field Rules';
|
|
18
|
+
}
|
|
19
|
+
else if (!fieldName) {
|
|
20
|
+
fieldName = 'Unknown Field';
|
|
21
|
+
}
|
|
22
|
+
return fieldName;
|
|
23
|
+
}
|
|
24
|
+
exports.getFieldDisplayName = getFieldDisplayName;
|
|
25
|
+
/**
|
|
26
|
+
* Format values for CSV display
|
|
27
|
+
* @param value - The value to format
|
|
28
|
+
* @returns {string} Formatted string value
|
|
29
|
+
*/
|
|
30
|
+
function formatValue(value) {
|
|
31
|
+
if (value === null || value === undefined) {
|
|
32
|
+
return 'N/A';
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
if (value === '')
|
|
36
|
+
return 'N/A';
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
40
|
+
return String(value);
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return value.length > 0 ? value.join(', ') : 'Empty Array';
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === 'object' && value !== null) {
|
|
46
|
+
// Priority order for extracting meaningful values
|
|
47
|
+
const priorityKeys = ['title', 'display_name', 'uid', 'value', 'key'];
|
|
48
|
+
for (const key of priorityKeys) {
|
|
49
|
+
if ((value === null || value === void 0 ? void 0 : value[key]) !== undefined && (value === null || value === void 0 ? void 0 : value[key]) !== '') {
|
|
50
|
+
return value[key];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (value === null || value === void 0 ? void 0 : value.data_type) {
|
|
54
|
+
``;
|
|
55
|
+
const simpleFieldTypes = ['text', 'boolean', 'number'];
|
|
56
|
+
if (simpleFieldTypes.includes(value.data_type) && (value === null || value === void 0 ? void 0 : value.default_value) !== undefined) {
|
|
57
|
+
return value.default_value;
|
|
58
|
+
}
|
|
59
|
+
if (value.data_type === 'field' || (value === null || value === void 0 ? void 0 : value.path)) {
|
|
60
|
+
return `${value.data_type} field`;
|
|
61
|
+
}
|
|
62
|
+
return `${value.data_type} field`;
|
|
63
|
+
}
|
|
64
|
+
// Special handling for field rules objects
|
|
65
|
+
if ((value === null || value === void 0 ? void 0 : value.path) && !(value === null || value === void 0 ? void 0 : value.data_type)) {
|
|
66
|
+
return value.path;
|
|
67
|
+
}
|
|
68
|
+
// For enum choices or other structured objects, try to extract meaningful info
|
|
69
|
+
const metadataKeys = ['mandatory', 'multiple', 'non_localizable'];
|
|
70
|
+
for (const key of metadataKeys) {
|
|
71
|
+
if ((value === null || value === void 0 ? void 0 : value[key]) !== undefined)
|
|
72
|
+
return `${key}: ${value[key]}`;
|
|
73
|
+
}
|
|
74
|
+
// If no meaningful properties found, stringify the object with char limit
|
|
75
|
+
const objectString = JSON.stringify(value);
|
|
76
|
+
if (objectString.length <= 200) {
|
|
77
|
+
return objectString;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
return `${objectString.substring(0, 200)}... (Please check the field as the char limit has been reached)`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return String(value);
|
|
84
|
+
}
|
|
85
|
+
exports.formatValue = formatValue;
|
|
86
|
+
/**
|
|
87
|
+
* Extract a value from an object using a path array
|
|
88
|
+
* @param obj - The object to traverse
|
|
89
|
+
* @param path - Array of path segments
|
|
90
|
+
* @returns The value at the path, or undefined if not found
|
|
91
|
+
*/
|
|
92
|
+
function extractValueFromPath(obj, path) {
|
|
93
|
+
if (!obj || !path || path.length === 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
try {
|
|
96
|
+
let value = obj;
|
|
97
|
+
for (const pathSegment of path) {
|
|
98
|
+
if (value && typeof value === 'object') {
|
|
99
|
+
value = value[pathSegment];
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
cli_utilities_1.log.debug(`Failed to extract value from path ${path.join('.')}:`, error);
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.extractValueFromPath = extractValueFromPath;
|
|
113
|
+
/**
|
|
114
|
+
* Generate a descriptive field path showing the hierarchical location
|
|
115
|
+
* @param field - The field object containing path information
|
|
116
|
+
* @param contentTypeName - The name of the content type (for root-level context)
|
|
117
|
+
* @returns {string} Descriptive field path in readable format
|
|
118
|
+
*/
|
|
119
|
+
function generateFieldPath(field, contentTypeName) {
|
|
120
|
+
if (!(field === null || field === void 0 ? void 0 : field.path) || field.path === 'N/A') {
|
|
121
|
+
return 'N/A';
|
|
122
|
+
}
|
|
123
|
+
let readablePath = contentTypeName || '';
|
|
124
|
+
const fieldPath = field.path;
|
|
125
|
+
if (fieldPath.includes('.')) {
|
|
126
|
+
const pathParts = fieldPath.split('.');
|
|
127
|
+
const readableParts = pathParts.map(part => {
|
|
128
|
+
if (part === 'schema')
|
|
129
|
+
return '';
|
|
130
|
+
return part;
|
|
131
|
+
}).filter(part => part !== '');
|
|
132
|
+
if (readableParts.length > 0) {
|
|
133
|
+
readablePath += readablePath ? ' → ' : '';
|
|
134
|
+
readablePath += readableParts.join(' → ');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (fieldPath.includes('[') && fieldPath.includes(']')) {
|
|
138
|
+
if (readablePath) {
|
|
139
|
+
readablePath += ' → ';
|
|
140
|
+
}
|
|
141
|
+
const displayName = typeof (field === null || field === void 0 ? void 0 : field.displayName) === 'string' ? field.displayName : '';
|
|
142
|
+
const uid = typeof (field === null || field === void 0 ? void 0 : field.uid) === 'string' ? field.uid : '';
|
|
143
|
+
readablePath += displayName || uid || fieldPath;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
if (readablePath) {
|
|
147
|
+
readablePath += ' → ';
|
|
148
|
+
}
|
|
149
|
+
readablePath += fieldPath;
|
|
150
|
+
}
|
|
151
|
+
return readablePath || 'N/A';
|
|
152
|
+
}
|
|
153
|
+
exports.generateFieldPath = generateFieldPath;
|
|
154
|
+
/**
|
|
155
|
+
* Add content type rows to CSV data
|
|
156
|
+
* @param csvRows - The CSV rows array to add to
|
|
157
|
+
* @param items - Array of content type items
|
|
158
|
+
* @param operation - The operation type ('added' or 'deleted')
|
|
159
|
+
* @param getSrNo - Function to get the next serial number
|
|
160
|
+
*/
|
|
161
|
+
function addContentTypeRows(csvRows, items, operation, getSrNo) {
|
|
162
|
+
if (!(items === null || items === void 0 ? void 0 : items.length))
|
|
163
|
+
return;
|
|
164
|
+
for (const item of items) {
|
|
165
|
+
const contentTypeName = (item === null || item === void 0 ? void 0 : item.title) || (item === null || item === void 0 ? void 0 : item.uid) || 'Unknown';
|
|
166
|
+
addCSVRow(csvRows, {
|
|
167
|
+
srNo: getSrNo(),
|
|
168
|
+
contentTypeName,
|
|
169
|
+
fieldName: 'Content Type',
|
|
170
|
+
fieldType: operation,
|
|
171
|
+
sourceValue: 'N/A',
|
|
172
|
+
targetValue: 'N/A'
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Generate CSV data from verbose results
|
|
178
|
+
* @param verboseRes - The verbose results containing all diff data
|
|
179
|
+
* @returns {CSVRow[]} Array of CSV rows
|
|
180
|
+
*/
|
|
181
|
+
function generateCSVDataFromVerbose(verboseRes) {
|
|
182
|
+
var _a, _b, _c;
|
|
183
|
+
const csvRows = [];
|
|
184
|
+
let srNo = 1;
|
|
185
|
+
if ((_a = verboseRes.modified) === null || _a === void 0 ? void 0 : _a.length) {
|
|
186
|
+
for (const moduleDetail of verboseRes.modified) {
|
|
187
|
+
const contentTypeName = ((_b = moduleDetail === null || moduleDetail === void 0 ? void 0 : moduleDetail.moduleDetails) === null || _b === void 0 ? void 0 : _b.title) || ((_c = moduleDetail === null || moduleDetail === void 0 ? void 0 : moduleDetail.moduleDetails) === null || _c === void 0 ? void 0 : _c.uid) || 'Unknown';
|
|
188
|
+
if (moduleDetail.modifiedFields) {
|
|
189
|
+
addFieldChangesToCSV(csvRows, {
|
|
190
|
+
contentTypeName,
|
|
191
|
+
modifiedFields: moduleDetail.modifiedFields,
|
|
192
|
+
operation: 'modified',
|
|
193
|
+
startSrNo: srNo
|
|
194
|
+
});
|
|
195
|
+
srNo += getFieldCount(moduleDetail.modifiedFields);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
addContentTypeRows(csvRows, verboseRes.added, 'added', () => srNo++);
|
|
200
|
+
addContentTypeRows(csvRows, verboseRes.deleted, 'deleted', () => srNo++);
|
|
201
|
+
return csvRows;
|
|
202
|
+
}
|
|
203
|
+
exports.generateCSVDataFromVerbose = generateCSVDataFromVerbose;
|
|
204
|
+
/**
|
|
205
|
+
* Add a CSV row with common properties
|
|
206
|
+
* @param csvRows - Array of CSV rows to add to
|
|
207
|
+
* @param params - Object containing CSV row parameters
|
|
208
|
+
*/
|
|
209
|
+
function addCSVRow(csvRows, params) {
|
|
210
|
+
csvRows.push({
|
|
211
|
+
srNo: params.srNo,
|
|
212
|
+
contentTypeName: params.contentTypeName,
|
|
213
|
+
fieldName: params.fieldName,
|
|
214
|
+
fieldPath: 'N/A',
|
|
215
|
+
operation: params.fieldType,
|
|
216
|
+
sourceBranchValue: params.sourceValue,
|
|
217
|
+
targetBranchValue: params.targetValue,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Add field changes to CSV rows
|
|
222
|
+
* @param csvRows - Array of CSV rows to add to
|
|
223
|
+
* @param params - Object containing field changes parameters
|
|
224
|
+
*/
|
|
225
|
+
function addFieldChangesToCSV(csvRows, params) {
|
|
226
|
+
const fieldTypes = interfaces_1.FIELD_TYPES;
|
|
227
|
+
let srNo = params.startSrNo;
|
|
228
|
+
fieldTypes.forEach(fieldType => {
|
|
229
|
+
const fields = params.modifiedFields[fieldType];
|
|
230
|
+
if (!fields)
|
|
231
|
+
return;
|
|
232
|
+
fields.forEach(field => {
|
|
233
|
+
var _a;
|
|
234
|
+
const fieldName = getFieldDisplayName(field);
|
|
235
|
+
if (((_a = field.propertyChanges) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
236
|
+
field.propertyChanges.forEach(propertyChange => {
|
|
237
|
+
addCSVRow(csvRows, {
|
|
238
|
+
srNo: srNo++,
|
|
239
|
+
contentTypeName: params.contentTypeName,
|
|
240
|
+
fieldName,
|
|
241
|
+
fieldType,
|
|
242
|
+
sourceValue: formatValue(propertyChange.newValue),
|
|
243
|
+
targetValue: formatValue(propertyChange.oldValue)
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
addCSVRow(csvRows, {
|
|
249
|
+
srNo: srNo++,
|
|
250
|
+
contentTypeName: params.contentTypeName,
|
|
251
|
+
fieldName,
|
|
252
|
+
fieldType,
|
|
253
|
+
sourceValue: fieldType === 'added' ? 'N/A' : formatValue(field),
|
|
254
|
+
targetValue: fieldType === 'deleted' ? 'N/A' : formatValue(field)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get total field count for serial number calculation
|
|
262
|
+
* @param modifiedFields - Field changes data
|
|
263
|
+
* @returns {number} Total number of fields
|
|
264
|
+
*/
|
|
265
|
+
function getFieldCount(modifiedFields) {
|
|
266
|
+
let count = 0;
|
|
267
|
+
const fieldTypes = interfaces_1.FIELD_TYPES;
|
|
268
|
+
fieldTypes.forEach(fieldType => {
|
|
269
|
+
const fields = modifiedFields[fieldType];
|
|
270
|
+
if (fields) {
|
|
271
|
+
fields.forEach(field => {
|
|
272
|
+
var _a;
|
|
273
|
+
if (((_a = field.propertyChanges) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
274
|
+
count += field.propertyChanges.length;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
count += 1;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
return count;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Export CSV report to file using pre-processed data
|
|
286
|
+
* @param moduleName - The module name (content-types, global-fields, etc.)
|
|
287
|
+
* @param diffData - The diff data from branch comparison (must include csvData)
|
|
288
|
+
* @param customPath - Optional custom path for CSV output
|
|
289
|
+
*/
|
|
290
|
+
function exportCSVReport(moduleName, diffData, customPath) {
|
|
291
|
+
let csvPath;
|
|
292
|
+
if (customPath) {
|
|
293
|
+
csvPath = (0, path_1.join)(customPath, `${(0, cli_utilities_1.sanitizePath)(moduleName)}-diff.csv`);
|
|
294
|
+
if (!(0, fs_1.existsSync)(customPath)) {
|
|
295
|
+
(0, fs_1.mkdirSync)(customPath, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
csvPath = (0, path_1.join)(process.cwd(), `${(0, cli_utilities_1.sanitizePath)(moduleName)}-diff.csv`);
|
|
300
|
+
}
|
|
301
|
+
const csvRows = diffData.csvData || [];
|
|
302
|
+
const csvHeader = interfaces_1.CSV_HEADER;
|
|
303
|
+
const csvContent = csvRows.map(row => `${row.srNo},"${row.contentTypeName}","${row.fieldName}","${row.fieldPath}","${row.operation}","${row.sourceBranchValue}","${row.targetBranchValue}"`).join('\n');
|
|
304
|
+
(0, fs_1.writeFileSync)(csvPath, csvHeader + csvContent);
|
|
305
|
+
cli_utilities_1.cliux.print(`CSV report generated at: ${csvPath}`, { color: 'green' });
|
|
306
|
+
}
|
|
307
|
+
exports.exportCSVReport = exportCSVReport;
|
package/oclif.manifest.json
CHANGED
|
@@ -124,7 +124,9 @@
|
|
|
124
124
|
"csdx cm:branches:diff --stack-api-key \"bltxxxxxxxx\" --base-branch \"main\" --compare-branch \"develop\"",
|
|
125
125
|
"csdx cm:branches:diff --stack-api-key \"bltxxxxxxxx\" --base-branch \"main\" --module \"content-types\"",
|
|
126
126
|
"csdx cm:branches:diff --stack-api-key \"bltxxxxxxxx\" --base-branch \"main\" --compare-branch \"develop\" --module \"content-types\"",
|
|
127
|
-
"csdx cm:branches:diff --stack-api-key \"bltxxxxxxxx\" --base-branch \"main\" --compare-branch \"develop\" --module \"content-types\" --format \"detailed-text\""
|
|
127
|
+
"csdx cm:branches:diff --stack-api-key \"bltxxxxxxxx\" --base-branch \"main\" --compare-branch \"develop\" --module \"content-types\" --format \"detailed-text\"",
|
|
128
|
+
"csdx cm:branches:diff --stack-api-key \"bltxxxxxxxx\" --base-branch \"main\" --compare-branch \"develop\" --module \"content-types\" --format \"compact-text\"",
|
|
129
|
+
"csdx cm:branches:diff --stack-api-key \"bltxxxxxxxx\" --base-branch \"main\" --compare-branch \"develop\" --module \"content-types\" --format \"detailed-text\" --csv-path \"./reports/diff-report.csv\""
|
|
128
130
|
],
|
|
129
131
|
"flags": {
|
|
130
132
|
"base-branch": {
|
|
@@ -172,6 +174,13 @@
|
|
|
172
174
|
"detailed-text"
|
|
173
175
|
],
|
|
174
176
|
"type": "option"
|
|
177
|
+
},
|
|
178
|
+
"csv-path": {
|
|
179
|
+
"description": "[optional] Custom path for CSV output file. If not provided, will use the current working directory.",
|
|
180
|
+
"name": "csv-path",
|
|
181
|
+
"hasDynamicHelp": false,
|
|
182
|
+
"multiple": false,
|
|
183
|
+
"type": "option"
|
|
175
184
|
}
|
|
176
185
|
},
|
|
177
186
|
"hasDynamicHelp": false,
|
|
@@ -181,7 +190,7 @@
|
|
|
181
190
|
"pluginName": "@contentstack/cli-cm-branches",
|
|
182
191
|
"pluginType": "core",
|
|
183
192
|
"strict": true,
|
|
184
|
-
"usage": "cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>]",
|
|
193
|
+
"usage": "cm:branches:diff [--base-branch <value>] [--compare-branch <value>] [-k <value>][--module <value>] [--format <value>] [--csv-path <value>]",
|
|
185
194
|
"isESM": false,
|
|
186
195
|
"relativePath": [
|
|
187
196
|
"lib",
|
|
@@ -355,5 +364,5 @@
|
|
|
355
364
|
]
|
|
356
365
|
}
|
|
357
366
|
},
|
|
358
|
-
"version": "1.
|
|
367
|
+
"version": "1.6.0"
|
|
359
368
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentstack/cli-cm-branches",
|
|
3
3
|
"description": "Contentstack CLI plugin to do branches operations",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"author": "Contentstack",
|
|
6
6
|
"bugs": "https://github.com/contentstack/cli/issues",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@contentstack/cli-command": "~1.6.
|
|
8
|
+
"@contentstack/cli-command": "~1.6.1",
|
|
9
9
|
"@oclif/core": "^4.3.0",
|
|
10
10
|
"@oclif/plugin-help": "^6.2.28",
|
|
11
|
-
"@contentstack/cli-utilities": "~1.
|
|
11
|
+
"@contentstack/cli-utilities": "~1.14.1",
|
|
12
12
|
"chalk": "^4.1.2",
|
|
13
13
|
"just-diff": "^6.0.2",
|
|
14
14
|
"lodash": "^4.17.21"
|