Skip to content
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fileignoreconfig:
- filename: pnpm-lock.yaml
checksum: 454f794a75f7d972451575ac5d7d208f691acd776fd01fe9f0d3277ae1142d10
- filename: package-lock.json
checksum: 6585e1ce86e49a60e3593d926180e0bf4e151f9ff989d44841410eee2fab59a4
checksum: 232dc3c29b538cc900979dbcddbede259430cefc2212da775c5aa8fc2739dcea
- filename: packages/contentstack-export/test/unit/export/modules/taxonomies.test.ts
checksum: f6175a8e89fb9dde4b0ff269d65a9e42c4374ef7efb1b828e54e2ebd7c4379fa
- filename: packages/contentstack-export/test/unit/export/modules/workflows.test.ts
Expand Down
579 changes: 236 additions & 343 deletions package-lock.json

Large diffs are not rendered by default.

24 changes: 13 additions & 11 deletions packages/contentstack-audit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ $ npm install -g @contentstack/cli-audit
$ csdx COMMAND
running command...
$ csdx (--version|-v)
@contentstack/cli-audit/2.0.0-beta.1 darwin-arm64 node-v24.12.0
@contentstack/cli-audit/2.0.0-beta.2 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand All @@ -41,13 +41,14 @@ Perform audits and find possible errors in the exported Contentstack data
```
USAGE
$ csdx cm:stacks:audit [-c <value>] [-d <value>] [--show-console-output] [--report-path <value>] [--modules
content-types|global-fields|entries|extensions|workflows|custom-roles|assets|field-rules...] [--columns <value>]
[--sort <value>] [--filter <value>] [--csv] [--no-truncate] [--no-header] [--output csv|json|yaml]
content-types|global-fields|entries|extensions|workflows|custom-roles|assets|field-rules|composable-studio...]
[--columns <value>] [--sort <value>] [--filter <value>] [--csv] [--no-truncate] [--no-header] [--output
csv|json|yaml]

FLAGS
--modules=<option>... Provide the list of modules to be audited
<options:
content-types|global-fields|entries|extensions|workflows|custom-roles|assets|field-rules>
<options: content-types|global-fields|entries|extensions|workflows|custom-roles|assets|field-ru
les|composable-studio>
--report-path=<value> Path to store the audit reports

COMMON FLAGS
Expand Down Expand Up @@ -89,18 +90,19 @@ Perform audits and fix possible errors in the exported Contentstack data.
```
USAGE
$ csdx cm:stacks:audit:fix [-c <value>] [-d <value>] [--show-console-output] [--report-path <value>] [--modules
content-types|global-fields|entries|extensions|workflows|custom-roles|assets|field-rules...] [--copy-path <value>
--copy-dir] [--fix-only reference|global_field|json:rte|json:extension|blocks|group|content_types...] [--columns
<value>] [--sort <value>] [--filter <value>] [--csv] [--no-truncate] [--no-header] [--output csv|json|yaml]
content-types|global-fields|entries|extensions|workflows|custom-roles|assets|field-rules|composable-studio...]
[--copy-path <value> --copy-dir] [--fix-only
reference|global_field|json:rte|json:extension|blocks|group|content_types...] [--columns <value>] [--sort <value>]
[--filter <value>] [--csv] [--no-truncate] [--no-header] [--output csv|json|yaml]

FLAGS
--copy-dir Create backup from the original data.
--copy-path=<value> Provide the path to backup the copied data
--fix-only=<option>... Provide the list of fix options
<options: reference|global_field|json:rte|json:extension|blocks|group|content_types>
--modules=<option>... Provide the list of modules to be audited
<options:
content-types|global-fields|entries|extensions|workflows|custom-roles|assets|field-rules>
<options: content-types|global-fields|entries|extensions|workflows|custom-roles|assets|field-r
ules|composable-studio>
--report-path=<value> Path to store the audit reports

COMMON FLAGS
Expand Down Expand Up @@ -155,5 +157,5 @@ DESCRIPTION
Display help for csdx.
```

_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.36/src/commands/help.ts)_
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.37/src/commands/help.ts)_
<!-- commandsstop -->
7 changes: 3 additions & 4 deletions packages/contentstack-audit/src/audit-base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid';
import isEmpty from 'lodash/isEmpty';
import { join, resolve } from 'path';
import cloneDeep from 'lodash/cloneDeep';
import { cliux, sanitizePath, TableFlags, TableHeader, log, configHandler, CLIProgressManager, clearProgressModuleSetting } from '@contentstack/cli-utilities';
import { cliux, sanitizePath, TableFlags, TableHeader, log, configHandler, CLIProgressManager, clearProgressModuleSetting, readContentTypeSchemas } from '@contentstack/cli-utilities';
import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
import config from './config';
import { print } from './util/log';
Expand Down Expand Up @@ -480,10 +480,9 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
* `gfSchema`. The values of these properties are the parsed JSON data from two different files.
*/
getCtAndGfSchema() {
const ctPath = join(
const ctDirPath = join(
this.sharedConfig.basePath,
this.sharedConfig.moduleConfig['content-types'].dirName,
this.sharedConfig.moduleConfig['content-types'].fileName,
);
const gfPath = join(
this.sharedConfig.basePath,
Expand All @@ -492,7 +491,7 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
);

const gfSchema = existsSync(gfPath) ? (JSON.parse(readFileSync(gfPath, 'utf8')) as ContentTypeStruct[]) : [];
const ctSchema = existsSync(ctPath) ? (JSON.parse(readFileSync(ctPath, 'utf8')) as ContentTypeStruct[]) : [];
const ctSchema = readContentTypeSchemas(ctDirPath) as ContentTypeStruct[];

return { ctSchema, gfSchema };
}
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-audit/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const config = {
moduleConfig: {
'content-types': {
name: 'content type',
fileName: 'schema.json',
fileName: 'schema.json', // Not used - reads from individual files
dirName: 'content_types',
},
'global-fields': {
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-export/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ $ npm install -g @contentstack/cli-cm-export
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-cm-export/2.0.0-beta.5 darwin-arm64 node-v24.12.0
@contentstack/cli-cm-export/2.0.0-beta.6 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,5 @@ export default class ContentTypesExport extends BaseClass {
await executeTask(contentTypes, writeWithProgress.bind(this), {
concurrency: this.exportConfig.writeConcurrency,
});

const schemaFilePath = path.join(this.contentTypesDirPath, 'schema.json');
log.debug(`Writing aggregate schema to: ${schemaFilePath}`, this.exportConfig.context);

return fsUtil.writeFile(schemaFilePath, contentTypes);
}
}
11 changes: 5 additions & 6 deletions packages/contentstack-export/src/export/modules/entries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as path from 'path';
import { ContentstackClient, FsUtility, handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities';
import { ContentstackClient, FsUtility, handleAndLogError, messageHandler, log, readContentTypeSchemas } from '@contentstack/cli-utilities';
import { Export, ExportProjects } from '@contentstack/cli-variants';
import { sanitizePath } from '@contentstack/cli-utilities';

Expand Down Expand Up @@ -30,7 +30,7 @@ export default class EntriesExport extends BaseClass {
private variantEntries!: any;
private entriesDirPath: string;
private localesFilePath: string;
private schemaFilePath: string;
private contentTypesDirPath: string;
private entriesFileHelper: FsUtility;
private projectInstance: ExportProjects;
public exportVariantEntry: boolean = false;
Expand All @@ -51,11 +51,10 @@ export default class EntriesExport extends BaseClass {
sanitizePath(exportConfig.modules.locales.dirName),
sanitizePath(exportConfig.modules.locales.fileName),
);
this.schemaFilePath = path.resolve(
this.contentTypesDirPath = path.resolve(
sanitizePath(exportConfig.exportDir),
sanitizePath(exportConfig.branchName || ''),
sanitizePath(exportConfig.modules.content_types.dirName),
'schema.json',
);
this.projectInstance = new ExportProjects(this.exportConfig);
this.exportConfig.context.module = MODULE_CONTEXTS.ENTRIES;
Expand All @@ -70,7 +69,7 @@ export default class EntriesExport extends BaseClass {
const [locales, contentTypes, entryRequestOptions, totalEntriesCount, variantInfo] =
await this.withLoadingSpinner('ENTRIES: Analyzing content structure and entries...', async () => {
const locales = fsUtil.readFile(this.localesFilePath) as Array<Record<string, unknown>>;
const contentTypes = fsUtil.readFile(this.schemaFilePath) as Array<Record<string, unknown>>;
const contentTypes = readContentTypeSchemas(this.contentTypesDirPath);

if (!Array.isArray(locales) || locales?.length === 0) {
log.debug(`No locales found in ${this.localesFilePath}`, this.exportConfig.context);
Expand All @@ -83,7 +82,7 @@ export default class EntriesExport extends BaseClass {
return [locales, contentTypes, [], 0, null];
}
log.debug(
`Loaded ${contentTypes?.length} content types from ${this.schemaFilePath}`,
`Loaded ${contentTypes?.length} content types from individual files in ${this.contentTypesDirPath}`,
this.exportConfig.context,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import * as path from 'path';
import { find, cloneDeep, map } from 'lodash';
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
import { sanitizePath, log, handleAndLogError, readContentTypeSchemas } from '@contentstack/cli-utilities';
import { ImportConfig, ModuleClassParams } from '../../types';
import BaseClass, { ApiOptions } from './base-class';
import { updateFieldRules } from '../../utils/content-type-helper';
Expand Down Expand Up @@ -475,7 +475,7 @@ export default class ContentTypesImport extends BaseClass {
const [cts, gfs, pendingGfs, pendingExt] = await this.withLoadingSpinner(
'CONTENT TYPES: Analyzing import data...',
async () => {
const cts = fsUtil.readFile(path.join(this.cTsFolderPath, 'schema.json'));
const cts = readContentTypeSchemas(this.cTsFolderPath);
const gfs = fsUtil.readFile(path.resolve(this.gFsFolderPath, this.gFsConfig.fileName));
const pendingGfs = fsUtil.readFile(this.gFsPendingPath);
const pendingExt = fsUtil.readFile(this.extPendingPath);
Expand Down
11 changes: 5 additions & 6 deletions packages/contentstack-import/src/import/modules/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import * as path from 'path';
import { writeFileSync } from 'fs';
import { isEmpty, values, cloneDeep, find, indexOf, forEach, remove } from 'lodash';
import { FsUtility, sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
import { FsUtility, sanitizePath, log, handleAndLogError, readContentTypeSchemas } from '@contentstack/cli-utilities';
import {
fsUtil,
lookupExtension,
Expand Down Expand Up @@ -231,7 +231,8 @@ export default class EntriesImport extends BaseClass {
return this.withLoadingSpinner('ENTRIES: Analyzing import data...', async () => {
log.debug('Loading content types for entry analysis', this.importConfig.context);

this.cTs = fsUtil.readFile(path.join(this.cTsPath, 'schema.json')) as Record<string, unknown>[];
this.cTs = readContentTypeSchemas(this.cTsPath);

if (!this.cTs || isEmpty(this.cTs)) {
return [0, 0, 0, 0, 0];
}
Expand Down Expand Up @@ -1205,10 +1206,8 @@ export default class EntriesImport extends BaseClass {
for (let cTUid of cTsWithFieldRules) {
log.debug(`Processing field rules for content type: ${cTUid}`, this.importConfig.context);

const cTs: Record<string, any>[] = fsUtil.readFile(path.join(this.cTsPath, 'schema.json')) as Record<
string,
unknown
>[];
// Read content types from individual files
const cTs = readContentTypeSchemas(this.cTsPath);
Comment thread
shafeeqd959 marked this conversation as resolved.
Outdated
const contentType: any = find(cTs, { uid: cTUid });

if (contentType.field_rules) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1164,4 +1164,35 @@ describe('ImportContentTypes', () => {
expect(makeConcurrentCallStub.called).to.be.true;
});
});

describe('analyzeImportData() with individual content type files', () => {
it('should read content types from individual files', async () => {
const mockContentTypes = [
{ uid: 'ct-1', title: 'CT 1', schema: [] },
{ uid: 'ct-2', title: 'CT 2', schema: [] },
];

// Stub readContentTypeSchemas to return mock content types
const readContentTypeSchemasStub = sinon.stub().returns(mockContentTypes);
sinon.stub(require('@contentstack/cli-utilities'), 'readContentTypeSchemas').value(readContentTypeSchemasStub);

fsUtilStub.readFile.returns([]);

await (importContentTypes as any).analyzeImportData();

expect((importContentTypes as any).cTs).to.deep.equal(mockContentTypes);
});

it('should return empty array when no individual files are found', async () => {
// Stub readContentTypeSchemas to return empty array (no individual files)
const readContentTypeSchemasStub = sinon.stub().returns([]);
sinon.stub(require('@contentstack/cli-utilities'), 'readContentTypeSchemas').value(readContentTypeSchemasStub);

fsUtilStub.readFile.returns([]);

await (importContentTypes as any).analyzeImportData();

expect((importContentTypes as any).cTs).to.deep.equal([]);
});
});
});
47 changes: 47 additions & 0 deletions packages/contentstack-utilities/src/content-type-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { resolve as pResolve } from 'node:path';
import { FsUtility } from './fs-utility';

/**
* Reads all content type schema files from a directory
* @param dirPath - Path to content types directory
* @param ignoredFiles - Files to ignore (defaults to schema.json, .DS_Store, __master.json, __priority.json)
* @returns Array of content type schemas
*/
export function readContentTypeSchemas(
dirPath: string,
ignoredFiles: string[] = ['schema.json', '.DS_Store', '__master.json', '__priority.json'],
): Record<string, unknown>[] {
const fsUtil = new FsUtility();
const files = fsUtil.readdir(dirPath);

if (!files || files.length === 0) {
return [];
}

const contentTypes: Record<string, unknown>[] = [];

for (const file of files) {
// Skip if not a JSON file
if (!file.endsWith('.json')) {
continue;
}

// Skip ignored files
if (ignoredFiles.includes(file)) {
continue;
}

try {
const filePath = pResolve(dirPath, file);
const contentType = fsUtil.readFile(filePath);
if (contentType) {
contentTypes.push(contentType as Record<string, unknown>);
}
} catch (error) {
// Skip files that cannot be parsed
console.warn(`Failed to read content type file ${file}:`, error);
}
}

return contentTypes;
}
1 change: 1 addition & 0 deletions packages/contentstack-utilities/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export {
export { default as printFlagDeprecation } from './flag-deprecation-check';
export * from './http-client';
export * from './fs-utility';
export * from './content-type-utils';
export { default as NodeCrypto } from './encrypter';
export { Args as args, Flags as flags, Command } from './cli-ux';
export * from './helpers';
Expand Down
Loading
Loading