Skip to content

Commit 87bffbe

Browse files
committed
Feat: Added Check for publish detials for Assets and Entries
1 parent b92216f commit 87bffbe

File tree

12 files changed

+844
-781
lines changed

12 files changed

+844
-781
lines changed

package-lock.json

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

packages/contentstack-audit/src/audit-base-command.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import config from './config';
1212
import { print } from './util/log';
1313
import { auditMsg } from './messages';
1414
import { BaseCommand } from './base-command';
15-
import { Entries, GlobalField, ContentType, Extensions, Workflows } from './modules';
15+
import { Entries, GlobalField, ContentType, Extensions, Workflows, Assets } from './modules';
1616
import {
1717
CommandNames,
1818
ContentTypeStruct,
@@ -58,7 +58,9 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
5858
missingSelectFeild,
5959
missingMandatoryFields,
6060
missingTitleFields,
61-
missingRefInCustomRoles
61+
missingRefInCustomRoles,
62+
missingEnvLocalesInAssets,
63+
missingEnvLocalesInEntries
6264
} = await this.scanAndFix();
6365

6466
this.showOutputOnScreen([
@@ -76,6 +78,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
7678
{ module: 'Entries Title Field', missingRefs: missingTitleFields },
7779
]);
7880
this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Custom Roles', missingRefs: missingRefInCustomRoles }]);
81+
this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Assets', missingRefs: missingEnvLocalesInAssets }]);
82+
this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Entries Missing Locale and Environments', missingRefs: missingEnvLocalesInEntries }])
7983
if (
8084
!isEmpty(missingCtRefs) ||
8185
!isEmpty(missingGfRefs) ||
@@ -84,7 +88,9 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
8488
!isEmpty(missingCtRefsInExtensions) ||
8589
!isEmpty(missingSelectFeild) ||
8690
!isEmpty(missingTitleFields) ||
87-
!isEmpty(missingRefInCustomRoles)
91+
!isEmpty(missingRefInCustomRoles) ||
92+
!isEmpty(missingEnvLocalesInAssets) ||
93+
!isEmpty(missingEnvLocalesInEntries)
8894
) {
8995
if (this.currentCommand === 'cm:stacks:audit') {
9096
this.log(this.$t(auditMsg.FINAL_REPORT_PATH, { path: this.sharedConfig.reportPath }), 'warn');
@@ -133,7 +139,9 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
133139
missingEntry,
134140
missingMandatoryFields,
135141
missingTitleFields,
136-
missingRefInCustomRoles;
142+
missingRefInCustomRoles,
143+
missingEnvLocalesInAssets,
144+
missingEnvLocalesInEntries;
137145

138146
for (const module of this.sharedConfig.flags.modules || this.sharedConfig.modules) {
139147
print([
@@ -153,6 +161,10 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
153161
fix: this.currentCommand === 'cm:stacks:audit:fix',
154162
};
155163
switch (module) {
164+
case 'assets':
165+
missingEnvLocalesInAssets = await new Assets(cloneDeep(constructorParam)).run();
166+
await this.prepareReport(module, missingEnvLocalesInAssets);
167+
break;
156168
case 'content-types':
157169
missingCtRefs = await new ContentType(cloneDeep(constructorParam)).run();
158170
await this.prepareReport(module, missingCtRefs);
@@ -167,6 +179,7 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
167179
missingSelectFeild = missingEntry.missingSelectFeild ?? {};
168180
missingMandatoryFields = missingEntry.missingMandatoryFields ?? {};
169181
missingTitleFields = missingEntry.missingTitleFields ?? {};
182+
missingEnvLocalesInEntries = missingEntry.missingEnvLocale??{};
170183
await this.prepareReport(module, missingEntryRefs);
171184

172185
await this.prepareReport(`Entries_Select_feild`, missingSelectFeild);
@@ -175,6 +188,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
175188

176189
await this.prepareReport('Entries_Title_feild', missingTitleFields);
177190

191+
await this.prepareReport('Entry_Missing_Locale_and_Env', missingEnvLocalesInEntries);
192+
178193
break;
179194
case 'workflows':
180195
missingCtRefsInWorkflow = await new Workflows({
@@ -220,6 +235,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
220235
missingMandatoryFields,
221236
missingTitleFields,
222237
missingRefInCustomRoles,
238+
missingEnvLocalesInAssets,
239+
missingEnvLocalesInEntries
223240
};
224241
}
225242

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const config = {
22
showTerminalOutput: true,
33
skipRefs: ['sys_assets'],
44
skipFieldTypes: ['taxonomy', 'group'],
5-
modules: ['content-types', 'global-fields', 'entries', 'extensions', 'workflows', 'custom-roles'],
5+
modules: ['content-types', 'global-fields', 'entries', 'extensions', 'workflows', 'custom-roles', 'assets'],
66
'fix-fields': ['reference', 'global_field', 'json:rte', 'json:extension', 'blocks', 'group', 'content_types'],
77
moduleConfig: {
88
'content-types': {
@@ -40,6 +40,16 @@ const config = {
4040
dirName: 'custom-roles',
4141
fileName: 'custom-roles.json',
4242
},
43+
'assets': {
44+
name: 'assets',
45+
dirName: 'assets',
46+
fileName: 'assets.json',
47+
},
48+
'environments': {
49+
name: 'environments',
50+
dirName: 'environments',
51+
fileName: 'environments.json',
52+
}
4353
},
4454
entries: {
4555
systemKeys: [
@@ -76,13 +86,18 @@ const config = {
7686
'min_instance',
7787
'missingFieldUid',
7888
'isPublished',
89+
'locale',
90+
'environment',
91+
'ctUid',
92+
'ctLocale'
7993
],
8094
ReportTitleForEntries: {
8195
Entries_Select_feild: 'Entries_Select_feild',
8296
Entries_Mandatory_feild: 'Entries_Mandatory_feild',
8397
Entries_Title_feild: 'Entries_Title_feild',
98+
Entry_Missing_Locale_and_Env: 'Entry_Missing_Locale_and_Env'
8499
},
85-
feild_level_modules: ['Entries_Title_feild', 'Entries_Mandatory_feild', 'Entries_Select_feild'],
100+
feild_level_modules: ['Entries_Title_feild', 'Entries_Mandatory_feild', 'Entries_Select_feild', 'Entry_Missing_Locale_and_Env'],
86101
};
87102

88103
export default config;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ const auditMsg = {
3737
AUDIT_CMD_DESCRIPTION: 'Perform audits and find possible errors in the exported Contentstack data',
3838
SCAN_WF_SUCCESS_MSG: 'Successfully completed the scanning of workflow with UID {uid} and name {name}.',
3939
SCAN_CR_SUCCESS_MSG: 'Successfully completed the scanning of custom role with UID {uid} and name {name}.',
40+
SCAN_ASSET_SUCCESS_MSG: 'Successfully completed the scanning of Asset with UID {uid}.',
41+
SCAN_ASSET_WARN_MSG: 'The locale {locale} or environment {environment} are not present for asset with uid {uid}',
42+
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'
4043
};
4144

4245
const auditFixMsg = {
@@ -49,6 +52,7 @@ const auditFixMsg = {
4952
WF_FIX_MSG: 'Successfully removed the workflow {uid} named {name}.',
5053
ENTRY_MANDATORY_FIELD_FIX: `Removing the publish details from the entry with UID '{uid}' in Locale '{locale}'...`,
5154
ENTRY_SELECT_FIELD_FIX: `Adding the value '{value}' in the select field of entry UID '{uid}'...`,
55+
ASSET_FIX: 'Fixed publish detials for Asset with UID {uid}',
5256
};
5357

5458
const messages: typeof errors &
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { join, resolve } from 'path';
2+
import { existsSync, readFileSync, writeFileSync } from 'fs';
3+
4+
import { FsUtility, sanitizePath, ux } from '@contentstack/cli-utilities';
5+
6+
import {
7+
LogFn,
8+
ConfigType,
9+
ContentTypeStruct,
10+
CtConstructorParam,
11+
ModuleConstructorParam,
12+
EntryStruct,
13+
} from '../types';
14+
import auditConfig from '../config';
15+
import { $t, auditFixMsg, auditMsg, commonMsg } from '../messages';
16+
import values from 'lodash/values';
17+
import { keys } from 'lodash';
18+
19+
/* The `ContentType` class is responsible for scanning content types, looking for references, and
20+
generating a report in JSON and CSV formats. */
21+
export default class Assets {
22+
public log: LogFn;
23+
protected fix: boolean;
24+
public fileName: string;
25+
public config: ConfigType;
26+
public folderPath: string;
27+
public currentUid!: string;
28+
public currentTitle!: string;
29+
public assets!: Record<string, any>;
30+
public locales: string[] = [];
31+
public environments: string[] = [];
32+
protected schema: ContentTypeStruct[] = [];
33+
protected missingEnvLocales: Record<string, any> = {};
34+
public moduleName: keyof typeof auditConfig.moduleConfig;
35+
36+
constructor({ log, fix, config, moduleName }: ModuleConstructorParam & CtConstructorParam) {
37+
this.log = log;
38+
this.config = config;
39+
this.fix = fix ?? false;
40+
this.moduleName = this.validateModules(moduleName!, this.config.moduleConfig);
41+
this.fileName = config.moduleConfig[this.moduleName].fileName;
42+
this.folderPath = resolve(
43+
sanitizePath(config.basePath),
44+
sanitizePath(config.moduleConfig[this.moduleName].dirName),
45+
);
46+
}
47+
48+
validateModules(
49+
moduleName: keyof typeof auditConfig.moduleConfig,
50+
moduleConfig: Record<string, unknown>,
51+
): keyof typeof auditConfig.moduleConfig {
52+
if (Object.keys(moduleConfig).includes(moduleName)) {
53+
return moduleName;
54+
}
55+
return 'assets';
56+
}
57+
/**
58+
* The `run` function checks if a folder path exists, sets the schema based on the module name,
59+
* iterates over the schema and looks for references, and returns a list of missing references.
60+
* @returns the `missingEnvLocales` object.
61+
*/
62+
async run(returnFixSchema = false) {
63+
if (!existsSync(this.folderPath)) {
64+
this.log(`Skipping ${this.moduleName} audit`, 'warn');
65+
this.log($t(auditMsg.NOT_VALID_PATH, { path: this.folderPath }), { color: 'yellow' });
66+
return returnFixSchema ? [] : {};
67+
}
68+
69+
await this.prerequisiteData();
70+
await this.lookForReference();
71+
72+
if (returnFixSchema) {
73+
return this.schema;
74+
}
75+
76+
for (let propName in this.missingEnvLocales) {
77+
if (!this.missingEnvLocales[propName].length) {
78+
delete this.missingEnvLocales[propName];
79+
}
80+
}
81+
82+
return this.missingEnvLocales;
83+
}
84+
85+
/**
86+
* @method prerequisiteData
87+
* The `prerequisiteData` function reads and parses JSON files to retrieve extension and marketplace
88+
* app data, and stores them in the `extensions` array.
89+
*/
90+
async prerequisiteData() {
91+
this.log(auditMsg.PREPARING_ENTRY_METADATA, 'info');
92+
93+
const localesFolderPath = resolve(this.config.basePath, this.config.moduleConfig.locales.dirName);
94+
const localesPath = join(localesFolderPath, this.config.moduleConfig.locales.fileName);
95+
const masterLocalesPath = join(localesFolderPath, 'master-locale.json');
96+
this.locales = existsSync(masterLocalesPath) ? values(JSON.parse(readFileSync(masterLocalesPath, 'utf8'))) : [];
97+
98+
if (existsSync(localesPath)) {
99+
this.locales.push(...values(JSON.parse(readFileSync(localesPath, 'utf8'))));
100+
}
101+
this.locales = this.locales.map((locale: any) => locale.code);
102+
const environmentPath = resolve(
103+
this.config.basePath,
104+
this.config.moduleConfig.environments.dirName,
105+
this.config.moduleConfig.environments.fileName,
106+
);
107+
this.environments = existsSync(environmentPath) ? keys(JSON.parse(readFileSync(environmentPath, 'utf8'))) : [];
108+
console.log(JSON.stringify(this.environments), JSON.stringify(this.locales));
109+
}
110+
111+
/**
112+
* The function checks if it can write the fix content to a file and if so, it writes the content as
113+
* JSON to the specified file path.
114+
*/
115+
async writeFixContent(filePath: string, schema: Record<string, EntryStruct>) {
116+
let canWrite = true;
117+
118+
if (this.fix) {
119+
if (!this.config.flags['copy-dir'] && !this.config.flags['external-config']?.skipConfirm) {
120+
canWrite = this.config.flags.yes || (await ux.confirm(commonMsg.FIX_CONFIRMATION));
121+
}
122+
123+
if (canWrite) {
124+
writeFileSync(filePath, JSON.stringify(schema));
125+
}
126+
}
127+
}
128+
129+
/**
130+
* This function traverse over the publish detials of the assets and remove the publish details where the locale or environment does not exist
131+
*/
132+
async lookForReference(): Promise<void> {
133+
let basePath = join(this.folderPath);
134+
let fsUtility = new FsUtility({ basePath, indexFileName: 'assets.json' });
135+
let indexer = fsUtility.indexFileContent;
136+
for (const fileIndex in indexer) {
137+
const assets = (await fsUtility.readChunkFiles.next()) as Record<string, EntryStruct>;
138+
this.assets = assets;
139+
for (const assetUid in assets) {
140+
this.assets[assetUid].publish_details = this.assets[assetUid].publish_details.filter((pd: any) => {
141+
if (this.locales.includes(pd.locale) && this.environments.includes(pd.environment)) {
142+
this.log($t(auditMsg.SCAN_ASSET_SUCCESS_MSG, { uid: assetUid }), { color: 'green' });
143+
return true;
144+
} else {
145+
this.log(
146+
$t(auditMsg.SCAN_ASSET_WARN_MSG, { uid: assetUid, locale: pd.locale, environment: pd.environment }),
147+
{ color: 'yellow' },
148+
);
149+
if (!this.missingEnvLocales[assetUid]) {
150+
this.missingEnvLocales[assetUid] = [{ uid: assetUid, locale: pd.locale, environment: pd.environment }];
151+
} else {
152+
this.missingEnvLocales[assetUid].push([
153+
...this.missingEnvLocales[assetUid],
154+
{ uid: assetUid, locale: pd.locale, environment: pd.environment },
155+
]);
156+
}
157+
this.log($t(auditMsg.SCAN_ASSET_SUCCESS_MSG, { uid: assetUid }), { color: 'green' });
158+
return false;
159+
}
160+
});
161+
if (this.fix) {
162+
this.log($t(auditFixMsg.ASSET_FIX, { uid: assetUid }), { color: 'green' });
163+
await this.writeFixContent(`${basePath}/${indexer[fileIndex]}`, this.assets);
164+
}
165+
}
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)