@contentstack/cli-cm-branches 1.5.2 → 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/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.5.2 linux-x64 node-v22.18.0
40
+ @contentstack/cli-cm-branches/1.6.1 linux-x64 node-v22.20.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
- host: this.cmaHost
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
@@ -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, diffData } = params;
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
- if ((baseBranchFieldExists === null || baseBranchFieldExists === void 0 ? void 0 : baseBranchFieldExists.display_name) && (compareBranchFieldExists === null || compareBranchFieldExists === void 0 ? void 0 : compareBranchFieldExists.display_name)) {
339
- const { modified, deleted, added } = await deepDiff(baseBranchFieldExists, compareBranchFieldExists);
340
- for (let field of Object.values(added)) {
341
- if (field) {
342
- params.listOfAddedFields.push({
343
- path: field['path'],
344
- displayName: field['displayName'],
345
- uid: field['uid'],
346
- field: field['fieldType'],
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
- for (let field of Object.values(deleted)) {
351
- if (field) {
352
- params.listOfDeletedFields.push({
353
- path: field['path'],
354
- displayName: field['displayName'],
355
- uid: field['uid'],
356
- field: field['fieldType'],
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
- for (let field of Object.values(modified)) {
361
- if (field) {
362
- params.listOfModifiedFields.push({
363
- path: field['path'],
364
- displayName: field['displayName'],
365
- uid: field['uid'],
366
- field: `${field['fieldType']} field`,
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({ restBaseObj, restCompareObj, currentPath, changes });
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['display_name'],
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;
@@ -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.5.2"
367
+ "version": "1.6.1"
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.5.2",
4
+ "version": "1.6.1",
5
5
  "author": "Contentstack",
6
6
  "bugs": "https://github.com/contentstack/cli/issues",
7
7
  "dependencies": {
8
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.14.0",
11
+ "@contentstack/cli-utilities": "~1.15.0",
12
12
  "chalk": "^4.1.2",
13
13
  "just-diff": "^6.0.2",
14
14
  "lodash": "^4.17.21"