@contentstack/cli-audit 1.7.4 → 1.8.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 CHANGED
@@ -19,7 +19,7 @@ $ npm install -g @contentstack/cli-audit
19
19
  $ csdx COMMAND
20
20
  running command...
21
21
  $ csdx (--version|-v)
22
- @contentstack/cli-audit/1.7.4 linux-x64 node-v18.20.5
22
+ @contentstack/cli-audit/1.8.0 linux-x64 node-v22.14.0
23
23
  $ csdx --help [COMMAND]
24
24
  USAGE
25
25
  $ csdx COMMAND
@@ -53,12 +53,12 @@ Perform audits and find possible errors in the exported Contentstack data
53
53
  ```
54
54
  USAGE
55
55
  $ csdx audit [--report-path <value>] [--modules
56
- content-types|global-fields|entries|extensions|workflows|custom-roles] [--columns <value> | ] [--sort <value>]
57
- [--filter <value>] [--csv | --no-truncate]
56
+ content-types|global-fields|entries|extensions|workflows|custom-roles|assets] [--columns <value> | ] [--sort
57
+ <value>] [--filter <value>] [--csv | --no-truncate]
58
58
 
59
59
  FLAGS
60
60
  --modules=<option>... Provide the list of modules to be audited
61
- <options: content-types|global-fields|entries|extensions|workflows|custom-roles>
61
+ <options: content-types|global-fields|entries|extensions|workflows|custom-roles|assets>
62
62
  --report-path=<value> Path to store the audit reports
63
63
 
64
64
  TABLE FLAGS
@@ -94,9 +94,9 @@ Perform audits and fix possible errors in the exported Contentstack data.
94
94
  ```
95
95
  USAGE
96
96
  $ csdx audit:fix [--report-path <value>] [--modules
97
- content-types|global-fields|entries|extensions|workflows|custom-roles] [--copy-path <value> --copy-dir] [--fix-only
98
- reference|global_field|json:rte|json:extension|blocks|group|content_types] [--columns <value> | ] [--sort <value>]
99
- [--filter <value>] [--csv | --no-truncate]
97
+ content-types|global-fields|entries|extensions|workflows|custom-roles|assets] [--copy-path <value> --copy-dir]
98
+ [--fix-only reference|global_field|json:rte|json:extension|blocks|group|content_types] [--columns <value> | ]
99
+ [--sort <value>] [--filter <value>] [--csv | --no-truncate]
100
100
 
101
101
  FLAGS
102
102
  --copy-dir Create backup from the original data.
@@ -104,7 +104,7 @@ FLAGS
104
104
  --fix-only=<option>... Provide the list of fix options
105
105
  <options: reference|global_field|json:rte|json:extension|blocks|group|content_types>
106
106
  --modules=<option>... Provide the list of modules to be audited
107
- <options: content-types|global-fields|entries|extensions|workflows|custom-roles>
107
+ <options: content-types|global-fields|entries|extensions|workflows|custom-roles|assets>
108
108
  --report-path=<value> Path to store the audit reports
109
109
 
110
110
  TABLE FLAGS
@@ -142,12 +142,12 @@ Perform audits and find possible errors in the exported Contentstack data
142
142
  ```
143
143
  USAGE
144
144
  $ csdx cm:stacks:audit [--report-path <value>] [--modules
145
- content-types|global-fields|entries|extensions|workflows|custom-roles] [--columns <value> | ] [--sort <value>]
146
- [--filter <value>] [--csv | --no-truncate]
145
+ content-types|global-fields|entries|extensions|workflows|custom-roles|assets] [--columns <value> | ] [--sort
146
+ <value>] [--filter <value>] [--csv | --no-truncate]
147
147
 
148
148
  FLAGS
149
149
  --modules=<option>... Provide the list of modules to be audited
150
- <options: content-types|global-fields|entries|extensions|workflows|custom-roles>
150
+ <options: content-types|global-fields|entries|extensions|workflows|custom-roles|assets>
151
151
  --report-path=<value> Path to store the audit reports
152
152
 
153
153
  TABLE FLAGS
@@ -185,9 +185,9 @@ Perform audits and fix possible errors in the exported Contentstack data.
185
185
  ```
186
186
  USAGE
187
187
  $ csdx cm:stacks:audit:fix [--report-path <value>] [--modules
188
- content-types|global-fields|entries|extensions|workflows|custom-roles] [--copy-path <value> --copy-dir] [--fix-only
189
- reference|global_field|json:rte|json:extension|blocks|group|content_types] [--columns <value> | ] [--sort <value>]
190
- [--filter <value>] [--csv | --no-truncate]
188
+ content-types|global-fields|entries|extensions|workflows|custom-roles|assets] [--copy-path <value> --copy-dir]
189
+ [--fix-only reference|global_field|json:rte|json:extension|blocks|group|content_types] [--columns <value> | ]
190
+ [--sort <value>] [--filter <value>] [--csv | --no-truncate]
191
191
 
192
192
  FLAGS
193
193
  --copy-dir Create backup from the original data.
@@ -195,7 +195,7 @@ FLAGS
195
195
  --fix-only=<option>... Provide the list of fix options
196
196
  <options: reference|global_field|json:rte|json:extension|blocks|group|content_types>
197
197
  --modules=<option>... Provide the list of modules to be audited
198
- <options: content-types|global-fields|entries|extensions|workflows|custom-roles>
198
+ <options: content-types|global-fields|entries|extensions|workflows|custom-roles|assets>
199
199
  --report-path=<value> Path to store the audit reports
200
200
 
201
201
  TABLE FLAGS
@@ -269,7 +269,7 @@ EXAMPLES
269
269
  $ csdx plugins
270
270
  ```
271
271
 
272
- _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.22/src/commands/plugins/index.ts)_
272
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.34/src/commands/plugins/index.ts)_
273
273
 
274
274
  ## `csdx plugins:add PLUGIN`
275
275
 
@@ -343,7 +343,7 @@ EXAMPLES
343
343
  $ csdx plugins:inspect myplugin
344
344
  ```
345
345
 
346
- _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.22/src/commands/plugins/inspect.ts)_
346
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.34/src/commands/plugins/inspect.ts)_
347
347
 
348
348
  ## `csdx plugins:install PLUGIN`
349
349
 
@@ -392,7 +392,7 @@ EXAMPLES
392
392
  $ csdx plugins:install someuser/someplugin
393
393
  ```
394
394
 
395
- _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.22/src/commands/plugins/install.ts)_
395
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.34/src/commands/plugins/install.ts)_
396
396
 
397
397
  ## `csdx plugins:link PATH`
398
398
 
@@ -423,7 +423,7 @@ EXAMPLES
423
423
  $ csdx plugins:link myplugin
424
424
  ```
425
425
 
426
- _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.22/src/commands/plugins/link.ts)_
426
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.34/src/commands/plugins/link.ts)_
427
427
 
428
428
  ## `csdx plugins:remove [PLUGIN]`
429
429
 
@@ -464,7 +464,7 @@ FLAGS
464
464
  --reinstall Reinstall all plugins after uninstalling.
465
465
  ```
466
466
 
467
- _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.22/src/commands/plugins/reset.ts)_
467
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.34/src/commands/plugins/reset.ts)_
468
468
 
469
469
  ## `csdx plugins:uninstall [PLUGIN]`
470
470
 
@@ -492,7 +492,7 @@ EXAMPLES
492
492
  $ csdx plugins:uninstall myplugin
493
493
  ```
494
494
 
495
- _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.22/src/commands/plugins/uninstall.ts)_
495
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.34/src/commands/plugins/uninstall.ts)_
496
496
 
497
497
  ## `csdx plugins:unlink [PLUGIN]`
498
498
 
@@ -536,5 +536,5 @@ DESCRIPTION
536
536
  Update installed plugins.
537
537
  ```
538
538
 
539
- _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.22/src/commands/plugins/update.ts)_
539
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.34/src/commands/plugins/update.ts)_
540
540
  <!-- commandsstop -->
@@ -33,6 +33,8 @@ export declare abstract class AuditBaseCommand extends BaseCommand<typeof AuditB
33
33
  missingMandatoryFields: Record<string, any> | undefined;
34
34
  missingTitleFields: Record<string, any> | undefined;
35
35
  missingRefInCustomRoles: {} | undefined;
36
+ missingEnvLocalesInAssets: Record<string, any> | undefined;
37
+ missingEnvLocalesInEntries: Record<string, any> | undefined;
36
38
  }>;
37
39
  /**
38
40
  * The `promptQueue` function prompts the user to enter a data directory path if the `data-dir` flag
@@ -41,7 +41,7 @@ class AuditBaseCommand extends base_command_1.BaseCommand {
41
41
  await this.promptQueue();
42
42
  await this.createBackUp();
43
43
  this.sharedConfig.reportPath = (0, path_1.resolve)(this.flags['report-path'] || process.cwd(), 'audit-report');
44
- const { missingCtRefs, missingGfRefs, missingEntryRefs, missingCtRefsInExtensions, missingCtRefsInWorkflow, missingSelectFeild, missingMandatoryFields, missingTitleFields, missingRefInCustomRoles } = await this.scanAndFix();
44
+ const { missingCtRefs, missingGfRefs, missingEntryRefs, missingCtRefsInExtensions, missingCtRefsInWorkflow, missingSelectFeild, missingMandatoryFields, missingTitleFields, missingRefInCustomRoles, missingEnvLocalesInAssets, missingEnvLocalesInEntries } = await this.scanAndFix();
45
45
  this.showOutputOnScreen([
46
46
  { module: 'Content types', missingRefs: missingCtRefs },
47
47
  { module: 'Global Fields', missingRefs: missingGfRefs },
@@ -57,6 +57,8 @@ class AuditBaseCommand extends base_command_1.BaseCommand {
57
57
  { module: 'Entries Title Field', missingRefs: missingTitleFields },
58
58
  ]);
59
59
  this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Custom Roles', missingRefs: missingRefInCustomRoles }]);
60
+ this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Assets', missingRefs: missingEnvLocalesInAssets }]);
61
+ this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Entries Missing Locale and Environments', missingRefs: missingEnvLocalesInEntries }]);
60
62
  if (!(0, isEmpty_1.default)(missingCtRefs) ||
61
63
  !(0, isEmpty_1.default)(missingGfRefs) ||
62
64
  !(0, isEmpty_1.default)(missingEntryRefs) ||
@@ -64,7 +66,9 @@ class AuditBaseCommand extends base_command_1.BaseCommand {
64
66
  !(0, isEmpty_1.default)(missingCtRefsInExtensions) ||
65
67
  !(0, isEmpty_1.default)(missingSelectFeild) ||
66
68
  !(0, isEmpty_1.default)(missingTitleFields) ||
67
- !(0, isEmpty_1.default)(missingRefInCustomRoles)) {
69
+ !(0, isEmpty_1.default)(missingRefInCustomRoles) ||
70
+ !(0, isEmpty_1.default)(missingEnvLocalesInAssets) ||
71
+ !(0, isEmpty_1.default)(missingEnvLocalesInEntries)) {
68
72
  if (this.currentCommand === 'cm:stacks:audit') {
69
73
  this.log(this.$t(messages_1.auditMsg.FINAL_REPORT_PATH, { path: this.sharedConfig.reportPath }), 'warn');
70
74
  }
@@ -88,7 +92,9 @@ class AuditBaseCommand extends base_command_1.BaseCommand {
88
92
  !(0, isEmpty_1.default)(missingCtRefsInWorkflow) ||
89
93
  !(0, isEmpty_1.default)(missingCtRefsInExtensions) ||
90
94
  !(0, isEmpty_1.default)(missingSelectFeild) ||
91
- !(0, isEmpty_1.default)(missingRefInCustomRoles));
95
+ !(0, isEmpty_1.default)(missingRefInCustomRoles) ||
96
+ !(0, isEmpty_1.default)(missingEnvLocalesInAssets) ||
97
+ !(0, isEmpty_1.default)(missingEnvLocalesInEntries));
92
98
  }
93
99
  /**
94
100
  * The `scan` function performs an audit on different modules (content-types, global-fields, and
@@ -97,9 +103,9 @@ class AuditBaseCommand extends base_command_1.BaseCommand {
97
103
  * and `missingEntryRefs`.
98
104
  */
99
105
  async scanAndFix() {
100
- var _a, _b, _c, _d;
106
+ var _a, _b, _c, _d, _e;
101
107
  let { ctSchema, gfSchema } = this.getCtAndGfSchema();
102
- let missingCtRefs, missingGfRefs, missingEntryRefs, missingCtRefsInExtensions, missingCtRefsInWorkflow, missingSelectFeild, missingEntry, missingMandatoryFields, missingTitleFields, missingRefInCustomRoles;
108
+ let missingCtRefs, missingGfRefs, missingEntryRefs, missingCtRefsInExtensions, missingCtRefsInWorkflow, missingSelectFeild, missingEntry, missingMandatoryFields, missingTitleFields, missingRefInCustomRoles, missingEnvLocalesInAssets, missingEnvLocalesInEntries;
103
109
  for (const module of this.sharedConfig.flags.modules || this.sharedConfig.modules) {
104
110
  (0, log_1.print)([
105
111
  {
@@ -117,6 +123,10 @@ class AuditBaseCommand extends base_command_1.BaseCommand {
117
123
  fix: this.currentCommand === 'cm:stacks:audit:fix',
118
124
  };
119
125
  switch (module) {
126
+ case 'assets':
127
+ missingEnvLocalesInAssets = await new modules_1.Assets((0, cloneDeep_1.default)(constructorParam)).run();
128
+ await this.prepareReport(module, missingEnvLocalesInAssets);
129
+ break;
120
130
  case 'content-types':
121
131
  missingCtRefs = await new modules_1.ContentType((0, cloneDeep_1.default)(constructorParam)).run();
122
132
  await this.prepareReport(module, missingCtRefs);
@@ -131,10 +141,12 @@ class AuditBaseCommand extends base_command_1.BaseCommand {
131
141
  missingSelectFeild = (_b = missingEntry.missingSelectFeild) !== null && _b !== void 0 ? _b : {};
132
142
  missingMandatoryFields = (_c = missingEntry.missingMandatoryFields) !== null && _c !== void 0 ? _c : {};
133
143
  missingTitleFields = (_d = missingEntry.missingTitleFields) !== null && _d !== void 0 ? _d : {};
144
+ missingEnvLocalesInEntries = (_e = missingEntry.missingEnvLocale) !== null && _e !== void 0 ? _e : {};
134
145
  await this.prepareReport(module, missingEntryRefs);
135
146
  await this.prepareReport(`Entries_Select_feild`, missingSelectFeild);
136
147
  await this.prepareReport('Entries_Mandatory_feild', missingMandatoryFields);
137
148
  await this.prepareReport('Entries_Title_feild', missingTitleFields);
149
+ await this.prepareReport('Entry_Missing_Locale_and_Env_in_Publish_Details', missingEnvLocalesInEntries);
138
150
  break;
139
151
  case 'workflows':
140
152
  missingCtRefsInWorkflow = await new modules_1.Workflows({
@@ -178,6 +190,8 @@ class AuditBaseCommand extends base_command_1.BaseCommand {
178
190
  missingMandatoryFields,
179
191
  missingTitleFields,
180
192
  missingRefInCustomRoles,
193
+ missingEnvLocalesInAssets,
194
+ missingEnvLocalesInEntries
181
195
  };
182
196
  }
183
197
  /**
@@ -40,6 +40,16 @@ declare const config: {
40
40
  dirName: string;
41
41
  fileName: string;
42
42
  };
43
+ assets: {
44
+ name: string;
45
+ dirName: string;
46
+ fileName: string;
47
+ };
48
+ environments: {
49
+ name: string;
50
+ dirName: string;
51
+ fileName: string;
52
+ };
43
53
  };
44
54
  entries: {
45
55
  systemKeys: string[];
@@ -49,6 +59,8 @@ declare const config: {
49
59
  Entries_Select_feild: string;
50
60
  Entries_Mandatory_feild: string;
51
61
  Entries_Title_feild: string;
62
+ Entry_Missing_Locale_and_Env: string;
63
+ Entry_Missing_Locale_and_Env_in_Publish_Details: string;
52
64
  };
53
65
  feild_level_modules: string[];
54
66
  };
@@ -4,7 +4,7 @@ const config = {
4
4
  showTerminalOutput: true,
5
5
  skipRefs: ['sys_assets'],
6
6
  skipFieldTypes: ['taxonomy', 'group'],
7
- modules: ['content-types', 'global-fields', 'entries', 'extensions', 'workflows', 'custom-roles'],
7
+ modules: ['content-types', 'global-fields', 'entries', 'extensions', 'workflows', 'custom-roles', 'assets'],
8
8
  'fix-fields': ['reference', 'global_field', 'json:rte', 'json:extension', 'blocks', 'group', 'content_types'],
9
9
  moduleConfig: {
10
10
  'content-types': {
@@ -42,6 +42,16 @@ const config = {
42
42
  dirName: 'custom-roles',
43
43
  fileName: 'custom-roles.json',
44
44
  },
45
+ 'assets': {
46
+ name: 'assets',
47
+ dirName: 'assets',
48
+ fileName: 'assets.json',
49
+ },
50
+ 'environments': {
51
+ name: 'environments',
52
+ dirName: 'environments',
53
+ fileName: 'environments.json',
54
+ }
45
55
  },
46
56
  entries: {
47
57
  systemKeys: [
@@ -78,12 +88,22 @@ const config = {
78
88
  'min_instance',
79
89
  'missingFieldUid',
80
90
  'isPublished',
91
+ 'locale',
92
+ 'environment',
93
+ 'ctUid',
94
+ 'ctLocale',
95
+ 'entry_uid',
96
+ 'publish_locale',
97
+ 'publish_environment',
98
+ 'asset_uid'
81
99
  ],
82
100
  ReportTitleForEntries: {
83
101
  Entries_Select_feild: 'Entries_Select_feild',
84
102
  Entries_Mandatory_feild: 'Entries_Mandatory_feild',
85
103
  Entries_Title_feild: 'Entries_Title_feild',
104
+ Entry_Missing_Locale_and_Env: 'Entry_Missing_Locale_and_Env',
105
+ Entry_Missing_Locale_and_Env_in_Publish_Details: 'Entry_Missing_Locale_and_Env_in_Publish_Details'
86
106
  },
87
- feild_level_modules: ['Entries_Title_feild', 'Entries_Mandatory_feild', 'Entries_Select_feild'],
107
+ feild_level_modules: ['Entries_Title_feild', 'Entries_Mandatory_feild', 'Entries_Select_feild', 'Entry_Missing_Locale_and_Env_in_Publish_Details'],
88
108
  };
89
109
  exports.default = config;
@@ -32,6 +32,12 @@ declare const auditMsg: {
32
32
  AUDIT_CMD_DESCRIPTION: string;
33
33
  SCAN_WF_SUCCESS_MSG: string;
34
34
  SCAN_CR_SUCCESS_MSG: string;
35
+ SCAN_ASSET_SUCCESS_MSG: string;
36
+ SCAN_ASSET_WARN_MSG: string;
37
+ ENTRY_PUBLISH_DETAILS: string;
38
+ CT_REFERENCE_FIELD: string;
39
+ ASSET_NOT_EXIST: string;
40
+ ENTRY_PUBLISH_DETAILS_NOT_EXIST: string;
35
41
  };
36
42
  declare const auditFixMsg: {
37
43
  COPY_DATA: string;
@@ -43,6 +49,7 @@ declare const auditFixMsg: {
43
49
  WF_FIX_MSG: string;
44
50
  ENTRY_MANDATORY_FIELD_FIX: string;
45
51
  ENTRY_SELECT_FIELD_FIX: string;
52
+ ASSET_FIX: string;
46
53
  };
47
54
  declare const messages: typeof errors & typeof commonMsg & typeof auditMsg & typeof auditFixMsg & typeof tableColumnDescriptions;
48
55
  /**
@@ -40,6 +40,12 @@ const auditMsg = {
40
40
  AUDIT_CMD_DESCRIPTION: 'Perform audits and find possible errors in the exported Contentstack data',
41
41
  SCAN_WF_SUCCESS_MSG: 'Successfully completed the scanning of workflow with UID {uid} and name {name}.',
42
42
  SCAN_CR_SUCCESS_MSG: 'Successfully completed the scanning of custom role with UID {uid} and name {name}.',
43
+ SCAN_ASSET_SUCCESS_MSG: `Successfully completed the scanning of Asset with UID '{uid}'.`,
44
+ SCAN_ASSET_WARN_MSG: `The locale '{locale}' or environment '{environment}' are not present for asset with uid '{uid}'`,
45
+ ENTRY_PUBLISH_DETAILS: `Removing the publish detials for entry '{uid}' of ct '{ctuid}' in locale '{locale}' as locale '{publocale}' or environment '{environment}' does not exist`,
46
+ CT_REFERENCE_FIELD: `The mentioned Reference Field is not Array field name 'reference_to' having display name 'display_name'`,
47
+ ASSET_NOT_EXIST: `The publish_details either does not exist or is not an array for asset uid '{uid}'`,
48
+ ENTRY_PUBLISH_DETAILS_NOT_EXIST: `The publish_details either does not exist or is not an array for entry uid '{uid}'`,
43
49
  };
44
50
  exports.auditMsg = auditMsg;
45
51
  const auditFixMsg = {
@@ -52,6 +58,7 @@ const auditFixMsg = {
52
58
  WF_FIX_MSG: 'Successfully removed the workflow {uid} named {name}.',
53
59
  ENTRY_MANDATORY_FIELD_FIX: `Removing the publish details from the entry with UID '{uid}' in Locale '{locale}'...`,
54
60
  ENTRY_SELECT_FIELD_FIX: `Adding the value '{value}' in the select field of entry UID '{uid}'...`,
61
+ ASSET_FIX: `Fixed publish detials for Asset with UID '{uid}'`,
55
62
  };
56
63
  exports.auditFixMsg = auditFixMsg;
57
64
  const messages = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, errors), commonMsg), auditMsg), auditFixMsg), tableColumnDescriptions);
@@ -0,0 +1,40 @@
1
+ import { LogFn, ConfigType, ContentTypeStruct, CtConstructorParam, ModuleConstructorParam, EntryStruct } from '../types';
2
+ import auditConfig from '../config';
3
+ export default class Assets {
4
+ log: LogFn;
5
+ protected fix: boolean;
6
+ fileName: string;
7
+ config: ConfigType;
8
+ folderPath: string;
9
+ currentUid: string;
10
+ currentTitle: string;
11
+ assets: Record<string, any>;
12
+ locales: string[];
13
+ environments: string[];
14
+ protected schema: ContentTypeStruct[];
15
+ protected missingEnvLocales: Record<string, any>;
16
+ moduleName: keyof typeof auditConfig.moduleConfig;
17
+ constructor({ log, fix, config, moduleName }: ModuleConstructorParam & CtConstructorParam);
18
+ validateModules(moduleName: keyof typeof auditConfig.moduleConfig, moduleConfig: Record<string, unknown>): keyof typeof auditConfig.moduleConfig;
19
+ /**
20
+ * The `run` function checks if a folder path exists, sets the schema based on the module name,
21
+ * iterates over the schema and looks for references, and returns a list of missing references.
22
+ * @returns the `missingEnvLocales` object.
23
+ */
24
+ run(returnFixSchema?: boolean): Promise<Record<string, any>>;
25
+ /**
26
+ * @method prerequisiteData
27
+ * The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
28
+ * app data, and stores them in the `extensions` array.
29
+ */
30
+ prerequisiteData(): Promise<void>;
31
+ /**
32
+ * The function checks if it can write the fix content to a file and if so, it writes the content as
33
+ * JSON to the specified file path.
34
+ */
35
+ writeFixContent(filePath: string, schema: Record<string, EntryStruct>): Promise<void>;
36
+ /**
37
+ * This function traverse over the publish detials of the assets and remove the publish details where the locale or environment does not exist
38
+ */
39
+ lookForReference(): Promise<void>;
40
+ }
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const path_1 = require("path");
5
+ const fs_1 = require("fs");
6
+ const cli_utilities_1 = require("@contentstack/cli-utilities");
7
+ const messages_1 = require("../messages");
8
+ const values_1 = tslib_1.__importDefault(require("lodash/values"));
9
+ const lodash_1 = require("lodash");
10
+ /* The `ContentType` class is responsible for scanning content types, looking for references, and
11
+ generating a report in JSON and CSV formats. */
12
+ class Assets {
13
+ constructor({ log, fix, config, moduleName }) {
14
+ this.locales = [];
15
+ this.environments = [];
16
+ this.schema = [];
17
+ this.missingEnvLocales = {};
18
+ this.log = log;
19
+ this.config = config;
20
+ this.fix = fix !== null && fix !== void 0 ? fix : false;
21
+ this.moduleName = this.validateModules(moduleName, this.config.moduleConfig);
22
+ this.fileName = config.moduleConfig[this.moduleName].fileName;
23
+ this.folderPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(config.basePath), (0, cli_utilities_1.sanitizePath)(config.moduleConfig[this.moduleName].dirName));
24
+ }
25
+ validateModules(moduleName, moduleConfig) {
26
+ if (Object.keys(moduleConfig).includes(moduleName)) {
27
+ return moduleName;
28
+ }
29
+ return 'assets';
30
+ }
31
+ /**
32
+ * The `run` function checks if a folder path exists, sets the schema based on the module name,
33
+ * iterates over the schema and looks for references, and returns a list of missing references.
34
+ * @returns the `missingEnvLocales` object.
35
+ */
36
+ async run(returnFixSchema = false) {
37
+ if (!(0, fs_1.existsSync)(this.folderPath)) {
38
+ this.log(`Skipping ${this.moduleName} audit`, 'warn');
39
+ this.log((0, messages_1.$t)(messages_1.auditMsg.NOT_VALID_PATH, { path: this.folderPath }), { color: 'yellow' });
40
+ return returnFixSchema ? [] : {};
41
+ }
42
+ await this.prerequisiteData();
43
+ await this.lookForReference();
44
+ if (returnFixSchema) {
45
+ return this.schema;
46
+ }
47
+ for (let propName in this.missingEnvLocales) {
48
+ if (Array.isArray(this.missingEnvLocales[propName])) {
49
+ if (!this.missingEnvLocales[propName].length) {
50
+ delete this.missingEnvLocales[propName];
51
+ }
52
+ }
53
+ }
54
+ return this.missingEnvLocales;
55
+ }
56
+ /**
57
+ * @method prerequisiteData
58
+ * The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
59
+ * app data, and stores them in the `extensions` array.
60
+ */
61
+ async prerequisiteData() {
62
+ this.log(messages_1.auditMsg.PREPARING_ENTRY_METADATA, 'info');
63
+ const localesFolderPath = (0, path_1.resolve)(this.config.basePath, this.config.moduleConfig.locales.dirName);
64
+ const localesPath = (0, path_1.join)(localesFolderPath, this.config.moduleConfig.locales.fileName);
65
+ const masterLocalesPath = (0, path_1.join)(localesFolderPath, 'master-locale.json');
66
+ this.locales = (0, fs_1.existsSync)(masterLocalesPath) ? (0, values_1.default)(JSON.parse((0, fs_1.readFileSync)(masterLocalesPath, 'utf8'))) : [];
67
+ if ((0, fs_1.existsSync)(localesPath)) {
68
+ this.locales.push(...(0, values_1.default)(JSON.parse((0, fs_1.readFileSync)(localesPath, 'utf8'))));
69
+ }
70
+ this.locales = this.locales.map((locale) => locale.code);
71
+ const environmentPath = (0, path_1.resolve)(this.config.basePath, this.config.moduleConfig.environments.dirName, this.config.moduleConfig.environments.fileName);
72
+ this.environments = (0, fs_1.existsSync)(environmentPath) ? (0, lodash_1.keys)(JSON.parse((0, fs_1.readFileSync)(environmentPath, 'utf8'))) : [];
73
+ }
74
+ /**
75
+ * The function checks if it can write the fix content to a file and if so, it writes the content as
76
+ * JSON to the specified file path.
77
+ */
78
+ async writeFixContent(filePath, schema) {
79
+ var _a;
80
+ let canWrite = true;
81
+ if (this.fix) {
82
+ if (!this.config.flags['copy-dir'] && !((_a = this.config.flags['external-config']) === null || _a === void 0 ? void 0 : _a.skipConfirm)) {
83
+ canWrite = this.config.flags.yes || (await cli_utilities_1.ux.confirm(messages_1.commonMsg.FIX_CONFIRMATION));
84
+ }
85
+ if (canWrite) {
86
+ (0, fs_1.writeFileSync)(filePath, JSON.stringify(schema));
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * This function traverse over the publish detials of the assets and remove the publish details where the locale or environment does not exist
92
+ */
93
+ async lookForReference() {
94
+ var _a, _b;
95
+ let basePath = (0, path_1.join)(this.folderPath);
96
+ let fsUtility = new cli_utilities_1.FsUtility({ basePath, indexFileName: 'assets.json' });
97
+ let indexer = fsUtility.indexFileContent;
98
+ for (const fileIndex in indexer) {
99
+ const assets = (await fsUtility.readChunkFiles.next());
100
+ this.assets = assets;
101
+ for (const assetUid in assets) {
102
+ if (((_a = this.assets[assetUid]) === null || _a === void 0 ? void 0 : _a.publish_details) && !Array.isArray(this.assets[assetUid].publish_details)) {
103
+ this.log((0, messages_1.$t)(messages_1.auditMsg.ASSET_NOT_EXIST, { uid: assetUid }), { color: 'red' });
104
+ }
105
+ this.assets[assetUid].publish_details = (_b = this.assets[assetUid]) === null || _b === void 0 ? void 0 : _b.publish_details.filter((pd) => {
106
+ var _a, _b;
107
+ if (((_a = this.locales) === null || _a === void 0 ? void 0 : _a.includes(pd === null || pd === void 0 ? void 0 : pd.locale)) && ((_b = this.environments) === null || _b === void 0 ? void 0 : _b.includes(pd === null || pd === void 0 ? void 0 : pd.environment))) {
108
+ this.log((0, messages_1.$t)(messages_1.auditMsg.SCAN_ASSET_SUCCESS_MSG, { uid: assetUid }), { color: 'green' });
109
+ return true;
110
+ }
111
+ else {
112
+ this.log((0, messages_1.$t)(messages_1.auditMsg.SCAN_ASSET_WARN_MSG, { uid: assetUid, locale: pd.locale, environment: pd.environment }), { color: 'yellow' });
113
+ if (!Object.keys(this.missingEnvLocales).includes(assetUid)) {
114
+ this.missingEnvLocales[assetUid] = [
115
+ { asset_uid: assetUid, publish_locale: pd.locale, publish_environment: pd.environment },
116
+ ];
117
+ }
118
+ else {
119
+ this.missingEnvLocales[assetUid].push({
120
+ asset_uid: assetUid,
121
+ publish_locale: pd.locale,
122
+ publish_environment: pd.environment,
123
+ });
124
+ }
125
+ this.log((0, messages_1.$t)(messages_1.auditMsg.SCAN_ASSET_SUCCESS_MSG, { uid: assetUid }), { color: 'green' });
126
+ return false;
127
+ }
128
+ });
129
+ if (this.fix) {
130
+ this.log((0, messages_1.$t)(messages_1.auditFixMsg.ASSET_FIX, { uid: assetUid }), { color: 'green' });
131
+ await this.writeFixContent(`${basePath}/${indexer[fileIndex]}`, this.assets);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ exports.default = Assets;
@@ -510,6 +510,9 @@ class ContentType {
510
510
  let fixStatus;
511
511
  const missingRefs = [];
512
512
  const { reference_to, data_type, display_name } = field;
513
+ if (!Array.isArray(reference_to)) {
514
+ this.log((0, messages_1.$t)(messages_1.auditMsg.CT_REFERENCE_FIELD, { reference_to, data_type, display_name }), { color: 'green' });
515
+ }
513
516
  for (const reference of reference_to !== null && reference_to !== void 0 ? reference_to : []) {
514
517
  // NOTE Can skip specific references keys (Ex, system defined keys can be skipped)
515
518
  if (this.config.skipRefs.includes(reference)) {
@@ -17,6 +17,8 @@ export default class Entries {
17
17
  protected missingSelectFeild: Record<string, any>;
18
18
  protected missingMandatoryFields: Record<string, any>;
19
19
  protected missingTitleFields: Record<string, any>;
20
+ protected missingEnvLocale: Record<string, any>;
21
+ environments: string[];
20
22
  entryMetaData: Record<string, any>[];
21
23
  moduleName: keyof typeof auditConfig.moduleConfig;
22
24
  constructor({ log, fix, config, moduleName, ctSchema, gfSchema }: ModuleConstructorParam & CtConstructorParam);
@@ -31,11 +33,13 @@ export default class Entries {
31
33
  missingSelectFeild?: undefined;
32
34
  missingMandatoryFields?: undefined;
33
35
  missingTitleFields?: undefined;
36
+ missingEnvLocale?: undefined;
34
37
  } | {
35
38
  missingEntryRefs: Record<string, any>;
36
39
  missingSelectFeild: Record<string, any>;
37
40
  missingMandatoryFields: Record<string, any>;
38
41
  missingTitleFields: Record<string, any>;
42
+ missingEnvLocale: Record<string, any>;
39
43
  }>;
40
44
  /**
41
45
  * The function removes any properties from the `missingRefs` object that have an empty array value.
@@ -12,6 +12,7 @@ const content_types_1 = tslib_1.__importDefault(require("./content-types"));
12
12
  const messages_1 = require("../messages");
13
13
  const util_1 = require("../util");
14
14
  const global_fields_1 = tslib_1.__importDefault(require("./global-fields"));
15
+ const lodash_1 = require("lodash");
15
16
  class Entries {
16
17
  constructor({ log, fix, config, moduleName, ctSchema, gfSchema }) {
17
18
  this.extensions = [];
@@ -19,6 +20,8 @@ class Entries {
19
20
  this.missingSelectFeild = {};
20
21
  this.missingMandatoryFields = {};
21
22
  this.missingTitleFields = {};
23
+ this.missingEnvLocale = {};
24
+ this.environments = [];
22
25
  this.entryMetaData = [];
23
26
  this.moduleName = 'entries';
24
27
  this.log = log;
@@ -42,7 +45,7 @@ class Entries {
42
45
  * @returns the `missingRefs` object.
43
46
  */
44
47
  async run() {
45
- var _a, _b, _c, _d;
48
+ var _a, _b, _c, _d, _e, _f;
46
49
  if (!(0, fs_1.existsSync)(this.folderPath)) {
47
50
  this.log(`Skipping ${this.moduleName} audit`, 'warn');
48
51
  this.log((0, messages_1.$t)(messages_1.auditMsg.NOT_VALID_PATH, { path: this.folderPath }), { color: 'yellow' });
@@ -112,6 +115,32 @@ class Entries {
112
115
  else {
113
116
  delete this.missingMandatoryFields[uid];
114
117
  }
118
+ const localKey = this.locales.map((locale) => locale.code);
119
+ if (((_e = this.entries[entryUid]) === null || _e === void 0 ? void 0 : _e.publish_details) && !Array.isArray(this.entries[entryUid].publish_details)) {
120
+ this.log((0, messages_1.$t)(messages_1.auditMsg.ENTRY_PUBLISH_DETAILS_NOT_EXIST, { uid: entryUid }), { color: 'red' });
121
+ }
122
+ this.entries[entryUid].publish_details = (_f = this.entries[entryUid]) === null || _f === void 0 ? void 0 : _f.publish_details.filter((pd) => {
123
+ var _a;
124
+ if ((localKey === null || localKey === void 0 ? void 0 : localKey.includes(pd.locale)) && ((_a = this.environments) === null || _a === void 0 ? void 0 : _a.includes(pd.environment))) {
125
+ return true;
126
+ }
127
+ else {
128
+ this.log((0, messages_1.$t)(messages_1.auditMsg.ENTRY_PUBLISH_DETAILS, {
129
+ uid: entryUid,
130
+ ctuid: ctSchema.uid,
131
+ locale: code,
132
+ publocale: pd.locale,
133
+ environment: pd.environment,
134
+ }), { color: 'red' });
135
+ if (!Object.keys(this.missingEnvLocale).includes(entryUid)) {
136
+ this.missingEnvLocale[entryUid] = [{ entry_uid: entryUid, publish_locale: pd.locale, publish_environment: pd.environment, ctUid: ctSchema.uid, ctLocale: code }];
137
+ }
138
+ else {
139
+ this.missingEnvLocale[entryUid].push({ entry_uid: entryUid, publish_locale: pd.locale, publish_environment: pd.environment, ctUid: ctSchema.uid, ctLocale: code });
140
+ }
141
+ return false;
142
+ }
143
+ });
115
144
  const message = (0, messages_1.$t)(messages_1.auditMsg.SCAN_ENTRY_SUCCESS_MSG, {
116
145
  title,
117
146
  local: code,
@@ -133,6 +162,7 @@ class Entries {
133
162
  missingSelectFeild: this.missingSelectFeild,
134
163
  missingMandatoryFields: this.missingMandatoryFields,
135
164
  missingTitleFields: this.missingTitleFields,
165
+ missingEnvLocale: this.missingEnvLocale,
136
166
  };
137
167
  }
138
168
  /**
@@ -231,7 +261,7 @@ class Entries {
231
261
  }
232
262
  for (const child of (_a = field === null || field === void 0 ? void 0 : field.schema) !== null && _a !== void 0 ? _a : []) {
233
263
  const { uid } = child;
234
- this.missingMandatoryFields[this.currentUid].push(...(this.validateMandatoryFields([...tree, { uid: field.uid, name: child.display_name, field: uid }], child, entry)));
264
+ this.missingMandatoryFields[this.currentUid].push(...this.validateMandatoryFields([...tree, { uid: field.uid, name: child.display_name, field: uid }], child, entry));
235
265
  if (!(entry === null || entry === void 0 ? void 0 : entry[uid]) && !child.hasOwnProperty('display_type')) {
236
266
  continue;
237
267
  }
@@ -548,7 +578,10 @@ class Entries {
548
578
  */
549
579
  validateSelectField(tree, fieldStructure, field) {
550
580
  const { display_name, enum: selectOptions, multiple, min_instance, display_type, data_type } = fieldStructure;
551
- if (field === null || field === '' || (Array.isArray(field) && field.length === 0) || (!field && data_type !== 'number')) {
581
+ if (field === null ||
582
+ field === '' ||
583
+ (Array.isArray(field) && field.length === 0) ||
584
+ (!field && data_type !== 'number')) {
552
585
  let missingCTSelectFieldValues = 'Not Selected';
553
586
  return [
554
587
  {
@@ -1001,6 +1034,8 @@ class Entries {
1001
1034
  if ((0, fs_1.existsSync)(localesPath)) {
1002
1035
  this.locales.push(...(0, values_1.default)(JSON.parse((0, fs_1.readFileSync)(localesPath, 'utf8'))));
1003
1036
  }
1037
+ const environmentPath = (0, path_1.resolve)(this.config.basePath, this.config.moduleConfig.environments.dirName, this.config.moduleConfig.environments.fileName);
1038
+ this.environments = (0, fs_1.existsSync)(environmentPath) ? (0, lodash_1.keys)(JSON.parse((0, fs_1.readFileSync)(environmentPath, 'utf8'))) : [];
1004
1039
  for (const { code } of this.locales) {
1005
1040
  for (const { uid } of this.ctSchema) {
1006
1041
  let basePath = (0, path_1.join)(this.folderPath, uid, code);
@@ -1014,7 +1049,7 @@ class Entries {
1014
1049
  this.missingTitleFields[entryUid] = {
1015
1050
  'Entry UID': entryUid,
1016
1051
  'Content Type UID': uid,
1017
- "Locale": code,
1052
+ Locale: code,
1018
1053
  };
1019
1054
  this.log(`The 'title' field in Entry with UID '${entryUid}' of Content Type '${uid}' in Locale '${code}' is empty.`, `error`);
1020
1055
  }
@@ -3,4 +3,5 @@ import GlobalField from './global-fields';
3
3
  import ContentType from './content-types';
4
4
  import Workflows from './workflows';
5
5
  import Extensions from './extensions';
6
- export { Entries, GlobalField, ContentType, Workflows, Extensions };
6
+ import Assets from './assets';
7
+ export { Entries, GlobalField, ContentType, Workflows, Extensions, Assets };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Extensions = exports.Workflows = exports.ContentType = exports.GlobalField = exports.Entries = void 0;
3
+ exports.Assets = exports.Extensions = exports.Workflows = exports.ContentType = exports.GlobalField = exports.Entries = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const entries_1 = tslib_1.__importDefault(require("./entries"));
6
6
  exports.Entries = entries_1.default;
@@ -12,3 +12,5 @@ const workflows_1 = tslib_1.__importDefault(require("./workflows"));
12
12
  exports.Workflows = workflows_1.default;
13
13
  const extensions_1 = tslib_1.__importDefault(require("./extensions"));
14
14
  exports.Extensions = extensions_1.default;
15
+ const assets_1 = tslib_1.__importDefault(require("./assets"));
16
+ exports.Assets = assets_1.default;
@@ -103,8 +103,15 @@ declare enum OutputColumn {
103
103
  'isPublished' = "isPublished",
104
104
  'Entry UID' = "Entry UID",
105
105
  'Content Type UID' = "Content Type UID",
106
- "Locale" = "Locale",
107
- "Content Type" = "ct",
108
- "locale" = "locale"
106
+ 'Locale' = "Locale",
107
+ 'Content Type' = "ct",
108
+ 'locale' = "locale",
109
+ 'environment' = "environment",
110
+ 'ctUid' = "ctUid",
111
+ 'ctLocale' = "ctLocale",
112
+ 'entry_uid' = "entry_uid",
113
+ 'publish_locale' = "publish_locale",
114
+ 'publish_environment' = "publish_environment",
115
+ 'asset_uid' = "asset_uid"
109
116
  }
110
117
  export { CtConstructorParam, ContentTypeStruct, ModuleConstructorParam, ReferenceFieldDataType, GlobalFieldDataType, ExtensionOrAppFieldDataType, JsonRTEFieldDataType, GroupFieldDataType, ModularBlocksDataType, RefErrorReturnType, ModularBlocksSchemaTypes, ModularBlockType, OutputColumn, ContentTypeSchemaType, GlobalFieldSchemaTypes, WorkflowExtensionsRefErrorReturnType, SelectFeildStruct, };
@@ -22,5 +22,12 @@ var OutputColumn;
22
22
  OutputColumn["Locale"] = "Locale";
23
23
  OutputColumn["Content Type"] = "ct";
24
24
  OutputColumn["locale"] = "locale";
25
+ OutputColumn["environment"] = "environment";
26
+ OutputColumn["ctUid"] = "ctUid";
27
+ OutputColumn["ctLocale"] = "ctLocale";
28
+ OutputColumn["entry_uid"] = "entry_uid";
29
+ OutputColumn["publish_locale"] = "publish_locale";
30
+ OutputColumn["publish_environment"] = "publish_environment";
31
+ OutputColumn["asset_uid"] = "asset_uid";
25
32
  })(OutputColumn || (OutputColumn = {}));
26
33
  exports.OutputColumn = OutputColumn;
@@ -8,7 +8,7 @@ type Locale = {
8
8
  type EntryStruct = {
9
9
  uid: string;
10
10
  title: string;
11
- publish_details: [];
11
+ publish_details: any[];
12
12
  } & {
13
13
  [key: string]: EntryReferenceFieldDataType[] | EntryGlobalFieldDataType | EntryJsonRTEFieldDataType | EntryGroupFieldDataType | EntryModularBlocksDataType[];
14
14
  };
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.7.4",
2
+ "version": "1.8.0",
3
3
  "commands": {
4
4
  "cm:stacks:audit:fix": {
5
5
  "id": "cm:stacks:audit:fix",
@@ -46,7 +46,8 @@
46
46
  "entries",
47
47
  "extensions",
48
48
  "workflows",
49
- "custom-roles"
49
+ "custom-roles",
50
+ "assets"
50
51
  ]
51
52
  },
52
53
  "copy-dir": {
@@ -185,7 +186,8 @@
185
186
  "entries",
186
187
  "extensions",
187
188
  "workflows",
188
- "custom-roles"
189
+ "custom-roles",
190
+ "assets"
189
191
  ]
190
192
  },
191
193
  "columns": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentstack/cli-audit",
3
- "version": "1.7.4",
3
+ "version": "1.8.0",
4
4
  "description": "Contentstack audit plugin",
5
5
  "author": "Contentstack CLI",
6
6
  "homepage": "https://github.com/contentstack/cli",
@@ -18,38 +18,35 @@
18
18
  "/oclif.manifest.json"
19
19
  ],
20
20
  "dependencies": {
21
- "@contentstack/cli-command": "~1.3.2",
22
- "@contentstack/cli-utilities": "~1.8.0",
21
+ "@contentstack/cli-command": "~1.3.3",
22
+ "@contentstack/cli-utilities": "~1.8.4",
23
23
  "@oclif/plugin-help": "^5.2.20",
24
- "@oclif/plugin-plugins": "^5.4.22",
24
+ "@oclif/plugin-plugins": "^5.4.34",
25
25
  "chalk": "^4.1.2",
26
26
  "fast-csv": "^4.3.6",
27
- "fs-extra": "^11.2.0",
27
+ "fs-extra": "^11.3.0",
28
28
  "lodash": "^4.17.21",
29
29
  "uuid": "^9.0.1",
30
30
  "winston": "^3.17.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@contentstack/cli-dev-dependencies": "^1.2.4",
34
- "@oclif/test": "^4.1.4",
33
+ "@oclif/test": "^4.1.11",
35
34
  "@types/chai": "^4.3.20",
36
35
  "@types/fs-extra": "^11.0.4",
37
36
  "@types/mocha": "^10.0.10",
38
- "@types/node": "^20.17.10",
37
+ "@types/node": "^20.17.19",
39
38
  "@types/uuid": "^9.0.8",
40
39
  "chai": "^4.5.0",
41
40
  "eslint": "^8.57.1",
42
41
  "eslint-config-oclif": "^4.0.0",
43
- "eslint-config-oclif-typescript": "^3.1.13",
42
+ "eslint-config-oclif-typescript": "^3.1.14",
44
43
  "mocha": "^10.8.2",
45
44
  "nyc": "^15.1.0",
46
45
  "oclif": "^3.17.2",
47
46
  "shx": "^0.3.4",
48
47
  "sinon": "^19.0.2",
49
- "ts-jest": "^29.2.5",
50
48
  "ts-node": "^10.9.2",
51
- "tslib": "^2.8.1",
52
- "typescript": "^5.7.2"
49
+ "typescript": "^5.7.3"
53
50
  },
54
51
  "oclif": {
55
52
  "bin": "csdx",