Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a4d908a
feat: add asset scanning support in import
Jun 5, 2026
25acf49
chore: removed whitespace
naman-contentstack Jun 9, 2026
cbffcfa
Merge pull request #210 from contentstack/feat/DX-8661
naman-contentstack Jun 9, 2026
1b6fb69
feat: add asset scanning support in bulk publish v1
Jun 10, 2026
420c32e
feat: enhance asset publishing with data-dir support for post-import …
Jun 11, 2026
f893ffe
Merge branch 'development' into feat/Asset-Scanning-v1
Jun 12, 2026
8aaeebe
Set maximum scan retries to zero
naman-contentstack Jun 16, 2026
b51e127
fix: minor fixes in bulk publish
Jun 28, 2026
0ca69b8
Merge branch 'development' into feat/Asset-Scanning-v1
naman-contentstack Jul 2, 2026
e5260b7
fix: Update flags in AssetsPublishCommand and add user reminder in Im…
Jul 10, 2026
32ba9c4
fix: Rename 'data-dir' to 'backup-dir' in asset publishing commands a…
Jul 14, 2026
2d575c1
merge development in feat/Asseet-scanning-v1
Jul 14, 2026
1a2732c
Merge branch 'feat/Asset-Scanning-v1' into fix/DX-9308
Jul 14, 2026
fc472c8
Merge pull request #282 from contentstack/fix/DX-9308
naman-contentstack Jul 16, 2026
402a4b2
merged latest changes from dev
Jul 22, 2026
53b9674
added support for orgplan check in export and import
Jul 23, 2026
78da02e
bumped dependencies version
Jul 28, 2026
19c2a36
handled assets in pending or quarantine states export failure
Jul 30, 2026
2188fc2
merged latest changes
Jul 30, 2026
601947f
added audit check and fix for assets in pending or quarantined state
Jul 30, 2026
a35b929
Merge branch 'development' into feat/Asset-Scanning-v1
Aug 3, 2026
5789088
Merge pull request #323 from contentstack/DX-10043
shafeeqd959 Aug 4, 2026
c92fd23
handled assets in not scanned status
Aug 4, 2026
56dd70b
Merge pull request #322 from contentstack/DX-9302
shafeeqd959 Aug 4, 2026
498c6aa
Merge branch 'development' into feat/Asset-Scanning-v1
Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fileignoreconfig:

- filename: pnpm-lock.yaml
checksum: 31e333d6769adbaae042c92ea0930fab168a0e06fc1bda406d49fd1042a7a9c7
version: '1.0'
checksum: 3d37dc1eb3401e49396b5893da28562f81c290d4b778e9276016e02dda9bb908
version: '1.0'
41 changes: 34 additions & 7 deletions packages/contentstack-audit/src/audit-base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
missingFieldRules,
missingMultipleFields,
missingRefsInComposableStudio,
missingScanStatusAssets,
missingAssetRefsInEntries,
} = await this.scanAndFix();

if (this.flags['show-console-output']) {
Expand Down Expand Up @@ -125,6 +127,12 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
{ module: 'Entries Changed Multiple Fields', missingRefs: missingMultipleFields },
]);
this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Studio', missingRefs: missingRefsInComposableStudio }]);
this.showOutputOnScreenWorkflowsAndExtension([
{ module: 'Assets Scan Status', missingRefs: missingScanStatusAssets },
]);
this.showOutputOnScreenWorkflowsAndExtension([
{ module: 'Entries Asset Field', missingRefs: missingAssetRefsInEntries },
]);
}
this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Summary', missingRefs: this.summaryDataToPrint }]);

Expand All @@ -141,7 +149,9 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
!isEmpty(missingEnvLocalesInEntries) ||
!isEmpty(missingFieldRules) ||
!isEmpty(missingMultipleFields) ||
!isEmpty(missingRefsInComposableStudio)
!isEmpty(missingRefsInComposableStudio) ||
!isEmpty(missingScanStatusAssets) ||
!isEmpty(missingAssetRefsInEntries)
) {
if (this.currentCommand === 'cm:stacks:audit') {
log.warn(this.$t(auditMsg.FINAL_REPORT_PATH, { path: this.sharedConfig.reportPath }), this.auditContext);
Expand Down Expand Up @@ -176,7 +186,9 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
!isEmpty(missingEnvLocalesInAssets) ||
!isEmpty(missingEnvLocalesInEntries) ||
!isEmpty(missingFieldRules) ||
!isEmpty(missingRefsInComposableStudio)
!isEmpty(missingRefsInComposableStudio) ||
!isEmpty(missingScanStatusAssets) ||
!isEmpty(missingAssetRefsInEntries)
);
}

Expand Down Expand Up @@ -207,6 +219,7 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
missingTitleFields?: Record<string, any>;
missingEnvLocale?: Record<string, any>;
missingMultipleFields?: Record<string, any>;
missingAssetRefs?: Record<string, any>;
} = {},
missingMandatoryFields,
missingTitleFields,
Expand All @@ -215,6 +228,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
missingEnvLocalesInEntries,
missingFieldRules,
missingMultipleFields,
missingAssetRefsInEntries,
missingScanStatusAssets,
missingRefsInComposableStudio;

const constructorParam: ModuleConstructorParam & CtConstructorParam = {
Expand Down Expand Up @@ -246,16 +261,21 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
constructorParam['moduleName'] = module;

switch (module) {
case 'assets':
case 'assets': {
log.info('Executing assets audit', this.auditContext);
missingEnvLocalesInAssets = await new Assets(cloneDeep(constructorParam)).run();
const assetsModule = new Assets(cloneDeep(constructorParam));
missingEnvLocalesInAssets = await assetsModule.run();
missingScanStatusAssets = assetsModule.missingScanStatusAssets ?? {};
await this.prepareReport(module, missingEnvLocalesInAssets);
await this.prepareReport('asset-scan-status', missingScanStatusAssets);
this.getAffectedData('assets', dataModuleWise['assets'], missingEnvLocalesInAssets);
this.getAffectedData('asset-scan-status', dataModuleWise['assets'], missingScanStatusAssets);
log.success(
`Assets audit completed. Found ${Object.keys(missingEnvLocalesInAssets || {}).length} issues`,
`Assets audit completed. Found ${Object.keys(missingEnvLocalesInAssets || {}).length} issues, ${Object.keys(missingScanStatusAssets).length} non-clean scan status assets`,
this.auditContext,
);
break;
}
case 'content-types':
log.info('Executing content-types audit', this.auditContext);
missingCtRefs = await new ContentType(cloneDeep(constructorParam)).run();
Expand Down Expand Up @@ -285,6 +305,7 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
missingTitleFields = missingEntry.missingTitleFields ?? {};
missingEnvLocalesInEntries = missingEntry.missingEnvLocale ?? {};
missingMultipleFields = missingEntry.missingMultipleFields ?? {};
missingAssetRefsInEntries = missingEntry.missingAssetRefs ?? {};
await this.prepareReport(module, missingEntryRefs);

await this.prepareReport(`Entries_Select_field`, missingSelectFeild);
Expand All @@ -296,6 +317,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
await this.prepareReport('Entry_Missing_Locale_and_Env_in_Publish_Details', missingEnvLocalesInEntries);

await this.prepareReport('Entry_Multiple_Fields', missingMultipleFields);

await this.prepareReport('Entries_Asset_field', missingAssetRefsInEntries);
this.getAffectedData('entries', dataModuleWise['entries'], missingEntry);
log.success(
`Entries audit completed. Found ${Object.keys(missingEntryRefs || {}).length} reference issues`,
Expand Down Expand Up @@ -421,6 +444,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
missingFieldRules,
missingMultipleFields,
missingRefsInComposableStudio,
missingScanStatusAssets,
missingAssetRefsInEntries,
};
}

Expand Down Expand Up @@ -612,7 +637,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
| keyof typeof config.moduleConfig
| keyof typeof config.ReportTitleForEntries
| 'field-rules'
| 'Summary',
| 'Summary'
| 'asset-scan-status',
listOfMissingRefs: Record<string, any>,
): Promise<void> {
log.debug(`Preparing report for module: ${moduleName}`, this.auditContext);
Expand Down Expand Up @@ -656,7 +682,8 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
| keyof typeof config.moduleConfig
| keyof typeof config.ReportTitleForEntries
| 'field-rules'
| 'Summary',
| 'Summary'
| 'asset-scan-status',
listOfMissingRefs: Record<string, any>,
): Promise<void> {
if (Object.keys(config.moduleConfig).includes(moduleName) || config.feild_level_modules.includes(moduleName)) {
Expand Down
8 changes: 8 additions & 0 deletions packages/contentstack-audit/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ const config = {
name: 'assets',
dirName: 'assets',
fileName: 'assets.json',
// Asset scan statuses that must block import/reference; any other value (including
// 'not_scanned', 'clean', or the field being absent) is treated as safe.
blockingScanStatuses: ['pending', 'quarantined'],
},
environments: {
name: 'environments',
Expand Down Expand Up @@ -110,6 +113,8 @@ const config = {
'publish_locale',
'publish_environment',
'asset_uid',
'scan_status',
'mandatory',
'selectedValue',
'ct_uid',
'action',
Expand All @@ -129,6 +134,7 @@ const config = {
Entry_Missing_Locale_and_Env: 'Entry_Missing_Locale_and_Env',
Entry_Missing_Locale_and_Env_in_Publish_Details: 'Entry_Missing_Locale_and_Env_in_Publish_Details',
Entry_Multiple_Fields: 'Entry_Multiple_Fields',
Entries_Asset_field: 'Entries_Asset_field',
},
feild_level_modules: [
'Entries_Title_field',
Expand All @@ -137,6 +143,8 @@ const config = {
'Entry_Missing_Locale_and_Env_in_Publish_Details',
'field-rules',
'Entry_Multiple_Fields',
'Entries_Asset_field',
'asset-scan-status',
'Summary',
],
fixSelectField: false,
Expand Down
4 changes: 4 additions & 0 deletions packages/contentstack-audit/src/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const auditMsg = {
FIELD_RULE_TARGET_ABSENT: `The target field '{target_field}' is not present in the schema of the content-type {ctUid}`,
FIELD_RULE_CONDITION_SCAN_MESSAGE: `Completed Scanning of Field Rule '{num}' condition of Content-type '{ctUid}'`,
FIELD_RULE_TARGET_SCAN_MESSAGE: `Completed Scanning of Field Rule '{num}' target of Content-type '{ctUid}'`,
SCAN_ASSET_QUARANTINE_MSG: `Asset with UID '{uid}' has a non-clean scan status ('{status}') and will be excluded from import.`,
ENTRY_ASSET_REF_WARN_MSG: `Entry '{uid}' field '{field}' references asset '{asset_uid}' with scan status '{status}'; it will be removed on fix.`,
};

const auditFixMsg = {
Expand All @@ -63,6 +65,8 @@ const auditFixMsg = {
ENTRY_SELECT_FIELD_FIX: `Adding the value '{value}' in the select field of entry UID '{uid}'...`,
ASSET_FIX: `Fixed publish detials for Asset with UID '{uid}'`,
FIELD_RULE_FIX_MESSAGE: `Fixed Field Rule '{num}' target of Content-type '{ctUid}`,
ASSET_SCAN_STATUS_FIX: `Removed asset with UID '{uid}' (scan status: '{status}') from assets.json.`,
ENTRY_ASSET_REF_FIX: `Removed reference to asset '{asset_uid}' from entry '{uid}' field '{field}'.`,
};

const messages: typeof errors &
Expand Down
30 changes: 26 additions & 4 deletions packages/contentstack-audit/src/modules/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default class Assets {
public environments: string[] = [];
protected schema: ContentTypeStruct[] = [];
protected missingEnvLocales: Record<string, any> = {};
public missingScanStatusAssets: Record<string, any> = {};
public moduleName: keyof typeof auditConfig.moduleConfig;

constructor({ fix, config, moduleName }: ModuleConstructorParam & CtConstructorParam) {
Expand Down Expand Up @@ -184,7 +185,24 @@ export default class Assets {

for (const assetUid in assets) {
log.debug(`Processing asset: ${assetUid}`, this.config.auditContext);


const scanStatus = this.assets[assetUid]?._asset_scan_status;
if (this.config.moduleConfig.assets.blockingScanStatuses.includes(scanStatus)) {
log.debug(`Asset ${assetUid} has a non-clean scan status: ${scanStatus}`, this.config.auditContext);
cliux.print($t(auditMsg.SCAN_ASSET_QUARANTINE_MSG, { uid: assetUid, status: scanStatus }), {
color: 'yellow',
});
this.missingScanStatusAssets[assetUid] = [
{ asset_uid: assetUid, filename: this.assets[assetUid].filename, scan_status: scanStatus },
];

if (this.fix) {
log.info($t(auditFixMsg.ASSET_SCAN_STATUS_FIX, { uid: assetUid, status: scanStatus }), this.config.auditContext);
delete this.assets[assetUid];
continue;
}
}

if (this.assets[assetUid]?.publish_details && !Array.isArray(this.assets[assetUid].publish_details)) {
log.debug(`Asset ${assetUid} has invalid publish_details format`, this.config.auditContext);
cliux.print($t(auditMsg.ASSET_NOT_EXIST, { uid: assetUid }), { color: 'red' });
Expand Down Expand Up @@ -226,15 +244,19 @@ export default class Assets {

const remainingPublishDetails = this.assets[assetUid].publish_details?.length || 0;
log.debug(`Asset ${assetUid} now has ${remainingPublishDetails} valid publish details`, this.config.auditContext);

if (this.fix) {
log.debug(`Fixing asset ${assetUid}`, this.config.auditContext);
log.info($t(auditFixMsg.ASSET_FIX, { uid: assetUid }), this.config.auditContext);
await this.writeFixContent(`${basePath}/${indexer[fileIndex]}`, this.assets);
}
}

if (this.fix) {
log.debug(`Writing fixed assets chunk to: ${basePath}/${indexer[fileIndex]}`, this.config.auditContext);
await this.writeFixContent(`${basePath}/${indexer[fileIndex]}`, this.assets);
}
}

log.debug(`Asset reference validation completed. Processed ${Object.keys(this.missingEnvLocales).length} assets with issues`, this.config.auditContext);
}
}
Loading
Loading