@contentstack/cli-audit 1.3.5 → 1.4.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.3.5 linux-x64 node-v18.19.0
22
+ @contentstack/cli-audit/1.4.0 linux-x64 node-v18.19.1
23
23
  $ csdx --help [COMMAND]
24
24
  USAGE
25
25
  $ csdx COMMAND
@@ -99,14 +99,14 @@ Perform audits and fix possible errors in the exported Contentstack data.
99
99
  USAGE
100
100
  $ csdx audit:fix [-c <value>] [-d <value>] [--report-path <value>] [--modules
101
101
  content-types|global-fields|entries] [--copy-path <value> --copy-dir] [--fix-only
102
- reference|global_field|json:rte|json:custom-field|blocks|group] [--columns <value> | ] [--sort <value>] [--filter
102
+ reference|global_field|json:rte|json:extension|blocks|group] [--columns <value> | ] [--sort <value>] [--filter
103
103
  <value>] [--csv | --no-truncate]
104
104
 
105
105
  FLAGS
106
106
  --copy-dir Create backup from the original data.
107
107
  --copy-path=<value> Provide the path to backup the copied data
108
108
  --fix-only=<option>... Provide the list of fix options
109
- <options: reference|global_field|json:rte|json:custom-field|blocks|group>
109
+ <options: reference|global_field|json:rte|json:extension|blocks|group>
110
110
  --modules=<option>... Provide the list of modules to be audited
111
111
  <options: content-types|global-fields|entries>
112
112
  --report-path=<value> Path to store the audit reports
@@ -198,14 +198,14 @@ Perform audits and fix possible errors in the exported Contentstack data.
198
198
  USAGE
199
199
  $ csdx cm:stacks:audit:fix [-c <value>] [-d <value>] [--report-path <value>] [--modules
200
200
  content-types|global-fields|entries] [--copy-path <value> --copy-dir] [--fix-only
201
- reference|global_field|json:rte|json:custom-field|blocks|group] [--columns <value> | ] [--sort <value>] [--filter
201
+ reference|global_field|json:rte|json:extension|blocks|group] [--columns <value> | ] [--sort <value>] [--filter
202
202
  <value>] [--csv | --no-truncate]
203
203
 
204
204
  FLAGS
205
205
  --copy-dir Create backup from the original data.
206
206
  --copy-path=<value> Provide the path to backup the copied data
207
207
  --fix-only=<option>... Provide the list of fix options
208
- <options: reference|global_field|json:rte|json:custom-field|blocks|group>
208
+ <options: reference|global_field|json:rte|json:extension|blocks|group>
209
209
  --modules=<option>... Provide the list of modules to be audited
210
210
  <options: content-types|global-fields|entries>
211
211
  --report-path=<value> Path to store the audit reports
@@ -26,5 +26,8 @@ declare const config: {
26
26
  fileName: string;
27
27
  };
28
28
  };
29
+ entries: {
30
+ systemKeys: string[];
31
+ };
29
32
  };
30
33
  export default config;
@@ -5,7 +5,7 @@ const config = {
5
5
  skipRefs: ['sys_assets'],
6
6
  skipFieldTypes: ['taxonomy'],
7
7
  modules: ['content-types', 'global-fields', 'entries'],
8
- 'fix-fields': ['reference', 'global_field', 'json:rte', 'json:custom-field', 'blocks', 'group'],
8
+ 'fix-fields': ['reference', 'global_field', 'json:rte', 'json:extension', 'blocks', 'group'],
9
9
  moduleConfig: {
10
10
  'content-types': {
11
11
  name: 'content type',
@@ -28,5 +28,23 @@ const config = {
28
28
  fileName: 'locales.json',
29
29
  },
30
30
  },
31
+ entries: {
32
+ systemKeys: [
33
+ 'uid',
34
+ 'ACL',
35
+ 'tags',
36
+ 'locale',
37
+ '_version',
38
+ '_metadata',
39
+ 'published',
40
+ 'created_at',
41
+ 'updated_at',
42
+ 'created_by',
43
+ 'updated_by',
44
+ '_in_progress',
45
+ '_restore_status',
46
+ 'publish_details',
47
+ ],
48
+ },
31
49
  };
32
50
  exports.default = config;
@@ -1,4 +1,4 @@
1
- import { LogFn, ConfigType, ModularBlockType, ContentTypeStruct, GroupFieldDataType, RefErrorReturnType, CtConstructorParam, GlobalFieldDataType, JsonRTEFieldDataType, ModularBlocksDataType, ModuleConstructorParam, ReferenceFieldDataType, ContentTypeSchemaType } from '../types';
1
+ import { LogFn, ConfigType, ModularBlockType, ContentTypeStruct, GroupFieldDataType, RefErrorReturnType, CtConstructorParam, GlobalFieldDataType, JsonRTEFieldDataType, ModularBlocksDataType, ModuleConstructorParam, ReferenceFieldDataType, ContentTypeSchemaType, ExtensionOrAppFieldDataType } from '../types';
2
2
  import auditConfig from '../config';
3
3
  export default class ContentType {
4
4
  log: LogFn;
@@ -8,6 +8,7 @@ export default class ContentType {
8
8
  folderPath: string;
9
9
  currentUid: string;
10
10
  currentTitle: string;
11
+ extensions: string[];
11
12
  inMemoryFix: boolean;
12
13
  gfSchema: ContentTypeStruct[];
13
14
  ctSchema: ContentTypeStruct[];
@@ -21,6 +22,12 @@ export default class ContentType {
21
22
  * @returns the `missingRefs` object.
22
23
  */
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>;
24
31
  /**
25
32
  * The function checks if it can write the fix content to a file and if so, it writes the content as
26
33
  * JSON to the specified file path.
@@ -47,6 +54,15 @@ export default class ContentType {
47
54
  * @returns an array of RefErrorReturnType.
48
55
  */
49
56
  validateReferenceField(tree: Record<string, unknown>[], field: ReferenceFieldDataType): RefErrorReturnType[];
57
+ /**
58
+ * The function `validateExtensionAndAppsField` checks if a given field has a valid extension or app
59
+ * reference and returns any missing references.
60
+ * @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
61
+ * @param {ExtensionOrAppFieldDataType} field - The `field` parameter is of type `ExtensionOrAppFieldDataType`.
62
+ * @returns The function `validateExtensionAndAppsField` returns an array of `RefErrorReturnType`
63
+ * objects.
64
+ */
65
+ validateExtensionAndAppField(tree: Record<string, unknown>[], field: ExtensionOrAppFieldDataType): RefErrorReturnType[];
50
66
  /**
51
67
  * The function "validateGlobalField" asynchronously validates a global field by looking for a
52
68
  * reference in a tree data structure.
@@ -130,6 +146,16 @@ export default class ContentType {
130
146
  * @returns an array of `ModularBlockType` objects.
131
147
  */
132
148
  fixModularBlocksReferences(tree: Record<string, unknown>[], blocks: ModularBlockType[]): ModularBlockType[];
149
+ /**
150
+ * The function checks for missing extension or app references in a given tree and fixes them if the
151
+ * fix flag is enabled.
152
+ * @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
153
+ * @param {ExtensionOrAppFieldDataType} field - The `field` parameter is of type
154
+ * `ExtensionOrAppFieldDataType`.
155
+ * @returns If the `fix` flag is true and there are missing references (`missingRefs` is not empty),
156
+ * then `null` is returned. Otherwise, the `field` parameter is returned.
157
+ */
158
+ fixMissingExtensionOrApp(tree: Record<string, unknown>[], field: ExtensionOrAppFieldDataType): ExtensionOrAppFieldDataType | null;
133
159
  /**
134
160
  * The function `fixMissingReferences` checks for missing references in a given tree and field, and
135
161
  * attempts to fix them by removing the missing references from the field's `reference_to` array.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
+ const map_1 = tslib_1.__importDefault(require("lodash/map"));
4
5
  const find_1 = tslib_1.__importDefault(require("lodash/find"));
5
6
  const isEmpty_1 = tslib_1.__importDefault(require("lodash/isEmpty"));
6
7
  const path_1 = require("path");
@@ -11,6 +12,7 @@ const messages_1 = require("../messages");
11
12
  generating a report in JSON and CSV formats. */
12
13
  class ContentType {
13
14
  constructor({ log, fix, config, moduleName, ctSchema, gfSchema }) {
15
+ this.extensions = [];
14
16
  this.inMemoryFix = false;
15
17
  this.schema = [];
16
18
  this.missingRefs = {};
@@ -37,6 +39,7 @@ class ContentType {
37
39
  return returnFixSchema ? [] : {};
38
40
  }
39
41
  this.schema = this.moduleName === 'content-types' ? this.ctSchema : this.gfSchema;
42
+ await this.prerequisiteData();
40
43
  for (const schema of (_a = this.schema) !== null && _a !== void 0 ? _a : []) {
41
44
  this.currentUid = schema.uid;
42
45
  this.currentTitle = schema.title;
@@ -58,6 +61,32 @@ class ContentType {
58
61
  }
59
62
  return this.missingRefs;
60
63
  }
64
+ /**
65
+ * @method prerequisiteData
66
+ * The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
67
+ * app data, and stores them in the `extensions` array.
68
+ */
69
+ async prerequisiteData() {
70
+ var _a;
71
+ const extensionPath = (0, path_1.resolve)(this.config.basePath, 'extensions', 'extensions.json');
72
+ const marketplacePath = (0, path_1.resolve)(this.config.basePath, 'marketplace_apps', 'marketplace_apps.json');
73
+ if ((0, fs_1.existsSync)(extensionPath)) {
74
+ try {
75
+ this.extensions = Object.keys(JSON.parse((0, fs_1.readFileSync)(extensionPath, 'utf8')));
76
+ }
77
+ catch (error) { }
78
+ }
79
+ if ((0, fs_1.existsSync)(marketplacePath)) {
80
+ try {
81
+ const marketplaceApps = JSON.parse((0, fs_1.readFileSync)(marketplacePath, 'utf8'));
82
+ for (const app of marketplaceApps) {
83
+ const metaData = (0, map_1.default)((0, map_1.default)((_a = app === null || app === void 0 ? void 0 : app.ui_location) === null || _a === void 0 ? void 0 : _a.locations, 'meta').flat(), 'extension_uid').filter((val) => val);
84
+ this.extensions.push(...metaData);
85
+ }
86
+ }
87
+ catch (error) { }
88
+ }
89
+ }
61
90
  /**
62
91
  * The function checks if it can write the fix content to a file and if so, it writes the content as
63
92
  * JSON to the specified file path.
@@ -102,12 +131,13 @@ class ContentType {
102
131
  await this.validateGlobalField([...tree, { uid: child.uid, name: child.display_name }], child);
103
132
  break;
104
133
  case 'json':
105
- if (child.field_metadata.extension) {
106
- if (!fixTypes.includes('json:custom-field'))
134
+ if ('extension' in child.field_metadata && child.field_metadata.extension) {
135
+ if (!fixTypes.includes('json:extension'))
107
136
  continue;
108
137
  // NOTE Custom field type
138
+ this.missingRefs[this.currentUid].push(...this.validateExtensionAndAppField([...tree, { uid: child.uid, name: child.display_name }], child));
109
139
  }
110
- else if (child.field_metadata.allow_json_rte) {
140
+ else if ('allow_json_rte' in child.field_metadata && child.field_metadata.allow_json_rte) {
111
141
  if (!fixTypes.includes('json:rte'))
112
142
  continue;
113
143
  // NOTE JSON RTE field type
@@ -134,6 +164,39 @@ class ContentType {
134
164
  validateReferenceField(tree, field) {
135
165
  return this.validateReferenceToValues(tree, field);
136
166
  }
167
+ /**
168
+ * The function `validateExtensionAndAppsField` checks if a given field has a valid extension or app
169
+ * reference and returns any missing references.
170
+ * @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
171
+ * @param {ExtensionOrAppFieldDataType} field - The `field` parameter is of type `ExtensionOrAppFieldDataType`.
172
+ * @returns The function `validateExtensionAndAppsField` returns an array of `RefErrorReturnType`
173
+ * objects.
174
+ */
175
+ validateExtensionAndAppField(tree, field) {
176
+ if (this.fix)
177
+ return [];
178
+ const missingRefs = [];
179
+ let { uid, extension_uid, display_name, data_type } = field;
180
+ if (!this.extensions.includes(extension_uid)) {
181
+ missingRefs.push({ uid, extension_uid, type: 'Extension or Apps' });
182
+ }
183
+ return missingRefs.length
184
+ ? [
185
+ {
186
+ tree,
187
+ data_type,
188
+ missingRefs,
189
+ display_name,
190
+ ct_uid: this.currentUid,
191
+ name: this.currentTitle,
192
+ treeStr: tree
193
+ .map(({ name }) => name)
194
+ .filter((val) => val)
195
+ .join(' ➜ '),
196
+ },
197
+ ]
198
+ : [];
199
+ }
137
200
  /**
138
201
  * The function "validateGlobalField" asynchronously validates a global field by looking for a
139
202
  * reference in a tree data structure.
@@ -221,8 +284,9 @@ class ContentType {
221
284
  let { reference_to, display_name, data_type } = field;
222
285
  for (const reference of reference_to !== null && reference_to !== void 0 ? reference_to : []) {
223
286
  // NOTE Can skip specific references keys (Ex, system defined keys can be skipped)
224
- if (this.config.skipRefs.includes(reference))
287
+ if (this.config.skipRefs.includes(reference)) {
225
288
  continue;
289
+ }
226
290
  const refExist = (0, find_1.default)(this.ctSchema, { uid: reference });
227
291
  if (!refExist) {
228
292
  missingRefs.push(reference);
@@ -270,14 +334,14 @@ class ContentType {
270
334
  case 'json':
271
335
  case 'reference':
272
336
  if (data_type === 'json') {
273
- if (field.field_metadata.extension) {
337
+ if ('extension' in field.field_metadata && field.field_metadata.extension) {
274
338
  // NOTE Custom field type
275
- if (!fixTypes.includes('json:custom-field'))
339
+ if (!fixTypes.includes('json:extension'))
276
340
  return field;
277
341
  // NOTE Fix logic
278
- return field;
342
+ return this.fixMissingExtensionOrApp(tree, field);
279
343
  }
280
- else if (field.field_metadata.allow_json_rte) {
344
+ else if ('allow_json_rte' in field.field_metadata && field.field_metadata.allow_json_rte) {
281
345
  if (!fixTypes.includes('json:rte'))
282
346
  return field;
283
347
  return this.fixMissingReferences(tree, field);
@@ -369,10 +433,10 @@ class ContentType {
369
433
  const refErrorObj = {
370
434
  tree,
371
435
  display_name,
372
- fixStatus: 'Fixed',
373
- missingRefs: [reference_to],
374
436
  ct_uid: this.currentUid,
375
437
  name: this.currentTitle,
438
+ missingRefs: [reference_to],
439
+ fixStatus: this.fix ? 'Fixed' : undefined,
376
440
  treeStr: tree.map(({ name }) => name).join(' ➜ '),
377
441
  };
378
442
  if (!schema) {
@@ -397,6 +461,36 @@ class ContentType {
397
461
  })
398
462
  .filter((val) => val);
399
463
  }
464
+ /**
465
+ * The function checks for missing extension or app references in a given tree and fixes them if the
466
+ * fix flag is enabled.
467
+ * @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
468
+ * @param {ExtensionOrAppFieldDataType} field - The `field` parameter is of type
469
+ * `ExtensionOrAppFieldDataType`.
470
+ * @returns If the `fix` flag is true and there are missing references (`missingRefs` is not empty),
471
+ * then `null` is returned. Otherwise, the `field` parameter is returned.
472
+ */
473
+ fixMissingExtensionOrApp(tree, field) {
474
+ const missingRefs = [];
475
+ const { uid, extension_uid, data_type, display_name } = field;
476
+ if (!this.extensions.includes(extension_uid)) {
477
+ missingRefs.push({ uid, extension_uid, type: 'Extension or Apps' });
478
+ }
479
+ if (this.fix && !(0, isEmpty_1.default)(missingRefs)) {
480
+ this.missingRefs[this.currentUid].push({
481
+ tree,
482
+ data_type,
483
+ missingRefs,
484
+ display_name,
485
+ fixStatus: 'Fixed',
486
+ ct_uid: this.currentUid,
487
+ name: this.currentTitle,
488
+ treeStr: tree.map(({ name }) => name).join(' ➜ '),
489
+ });
490
+ return null;
491
+ }
492
+ return field;
493
+ }
400
494
  /**
401
495
  * The function `fixMissingReferences` checks for missing references in a given tree and field, and
402
496
  * attempts to fix them by removing the missing references from the field's `reference_to` array.
@@ -412,8 +506,9 @@ class ContentType {
412
506
  const { reference_to, data_type, display_name } = field;
413
507
  for (const reference of reference_to !== null && reference_to !== void 0 ? reference_to : []) {
414
508
  // NOTE Can skip specific references keys (Ex, system defined keys can be skipped)
415
- if (this.config.skipRefs.includes(reference))
509
+ if (this.config.skipRefs.includes(reference)) {
416
510
  continue;
511
+ }
417
512
  const refExist = (0, find_1.default)(this.ctSchema, { uid: reference });
418
513
  if (!refExist) {
419
514
  missingRefs.push(reference);
@@ -1,5 +1,5 @@
1
1
  import auditConfig from '../config';
2
- import { LogFn, Locale, ConfigType, EntryStruct, EntryFieldType, ModularBlockType, ContentTypeStruct, CtConstructorParam, GroupFieldDataType, GlobalFieldDataType, JsonRTEFieldDataType, ContentTypeSchemaType, ModularBlocksDataType, ModuleConstructorParam, ReferenceFieldDataType, EntryRefErrorReturnType, EntryGroupFieldDataType, EntryGlobalFieldDataType, EntryJsonRTEFieldDataType, EntryModularBlocksDataType, EntryReferenceFieldDataType } from '../types';
2
+ import { LogFn, Locale, ConfigType, EntryStruct, EntryFieldType, ModularBlockType, ContentTypeStruct, CtConstructorParam, GroupFieldDataType, GlobalFieldDataType, JsonRTEFieldDataType, ContentTypeSchemaType, ModularBlocksDataType, ModuleConstructorParam, ReferenceFieldDataType, EntryRefErrorReturnType, EntryGroupFieldDataType, EntryGlobalFieldDataType, EntryJsonRTEFieldDataType, EntryModularBlocksDataType, EntryReferenceFieldDataType, ExtensionOrAppFieldDataType, EntryExtensionOrAppFieldDataType } from '../types';
3
3
  export default class Entries {
4
4
  log: LogFn;
5
5
  protected fix: boolean;
@@ -9,6 +9,7 @@ export default class Entries {
9
9
  folderPath: string;
10
10
  currentUid: string;
11
11
  currentTitle: string;
12
+ extensions: string[];
12
13
  gfSchema: ContentTypeStruct[];
13
14
  ctSchema: ContentTypeStruct[];
14
15
  protected entries: Record<string, EntryStruct>;
@@ -63,6 +64,28 @@ export default class Entries {
63
64
  * `tree`, `fieldStructure`, and `field`.
64
65
  */
65
66
  validateReferenceField(tree: Record<string, unknown>[], fieldStructure: ReferenceFieldDataType, field: EntryReferenceFieldDataType[]): EntryRefErrorReturnType[];
67
+ /**
68
+ * The function `validateExtensionAndAppField` checks if a given field has a valid extension
69
+ * reference and returns any missing references.
70
+ * @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
71
+ * @param {ExtensionOrAppFieldDataType} fieldStructure - The `fieldStructure` parameter is of type
72
+ * `ExtensionOrAppFieldDataType` and represents the structure of a field in an extension or app. It
73
+ * contains properties such as `uid`, `display_name`, and `data_type`.
74
+ * @param {EntryExtensionOrAppFieldDataType} field - The `field` parameter is of type
75
+ * `EntryExtensionOrAppFieldDataType`, which is an object containing information about a specific
76
+ * field in an entry. It has the following properties:
77
+ * @returns an array containing an object if there are missing references. If there are no missing
78
+ * references, an empty array is returned.
79
+ */
80
+ validateExtensionAndAppField(tree: Record<string, unknown>[], fieldStructure: ExtensionOrAppFieldDataType, field: EntryExtensionOrAppFieldDataType): {
81
+ tree: Record<string, unknown>[];
82
+ data_type: string;
83
+ missingRefs: any[];
84
+ display_name: string;
85
+ ct_uid: string;
86
+ name: string;
87
+ treeStr: string;
88
+ }[];
66
89
  /**
67
90
  * The function "validateGlobalField" is an asynchronous function that takes in a tree,
68
91
  * fieldStructure, and field as parameters and looks for references in the tree.
@@ -129,6 +152,7 @@ export default class Entries {
129
152
  * objects.
130
153
  */
131
154
  validateReferenceValues(tree: Record<string, unknown>[], fieldStructure: ReferenceFieldDataType, field: EntryReferenceFieldDataType[]): EntryRefErrorReturnType[];
155
+ removeMissingKeysOnEntry(schema: ContentTypeSchemaType[], entry: EntryFieldType): void;
132
156
  /**
133
157
  * The function `runFixOnSchema` takes in a tree, schema, and entry, and applies fixes to the entry
134
158
  * based on the schema.
@@ -169,6 +193,20 @@ export default class Entries {
169
193
  * @returns the updated `entry` array after performing some modifications.
170
194
  */
171
195
  fixModularBlocksReferences(tree: Record<string, unknown>[], blocks: ModularBlockType[], entry: EntryModularBlocksDataType[]): EntryModularBlocksDataType[];
196
+ /**
197
+ * The function `fixMissingExtensionOrApp` checks if a field in an entry has a valid extension or app
198
+ * reference, and fixes it if necessary.
199
+ * @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
200
+ * @param {ExtensionOrAppFieldDataType} field - The `field` parameter is of type
201
+ * `ExtensionOrAppFieldDataType`, which is an object with properties `uid`, `display_name`, and
202
+ * `data_type`.
203
+ * @param {EntryExtensionOrAppFieldDataType} entry - The `entry` parameter is of type
204
+ * `EntryExtensionOrAppFieldDataType`, which is an object containing the data for a specific entry.
205
+ * It may have a property with the key specified by the `uid` variable, which represents the field
206
+ * that contains the extension or app data.
207
+ * @returns the `field` parameter.
208
+ */
209
+ fixMissingExtensionOrApp(tree: Record<string, unknown>[], field: ExtensionOrAppFieldDataType, entry: EntryExtensionOrAppFieldDataType): ExtensionOrAppFieldDataType;
172
210
  /**
173
211
  * The function `fixGroupField` takes in a tree, a field, and an entry, and if the field has a
174
212
  * schema, it runs a fix on the schema and returns the updated entry, otherwise it returns the
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
+ const map_1 = tslib_1.__importDefault(require("lodash/map"));
4
5
  const find_1 = tslib_1.__importDefault(require("lodash/find"));
5
6
  const values_1 = tslib_1.__importDefault(require("lodash/values"));
6
7
  const isEmpty_1 = tslib_1.__importDefault(require("lodash/isEmpty"));
@@ -12,6 +13,7 @@ const messages_1 = require("../messages");
12
13
  const global_fields_1 = tslib_1.__importDefault(require("./global-fields"));
13
14
  class Entries {
14
15
  constructor({ log, fix, config, moduleName, ctSchema, gfSchema }) {
16
+ this.extensions = [];
15
17
  this.missingRefs = {};
16
18
  this.entryMetaData = [];
17
19
  this.moduleName = 'entries';
@@ -53,6 +55,9 @@ class Entries {
53
55
  if (!this.missingRefs[this.currentUid]) {
54
56
  this.missingRefs[this.currentUid] = [];
55
57
  }
58
+ if (this.fix) {
59
+ this.removeMissingKeysOnEntry(ctSchema.schema, this.entries[entryUid]);
60
+ }
56
61
  this.lookForReference([{ locale: code, uid, name: title }], ctSchema, this.entries[entryUid]);
57
62
  this.log((0, messages_1.$t)(messages_1.auditMsg.SCAN_ENTRY_SUCCESS_MSG, {
58
63
  title,
@@ -85,6 +90,7 @@ class Entries {
85
90
  * `gfSchema` properties using the `ContentType` class.
86
91
  */
87
92
  async fixPrerequisiteData() {
93
+ var _a;
88
94
  this.ctSchema = (await new content_types_1.default({
89
95
  fix: true,
90
96
  log: () => { },
@@ -101,6 +107,24 @@ class Entries {
101
107
  ctSchema: this.ctSchema,
102
108
  gfSchema: this.gfSchema,
103
109
  }).run(true));
110
+ const extensionPath = (0, path_1.resolve)(this.config.basePath, 'extensions', 'extensions.json');
111
+ const marketplacePath = (0, path_1.resolve)(this.config.basePath, 'marketplace_apps', 'marketplace_apps.json');
112
+ if ((0, fs_1.existsSync)(extensionPath)) {
113
+ try {
114
+ this.extensions = Object.keys(JSON.parse((0, fs_1.readFileSync)(extensionPath, 'utf8')));
115
+ }
116
+ catch (error) { }
117
+ }
118
+ if ((0, fs_1.existsSync)(marketplacePath)) {
119
+ try {
120
+ const marketplaceApps = JSON.parse((0, fs_1.readFileSync)(marketplacePath, 'utf8'));
121
+ for (const app of marketplaceApps) {
122
+ const metaData = (0, map_1.default)((0, map_1.default)((_a = app === null || app === void 0 ? void 0 : app.ui_location) === null || _a === void 0 ? void 0 : _a.locations, 'meta').flat(), 'extension_uid').filter((val) => val);
123
+ this.extensions.push(...metaData);
124
+ }
125
+ }
126
+ catch (error) { }
127
+ }
104
128
  }
105
129
  /**
106
130
  * The function checks if it can write the fix content to a file and if so, it writes the content as
@@ -147,10 +171,11 @@ class Entries {
147
171
  this.validateGlobalField([...tree, { uid: child.uid, name: child.display_name, field: uid }], child, entry[uid]);
148
172
  break;
149
173
  case 'json':
150
- if (child.field_metadata.extension) {
174
+ if ('extension' in child.field_metadata && child.field_metadata.extension) {
175
+ this.missingRefs[this.currentUid].push(...this.validateExtensionAndAppField([...tree, { uid: child.uid, name: child.display_name, field: uid }], child, entry));
151
176
  // NOTE Custom field type
152
177
  }
153
- else if (child.field_metadata.allow_json_rte) {
178
+ else if ('allow_json_rte' in child.field_metadata && child.field_metadata.allow_json_rte) {
154
179
  // NOTE JSON RTE field type
155
180
  this.validateJsonRTEFields([...tree, { uid: child.uid, name: child.display_name, field: uid }], child, entry[uid]);
156
181
  }
@@ -180,6 +205,47 @@ class Entries {
180
205
  validateReferenceField(tree, fieldStructure, field) {
181
206
  return this.validateReferenceValues(tree, fieldStructure, field);
182
207
  }
208
+ /**
209
+ * The function `validateExtensionAndAppField` checks if a given field has a valid extension
210
+ * reference and returns any missing references.
211
+ * @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
212
+ * @param {ExtensionOrAppFieldDataType} fieldStructure - The `fieldStructure` parameter is of type
213
+ * `ExtensionOrAppFieldDataType` and represents the structure of a field in an extension or app. It
214
+ * contains properties such as `uid`, `display_name`, and `data_type`.
215
+ * @param {EntryExtensionOrAppFieldDataType} field - The `field` parameter is of type
216
+ * `EntryExtensionOrAppFieldDataType`, which is an object containing information about a specific
217
+ * field in an entry. It has the following properties:
218
+ * @returns an array containing an object if there are missing references. If there are no missing
219
+ * references, an empty array is returned.
220
+ */
221
+ validateExtensionAndAppField(tree, fieldStructure, field) {
222
+ if (this.fix)
223
+ return [];
224
+ const missingRefs = [];
225
+ let { uid, display_name, data_type } = fieldStructure || {};
226
+ if (field[uid]) {
227
+ let { metadata: { extension_uid } = { extension_uid: '' } } = field[uid] || {};
228
+ if (extension_uid && !this.extensions.includes(extension_uid)) {
229
+ missingRefs.push({ uid, extension_uid, type: 'Extension or Apps' });
230
+ }
231
+ }
232
+ return missingRefs.length
233
+ ? [
234
+ {
235
+ tree,
236
+ data_type,
237
+ missingRefs,
238
+ display_name,
239
+ ct_uid: this.currentUid,
240
+ name: this.currentTitle,
241
+ treeStr: tree
242
+ .map(({ name }) => name)
243
+ .filter((val) => val)
244
+ .join(' ➜ '),
245
+ },
246
+ ]
247
+ : [];
248
+ }
183
249
  /**
184
250
  * The function "validateGlobalField" is an asynchronous function that takes in a tree,
185
251
  * fieldStructure, and field as parameters and looks for references in the tree.
@@ -317,6 +383,17 @@ class Entries {
317
383
  ]
318
384
  : [];
319
385
  }
386
+ removeMissingKeysOnEntry(schema, entry) {
387
+ // NOTE remove invalid entry keys
388
+ const ctFields = (0, map_1.default)(schema, 'uid');
389
+ const entryFields = Object.keys(entry !== null && entry !== void 0 ? entry : {});
390
+ entryFields.forEach((eKey) => {
391
+ // NOTE Key should not be system key and not exist in schema means it's invalid entry key
392
+ if (!this.config.entries.systemKeys.includes(eKey) && !ctFields.includes(eKey)) {
393
+ delete entry[eKey];
394
+ }
395
+ });
396
+ }
320
397
  /**
321
398
  * The function `runFixOnSchema` takes in a tree, schema, and entry, and applies fixes to the entry
322
399
  * based on the schema.
@@ -345,11 +422,12 @@ class Entries {
345
422
  case 'json':
346
423
  case 'reference':
347
424
  if (data_type === 'json') {
348
- if (field.field_metadata.extension) {
425
+ if ('extension' in field.field_metadata && field.field_metadata.extension) {
349
426
  // NOTE Custom field type
427
+ this.fixMissingExtensionOrApp([...tree, { uid: field.uid, name: field.display_name, data_type: field.data_type }], field, entry);
350
428
  break;
351
429
  }
352
- else if (field.field_metadata.allow_json_rte) {
430
+ else if ('allow_json_rte' in field.field_metadata && field.field_metadata.allow_json_rte) {
353
431
  this.fixJsonRteMissingReferences([...tree, { uid: field.uid, name: field.display_name, data_type: field.data_type }], field, entry[uid]);
354
432
  break;
355
433
  }
@@ -410,6 +488,43 @@ class Entries {
410
488
  });
411
489
  return entry;
412
490
  }
491
+ /**
492
+ * The function `fixMissingExtensionOrApp` checks if a field in an entry has a valid extension or app
493
+ * reference, and fixes it if necessary.
494
+ * @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
495
+ * @param {ExtensionOrAppFieldDataType} field - The `field` parameter is of type
496
+ * `ExtensionOrAppFieldDataType`, which is an object with properties `uid`, `display_name`, and
497
+ * `data_type`.
498
+ * @param {EntryExtensionOrAppFieldDataType} entry - The `entry` parameter is of type
499
+ * `EntryExtensionOrAppFieldDataType`, which is an object containing the data for a specific entry.
500
+ * It may have a property with the key specified by the `uid` variable, which represents the field
501
+ * that contains the extension or app data.
502
+ * @returns the `field` parameter.
503
+ */
504
+ fixMissingExtensionOrApp(tree, field, entry) {
505
+ const missingRefs = [];
506
+ let { uid, display_name, data_type } = field || {};
507
+ if (entry[uid]) {
508
+ let { metadata: { extension_uid } = { extension_uid: '' } } = entry[uid] || {};
509
+ if (extension_uid && !this.extensions.includes(extension_uid)) {
510
+ missingRefs.push({ uid, extension_uid, type: 'Extension or Apps' });
511
+ }
512
+ }
513
+ if (this.fix && !(0, isEmpty_1.default)(missingRefs)) {
514
+ this.missingRefs[this.currentUid].push({
515
+ tree,
516
+ data_type,
517
+ missingRefs,
518
+ display_name,
519
+ fixStatus: 'Fixed',
520
+ ct_uid: this.currentUid,
521
+ name: this.currentTitle,
522
+ treeStr: tree.map(({ name }) => name).join(' ➜ '),
523
+ });
524
+ delete entry[uid];
525
+ }
526
+ return field;
527
+ }
413
528
  /**
414
529
  * The function `fixGroupField` takes in a tree, a field, and an entry, and if the field has a
415
530
  * schema, it runs a fix on the schema and returns the updated entry, otherwise it returns the
@@ -2,4 +2,7 @@ import { ux } from "@contentstack/cli-utilities";
2
2
  type IFlags = typeof ux.table.Flags;
3
3
  type IncludeFlags<T, K extends keyof T> = Pick<T, K>;
4
4
  type CommandNames = 'cm:stacks:audit' | 'cm:stacks:audit:fix';
5
- export { IFlags, IncludeFlags, CommandNames };
5
+ interface AnyProperty {
6
+ [propName: string]: any;
7
+ }
8
+ export { IFlags, IncludeFlags, CommandNames, AnyProperty };
@@ -1,6 +1,7 @@
1
1
  import config from '../config';
2
+ import { AnyProperty } from './common';
2
3
  import { ConfigType, LogFn } from './utils';
3
- type ContentTypeSchemaType = ReferenceFieldDataType | GlobalFieldDataType | CustomFieldDataType | JsonRTEFieldDataType | GroupFieldDataType | ModularBlocksDataType;
4
+ type ContentTypeSchemaType = ReferenceFieldDataType | GlobalFieldDataType | ExtensionOrAppFieldDataType | JsonRTEFieldDataType | GroupFieldDataType | ModularBlocksDataType;
4
5
  type ContentTypeStruct = {
5
6
  uid: string;
6
7
  title: string;
@@ -22,10 +23,9 @@ type CommonDataTypeStruct = {
22
23
  data_type: string;
23
24
  display_name: string;
24
25
  field_metadata: {
25
- extension: boolean;
26
26
  ref_multiple: boolean;
27
27
  allow_json_rte: boolean;
28
- } & Record<string, unknown>;
28
+ } & AnyProperty;
29
29
  };
30
30
  type RefErrorReturnType = {
31
31
  name: string;
@@ -44,9 +44,11 @@ type GlobalFieldDataType = CommonDataTypeStruct & {
44
44
  reference_to?: string;
45
45
  schema: GlobalFieldSchemaTypes[];
46
46
  };
47
- type CustomFieldDataType = CommonDataTypeStruct & {
48
- reference_to: string[];
47
+ type ExtensionOrAppFieldDataType = Omit<CommonDataTypeStruct, 'field_metadata'> & {
49
48
  extension_uid: string;
49
+ field_metadata: {
50
+ extension: boolean;
51
+ };
50
52
  };
51
53
  type JsonRTEFieldDataType = CommonDataTypeStruct & {
52
54
  reference_to: string[];
@@ -58,13 +60,13 @@ type ModularBlockType = {
58
60
  uid: string;
59
61
  title: string;
60
62
  reference_to?: string;
61
- schema: (JsonRTEFieldDataType | ModularBlocksDataType | ReferenceFieldDataType | CustomFieldDataType | GroupFieldDataType)[];
63
+ schema: (JsonRTEFieldDataType | ModularBlocksDataType | ReferenceFieldDataType | ExtensionOrAppFieldDataType | GroupFieldDataType)[];
62
64
  };
63
65
  type ModularBlocksDataType = CommonDataTypeStruct & {
64
66
  blocks: ModularBlockType[];
65
67
  };
66
- type GroupFieldSchemaTypes = GroupFieldDataType | CommonDataTypeStruct | GlobalFieldDataType | ReferenceFieldDataType;
67
- type GlobalFieldSchemaTypes = ReferenceFieldDataType | GroupFieldDataType | CustomFieldDataType;
68
+ type GroupFieldSchemaTypes = GroupFieldDataType | CommonDataTypeStruct | GlobalFieldDataType | ReferenceFieldDataType | ExtensionOrAppFieldDataType;
69
+ type GlobalFieldSchemaTypes = ReferenceFieldDataType | GroupFieldDataType | ExtensionOrAppFieldDataType;
68
70
  type ModularBlocksSchemaTypes = ReferenceFieldDataType | JsonRTEFieldDataType;
69
71
  declare enum OutputColumn {
70
72
  Title = "name",
@@ -73,4 +75,4 @@ declare enum OutputColumn {
73
75
  'Missing references' = "missingRefs",
74
76
  Path = "treeStr"
75
77
  }
76
- export { CtConstructorParam, ContentTypeStruct, ModuleConstructorParam, ReferenceFieldDataType, GlobalFieldDataType, CustomFieldDataType, JsonRTEFieldDataType, GroupFieldDataType, ModularBlocksDataType, RefErrorReturnType, ModularBlocksSchemaTypes, ModularBlockType, OutputColumn, ContentTypeSchemaType, GlobalFieldSchemaTypes, };
78
+ export { CtConstructorParam, ContentTypeStruct, ModuleConstructorParam, ReferenceFieldDataType, GlobalFieldDataType, ExtensionOrAppFieldDataType, JsonRTEFieldDataType, GroupFieldDataType, ModularBlocksDataType, RefErrorReturnType, ModularBlocksSchemaTypes, ModularBlockType, OutputColumn, ContentTypeSchemaType, GlobalFieldSchemaTypes, };
@@ -1,3 +1,4 @@
1
+ import { AnyProperty } from './common';
1
2
  type Locale = {
2
3
  uid: string;
3
4
  code: string;
@@ -17,8 +18,13 @@ type EntryReferenceFieldDataType = {
17
18
  type EntryGlobalFieldDataType = {
18
19
  [key: string]: EntryStruct;
19
20
  };
20
- type EntryCustomFieldDataType = {
21
- [key: string]: EntryStruct;
21
+ type EntryExtensionOrAppFieldDataType = {
22
+ [key: string]: {
23
+ uid: string;
24
+ metadata: {
25
+ extension_uid: string;
26
+ } & AnyProperty;
27
+ };
22
28
  };
23
29
  type EntryJsonRTEFieldDataType = {
24
30
  uid: string;
@@ -45,5 +51,5 @@ type EntryRefErrorReturnType = {
45
51
  tree: Record<string, unknown>[];
46
52
  missingRefs: string[] | Record<string, unknown>[];
47
53
  };
48
- type EntryFieldType = EntryStruct | EntryGlobalFieldDataType | EntryModularBlocksDataType | EntryGroupFieldDataType;
49
- export { Locale, EntryStruct, EntryFieldType, EntryGlobalFieldDataType, EntryCustomFieldDataType, EntryJsonRTEFieldDataType, EntryGroupFieldDataType, EntryModularBlocksDataType, EntryReferenceFieldDataType, EntryRefErrorReturnType, GroupFieldType, EntryModularBlockType, };
54
+ type EntryFieldType = EntryStruct | EntryGlobalFieldDataType | EntryModularBlocksDataType | EntryGroupFieldDataType | EntryExtensionOrAppFieldDataType;
55
+ export { Locale, EntryStruct, EntryFieldType, EntryGlobalFieldDataType, EntryExtensionOrAppFieldDataType, EntryJsonRTEFieldDataType, EntryGroupFieldDataType, EntryModularBlocksDataType, EntryReferenceFieldDataType, EntryRefErrorReturnType, GroupFieldType, EntryModularBlockType, };
@@ -0,0 +1,38 @@
1
+ type AppLocation = 'cs.cm.stack.config' | 'cs.cm.stack.dashboard' | 'cs.cm.stack.sidebar' | 'cs.cm.stack.custom_field' | 'cs.cm.stack.rte' | 'cs.cm.stack.asset_sidebar' | 'cs.org.config';
2
+ interface ExtensionMeta {
3
+ uid?: string;
4
+ name?: string;
5
+ description?: string;
6
+ path?: string;
7
+ signed: boolean;
8
+ extension_uid?: string;
9
+ data_type?: string;
10
+ enabled?: boolean;
11
+ width?: number;
12
+ blur?: boolean;
13
+ default_width?: 'full' | 'half';
14
+ }
15
+ interface Extension {
16
+ type: AppLocation;
17
+ meta: ExtensionMeta[];
18
+ }
19
+ interface LocationConfiguration {
20
+ signed: boolean;
21
+ base_url: string;
22
+ locations: Extension[];
23
+ }
24
+ type MarketplaceAppsInstallationData = {
25
+ uid: string;
26
+ status: string;
27
+ configuration: any;
28
+ server_configuration: any;
29
+ target: {
30
+ type: string;
31
+ uid: string;
32
+ };
33
+ ui_location: LocationConfiguration;
34
+ } & AnyProperty;
35
+ interface AnyProperty {
36
+ [propName: string]: any;
37
+ }
38
+ export { MarketplaceAppsInstallationData };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.5",
2
+ "version": "1.4.0",
3
3
  "commands": {
4
4
  "cm:stacks:audit:fix": {
5
5
  "id": "cm:stacks:audit:fix",
@@ -85,7 +85,7 @@
85
85
  "reference",
86
86
  "global_field",
87
87
  "json:rte",
88
- "json:custom-field",
88
+ "json:extension",
89
89
  "blocks",
90
90
  "group"
91
91
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentstack/cli-audit",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "description": "Contentstack audit plugin",
5
5
  "author": "Contentstack CLI",
6
6
  "homepage": "https://github.com/contentstack/cli",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "@contentstack/cli-command": "~1.2.16",
22
- "@contentstack/cli-utilities": "~1.5.11",
22
+ "@contentstack/cli-utilities": "~1.5.12",
23
23
  "@oclif/plugin-help": "^5",
24
24
  "@oclif/plugin-plugins": "^4.1.9",
25
25
  "chalk": "^4.1.2",