Skip to content
15 changes: 10 additions & 5 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@

fileignoreconfig:
- filename: package-lock.json
checksum: ca243e72c60bd55daf7cea89c8a9dbf2ba984d5cf8b15a80233eb115fee8daa0
- filename: pnpm-lock.yaml
checksum: a0bc2e8eb75b43a9b37b94bade8ce482f43aa7aeaf4fb85c3a7185d95f92ba40
- filename: package-lock.json
checksum: 8f542a625fb86173eecc0cb3ae7fe36354ccddd6031ba054077151d92135462c
- filename: pnpm-lock.yaml
checksum: a0d51f8126adbbea646a7c803f67d9b4e8c791b14e81caf76cd673083181ced0
- filename: packages/contentstack-import/src/import/modules/environments.ts
checksum: f61c635eaec8026e0cfa80a5ab8272f7946531f6d89505dc0d247b4c7ab0eab7
- filename: packages/contentstack-import/src/import/modules/taxonomies.ts
checksum: f4f44b6031d2936ec2da98b39bb5f5c1bd0f3be34dbe498c75e64a35b7d25a33
- filename: packages/contentstack-bootstrap/src/bootstrap/utils.ts
checksum: 5ab20e057fa9c4c300f7a882d30e1c68bbc91ed19de520488107e8c37239682a
version: '1.0'
2 changes: 1 addition & 1 deletion 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.3 darwin-arm64 node-v24.12.0
@contentstack/cli-audit/2.0.0-beta.3 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
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-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-auth
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-auth/2.0.0-beta.4 darwin-arm64 node-v24.12.0
@contentstack/cli-auth/2.0.0-beta.4 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-bootstrap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $ npm install -g @contentstack/cli-cm-bootstrap
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-cm-bootstrap/2.0.0-beta.7 darwin-arm64 node-v24.12.0
@contentstack/cli-cm-bootstrap/2.0.0-beta.7 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-branches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ $ npm install -g @contentstack/cli-cm-branches
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-cm-branches/1.6.3 darwin-arm64 node-v24.12.0
@contentstack/cli-cm-branches/1.6.3 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
1,797 changes: 1,797 additions & 0 deletions packages/contentstack-bulk-publish/README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/contentstack-clone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ $ npm install -g @contentstack/cli-cm-clone
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-cm-clone/2.0.0-beta.8 darwin-arm64 node-v24.12.0
@contentstack/cli-cm-clone/2.0.0-beta.8 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-config
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-config/2.0.0-beta darwin-arm64 node-v24.12.0
@contentstack/cli-config/2.0.0-beta darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
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.8 darwin-arm64 node-v24.12.0
@contentstack/cli-cm-export/2.0.0-beta.8 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import * as path from 'path';
import {
ContentstackClient,
handleAndLogError,
messageHandler,
log,
sanitizePath,
} from '@contentstack/cli-utilities';
import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePath } from '@contentstack/cli-utilities';
import { PATH_CONSTANTS } from '../../constants';

import BaseClass from './base-class';
Expand Down Expand Up @@ -94,7 +88,6 @@ export default class ContentTypesExport extends BaseClass {
await this.writeContentTypes(this.contentTypes);

this.completeProgressWithMessage();

} catch (error) {
handleAndLogError(error, { ...this.exportConfig.context });
this.completeProgress(false, error?.message || 'Content types export failed');
Expand Down Expand Up @@ -162,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, PATH_CONSTANTS.FILES.SCHEMA);
log.debug(`Writing aggregate schema to: ${schemaFilePath}`, this.exportConfig.context);

return fsUtil.writeFile(schemaFilePath, contentTypes);
}
}
21 changes: 6 additions & 15 deletions packages/contentstack-export/src/export/modules/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,12 @@ import {
handleAndLogError,
messageHandler,
log,
readContentTypeSchemas,
sanitizePath,
} from '@contentstack/cli-utilities';
import { PATH_CONSTANTS } from '../../constants';
import { Export, ExportProjects } from '@contentstack/cli-variants';

import {
fsUtil,
PROCESS_NAMES,
MODULE_CONTEXTS,
PROCESS_STATUS,
MODULE_NAMES,
} from '../../utils';
import { fsUtil, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils';
import BaseClass, { ApiOptions } from './base-class';
import { ExportConfig, ModuleClassParams } from '../../types';

Expand All @@ -37,7 +31,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 @@ -58,11 +52,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),
PATH_CONSTANTS.FILES.SCHEMA,
);
this.projectInstance = new ExportProjects(this.exportConfig);
this.exportConfig.context.module = MODULE_CONTEXTS.ENTRIES;
Expand All @@ -77,7 +70,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 @@ -90,7 +83,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 Expand Up @@ -121,7 +114,6 @@ export default class EntriesExport extends BaseClass {
if (this.entriesConfig.exportVersions) {
progress.addProcess(PROCESS_NAMES.ENTRY_VERSIONS, totalEntriesCount);
}

}

// Process entry collections
Expand Down Expand Up @@ -170,7 +162,6 @@ export default class EntriesExport extends BaseClass {
}

this.completeProgressWithMessage();

} catch (error) {
handleAndLogError(error, { ...this.exportConfig.context });
this.completeProgress(false, error?.message || 'Entries export failed');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ describe('ExportContentTypes', () => {
// Stub FsUtility methods
sinon.stub(FsUtility.prototype, 'writeFile').resolves();
sinon.stub(FsUtility.prototype, 'makeDirectory').resolves();
// Stub FsUtility.prototype.readdir and readFile for readContentTypeSchemas support
sinon.stub(FsUtility.prototype, 'readdir').returns([]);
sinon.stub(FsUtility.prototype, 'readFile').returns(undefined);
});

afterEach(() => {
Expand Down Expand Up @@ -314,6 +317,7 @@ describe('ExportContentTypes', () => {

it('should handle empty content types', async () => {
const writeFileStub = FsUtility.prototype.writeFile as sinon.SinonStub;
const completeProgressStub = sinon.stub(exportContentTypes as any, 'completeProgress');

mockStackClient.contentType.returns({
query: sinon.stub().returns({
Expand All @@ -327,8 +331,10 @@ describe('ExportContentTypes', () => {
exportContentTypes.contentTypes = [];
await exportContentTypes.start();

// Verify writeFile was called even with empty array
expect(writeFileStub.called).to.be.true;
// With empty content types, writeFile is not called (no files to write)
// But completeProgress should be called to mark the process as complete
expect(completeProgressStub.called).to.be.true;
expect(completeProgressStub.calledWith(true)).to.be.true;
});

it('should handle errors during export without throwing', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ describe('EntriesExport', () => {
createFolderStub.callsFake(() => {
// Do nothing - prevent actual directory creation
});

// Stub FsUtility.prototype.readdir and readFile for readContentTypeSchemas support
// readContentTypeSchemas creates its own FsUtility instance, so we need to stub the prototype
sandbox.stub(FsUtility.prototype, 'readdir').returns([]);
sandbox.stub(FsUtility.prototype, 'readFile').returns(undefined);

entriesExport = new EntriesExport({
exportConfig: mockExportConfig,
Expand Down Expand Up @@ -173,16 +178,15 @@ describe('EntriesExport', () => {
mockExportConfig.modules.locales.dirName,
mockExportConfig.modules.locales.fileName,
);
const expectedSchemaPath = path.resolve(
const expectedContentTypesDirPath = path.resolve(
mockExportConfig.exportDir,
mockExportConfig.branchName || '',
mockExportConfig.modules.content_types.dirName,
'schema.json',
);

expect(entriesExport.entriesDirPath).to.equal(expectedEntriesPath);
expect(entriesExport.localesFilePath).to.equal(expectedLocalesPath);
expect(entriesExport.schemaFilePath).to.equal(expectedSchemaPath);
expect(entriesExport.contentTypesDirPath).to.equal(expectedContentTypesDirPath);
});

it('should initialize ExportProjects instance', () => {
Expand All @@ -197,27 +201,29 @@ describe('EntriesExport', () => {

describe('start() method - Early Returns', () => {
it('should return early when no content types are found', async () => {
mockFsUtil.readFile
.onFirstCall()
.returns([{ code: 'en-us' }]) // locales
.onSecondCall()
.returns([]); // content types
// Stub mockFsUtil.readFile for locales
mockFsUtil.readFile.returns([{ code: 'en-us' }]);

// Stub FsUtility.prototype for readContentTypeSchemas to return empty
(FsUtility.prototype.readdir as sinon.SinonStub).returns([]); // No content type files

await entriesExport.start();

// Should not attempt to fetch entries
expect(mockStackAPIClient.contentType.called).to.be.false;
// Should read both locales and content types files
expect(mockFsUtil.readFile.calledTwice).to.be.true;
// Should read locales file
expect(mockFsUtil.readFile.called).to.be.true;
});

it('should handle empty locales array gracefully', async () => {
const contentTypes = [{ uid: 'ct-1', title: 'Content Type 1' }];
mockFsUtil.readFile
.onFirstCall()
.returns([]) // empty locales
.onSecondCall()
.returns(contentTypes);
const contentTypes = [{ uid: 'ct-1', title: 'Content Type 1', schema: [] as any }];

// Stub mockFsUtil.readFile for locales
mockFsUtil.readFile.returns([]); // empty locales

// Stub FsUtility.prototype for readContentTypeSchemas to return content types
(FsUtility.prototype.readdir as sinon.SinonStub).returns(['ct-1.json']);
(FsUtility.prototype.readFile as sinon.SinonStub).returns(contentTypes[0]);

await entriesExport.start();

Expand All @@ -226,14 +232,14 @@ describe('EntriesExport', () => {
});

it('should handle non-array locales gracefully', async () => {
const contentTypes = [{ uid: 'ct-1', title: 'Content Type 1' }];
// Use empty array instead of null to avoid Object.keys error
// The code checks !Array.isArray first, so empty array will work
mockFsUtil.readFile
.onFirstCall()
.returns([]) // empty locales array
.onSecondCall()
.returns(contentTypes);
const contentTypes = [{ uid: 'ct-1', title: 'Content Type 1', schema: [] as any }];

// Stub mockFsUtil.readFile for locales
mockFsUtil.readFile.returns([]); // empty locales array

// Stub FsUtility.prototype for readContentTypeSchemas to return content types
(FsUtility.prototype.readdir as sinon.SinonStub).returns(['ct-1.json']);
(FsUtility.prototype.readFile as sinon.SinonStub).returns(contentTypes[0]);

// Mock entry query for when entries are processed
const mockEntryQuery = {
Expand Down Expand Up @@ -1055,11 +1061,19 @@ describe('EntriesExport', () => {
it('should process all request objects and complete file writing', async () => {
const locales = [{ code: 'en-us' }];
const contentTypes = [
{ uid: 'ct-1', title: 'Content Type 1' },
{ uid: 'ct-2', title: 'Content Type 2' },
{ uid: 'ct-1', title: 'Content Type 1', schema: [] as any },
{ uid: 'ct-2', title: 'Content Type 2', schema: [] as any },
];

mockFsUtil.readFile.onFirstCall().returns(locales).onSecondCall().returns(contentTypes);
mockFsUtil.readFile.returns(locales); // For locales file

// Stub FsUtility.prototype for readContentTypeSchemas
(FsUtility.prototype.readdir as sinon.SinonStub).returns(['ct-1.json', 'ct-2.json']);
(FsUtility.prototype.readFile as sinon.SinonStub).callsFake((filePath: string) => {
if (filePath.includes('ct-1.json')) return contentTypes[0];
if (filePath.includes('ct-2.json')) return contentTypes[1];
return undefined;
});

const mockEntryQuery = {
query: sandbox.stub().returns({
Expand Down Expand Up @@ -1098,9 +1112,13 @@ describe('EntriesExport', () => {

it('should handle errors during entry processing gracefully', async () => {
const locales = [{ code: 'en-us' }];
const contentTypes = [{ uid: 'ct-1', title: 'Content Type 1' }];
const contentTypes = [{ uid: 'ct-1', title: 'Content Type 1', schema: [] as any }];

mockFsUtil.readFile.onFirstCall().returns(locales).onSecondCall().returns(contentTypes);
mockFsUtil.readFile.returns(locales); // For locales file

// Stub FsUtility.prototype for readContentTypeSchemas
(FsUtility.prototype.readdir as sinon.SinonStub).returns(['ct-1.json']);
(FsUtility.prototype.readFile as sinon.SinonStub).returns(contentTypes[0]);

const processingError = new Error('Entry processing failed');
const getEntriesStub = sandbox.stub(entriesExport, 'getEntries').rejects(processingError);
Expand Down
Loading
Loading