Skip to content

Commit 5b2afd0

Browse files
authored
Merge pull request #1313 from contentstack/staging
Sprint 52 release
2 parents 3ffcdd3 + 63add36 commit 5b2afd0

73 files changed

Lines changed: 2062 additions & 1024 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package-lock.json

Lines changed: 686 additions & 169 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/contentstack-audit/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ $ npm install -g @contentstack/cli-audit
1919
$ csdx COMMAND
2020
running command...
2121
$ csdx (--version|-v)
22-
@contentstack/cli-audit/1.3.5 darwin-arm64 node-v20.8.0
22+
@contentstack/cli-audit/1.4.0 darwin-arm64 node-v20.8.0
2323
$ csdx --help [COMMAND]
2424
USAGE
2525
$ csdx COMMAND
@@ -99,14 +99,14 @@ Perform audits and fix possible errors in the exported Contentstack data.
9999
USAGE
100100
$ csdx audit:fix [-c <value>] [-d <value>] [--report-path <value>] [--modules
101101
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
103103
<value>] [--csv | --no-truncate]
104104
105105
FLAGS
106106
--copy-dir Create backup from the original data.
107107
--copy-path=<value> Provide the path to backup the copied data
108108
--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>
110110
--modules=<option>... Provide the list of modules to be audited
111111
<options: content-types|global-fields|entries>
112112
--report-path=<value> Path to store the audit reports
@@ -198,14 +198,14 @@ Perform audits and fix possible errors in the exported Contentstack data.
198198
USAGE
199199
$ csdx cm:stacks:audit:fix [-c <value>] [-d <value>] [--report-path <value>] [--modules
200200
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
202202
<value>] [--csv | --no-truncate]
203203
204204
FLAGS
205205
--copy-dir Create backup from the original data.
206206
--copy-path=<value> Provide the path to backup the copied data
207207
--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>
209209
--modules=<option>... Provide the list of modules to be audited
210210
<options: content-types|global-fields|entries>
211211
--report-path=<value> Path to store the audit reports

packages/contentstack-audit/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/cli-audit",
3-
"version": "1.3.5",
3+
"version": "1.4.0",
44
"description": "Contentstack audit plugin",
55
"author": "Contentstack CLI",
66
"homepage": "https://github.com/contentstack/cli",
@@ -19,7 +19,7 @@
1919
],
2020
"dependencies": {
2121
"@contentstack/cli-command": "~1.2.16",
22-
"@contentstack/cli-utilities": "~1.5.11",
22+
"@contentstack/cli-utilities": "~1.5.12",
2323
"@oclif/plugin-help": "^5",
2424
"@oclif/plugin-plugins": "^4.1.9",
2525
"chalk": "^4.1.2",

packages/contentstack-audit/src/config/index.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const config = {
33
skipRefs: ['sys_assets'],
44
skipFieldTypes: ['taxonomy'],
55
modules: ['content-types', 'global-fields', 'entries'],
6-
'fix-fields': ['reference', 'global_field', 'json:rte', 'json:custom-field', 'blocks', 'group'],
6+
'fix-fields': ['reference', 'global_field', 'json:rte', 'json:extension', 'blocks', 'group'],
77
moduleConfig: {
88
'content-types': {
99
name: 'content type',
@@ -26,6 +26,24 @@ const config = {
2626
fileName: 'locales.json',
2727
},
2828
},
29+
entries: {
30+
systemKeys: [
31+
'uid',
32+
'ACL',
33+
'tags',
34+
'locale',
35+
'_version',
36+
'_metadata',
37+
'published',
38+
'created_at',
39+
'updated_at',
40+
'created_by',
41+
'updated_by',
42+
'_in_progress',
43+
'_restore_status',
44+
'publish_details',
45+
],
46+
},
2947
};
3048

3149
export default config;

packages/contentstack-audit/src/modules/content-types.ts

Lines changed: 133 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import map from 'lodash/map';
12
import find from 'lodash/find';
23
import isEmpty from 'lodash/isEmpty';
34
import { join, resolve } from 'path';
4-
import { existsSync, writeFileSync } from 'fs';
5+
import { existsSync, readFileSync, writeFileSync } from 'fs';
6+
57
import { ux } from '@contentstack/cli-utilities';
68

79
import {
@@ -19,9 +21,11 @@ import {
1921
ReferenceFieldDataType,
2022
ContentTypeSchemaType,
2123
GlobalFieldSchemaTypes,
24+
ExtensionOrAppFieldDataType,
2225
} from '../types';
2326
import auditConfig from '../config';
2427
import { $t, auditFixMsg, auditMsg, commonMsg } from '../messages';
28+
import { MarketplaceAppsInstallationData } from '../types/extension';
2529

2630
/* The `ContentType` class is responsible for scanning content types, looking for references, and
2731
generating a report in JSON and CSV formats. */
@@ -33,6 +37,7 @@ export default class ContentType {
3337
public folderPath: string;
3438
public currentUid!: string;
3539
public currentTitle!: string;
40+
public extensions: string[] = [];
3641
public inMemoryFix: boolean = false;
3742
public gfSchema: ContentTypeStruct[];
3843
public ctSchema: ContentTypeStruct[];
@@ -67,6 +72,8 @@ export default class ContentType {
6772

6873
this.schema = this.moduleName === 'content-types' ? this.ctSchema : this.gfSchema;
6974

75+
await this.prerequisiteData();
76+
7077
for (const schema of this.schema ?? []) {
7178
this.currentUid = schema.uid;
7279
this.currentTitle = schema.title;
@@ -96,6 +103,35 @@ export default class ContentType {
96103
return this.missingRefs;
97104
}
98105

106+
/**
107+
* @method prerequisiteData
108+
* The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
109+
* app data, and stores them in the `extensions` array.
110+
*/
111+
async prerequisiteData() {
112+
const extensionPath = resolve(this.config.basePath, 'extensions', 'extensions.json');
113+
const marketplacePath = resolve(this.config.basePath, 'marketplace_apps', 'marketplace_apps.json');
114+
115+
if (existsSync(extensionPath)) {
116+
try {
117+
this.extensions = Object.keys(JSON.parse(readFileSync(extensionPath, 'utf8')));
118+
} catch (error) {}
119+
}
120+
121+
if (existsSync(marketplacePath)) {
122+
try {
123+
const marketplaceApps: MarketplaceAppsInstallationData[] = JSON.parse(readFileSync(marketplacePath, 'utf8'));
124+
125+
for (const app of marketplaceApps) {
126+
const metaData = map(map(app?.ui_location?.locations, 'meta').flat(), 'extension_uid').filter(
127+
(val) => val,
128+
) as string[];
129+
this.extensions.push(...metaData);
130+
}
131+
} catch (error) {}
132+
}
133+
}
134+
99135
/**
100136
* The function checks if it can write the fix content to a file and if so, it writes the content as
101137
* JSON to the specified file path.
@@ -157,10 +193,16 @@ export default class ContentType {
157193
);
158194
break;
159195
case 'json':
160-
if (child.field_metadata.extension) {
161-
if (!fixTypes.includes('json:custom-field')) continue;
196+
if ('extension' in child.field_metadata && child.field_metadata.extension) {
197+
if (!fixTypes.includes('json:extension')) continue;
162198
// NOTE Custom field type
163-
} else if (child.field_metadata.allow_json_rte) {
199+
this.missingRefs[this.currentUid].push(
200+
...this.validateExtensionAndAppField(
201+
[...tree, { uid: child.uid, name: child.display_name }],
202+
child as ExtensionOrAppFieldDataType,
203+
),
204+
);
205+
} else if ('allow_json_rte' in child.field_metadata && child.field_metadata.allow_json_rte) {
164206
if (!fixTypes.includes('json:rte')) continue;
165207
// NOTE JSON RTE field type
166208
this.missingRefs[this.currentUid].push(
@@ -199,6 +241,45 @@ export default class ContentType {
199241
return this.validateReferenceToValues(tree, field);
200242
}
201243

244+
/**
245+
* The function `validateExtensionAndAppsField` checks if a given field has a valid extension or app
246+
* reference and returns any missing references.
247+
* @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
248+
* @param {ExtensionOrAppFieldDataType} field - The `field` parameter is of type `ExtensionOrAppFieldDataType`.
249+
* @returns The function `validateExtensionAndAppsField` returns an array of `RefErrorReturnType`
250+
* objects.
251+
*/
252+
validateExtensionAndAppField(
253+
tree: Record<string, unknown>[],
254+
field: ExtensionOrAppFieldDataType,
255+
): RefErrorReturnType[] {
256+
if (this.fix) return [];
257+
258+
const missingRefs = [];
259+
let { uid, extension_uid, display_name, data_type } = field;
260+
261+
if (!this.extensions.includes(extension_uid)) {
262+
missingRefs.push({ uid, extension_uid, type: 'Extension or Apps' } as any);
263+
}
264+
265+
return missingRefs.length
266+
? [
267+
{
268+
tree,
269+
data_type,
270+
missingRefs,
271+
display_name,
272+
ct_uid: this.currentUid,
273+
name: this.currentTitle,
274+
treeStr: tree
275+
.map(({ name }) => name)
276+
.filter((val) => val)
277+
.join(' ➜ '),
278+
},
279+
]
280+
: [];
281+
}
282+
202283
/**
203284
* The function "validateGlobalField" asynchronously validates a global field by looking for a
204285
* reference in a tree data structure.
@@ -298,7 +379,9 @@ export default class ContentType {
298379

299380
for (const reference of reference_to ?? []) {
300381
// NOTE Can skip specific references keys (Ex, system defined keys can be skipped)
301-
if (this.config.skipRefs.includes(reference)) continue;
382+
if (this.config.skipRefs.includes(reference)) {
383+
continue;
384+
}
302385

303386
const refExist = find(this.ctSchema, { uid: reference });
304387

@@ -350,14 +433,13 @@ export default class ContentType {
350433
case 'json':
351434
case 'reference':
352435
if (data_type === 'json') {
353-
if (field.field_metadata.extension) {
436+
if ('extension' in field.field_metadata && field.field_metadata.extension) {
354437
// NOTE Custom field type
355-
if (!fixTypes.includes('json:custom-field')) return field;
438+
if (!fixTypes.includes('json:extension')) return field;
356439

357440
// NOTE Fix logic
358-
359-
return field;
360-
} else if (field.field_metadata.allow_json_rte) {
441+
return this.fixMissingExtensionOrApp(tree, field as ExtensionOrAppFieldDataType);
442+
} else if ('allow_json_rte' in field.field_metadata && field.field_metadata.allow_json_rte) {
361443
if (!fixTypes.includes('json:rte')) return field;
362444

363445
return this.fixMissingReferences(tree, field as JsonRTEFieldDataType);
@@ -456,10 +538,10 @@ export default class ContentType {
456538
const refErrorObj = {
457539
tree,
458540
display_name,
459-
fixStatus: 'Fixed',
460-
missingRefs: [reference_to],
461541
ct_uid: this.currentUid,
462542
name: this.currentTitle,
543+
missingRefs: [reference_to],
544+
fixStatus: this.fix ? 'Fixed' : undefined,
463545
treeStr: tree.map(({ name }) => name).join(' ➜ '),
464546
};
465547

@@ -499,6 +581,41 @@ export default class ContentType {
499581
.filter((val) => val) as ModularBlockType[];
500582
}
501583

584+
/**
585+
* The function checks for missing extension or app references in a given tree and fixes them if the
586+
* fix flag is enabled.
587+
* @param {Record<string, unknown>[]} tree - An array of objects representing a tree structure.
588+
* @param {ExtensionOrAppFieldDataType} field - The `field` parameter is of type
589+
* `ExtensionOrAppFieldDataType`.
590+
* @returns If the `fix` flag is true and there are missing references (`missingRefs` is not empty),
591+
* then `null` is returned. Otherwise, the `field` parameter is returned.
592+
*/
593+
fixMissingExtensionOrApp(tree: Record<string, unknown>[], field: ExtensionOrAppFieldDataType) {
594+
const missingRefs: string[] = [];
595+
const { uid, extension_uid, data_type, display_name } = field;
596+
597+
if (!this.extensions.includes(extension_uid)) {
598+
missingRefs.push({ uid, extension_uid, type: 'Extension or Apps' } as any);
599+
}
600+
601+
if (this.fix && !isEmpty(missingRefs)) {
602+
this.missingRefs[this.currentUid].push({
603+
tree,
604+
data_type,
605+
missingRefs,
606+
display_name,
607+
fixStatus: 'Fixed',
608+
ct_uid: this.currentUid,
609+
name: this.currentTitle,
610+
treeStr: tree.map(({ name }) => name).join(' ➜ '),
611+
});
612+
613+
return null
614+
}
615+
616+
return field;
617+
}
618+
502619
/**
503620
* The function `fixMissingReferences` checks for missing references in a given tree and field, and
504621
* attempts to fix them by removing the missing references from the field's `reference_to` array.
@@ -515,7 +632,9 @@ export default class ContentType {
515632

516633
for (const reference of reference_to ?? []) {
517634
// NOTE Can skip specific references keys (Ex, system defined keys can be skipped)
518-
if (this.config.skipRefs.includes(reference)) continue;
635+
if (this.config.skipRefs.includes(reference)) {
636+
continue;
637+
}
519638

520639
const refExist = find(this.ctSchema, { uid: reference });
521640

@@ -557,6 +676,7 @@ export default class ContentType {
557676
*/
558677
fixGroupField(tree: Record<string, unknown>[], field: GroupFieldDataType) {
559678
const { data_type, display_name } = field;
679+
560680
field.schema = this.runFixOnSchema(tree, field.schema as ContentTypeSchemaType[]);
561681

562682
if (isEmpty(field.schema)) {

0 commit comments

Comments
 (0)