@contentstack/cli-audit 1.9.0 → 1.11.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 +89 -57
- package/lib/audit-base-command.d.ts +7 -5
- package/lib/audit-base-command.js +75 -61
- package/lib/commands/cm/stacks/audit/fix.js +2 -2
- package/lib/commands/cm/stacks/audit/index.js +2 -2
- package/lib/config/index.d.ts +1 -0
- package/lib/config/index.js +27 -8
- package/lib/messages/index.d.ts +5 -0
- package/lib/messages/index.js +7 -2
- package/lib/modules/assets.d.ts +1 -1
- package/lib/modules/assets.js +1 -1
- package/lib/modules/content-types.d.ts +1 -1
- package/lib/modules/content-types.js +1 -1
- package/lib/modules/custom-roles.js +1 -1
- package/lib/modules/entries.d.ts +4 -0
- package/lib/modules/entries.js +46 -4
- package/lib/modules/extensions.js +2 -2
- package/lib/modules/field_rules.d.ts +54 -0
- package/lib/modules/field_rules.js +265 -0
- package/lib/modules/global-fields.d.ts +1 -1
- package/lib/modules/index.d.ts +2 -1
- package/lib/modules/index.js +3 -1
- package/lib/modules/workflows.js +2 -2
- package/lib/types/common.d.ts +1 -3
- package/lib/types/content-types.d.ts +22 -3
- package/lib/types/content-types.js +5 -2
- package/lib/util/flags.d.ts +3 -10
- package/lib/util/flags.js +4 -24
- package/lib/util/log.js +1 -2
- package/oclif.manifest.json +198 -105
- package/package.json +13 -12
package/lib/config/index.js
CHANGED
|
@@ -4,7 +4,16 @@ const config = {
|
|
|
4
4
|
showTerminalOutput: true,
|
|
5
5
|
skipRefs: ['sys_assets'],
|
|
6
6
|
skipFieldTypes: ['taxonomy', 'group'],
|
|
7
|
-
modules: [
|
|
7
|
+
modules: [
|
|
8
|
+
'content-types',
|
|
9
|
+
'global-fields',
|
|
10
|
+
'entries',
|
|
11
|
+
'extensions',
|
|
12
|
+
'workflows',
|
|
13
|
+
'custom-roles',
|
|
14
|
+
'assets',
|
|
15
|
+
'field-rules',
|
|
16
|
+
],
|
|
8
17
|
'fix-fields': ['reference', 'global_field', 'json:rte', 'json:extension', 'blocks', 'group', 'content_types'],
|
|
9
18
|
moduleConfig: {
|
|
10
19
|
'content-types': {
|
|
@@ -42,16 +51,16 @@ const config = {
|
|
|
42
51
|
dirName: 'custom-roles',
|
|
43
52
|
fileName: 'custom-roles.json',
|
|
44
53
|
},
|
|
45
|
-
|
|
54
|
+
assets: {
|
|
46
55
|
name: 'assets',
|
|
47
56
|
dirName: 'assets',
|
|
48
57
|
fileName: 'assets.json',
|
|
49
58
|
},
|
|
50
|
-
|
|
59
|
+
environments: {
|
|
51
60
|
name: 'environments',
|
|
52
61
|
dirName: 'environments',
|
|
53
62
|
fileName: 'environments.json',
|
|
54
|
-
}
|
|
63
|
+
},
|
|
55
64
|
},
|
|
56
65
|
entries: {
|
|
57
66
|
systemKeys: [
|
|
@@ -96,16 +105,26 @@ const config = {
|
|
|
96
105
|
'publish_locale',
|
|
97
106
|
'publish_environment',
|
|
98
107
|
'asset_uid',
|
|
99
|
-
'selectedValue'
|
|
108
|
+
'selectedValue',
|
|
109
|
+
'ct_uid',
|
|
110
|
+
'action',
|
|
100
111
|
],
|
|
101
112
|
ReportTitleForEntries: {
|
|
102
113
|
Entries_Select_feild: 'Entries_Select_feild',
|
|
103
114
|
Entries_Mandatory_feild: 'Entries_Mandatory_feild',
|
|
104
115
|
Entries_Title_feild: 'Entries_Title_feild',
|
|
105
116
|
Entry_Missing_Locale_and_Env: 'Entry_Missing_Locale_and_Env',
|
|
106
|
-
Entry_Missing_Locale_and_Env_in_Publish_Details: 'Entry_Missing_Locale_and_Env_in_Publish_Details'
|
|
117
|
+
Entry_Missing_Locale_and_Env_in_Publish_Details: 'Entry_Missing_Locale_and_Env_in_Publish_Details',
|
|
118
|
+
Entry_Multiple_Fields: "Entry_Multiple_Fields"
|
|
107
119
|
},
|
|
108
|
-
feild_level_modules: [
|
|
109
|
-
|
|
120
|
+
feild_level_modules: [
|
|
121
|
+
'Entries_Title_feild',
|
|
122
|
+
'Entries_Mandatory_feild',
|
|
123
|
+
'Entries_Select_feild',
|
|
124
|
+
'Entry_Missing_Locale_and_Env_in_Publish_Details',
|
|
125
|
+
'field-rules',
|
|
126
|
+
'Entry_Multiple_Fields'
|
|
127
|
+
],
|
|
128
|
+
fixSelectField: false,
|
|
110
129
|
};
|
|
111
130
|
exports.default = config;
|
package/lib/messages/index.d.ts
CHANGED
|
@@ -38,6 +38,10 @@ declare const auditMsg: {
|
|
|
38
38
|
CT_REFERENCE_FIELD: string;
|
|
39
39
|
ASSET_NOT_EXIST: string;
|
|
40
40
|
ENTRY_PUBLISH_DETAILS_NOT_EXIST: string;
|
|
41
|
+
FIELD_RULE_CONDITION_ABSENT: string;
|
|
42
|
+
FIELD_RULE_TARGET_ABSENT: string;
|
|
43
|
+
FIELD_RULE_CONDITION_SCAN_MESSAGE: string;
|
|
44
|
+
FIELD_RULE_TARGET_SCAN_MESSAGE: string;
|
|
41
45
|
};
|
|
42
46
|
declare const auditFixMsg: {
|
|
43
47
|
COPY_DATA: string;
|
|
@@ -50,6 +54,7 @@ declare const auditFixMsg: {
|
|
|
50
54
|
ENTRY_MANDATORY_FIELD_FIX: string;
|
|
51
55
|
ENTRY_SELECT_FIELD_FIX: string;
|
|
52
56
|
ASSET_FIX: string;
|
|
57
|
+
FIELD_RULE_FIX_MESSAGE: string;
|
|
53
58
|
};
|
|
54
59
|
declare const messages: typeof errors & typeof commonMsg & typeof auditMsg & typeof auditFixMsg & typeof tableColumnDescriptions;
|
|
55
60
|
/**
|
package/lib/messages/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.tableColumnDescriptions = exports.auditFixMsg = exports.auditMsg = exports.commonMsg = exports.errors =
|
|
3
|
+
exports.tableColumnDescriptions = exports.auditFixMsg = exports.auditMsg = exports.commonMsg = exports.errors = void 0;
|
|
4
|
+
exports.$t = $t;
|
|
4
5
|
const tslib_1 = require("tslib");
|
|
5
6
|
const memoize_1 = tslib_1.__importDefault(require("lodash/memoize"));
|
|
6
7
|
const errors = {};
|
|
@@ -46,6 +47,10 @@ const auditMsg = {
|
|
|
46
47
|
CT_REFERENCE_FIELD: `The mentioned Reference field is not Array field reference is '{reference_to}' having display name '{display_name}''`,
|
|
47
48
|
ASSET_NOT_EXIST: `The publish_details either does not exist or is not an array for asset uid '{uid}'`,
|
|
48
49
|
ENTRY_PUBLISH_DETAILS_NOT_EXIST: `The publish_details either does not exist or is not an array for entry uid '{uid}'`,
|
|
50
|
+
FIELD_RULE_CONDITION_ABSENT: `The operand field '{condition_field}' is not present in the schema of the content-type {ctUid}`,
|
|
51
|
+
FIELD_RULE_TARGET_ABSENT: `The target field '{target_field}' is not present in the schema of the content-type {ctUid}`,
|
|
52
|
+
FIELD_RULE_CONDITION_SCAN_MESSAGE: `Completed Scanning of Field Rule '{num}' condition of Content-type '{ctUid}'`,
|
|
53
|
+
FIELD_RULE_TARGET_SCAN_MESSAGE: `Completed Scanning of Field Rule '{num}' target of Content-type '{ctUid}'`
|
|
49
54
|
};
|
|
50
55
|
exports.auditMsg = auditMsg;
|
|
51
56
|
const auditFixMsg = {
|
|
@@ -59,6 +64,7 @@ const auditFixMsg = {
|
|
|
59
64
|
ENTRY_MANDATORY_FIELD_FIX: `Removing the publish details from the entry with UID '{uid}' in Locale '{locale}'...`,
|
|
60
65
|
ENTRY_SELECT_FIELD_FIX: `Adding the value '{value}' in the select field of entry UID '{uid}'...`,
|
|
61
66
|
ASSET_FIX: `Fixed publish detials for Asset with UID '{uid}'`,
|
|
67
|
+
FIELD_RULE_FIX_MESSAGE: `Fixed Field Rule '{num}' target of Content-type '{ctUid}`,
|
|
62
68
|
};
|
|
63
69
|
exports.auditFixMsg = auditFixMsg;
|
|
64
70
|
const messages = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, errors), commonMsg), auditMsg), auditFixMsg), tableColumnDescriptions);
|
|
@@ -84,5 +90,4 @@ function $t(msg, args) {
|
|
|
84
90
|
});
|
|
85
91
|
return transfer(msg, args);
|
|
86
92
|
}
|
|
87
|
-
exports.$t = $t;
|
|
88
93
|
exports.default = messages;
|
package/lib/modules/assets.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export default class Assets {
|
|
|
21
21
|
* iterates over the schema and looks for references, and returns a list of missing references.
|
|
22
22
|
* @returns the `missingEnvLocales` object.
|
|
23
23
|
*/
|
|
24
|
-
run(returnFixSchema?: boolean): Promise<Record<string, any
|
|
24
|
+
run(returnFixSchema?: boolean): Promise<Record<string, any> | ContentTypeStruct[]>;
|
|
25
25
|
/**
|
|
26
26
|
* @method prerequisiteData
|
|
27
27
|
* The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
|
package/lib/modules/assets.js
CHANGED
|
@@ -80,7 +80,7 @@ class Assets {
|
|
|
80
80
|
let canWrite = true;
|
|
81
81
|
if (this.fix) {
|
|
82
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.
|
|
83
|
+
canWrite = this.config.flags.yes || (await cli_utilities_1.cliux.confirm(messages_1.commonMsg.FIX_CONFIRMATION));
|
|
84
84
|
}
|
|
85
85
|
if (canWrite) {
|
|
86
86
|
(0, fs_1.writeFileSync)(filePath, JSON.stringify(schema));
|
|
@@ -22,7 +22,7 @@ export default class ContentType {
|
|
|
22
22
|
* iterates over the schema and looks for references, and returns a list of missing references.
|
|
23
23
|
* @returns the `missingRefs` object.
|
|
24
24
|
*/
|
|
25
|
-
run(returnFixSchema?: boolean): Promise<Record<string, any
|
|
25
|
+
run(returnFixSchema?: boolean): Promise<Record<string, any> | ContentTypeStruct[]>;
|
|
26
26
|
/**
|
|
27
27
|
* @method prerequisiteData
|
|
28
28
|
* The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
|
|
@@ -102,7 +102,7 @@ class ContentType {
|
|
|
102
102
|
let canWrite = true;
|
|
103
103
|
if (!this.inMemoryFix && this.fix) {
|
|
104
104
|
if (!this.config.flags['copy-dir'] && !((_a = this.config.flags['external-config']) === null || _a === void 0 ? void 0 : _a.skipConfirm)) {
|
|
105
|
-
canWrite = (_b = this.config.flags.yes) !== null && _b !== void 0 ? _b : (await cli_utilities_1.
|
|
105
|
+
canWrite = (_b = this.config.flags.yes) !== null && _b !== void 0 ? _b : (await cli_utilities_1.cliux.confirm(messages_1.commonMsg.FIX_CONFIRMATION));
|
|
106
106
|
}
|
|
107
107
|
if (canWrite) {
|
|
108
108
|
(0, fs_1.writeFileSync)((0, path_1.join)(this.folderPath, this.config.moduleConfig[this.moduleName].fileName), JSON.stringify(this.schema));
|
|
@@ -117,7 +117,7 @@ class CustomRoles {
|
|
|
117
117
|
(this.config.flags['copy-dir'] ||
|
|
118
118
|
((_a = this.config.flags['external-config']) === null || _a === void 0 ? void 0 : _a.skipConfirm) ||
|
|
119
119
|
this.config.flags.yes ||
|
|
120
|
-
(await cli_utilities_1.
|
|
120
|
+
(await cli_utilities_1.cliux.confirm(messages_1.commonMsg.FIX_CONFIRMATION)))) {
|
|
121
121
|
(0, fs_1.writeFileSync)((0, path_1.join)(this.folderPath, this.config.moduleConfig[this.moduleName].fileName), JSON.stringify(newCustomRoleSchema));
|
|
122
122
|
}
|
|
123
123
|
}
|
package/lib/modules/entries.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export default class Entries {
|
|
|
18
18
|
protected missingMandatoryFields: Record<string, any>;
|
|
19
19
|
protected missingTitleFields: Record<string, any>;
|
|
20
20
|
protected missingEnvLocale: Record<string, any>;
|
|
21
|
+
protected missingMultipleField: Record<string, any>;
|
|
21
22
|
environments: string[];
|
|
22
23
|
entryMetaData: Record<string, any>[];
|
|
23
24
|
moduleName: keyof typeof auditConfig.moduleConfig;
|
|
@@ -34,12 +35,14 @@ export default class Entries {
|
|
|
34
35
|
missingMandatoryFields?: undefined;
|
|
35
36
|
missingTitleFields?: undefined;
|
|
36
37
|
missingEnvLocale?: undefined;
|
|
38
|
+
missingMultipleFields?: undefined;
|
|
37
39
|
} | {
|
|
38
40
|
missingEntryRefs: Record<string, any>;
|
|
39
41
|
missingSelectFeild: Record<string, any>;
|
|
40
42
|
missingMandatoryFields: Record<string, any>;
|
|
41
43
|
missingTitleFields: Record<string, any>;
|
|
42
44
|
missingEnvLocale: Record<string, any>;
|
|
45
|
+
missingMultipleFields: Record<string, any>;
|
|
43
46
|
}>;
|
|
44
47
|
/**
|
|
45
48
|
* The function removes any properties from the `missingRefs` object that have an empty array value.
|
|
@@ -195,6 +198,7 @@ export default class Entries {
|
|
|
195
198
|
* @returns if there is missing field returns field and path
|
|
196
199
|
* Else empty array
|
|
197
200
|
*/
|
|
201
|
+
removeEmojiAndImages(str: string): string;
|
|
198
202
|
validateSelectField(tree: Record<string, unknown>[], fieldStructure: SelectFeildStruct, field: any): {
|
|
199
203
|
uid: string;
|
|
200
204
|
name: string;
|
package/lib/modules/entries.js
CHANGED
|
@@ -21,6 +21,7 @@ class Entries {
|
|
|
21
21
|
this.missingMandatoryFields = {};
|
|
22
22
|
this.missingTitleFields = {};
|
|
23
23
|
this.missingEnvLocale = {};
|
|
24
|
+
this.missingMultipleField = {};
|
|
24
25
|
this.environments = [];
|
|
25
26
|
this.entryMetaData = [];
|
|
26
27
|
this.moduleName = 'entries';
|
|
@@ -66,6 +67,7 @@ class Entries {
|
|
|
66
67
|
const { uid, title } = entry;
|
|
67
68
|
this.currentUid = uid;
|
|
68
69
|
this.currentTitle = title;
|
|
70
|
+
this.currentTitle = this.removeEmojiAndImages(this.currentTitle);
|
|
69
71
|
if (!this.missingRefs[this.currentUid]) {
|
|
70
72
|
this.missingRefs[this.currentUid] = [];
|
|
71
73
|
}
|
|
@@ -78,7 +80,7 @@ class Entries {
|
|
|
78
80
|
if (this.fix) {
|
|
79
81
|
this.removeMissingKeysOnEntry(ctSchema.schema, this.entries[entryUid]);
|
|
80
82
|
}
|
|
81
|
-
this.lookForReference([{ locale: code, uid, name: title }], ctSchema, this.entries[entryUid]);
|
|
83
|
+
this.lookForReference([{ locale: code, uid, name: this.removeEmojiAndImages(title) }], ctSchema, this.entries[entryUid]);
|
|
82
84
|
if ((_a = this.missingRefs[this.currentUid]) === null || _a === void 0 ? void 0 : _a.length) {
|
|
83
85
|
this.missingRefs[this.currentUid].forEach((entry) => {
|
|
84
86
|
entry.ct = ctSchema.uid;
|
|
@@ -177,6 +179,7 @@ class Entries {
|
|
|
177
179
|
missingMandatoryFields: this.missingMandatoryFields,
|
|
178
180
|
missingTitleFields: this.missingTitleFields,
|
|
179
181
|
missingEnvLocale: this.missingEnvLocale,
|
|
182
|
+
missingMultipleFields: this.missingMultipleField
|
|
180
183
|
};
|
|
181
184
|
}
|
|
182
185
|
/**
|
|
@@ -249,7 +252,7 @@ class Entries {
|
|
|
249
252
|
let canWrite = true;
|
|
250
253
|
if (this.fix) {
|
|
251
254
|
if (!this.config.flags['copy-dir'] && !((_a = this.config.flags['external-config']) === null || _a === void 0 ? void 0 : _a.skipConfirm)) {
|
|
252
|
-
canWrite = this.config.flags.yes || (await cli_utilities_1.
|
|
255
|
+
canWrite = this.config.flags.yes || (await cli_utilities_1.cliux.confirm(messages_1.commonMsg.FIX_CONFIRMATION));
|
|
253
256
|
}
|
|
254
257
|
if (canWrite) {
|
|
255
258
|
(0, fs_1.writeFileSync)(filePath, JSON.stringify(schema));
|
|
@@ -274,7 +277,24 @@ class Entries {
|
|
|
274
277
|
entry = this.runFixOnSchema(tree, field.schema, entry);
|
|
275
278
|
}
|
|
276
279
|
for (const child of (_a = field === null || field === void 0 ? void 0 : field.schema) !== null && _a !== void 0 ? _a : []) {
|
|
277
|
-
const { uid } = child;
|
|
280
|
+
const { uid, multiple, data_type } = child;
|
|
281
|
+
if (multiple && entry[uid] && !Array.isArray(entry[uid])) {
|
|
282
|
+
if (!this.missingMultipleField[this.currentUid]) {
|
|
283
|
+
this.missingMultipleField[this.currentUid] = [];
|
|
284
|
+
}
|
|
285
|
+
this.missingMultipleField[this.currentUid].push({
|
|
286
|
+
uid: this.currentUid,
|
|
287
|
+
name: this.currentTitle,
|
|
288
|
+
field_uid: uid,
|
|
289
|
+
data_type,
|
|
290
|
+
multiple,
|
|
291
|
+
tree,
|
|
292
|
+
treeStr: tree
|
|
293
|
+
.map(({ name }) => name)
|
|
294
|
+
.filter((val) => val)
|
|
295
|
+
.join(' ➜ '),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
278
298
|
this.missingMandatoryFields[this.currentUid].push(...this.validateMandatoryFields([...tree, { uid: field.uid, name: child.display_name, field: uid }], child, entry));
|
|
279
299
|
if (!(entry === null || entry === void 0 ? void 0 : entry[uid]) && !child.hasOwnProperty('display_type')) {
|
|
280
300
|
continue;
|
|
@@ -551,10 +571,29 @@ class Entries {
|
|
|
551
571
|
runFixOnSchema(tree, schema, entry) {
|
|
552
572
|
// NOTE Global field Fix
|
|
553
573
|
schema.forEach((field) => {
|
|
554
|
-
|
|
574
|
+
var _a;
|
|
575
|
+
var _b, _c;
|
|
576
|
+
const { uid, data_type, multiple } = field;
|
|
555
577
|
if (!Object(entry).hasOwnProperty(uid)) {
|
|
556
578
|
return;
|
|
557
579
|
}
|
|
580
|
+
if (multiple && entry[uid] && !Array.isArray(entry[uid])) {
|
|
581
|
+
(_a = (_b = this.missingMultipleField)[_c = this.currentUid]) !== null && _a !== void 0 ? _a : (_b[_c] = []);
|
|
582
|
+
this.missingMultipleField[this.currentUid].push({
|
|
583
|
+
uid: this.currentUid,
|
|
584
|
+
name: this.currentTitle,
|
|
585
|
+
field_uid: uid,
|
|
586
|
+
data_type,
|
|
587
|
+
multiple,
|
|
588
|
+
tree,
|
|
589
|
+
treeStr: tree
|
|
590
|
+
.map(({ name }) => name)
|
|
591
|
+
.filter(Boolean)
|
|
592
|
+
.join(' ➜ '),
|
|
593
|
+
'fixStatus': 'Fixed',
|
|
594
|
+
});
|
|
595
|
+
entry[uid] = [entry[uid]];
|
|
596
|
+
}
|
|
558
597
|
switch (data_type) {
|
|
559
598
|
case 'global_field':
|
|
560
599
|
entry[uid] = this.fixGlobalFieldReferences([...tree, { uid: field.uid, name: field.display_name, data_type: field.data_type }], field, entry[uid]);
|
|
@@ -603,6 +642,9 @@ class Entries {
|
|
|
603
642
|
* @returns if there is missing field returns field and path
|
|
604
643
|
* Else empty array
|
|
605
644
|
*/
|
|
645
|
+
removeEmojiAndImages(str) {
|
|
646
|
+
return str.replace(/[\p{Emoji}\p{Emoji_Presentation}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Emoji_Component}]+/gu, '');
|
|
647
|
+
}
|
|
606
648
|
validateSelectField(tree, fieldStructure, field) {
|
|
607
649
|
const { display_name, enum: selectOptions, multiple, min_instance, display_type, data_type } = fieldStructure;
|
|
608
650
|
if (field === null ||
|
|
@@ -74,7 +74,7 @@ class Extensions {
|
|
|
74
74
|
}
|
|
75
75
|
else {
|
|
76
76
|
this.log((0, messages_1.$t)(messages_1.commonMsg.EXTENSION_FIX_WARN, { title: title, uid }), { color: 'yellow' });
|
|
77
|
-
const shouldDelete = this.config.flags.yes || (await cli_utilities_1.
|
|
77
|
+
const shouldDelete = this.config.flags.yes || (await cli_utilities_1.cliux.confirm(messages_1.commonMsg.EXTENSION_FIX_CONFIRMATION));
|
|
78
78
|
if (shouldDelete) {
|
|
79
79
|
delete newExtensionSchema[uid];
|
|
80
80
|
}
|
|
@@ -88,7 +88,7 @@ class Extensions {
|
|
|
88
88
|
(this.config.flags['copy-dir'] ||
|
|
89
89
|
((_a = this.config.flags['external-config']) === null || _a === void 0 ? void 0 : _a.skipConfirm) ||
|
|
90
90
|
this.config.flags.yes ||
|
|
91
|
-
(await cli_utilities_1.
|
|
91
|
+
(await cli_utilities_1.cliux.confirm(messages_1.commonMsg.FIX_CONFIRMATION)))) {
|
|
92
92
|
(0, fs_1.writeFileSync)((0, path_1.join)(this.folderPath, this.config.moduleConfig[this.moduleName].fileName), JSON.stringify(fixedExtensions));
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Locale } from '@contentstack/cli-utilities';
|
|
2
|
+
import { LogFn, ConfigType, ModularBlockType, ContentTypeStruct, GroupFieldDataType, CtConstructorParam, GlobalFieldDataType, ModularBlocksDataType, ModuleConstructorParam, EntryStruct } from '../types';
|
|
3
|
+
import auditConfig from '../config';
|
|
4
|
+
export default class FieldRule {
|
|
5
|
+
log: LogFn;
|
|
6
|
+
protected fix: boolean;
|
|
7
|
+
fileName: string;
|
|
8
|
+
config: ConfigType;
|
|
9
|
+
folderPath: string;
|
|
10
|
+
currentUid: string;
|
|
11
|
+
currentTitle: string;
|
|
12
|
+
extensions: string[];
|
|
13
|
+
inMemoryFix: boolean;
|
|
14
|
+
gfSchema: ContentTypeStruct[];
|
|
15
|
+
ctSchema: ContentTypeStruct[];
|
|
16
|
+
protected schema: ContentTypeStruct[];
|
|
17
|
+
protected missingRefs: Record<string, any>;
|
|
18
|
+
moduleName: keyof typeof auditConfig.moduleConfig;
|
|
19
|
+
schemaMap: any;
|
|
20
|
+
locales: Locale[];
|
|
21
|
+
protected entries: Record<string, EntryStruct>;
|
|
22
|
+
protected missingSelectFeild: Record<string, any>;
|
|
23
|
+
protected missingMandatoryFields: Record<string, any>;
|
|
24
|
+
protected missingEnvLocale: Record<string, any>;
|
|
25
|
+
entryMetaData: Record<string, any>[];
|
|
26
|
+
action: string[];
|
|
27
|
+
constructor({ log, fix, config, moduleName, ctSchema, gfSchema }: ModuleConstructorParam & CtConstructorParam);
|
|
28
|
+
validateModules(moduleName: keyof typeof auditConfig.moduleConfig, moduleConfig: Record<string, unknown>): keyof typeof auditConfig.moduleConfig;
|
|
29
|
+
/**
|
|
30
|
+
* The `run` function checks if a folder path exists, sets the schema based on the module name,
|
|
31
|
+
* iterates over the schema and looks for references, and returns a list of missing references.
|
|
32
|
+
* @returns the `missingRefs` object.
|
|
33
|
+
*/
|
|
34
|
+
run(): Promise<Record<string, any>>;
|
|
35
|
+
validateFieldRules(schema: Record<string, unknown>): void;
|
|
36
|
+
fixFieldRules(schema: Record<string, unknown>): void;
|
|
37
|
+
addMissingReferences(actions: Record<string, unknown>, fixStatus?: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* @method prerequisiteData
|
|
40
|
+
* The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
|
|
41
|
+
* app data, and stores them in the `extensions` array.
|
|
42
|
+
*/
|
|
43
|
+
prerequisiteData(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* The function checks if it can write the fix content to a file and if so, it writes the content as
|
|
46
|
+
* JSON to the specified file path.
|
|
47
|
+
*/
|
|
48
|
+
writeFixContent(): Promise<void>;
|
|
49
|
+
lookForReference(tree: Record<string, unknown>[], field: ContentTypeStruct | GlobalFieldDataType | ModularBlockType | GroupFieldDataType, parent?: string | null): Promise<void>;
|
|
50
|
+
validateGlobalField(tree: Record<string, unknown>[], field: GlobalFieldDataType, parent: string | null): Promise<void>;
|
|
51
|
+
validateModularBlocksField(tree: Record<string, unknown>[], field: ModularBlocksDataType, parent: string | null): Promise<void>;
|
|
52
|
+
validateGroupField(tree: Record<string, unknown>[], field: GroupFieldDataType, parent: string | null): Promise<void>;
|
|
53
|
+
prepareEntryMetaData(): Promise<void>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const map_1 = tslib_1.__importDefault(require("lodash/map"));
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const cli_utilities_1 = require("@contentstack/cli-utilities");
|
|
8
|
+
const messages_1 = require("../messages");
|
|
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 FieldRule {
|
|
13
|
+
constructor({ log, fix, config, moduleName, ctSchema, gfSchema }) {
|
|
14
|
+
this.extensions = [];
|
|
15
|
+
this.inMemoryFix = false;
|
|
16
|
+
this.schema = [];
|
|
17
|
+
this.missingRefs = {};
|
|
18
|
+
this.schemaMap = [];
|
|
19
|
+
this.missingSelectFeild = {};
|
|
20
|
+
this.missingMandatoryFields = {};
|
|
21
|
+
this.missingEnvLocale = {};
|
|
22
|
+
this.entryMetaData = [];
|
|
23
|
+
this.action = ['show', 'hide'];
|
|
24
|
+
this.log = log;
|
|
25
|
+
this.config = config;
|
|
26
|
+
this.fix = fix !== null && fix !== void 0 ? fix : false;
|
|
27
|
+
this.ctSchema = ctSchema;
|
|
28
|
+
this.gfSchema = gfSchema;
|
|
29
|
+
this.moduleName = this.validateModules(moduleName, this.config.moduleConfig);
|
|
30
|
+
this.fileName = config.moduleConfig[this.moduleName].fileName;
|
|
31
|
+
this.folderPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(config.basePath), (0, cli_utilities_1.sanitizePath)(config.moduleConfig[this.moduleName].dirName));
|
|
32
|
+
}
|
|
33
|
+
validateModules(moduleName, moduleConfig) {
|
|
34
|
+
if (Object.keys(moduleConfig).includes(moduleName)) {
|
|
35
|
+
return moduleName;
|
|
36
|
+
}
|
|
37
|
+
return 'content-types';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The `run` function checks if a folder path exists, sets the schema based on the module name,
|
|
41
|
+
* iterates over the schema and looks for references, and returns a list of missing references.
|
|
42
|
+
* @returns the `missingRefs` object.
|
|
43
|
+
*/
|
|
44
|
+
async run() {
|
|
45
|
+
var _a;
|
|
46
|
+
if (!(0, fs_1.existsSync)(this.folderPath)) {
|
|
47
|
+
this.log(`Skipping ${this.moduleName} audit`, 'warn');
|
|
48
|
+
this.log((0, messages_1.$t)(messages_1.auditMsg.NOT_VALID_PATH, { path: this.folderPath }), { color: 'yellow' });
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
this.schema = this.moduleName === 'content-types' ? this.ctSchema : this.gfSchema;
|
|
52
|
+
await this.prerequisiteData();
|
|
53
|
+
await this.prepareEntryMetaData();
|
|
54
|
+
for (const schema of (_a = this.schema) !== null && _a !== void 0 ? _a : []) {
|
|
55
|
+
this.currentUid = schema.uid;
|
|
56
|
+
this.currentTitle = schema.title;
|
|
57
|
+
this.missingRefs[this.currentUid] = [];
|
|
58
|
+
const { uid, title } = schema;
|
|
59
|
+
await this.lookForReference([{ uid, name: title }], schema, null);
|
|
60
|
+
this.missingRefs[this.currentUid] = [];
|
|
61
|
+
if (this.fix) {
|
|
62
|
+
this.fixFieldRules(schema);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.validateFieldRules(schema);
|
|
66
|
+
}
|
|
67
|
+
this.schemaMap = [];
|
|
68
|
+
this.log((0, messages_1.$t)(messages_1.auditMsg.SCAN_CT_SUCCESS_MSG, { title, module: this.config.moduleConfig[this.moduleName].name }), 'info');
|
|
69
|
+
}
|
|
70
|
+
if (this.fix) {
|
|
71
|
+
await this.writeFixContent();
|
|
72
|
+
}
|
|
73
|
+
for (let propName in this.missingRefs) {
|
|
74
|
+
if (!this.missingRefs[propName].length) {
|
|
75
|
+
delete this.missingRefs[propName];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return this.missingRefs;
|
|
79
|
+
}
|
|
80
|
+
validateFieldRules(schema) {
|
|
81
|
+
if (Array.isArray(schema.field_rules)) {
|
|
82
|
+
let count = 0;
|
|
83
|
+
schema.field_rules.forEach((fr) => {
|
|
84
|
+
fr.actions.forEach((actions) => {
|
|
85
|
+
if (!this.schemaMap.includes(actions.target_field)) {
|
|
86
|
+
this.log((0, messages_1.$t)(messages_1.auditMsg.FIELD_RULE_TARGET_ABSENT, {
|
|
87
|
+
target_field: actions.target_field,
|
|
88
|
+
ctUid: schema.uid,
|
|
89
|
+
}), 'error');
|
|
90
|
+
this.addMissingReferences(actions);
|
|
91
|
+
}
|
|
92
|
+
this.log((0, messages_1.$t)(messages_1.auditMsg.FIELD_RULE_TARGET_SCAN_MESSAGE, { num: count.toString(), ctUid: schema.uid }), 'info');
|
|
93
|
+
});
|
|
94
|
+
fr.conditions.forEach((actions) => {
|
|
95
|
+
if (!this.schemaMap.includes(actions.operand_field)) {
|
|
96
|
+
this.addMissingReferences(actions);
|
|
97
|
+
this.log((0, messages_1.$t)(messages_1.auditMsg.FIELD_RULE_CONDITION_ABSENT, { condition_field: actions.operand_field }), 'error');
|
|
98
|
+
}
|
|
99
|
+
this.log((0, messages_1.$t)(messages_1.auditMsg.FIELD_RULE_CONDITION_SCAN_MESSAGE, { num: count.toString(), ctUid: schema.uid }), 'info');
|
|
100
|
+
});
|
|
101
|
+
count = count + 1;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
fixFieldRules(schema) {
|
|
106
|
+
if (!Array.isArray(schema.field_rules))
|
|
107
|
+
return;
|
|
108
|
+
schema.field_rules = schema.field_rules
|
|
109
|
+
.map((fr, index) => {
|
|
110
|
+
var _a, _b, _c, _d;
|
|
111
|
+
const validActions = (_b = (_a = fr.actions) === null || _a === void 0 ? void 0 : _a.filter(action => {
|
|
112
|
+
const isValid = this.schemaMap.includes(action.target_field);
|
|
113
|
+
const logMsg = isValid
|
|
114
|
+
? messages_1.auditMsg.FIELD_RULE_TARGET_SCAN_MESSAGE
|
|
115
|
+
: messages_1.auditMsg.FIELD_RULE_TARGET_ABSENT;
|
|
116
|
+
this.log((0, messages_1.$t)(logMsg, Object.assign({ num: index.toString(), ctUid: schema.uid }, (action.target_field && { target_field: action.target_field }))), isValid ? 'info' : 'error');
|
|
117
|
+
if (!isValid) {
|
|
118
|
+
this.addMissingReferences(action, 'Fixed');
|
|
119
|
+
this.log((0, messages_1.$t)(messages_1.auditFixMsg.FIELD_RULE_FIX_MESSAGE, {
|
|
120
|
+
num: index.toString(),
|
|
121
|
+
ctUid: schema.uid
|
|
122
|
+
}), 'info');
|
|
123
|
+
}
|
|
124
|
+
return isValid;
|
|
125
|
+
})) !== null && _b !== void 0 ? _b : [];
|
|
126
|
+
const validConditions = (_d = (_c = fr.conditions) === null || _c === void 0 ? void 0 : _c.filter(condition => {
|
|
127
|
+
const isValid = this.schemaMap.includes(condition.operand_field);
|
|
128
|
+
const logMsg = isValid
|
|
129
|
+
? messages_1.auditMsg.FIELD_RULE_CONDITION_SCAN_MESSAGE
|
|
130
|
+
: messages_1.auditMsg.FIELD_RULE_CONDITION_ABSENT;
|
|
131
|
+
this.log((0, messages_1.$t)(logMsg, Object.assign({ num: index.toString(), ctUid: schema.uid }, (condition.operand_field && { condition_field: condition.operand_field }))), isValid ? 'info' : 'error');
|
|
132
|
+
if (!isValid) {
|
|
133
|
+
this.addMissingReferences(condition, 'Fixed');
|
|
134
|
+
this.log((0, messages_1.$t)(messages_1.auditFixMsg.FIELD_RULE_FIX_MESSAGE, {
|
|
135
|
+
num: index.toString(),
|
|
136
|
+
ctUid: schema.uid
|
|
137
|
+
}), 'info');
|
|
138
|
+
}
|
|
139
|
+
return isValid;
|
|
140
|
+
})) !== null && _d !== void 0 ? _d : [];
|
|
141
|
+
return (validActions.length && validConditions.length) ? Object.assign(Object.assign({}, fr), { actions: validActions, conditions: validConditions }) : null;
|
|
142
|
+
})
|
|
143
|
+
.filter(Boolean);
|
|
144
|
+
}
|
|
145
|
+
addMissingReferences(actions, fixStatus) {
|
|
146
|
+
if (fixStatus) {
|
|
147
|
+
this.missingRefs[this.currentUid].push({
|
|
148
|
+
ctUid: this.currentUid,
|
|
149
|
+
action: actions,
|
|
150
|
+
fixStatus: 'Fixed',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.missingRefs[this.currentUid].push({ action: actions, ctUid: this.currentUid });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* @method prerequisiteData
|
|
159
|
+
* The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
|
|
160
|
+
* app data, and stores them in the `extensions` array.
|
|
161
|
+
*/
|
|
162
|
+
async prerequisiteData() {
|
|
163
|
+
var _a;
|
|
164
|
+
const extensionPath = (0, path_1.resolve)(this.config.basePath, 'extensions', 'extensions.json');
|
|
165
|
+
const marketplacePath = (0, path_1.resolve)(this.config.basePath, 'marketplace_apps', 'marketplace_apps.json');
|
|
166
|
+
if ((0, fs_1.existsSync)(extensionPath)) {
|
|
167
|
+
try {
|
|
168
|
+
this.extensions = Object.keys(JSON.parse((0, fs_1.readFileSync)(extensionPath, 'utf8')));
|
|
169
|
+
}
|
|
170
|
+
catch (error) { }
|
|
171
|
+
}
|
|
172
|
+
if ((0, fs_1.existsSync)(marketplacePath)) {
|
|
173
|
+
try {
|
|
174
|
+
const marketplaceApps = JSON.parse((0, fs_1.readFileSync)(marketplacePath, 'utf8'));
|
|
175
|
+
for (const app of marketplaceApps) {
|
|
176
|
+
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);
|
|
177
|
+
this.extensions.push(...metaData);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) { }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The function checks if it can write the fix content to a file and if so, it writes the content as
|
|
185
|
+
* JSON to the specified file path.
|
|
186
|
+
*/
|
|
187
|
+
async writeFixContent() {
|
|
188
|
+
var _a, _b;
|
|
189
|
+
let canWrite = true;
|
|
190
|
+
if (this.fix) {
|
|
191
|
+
if (!this.config.flags['copy-dir'] && !((_a = this.config.flags['external-config']) === null || _a === void 0 ? void 0 : _a.skipConfirm)) {
|
|
192
|
+
canWrite = (_b = this.config.flags.yes) !== null && _b !== void 0 ? _b : (await cli_utilities_1.cliux.confirm(messages_1.commonMsg.FIX_CONFIRMATION));
|
|
193
|
+
}
|
|
194
|
+
if (canWrite) {
|
|
195
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(this.folderPath, this.config.moduleConfig[this.moduleName].fileName), JSON.stringify(this.schema));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async lookForReference(tree, field, parent = null) {
|
|
200
|
+
var _a, _b;
|
|
201
|
+
const fixTypes = (_a = this.config.flags['fix-only']) !== null && _a !== void 0 ? _a : this.config['fix-fields'];
|
|
202
|
+
for (let child of (_b = field.schema) !== null && _b !== void 0 ? _b : []) {
|
|
203
|
+
if (parent !== null) {
|
|
204
|
+
this.schemaMap.push(`${parent}.${child === null || child === void 0 ? void 0 : child.uid}`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
this.schemaMap.push(child.uid);
|
|
208
|
+
}
|
|
209
|
+
if (!fixTypes.includes(child.data_type) && child.data_type !== 'json')
|
|
210
|
+
continue;
|
|
211
|
+
switch (child.data_type) {
|
|
212
|
+
case 'global_field':
|
|
213
|
+
await this.validateGlobalField([...tree, { uid: child.uid, name: child.display_name }], child, parent ? `${parent}.${child === null || child === void 0 ? void 0 : child.uid}` : child === null || child === void 0 ? void 0 : child.uid);
|
|
214
|
+
break;
|
|
215
|
+
case 'blocks':
|
|
216
|
+
await this.validateModularBlocksField([...tree, { uid: child.uid, name: child.display_name }], child, parent ? `${parent}.${child === null || child === void 0 ? void 0 : child.uid}` : child === null || child === void 0 ? void 0 : child.uid);
|
|
217
|
+
break;
|
|
218
|
+
case 'group':
|
|
219
|
+
await this.validateGroupField([...tree, { uid: child.uid, name: child.display_name }], child, parent ? `${parent}.${child === null || child === void 0 ? void 0 : child.uid}` : child === null || child === void 0 ? void 0 : child.uid);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async validateGlobalField(tree, field, parent) {
|
|
225
|
+
await this.lookForReference(tree, field, parent);
|
|
226
|
+
}
|
|
227
|
+
async validateModularBlocksField(tree, field, parent) {
|
|
228
|
+
const { blocks } = field;
|
|
229
|
+
for (const block of blocks) {
|
|
230
|
+
const { uid, title } = block;
|
|
231
|
+
await this.lookForReference([...tree, { uid, name: title }], block, parent + '.' + block.uid);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async validateGroupField(tree, field, parent) {
|
|
235
|
+
// NOTE Any Group Field related logic can be added here (Ex data serialization or picking any metadata for report etc.,)
|
|
236
|
+
await this.lookForReference(tree, field, parent);
|
|
237
|
+
}
|
|
238
|
+
async prepareEntryMetaData() {
|
|
239
|
+
var _a;
|
|
240
|
+
this.log(messages_1.auditMsg.PREPARING_ENTRY_METADATA, 'info');
|
|
241
|
+
const localesFolderPath = (0, path_1.resolve)(this.config.basePath, this.config.moduleConfig.locales.dirName);
|
|
242
|
+
const localesPath = (0, path_1.join)(localesFolderPath, this.config.moduleConfig.locales.fileName);
|
|
243
|
+
const masterLocalesPath = (0, path_1.join)(localesFolderPath, 'master-locale.json');
|
|
244
|
+
this.locales = (0, fs_1.existsSync)(masterLocalesPath) ? (0, lodash_1.values)(JSON.parse((0, fs_1.readFileSync)(masterLocalesPath, 'utf8'))) : [];
|
|
245
|
+
if ((0, fs_1.existsSync)(localesPath)) {
|
|
246
|
+
this.locales.push(...(0, lodash_1.values)(JSON.parse((0, fs_1.readFileSync)(localesPath, 'utf8'))));
|
|
247
|
+
}
|
|
248
|
+
const entriesFolderPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.config.basePath), 'entries');
|
|
249
|
+
for (const { code } of this.locales) {
|
|
250
|
+
for (const { uid } of (_a = this.ctSchema) !== null && _a !== void 0 ? _a : []) {
|
|
251
|
+
let basePath = (0, path_1.join)(entriesFolderPath, uid, code);
|
|
252
|
+
let fsUtility = new cli_utilities_1.FsUtility({ basePath, indexFileName: 'index.json' });
|
|
253
|
+
let indexer = fsUtility.indexFileContent;
|
|
254
|
+
for (const _ in indexer) {
|
|
255
|
+
const entries = (await fsUtility.readChunkFiles.next());
|
|
256
|
+
for (const entryUid in entries) {
|
|
257
|
+
let { title } = entries[entryUid];
|
|
258
|
+
this.entryMetaData.push({ uid: entryUid, title, ctUid: uid });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
exports.default = FieldRule;
|
|
@@ -6,7 +6,7 @@ export default class GlobalField extends ContentType {
|
|
|
6
6
|
* references.
|
|
7
7
|
* @returns the value of the variable `missingRefs`.
|
|
8
8
|
*/
|
|
9
|
-
run(returnFixSchema?: boolean): Promise<Record<string, any
|
|
9
|
+
run(returnFixSchema?: boolean): Promise<Record<string, any> | import("../types").ContentTypeStruct[]>;
|
|
10
10
|
/**
|
|
11
11
|
* The function validates a field containing modular blocks by traversing each block and checking for
|
|
12
12
|
* references in a given tree.
|