diff --git a/.talismanrc b/.talismanrc index df8a173..b7172be 100644 --- a/.talismanrc +++ b/.talismanrc @@ -3,7 +3,7 @@ fileignoreconfig: ignore_detectors: - filecontent - filename: package-lock.json - checksum: af3bffd9f46b71daf5b8dd0b3eace75dfd367e359b11503259dc00a8c7105bf0 + checksum: 59653dc17458f6bddffb6178bfb8f8191ad4715f41a788082a11d3b08e966b4c - filename: .husky/pre-commit checksum: 5baabd7d2c391648163f9371f0e5e9484f8fb90fa2284cfc378732ec3192c193 - filename: test/request.spec.ts @@ -14,4 +14,6 @@ fileignoreconfig: checksum: 08ccd6342b3adbeb7b85309a034b4df4b2ad905a0cc2a3778ab483b61ba41b9e - filename: test/retryPolicy/delivery-sdk-handlers.spec.ts checksum: 6d22d7482aa6dccba5554ae497e5b0c3572357a5cead6f4822ee4428edc12207 +- filename: test/contentstack-core.spec.ts + checksum: 2d1e0f63ad8ea37890de2aa6c7e394c83488888f4a40ad7a71eeba2290b95924 version: "" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cee9d05..41bafda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ ## Change log +### Version: 1.3.8 +#### Date: Jan-12-2026 + - Fix: Add .js extensions to relative imports in ESM build for proper module resolution + - Fix: Change lodash import from named import to default import for ESM compatibility with CommonJS modules + +### Version: 1.3.7 +#### Date: Jan-12-2026 + - Fix: Improve error messages + + +### Version: 1.3.8 +#### Date: Jan-15-2026 + - Fix: Add .js extensions to relative imports in ESM build for proper module resolution + - Fix: Change lodash import from named import to default import for ESM compatibility with CommonJS modules + ### Version: 1.3.7 #### Date: Jan-12-2026 - Fix: Improve error messages diff --git a/README.md b/README.md index 74d78d1..34dc0f4 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,129 @@ +# @contentstack/core + [![Contentstack](https://www.contentstack.com/docs/static/images/contentstack.png)](https://www.contentstack.com/) -## TypeScript Core SDK for Contentstack + +TypeScript Core SDK for Contentstack - A foundational library providing core modules and utilities for Contentstack TypeScript SDKs. + +## About Contentstack Contentstack is a headless CMS with an API-first approach. It is a CMS that developers can use to build powerful cross-platform applications in their favorite languages. Build your application frontend, and Contentstack will take care of the rest. [Read More](https://www.contentstack.com/). -### Prerequisite +## Description -You need Node.js version 4.4.7 or later installed to use the Contentstack TS Core SDK. +This package contains core modules and utilities used by the [Contentstack TypeScript Delivery SDK](https://github.com/contentstack/contentstack-javascript/). It provides essential functionality including HTTP client configuration, error handling, request management, parameter serialization, and retry policies. -## Installation +## Features -``` +- **HTTP Client**: Configurable Axios-based HTTP client with support for custom adapters +- **Error Handling**: Comprehensive error classes for API and Contentstack-specific errors +- **Request Management**: Request handling with interceptors and custom error callbacks +- **Parameter Serialization**: Custom parameter serialization for API requests +- **Retry Policies**: Built-in retry logic for handling rate limits and transient errors +- **TypeScript Support**: Full TypeScript definitions included +- **Multiple Build Formats**: Supports CommonJS, ESM, UMD, and TypeScript declarations + +## Important Note + +**This package is an internal dependency** used by Contentstack TypeScript SDKs. End users should **not** install this package directly. Instead, install the appropriate Contentstack SDK (e.g., [Contentstack TypeScript Delivery SDK](https://github.com/contentstack/contentstack-typescript/)), which will automatically include this package as a dependency. + +## For SDK Developers + +If you are developing or maintaining a Contentstack SDK and need to use this core package directly, you can install it as a dependency: + +```bash npm install @contentstack/core ``` -## Use case +Then import the modules: -This package contains some core modules and utilities used by the [Contentstack Typescript Delivery SDK](https://github.com/contentstack/contentstack-javascript/) SDK. +```typescript +import { + httpClient, + ContentstackError, + ApiError, + // ... other exports +} from '@contentstack/core'; +``` ## Development -### Create the build: +### Prerequisites +- Node.js version 4.4.7 or later + +### Setup + +Clone the repository and install dependencies: + +```bash +git clone https://github.com/contentstack/contentstack-js-core.git +cd contentstack-js-core +npm install ``` + +### Build + +Build all output formats (CommonJS, ESM, UMD, and TypeScript declarations): + +```bash npm run build ``` -### Run Scripts: - -Run the unit tests: +Build specific formats: +```bash +npm run build:cjs # CommonJS +npm run build:esm # ES Modules +npm run build:umd # UMD +npm run build:types # TypeScript declarations ``` + +### Testing + +Run unit tests: + +```bash npm run test ``` -Run the lint tests: +Run linting: -``` +```bash npm run lint ``` -Pack the SDK: +### Packaging -``` +Create a package tarball: + +```bash npm run package -``` \ No newline at end of file +``` + +### Clean + +Clean build artifacts: + +```bash +npm run clean +``` + +## License + +This project is licensed under the MIT License. See the [LICENSE.txt](LICENSE.txt) file for details. + +## Repository + +- **GitHub**: [contentstack/contentstack-js-core](https://github.com/contentstack/contentstack-js-core) + +## Related Projects + +- [Contentstack TypeScript Delivery SDK](https://github.com/contentstack/contentstack-javascript/) + +## Support + +For issues and feature requests, please visit the [GitHub Issues](https://github.com/contentstack/contentstack-js-core/issues) page. + +--- + +Copyright (c) 2016-2025 Contentstack. All rights reserved. diff --git a/package-lock.json b/package-lock.json index f9b8547..8c42a23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/core", - "version": "1.3.7", + "version": "1.3.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/core", - "version": "1.3.7", + "version": "1.3.8", "license": "MIT", "dependencies": { "axios": "^1.12.2", diff --git a/package.json b/package.json index 5675428..b712672 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/core", - "version": "1.3.7", + "version": "1.3.8", "type": "commonjs", "main": "./dist/cjs/src/index.js", "types": "./dist/cjs/src/index.d.ts", diff --git a/src/lib/contentstack-core.ts b/src/lib/contentstack-core.ts index 15a1ac9..69f7b3c 100644 --- a/src/lib/contentstack-core.ts +++ b/src/lib/contentstack-core.ts @@ -1,4 +1,4 @@ -import { cloneDeep } from 'lodash'; +import _ from 'lodash'; import { serialize } from './param-serializer'; import axios, { AxiosRequestHeaders, getAdapter } from 'axios'; import { AxiosInstance, HttpClientParams } from './types'; @@ -20,6 +20,7 @@ export function httpClient(options: HttpClientParams): AxiosInstance { const title = [data.name, data.message].filter((a) => a).join(' - '); console.error(ERROR_MESSAGES.CONSOLE.ERROR_WITH_TITLE(title)); } + return; } if (data !== undefined) { @@ -38,7 +39,7 @@ export function httpClient(options: HttpClientParams): AxiosInstance { const config: HttpClientParams = { ...defaultConfig, - ...cloneDeep(options), + ..._.cloneDeep(options), }; if (config.apiKey && config.headers) { diff --git a/test/contentstack-core.spec.ts b/test/contentstack-core.spec.ts index e69a7d3..c1bb031 100644 --- a/test/contentstack-core.spec.ts +++ b/test/contentstack-core.spec.ts @@ -2,9 +2,8 @@ import { AxiosInstance } from '../src'; import { httpClient } from '../src/lib/contentstack-core'; import MockAdapter from 'axios-mock-adapter'; describe('contentstackCore', () => { - it('should return default config when no config is passed', (done) => { - const client = httpClient({}); - done(); + it('should return default config when no config is passed', () => { + httpClient({}); }); describe('logHandler', () => { @@ -17,7 +16,9 @@ describe('contentstackCore', () => { const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); httpClient({}).defaults.logHandler('error', error); - expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Error - Something went wrong. Review the error details and try again.'); + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'Error: Error - Something went wrong. Review the error details and try again.' + ); consoleErrorSpy.mockRestore(); }); @@ -152,7 +153,7 @@ describe('contentstackCore', () => { it('should call the onError function when an error occurs', async () => { // Suppress expected console.error from network error const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); - + const onError = jest.fn(); const options = { defaultHostname: 'cdn.contentstack.io', @@ -166,7 +167,7 @@ describe('contentstackCore', () => { } catch (error: unknown) { expect(onError).toBeCalledWith(error); } - + consoleErrorSpy.mockRestore(); }); @@ -188,4 +189,74 @@ describe('contentstackCore', () => { expect(client.httpClientParams.onError).not.toBeCalled(); }); }); + + describe('config deep cloning', () => { + it('should properly handle nested objects in params using cloneDeep', () => { + const options = { + defaultHostname: 'example.com', + params: { + environment: 'test', + nested: { + level1: { + level2: { + value: 'deep-nested', + }, + }, + }, + }, + }; + + const instance = httpClient(options); + + // Verify nested structure is properly accessible + // This test ensures cloneDeep is working correctly (ESM import fix) + expect(instance.httpClientParams.params?.nested?.level1?.level2?.value).toBe('deep-nested'); + expect(instance.httpClientParams.params?.environment).toBe('test'); + }); + + it('should handle complex nested structures in params', () => { + const complexOptions = { + defaultHostname: 'example.com', + params: { + environment: 'production', + filters: { + category: { + name: 'tech', + tags: ['javascript', 'typescript'], + }, + }, + }, + }; + + const instance = httpClient(complexOptions); + + // Verify complex nested structure is properly handled + expect(instance.httpClientParams.params?.filters?.category?.name).toBe('tech'); + expect(instance.httpClientParams.params?.filters?.category?.tags).toEqual(['javascript', 'typescript']); + }); + + it('should work correctly with lodash cloneDeep import (ESM compatibility)', () => { + // This test verifies that the lodash import works correctly in ESM + // by ensuring nested object cloning works as expected + const options = { + defaultHostname: 'example.com', + params: { + query: { + type: 'entry', + include: { + count: true, + schema: true, + }, + }, + }, + }; + + const instance = httpClient(options); + + // If cloneDeep wasn't working (due to import issues), this would fail + expect(instance.httpClientParams.params?.query?.type).toBe('entry'); + expect(instance.httpClientParams.params?.query?.include?.count).toBe(true); + expect(instance.httpClientParams.params?.query?.include?.schema).toBe(true); + }); + }); }); diff --git a/tools/postbuild.js b/tools/postbuild.js index 41b8cb6..45a1f37 100644 --- a/tools/postbuild.js +++ b/tools/postbuild.js @@ -5,6 +5,7 @@ const path = require('path'); * Post-build script to create package.json in dist/esm/ * This marks the ESM output directory as ES modules, resolving * the conflict with root package.json's "type": "commonjs" + * Also adds .js extensions to relative imports for ESM compatibility */ const esmPackageJsonPath = path.join(__dirname, '../dist/esm/package.json'); const esmPackageJson = { @@ -19,3 +20,55 @@ if (!fs.existsSync(esmDir)) { // Write the package.json file fs.writeFileSync(esmPackageJsonPath, JSON.stringify(esmPackageJson, null, 2) + '\n', 'utf8'); + +// Fix relative imports to include .js extensions for ESM compatibility +const esmSrcDir = path.join(__dirname, '../dist/esm/src'); + +function fixImportsInFile(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + + // Replace relative imports/exports without .js extension + // Matches: './something' or '../something' but not './something.js' or './something.json' + const fixedContent = content + .replace(/(from\s+['"])(\.\.?\/[^'"]+)(['"])/g, (match, prefix, importPath, suffix) => { + // Skip if already has extension or is a JSON import + if (importPath.endsWith('.js') || importPath.endsWith('.json') || importPath.endsWith('.mjs')) { + return match; + } + + // Add .js extension + return `${prefix}${importPath}.js${suffix}`; + }) + .replace(/(export\s+\*\s+from\s+['"])(\.\.?\/[^'"]+)(['"])/g, (match, prefix, importPath, suffix) => { + // Skip if already has extension or is a JSON import + if (importPath.endsWith('.js') || importPath.endsWith('.json') || importPath.endsWith('.mjs')) { + return match; + } + + // Add .js extension + return `${prefix}${importPath}.js${suffix}`; + }); + + if (content !== fixedContent) { + fs.writeFileSync(filePath, fixedContent, 'utf8'); + } +} + +function processDirectory(dir) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + processDirectory(fullPath); + } else if (entry.isFile() && entry.name.endsWith('.js')) { + fixImportsInFile(fullPath); + } + } +} + +// Process all .js files in the ESM build +if (fs.existsSync(esmSrcDir)) { + processDirectory(esmSrcDir); +}