Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions packages/contentstack-export/src/export/module-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ class ModuleExporter {
exportModules.push('stack');
}

exportModules.push(moduleName);

if (!this.exportConfig.skipDependencies) {
const {
modules: { [moduleName]: { dependencies = [] } = {} },
Expand All @@ -115,6 +113,8 @@ class ModuleExporter {
}
}

exportModules.push(moduleName);

for (const moduleName of exportModules) {
await this.exportByModuleByName(moduleName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export default class ContentTypesExport extends BaseClass {
}

sanitizeAttribs(contentTypes: Record<string, unknown>[]): Record<string, unknown>[] {
log.debug(`Sanitizing ${contentTypes.length} content types`, this.exportConfig.context);
log.debug(`Sanitizing ${contentTypes?.length} content types`, this.exportConfig.context);

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

Expand All @@ -121,7 +121,7 @@ export default class ContentTypesExport extends BaseClass {
}

async writeContentTypes(contentTypes: Record<string, unknown>[]) {
log.debug(`Writing ${contentTypes.length} content types to disk`, this.exportConfig.context);
log.debug(`Writing ${contentTypes?.length} content types to disk`, this.exportConfig.context);

function write(contentType: Record<string, unknown>) {
return fsUtil.writeFile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default class ExportCustomRoles extends BaseClass {
await this.getLocales();
await this.getCustomRolesLocales();

log.debug(`Custom roles export completed. Total custom roles: ${Object.keys(this.customRoles).length}`, this.exportConfig.context);
log.debug(`Custom roles export completed. Total custom roles: ${Object.keys(this.customRoles)?.length}`, this.exportConfig.context);
}

async getCustomRoles(): Promise<void> {
Expand All @@ -75,8 +75,8 @@ export default class ExportCustomRoles extends BaseClass {
}

customRoles.forEach((role: any) => {
log.debug(`Processing custom role: ${role.name} (${role.uid})`, this.exportConfig.context);
log.info(messageHandler.parse('ROLES_EXPORTING_ROLE', role.name), this.exportConfig.context);
log.debug(`Processing custom role: ${role?.name} (${role?.uid})`, this.exportConfig.context);
log.info(messageHandler.parse('ROLES_EXPORTING_ROLE', role?.name), this.exportConfig.context);
this.customRoles[role.uid] = role;
});

Expand All @@ -93,7 +93,7 @@ export default class ExportCustomRoles extends BaseClass {
.query({})
.find()
.then((data: any) => {
log.debug(`Fetched ${data.items?.length || 0} locales`, this.exportConfig.context);
log.debug(`Fetched ${data?.items?.length || 0} locales`, this.exportConfig.context);
return data;
})
.catch((err: any) => {
Expand All @@ -102,23 +102,23 @@ export default class ExportCustomRoles extends BaseClass {
});

for (const locale of locales.items) {
log.debug(`Mapping locale: ${locale.name} (${locale.uid})`, this.exportConfig.context);
log.debug(`Mapping locale: ${locale?.name} (${locale?.uid})`, this.exportConfig.context);
this.sourceLocalesMap[locale.uid] = locale;
}

log.debug(`Mapped ${Object.keys(this.sourceLocalesMap).length} locales`, this.exportConfig.context);
log.debug(`Mapped ${Object.keys(this.sourceLocalesMap)?.length} locales`, this.exportConfig.context);
}

async getCustomRolesLocales() {
log.debug('Processing custom roles locales mapping...', this.exportConfig.context);

for (const role of values(this.customRoles)) {
const customRole = role as Record<string, any>;
log.debug(`Processing locales for custom role: ${customRole.name}`, this.exportConfig.context);
log.debug(`Processing locales for custom role: ${customRole?.name}`, this.exportConfig.context);

const rulesLocales = find(customRole.rules, (rule: any) => rule.module === 'locale');
if (rulesLocales?.locales?.length) {
log.debug(`Found ${rulesLocales.locales.length} locales for role: ${customRole.name}`, this.exportConfig.context);
log.debug(`Found ${rulesLocales.locales.length} locales for role: ${customRole?.name}`, this.exportConfig.context);
forEach(rulesLocales.locales, (locale: any) => {
log.debug(`Adding locale ${locale} to custom roles mapping`, this.exportConfig.context);
this.localesMap[locale] = 1;
Expand All @@ -127,7 +127,7 @@ export default class ExportCustomRoles extends BaseClass {
}

if (keys(this.localesMap)?.length) {
log.debug(`Processing ${keys(this.localesMap).length} custom role locales`, this.exportConfig.context);
log.debug(`Processing ${keys(this.localesMap)?.length} custom role locales`, this.exportConfig.context);

for (const locale in this.localesMap) {
if (this.sourceLocalesMap[locale] !== undefined) {
Expand Down
99 changes: 62 additions & 37 deletions packages/contentstack-export/src/export/modules/entries.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import * as path from 'path';
import {
ContentstackClient,
FsUtility,
handleAndLogError,
messageHandler,
log,
} from '@contentstack/cli-utilities';
import { ContentstackClient, FsUtility, handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities';
import { Export, ExportProjects } from '@contentstack/cli-variants';
import { sanitizePath } from '@contentstack/cli-utilities';

Expand Down Expand Up @@ -65,15 +59,18 @@ export default class EntriesExport extends BaseClass {
try {
log.debug('Starting entries export process...', this.exportConfig.context);
const locales = fsUtil.readFile(this.localesFilePath) as Array<Record<string, unknown>>;
log.debug(`Loaded ${locales.length} locales from ${this.localesFilePath}`, this.exportConfig.context);

if (!Array.isArray(locales) || locales?.length === 0) {
log.debug(`No locales found in ${this.localesFilePath}`, this.exportConfig.context);
} else {
log.debug(`Loaded ${locales?.length} locales from ${this.localesFilePath}`, this.exportConfig.context);
}

const contentTypes = fsUtil.readFile(this.schemaFilePath) as Array<Record<string, unknown>>;
log.debug(`Loaded ${contentTypes.length} content types from ${this.schemaFilePath}`, this.exportConfig.context);

if (contentTypes.length === 0) {
if (contentTypes?.length === 0) {
log.info(messageHandler.parse('CONTENT_TYPE_NO_TYPES'), this.exportConfig.context);
return;
}
log.debug(`Loaded ${contentTypes?.length} content types from ${this.schemaFilePath}`, this.exportConfig.context);

// NOTE Check if variant is enabled in specific stack
if (this.exportConfig.personalizationEnabled) {
Expand All @@ -95,18 +92,20 @@ export default class EntriesExport extends BaseClass {
}

const entryRequestOptions = this.createRequestObjects(locales, contentTypes);
log.debug(`Created ${entryRequestOptions.length} entry request objects for processing`, this.exportConfig.context);

log.debug(
`Created ${entryRequestOptions.length} entry request objects for processing`,
this.exportConfig.context,
);

for (let entryRequestOption of entryRequestOptions) {
log.debug(`Processing entries for content type: ${entryRequestOption.contentType}, locale: ${entryRequestOption.locale}`, this.exportConfig.context);
log.debug(
`Processing entries for content type: ${entryRequestOption.contentType}, locale: ${entryRequestOption.locale}`,
this.exportConfig.context,
);
await this.getEntries(entryRequestOption);
this.entriesFileHelper?.completeFile(true);
log.success(
messageHandler.parse(
'ENTRIES_EXPORT_COMPLETE',
entryRequestOption.contentType,
entryRequestOption.locale,
),
messageHandler.parse('ENTRIES_EXPORT_COMPLETE', entryRequestOption.contentType, entryRequestOption.locale),
this.exportConfig.context,
);
}
Expand All @@ -120,8 +119,18 @@ export default class EntriesExport extends BaseClass {
locales: Array<Record<string, unknown>>,
contentTypes: Array<Record<string, unknown>>,
): Array<Record<string, any>> {
log.debug(`Creating request objects for ${contentTypes.length} content types and ${locales.length} locales`, this.exportConfig.context);

if (!Array.isArray(locales) || locales?.length === 0) {
log.debug('No locales found, using master locale only', this.exportConfig.context);
} else {
log.debug(`Found ${locales.length} locales for export`, this.exportConfig.context);
}
if (!Array.isArray(contentTypes) || contentTypes.length === 0) {
log.debug('No content types found, skipping entries export', this.exportConfig.context);
return [];
} else {
log.debug(`Found ${contentTypes.length} content types for export`, this.exportConfig.context);
}

let requestObjects: Array<Record<string, any>> = [];
contentTypes.forEach((contentType) => {
if (Object.keys(locales).length !== 0) {
Expand Down Expand Up @@ -165,17 +174,21 @@ export default class EntriesExport extends BaseClass {
.entry()
.query(requestObject)
.find();

log.debug(`Fetched ${entriesSearchResponse.items?.length || 0} entries out of total ${entriesSearchResponse.count}`, this.exportConfig.context);

log.debug(
`Fetched ${entriesSearchResponse.items?.length || 0} entries out of total ${entriesSearchResponse.count}`,
this.exportConfig.context,
);
} catch (error) {
handleAndLogError(error, {
...this.exportConfig.context,
contentType: options.contentType,
locale: options.locale,
});
throw error;
}

if (Array.isArray(entriesSearchResponse.items) && entriesSearchResponse.items.length > 0) {
if (Array.isArray(entriesSearchResponse?.items) && entriesSearchResponse?.items?.length > 0) {
if (options.skip === 0) {
const entryBasePath = path.join(
sanitizePath(this.entriesDirPath),
Expand All @@ -194,10 +207,10 @@ export default class EntriesExport extends BaseClass {
});
log.debug('Initialized FsUtility for writing entries', this.exportConfig.context);
}

log.debug(`Writing ${entriesSearchResponse.items.length} entries to file`, this.exportConfig.context);
this.entriesFileHelper.writeIntoFile(entriesSearchResponse.items, { mapKeyVal: true });

if (this.entriesConfig.exportVersions) {
log.debug('Exporting entry versions is enabled', this.exportConfig.context);
let versionedEntryPath = path.join(
Expand Down Expand Up @@ -227,7 +240,10 @@ export default class EntriesExport extends BaseClass {

options.skip += this.entriesConfig.limit || 100;
if (options.skip >= entriesSearchResponse.count) {
log.debug(`Completed fetching all entries for content type: ${options.contentType}, locale: ${options.locale}`, this.exportConfig.context);
log.debug(
`Completed fetching all entries for content type: ${options.contentType}, locale: ${options.locale}`,
this.exportConfig.context,
);
return Promise.resolve(true);
}
log.debug(`Continuing to fetch entries with skip: ${options.skip}`, this.exportConfig.context);
Expand All @@ -240,7 +256,7 @@ export default class EntriesExport extends BaseClass {
options: { locale: string; contentType: string; versionedEntryPath: string },
): Promise<void> {
log.debug(`Fetching versions for ${entries.length} entries`, this.exportConfig.context);

const onSuccess = ({ response, apiData: entry }: any) => {
const versionFilePath = path.join(sanitizePath(options.versionedEntryPath), sanitizePath(`${entry.uid}.json`));
log.debug(`Writing versioned entry to: ${versionFilePath}`, this.exportConfig.context);
Expand All @@ -262,7 +278,10 @@ export default class EntriesExport extends BaseClass {
);
};

log.debug(`Starting concurrent calls for versioned entries with batch limit: ${this.entriesConfig.batchLimit}`, this.exportConfig.context);
log.debug(
`Starting concurrent calls for versioned entries with batch limit: ${this.entriesConfig.batchLimit}`,
this.exportConfig.context,
);
return await this.makeConcurrentCall(
{
apiBatches: [entries],
Expand All @@ -289,7 +308,7 @@ export default class EntriesExport extends BaseClass {
isLastRequest: boolean;
}) {
log.debug(`Processing versioned entry: ${entry.uid}`, this.exportConfig.context);

return new Promise(async (resolve, reject) => {
return await this.getEntryByVersion(apiParams.queryParam, entry)
.then((response) => {
Expand Down Expand Up @@ -323,21 +342,27 @@ export default class EntriesExport extends BaseClass {
},
version: entry._version,
};

log.debug(`Fetching entry version ${entry._version} for uid: ${entry.uid}`, this.exportConfig.context);

const entryResponse = await this.stackAPIClient
.contentType(options.contentType)
.entry(entry.uid)
.fetch(queryRequestObject);
entries.push(entryResponse);

if (--entry._version > 0) {
log.debug(`Continuing to fetch previous version ${entry._version} for entry: ${entry.uid}`, this.exportConfig.context);
log.debug(
`Continuing to fetch previous version ${entry._version} for entry: ${entry.uid}`,
this.exportConfig.context,
);
return await this.getEntryByVersion(options, entry, entries);
}

log.debug(`Completed fetching all versions for entry: ${entry.uid}, total versions: ${entries.length}`, this.exportConfig.context);

log.debug(
`Completed fetching all versions for entry: ${entry.uid}, total versions: ${entries.length}`,
this.exportConfig.context,
);
return entries;
}
}
39 changes: 37 additions & 2 deletions packages/contentstack-utilities/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { checkSync } from 'recheck';
import traverse from 'traverse';
import authHandler from './auth-handler';
import { HttpClient, cliux, configHandler } from '.';

export const isAuthenticated = () => authHandler.isAuthenticated();
export const doesBranchExist = async (stack, branchName) => {
return stack
Expand Down Expand Up @@ -92,9 +93,38 @@ export const formatError = function (error: any) {
parsedError = error;
}

// Check if parsedError is an empty object
if (parsedError && typeof parsedError === 'object' && Object.keys(parsedError).length === 0) {
return `An unknown error occurred. ${error}`;
if (
!parsedError.message &&
!parsedError.code &&
!parsedError.status &&
!parsedError.errorMessage &&
!parsedError.error_message
) {
return `An unknown error occurred. ${error}`;
}
}

if (parsedError?.response?.data?.errorMessage) {
return parsedError.response.data.errorMessage;
}

if (parsedError?.errorMessage) {
return parsedError.errorMessage;
}

const status = parsedError?.status || parsedError?.response?.status;
const errorCode = parsedError?.errorCode || parsedError?.response?.data?.errorCode;
if (status === 422 && errorCode === 104) {
return 'Invalid email or password. Please check your credentials and try again.';
}

if (status === 401) {
return 'Authentication failed. Please check your credentials.';
}

if (status === 403) {
return 'Access denied. Please check your permissions.';
}

// Check for specific SSL error
Expand All @@ -107,6 +137,11 @@ export const formatError = function (error: any) {
return 'Self-signed certificate in the certificate chain! Please ensure your certificate configuration is correct and the necessary CA certificates are trusted.';
}

// ENHANCED: Handle network errors with user-friendly messages
if (['ECONNREFUSED', 'ENOTFOUND', 'ETIMEDOUT', 'ENETUNREACH'].includes(parsedError?.code)) {
return `Connection failed: Unable to reach the server. Please check your internet connection.`;
}

// Determine the error message
let message =
parsedError.errorMessage || parsedError.error_message || parsedError?.code || parsedError.message || parsedError;
Expand Down
Loading
Loading