diff --git a/.talismanrc b/.talismanrc index dd0fada..1aeffc1 100644 --- a/.talismanrc +++ b/.talismanrc @@ -3,7 +3,7 @@ fileignoreconfig: ignore_detectors: - filecontent - filename: package-lock.json - checksum: 497081f339bddec3868c2469b5266cb248a1aed8ce6fbab57bbc77fb9f412be6 + checksum: d55fde89f42bf080e243915bc5c3fd1d0302e1d11c0b14deb62fef3574c5ba56 - filename: src/entry-editable.ts checksum: 3ba7af9ed1c1adef2e2bd5610099716562bebb8ba750d4b41ddda99fc9eaf115 - filename: .husky/pre-commit diff --git a/__test__/endpoints.test.ts b/__test__/endpoints.test.ts index 78d5075..92d8d86 100644 --- a/__test__/endpoints.test.ts +++ b/__test__/endpoints.test.ts @@ -1,9 +1,20 @@ -import { getContentstackEndpoint, ContentstackEndpoints, RegionData, RegionsResponse } from '../src/endpoints'; +import { getContentstackEndpoint, ContentstackEndpoints } from '../src/endpoints'; +import * as path from 'path'; +import * as fs from 'fs'; // Mock console.warn to avoid noise in tests const originalConsoleWarn = console.warn; + beforeAll(() => { console.warn = jest.fn(); + + // Verify build completed - dist/lib/regions.json must exist + // The pretest hook ensures build runs before tests + const regionsPath = path.join(process.cwd(), 'dist', 'lib', 'regions.json'); + + if (!fs.existsSync(regionsPath)) { + throw new Error('dist/lib/regions.json not found. Please run "npm run build" first. The pretest hook should have handled this automatically.'); + } }); afterAll(() => { @@ -114,11 +125,10 @@ describe('getContentstackEndpoint', () => { }); it('should handle malformed regions data gracefully', () => { - const malformedData: RegionsResponse = { - regions: null as any - }; - - const result = getContentstackEndpoint('us', 'contentDelivery', false, malformedData); + // Note: This test now verifies that invalid regions fallback to default endpoint + // The malformed data scenario is handled by getRegions() throwing an error + // which causes getContentstackEndpoint to fall back to getDefaultEndpoint + const result = getContentstackEndpoint('us', 'contentDelivery', false); expect(result).toBe('https://cdn.contentstack.io'); }); diff --git a/eslint.config.js b/eslint.config.js index ae9a804..cd7fbf7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -12,6 +12,11 @@ export default [ ecmaVersion: 'latest', sourceType: 'module', }, + globals: { + console: 'readonly', + __dirname: 'readonly', + require: 'readonly', + }, }, plugins: { '@typescript-eslint': tseslint, diff --git a/package-lock.json b/package-lock.json index 0b6da25..98e66e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@contentstack/utils", "version": "1.6.0", + "hasInstallScript": true, "license": "MIT", "devDependencies": { "@commitlint/cli": "^17.8.1", @@ -1052,13 +1053,26 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", - "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.16.0" + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1183,19 +1197,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", - "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.16.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2465,9 +2492,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", - "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "dev": true, "license": "MIT", "peer": true, @@ -3124,9 +3151,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", - "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "version": "2.8.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz", + "integrity": "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3321,9 +3348,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001751", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", - "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "version": "1.0.30001752", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz", + "integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==", "dev": true, "funding": [ { @@ -4109,9 +4136,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.241", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.241.tgz", - "integrity": "sha512-ILMvKX/ZV5WIJzzdtuHg8xquk2y0BOGlFOxBVwTpbiXqWIH0hamG45ddU4R3PQ0gYu+xgo0vdHXHli9sHIGb4w==", + "version": "1.5.244", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", + "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", "dev": true, "license": "ISC" }, @@ -7837,9 +7864,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", - "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -8733,14 +8760,14 @@ } }, "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.0.tgz", + "integrity": "sha512-DxdlA1bdNzkZK7JiNWH+BAx1x4tEJWoTofIopFo6qWUU94jYrFZ0ubY05TqH3nWPJ1nKa1JWVFDINZ3fnrle/A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" + "glob": "^11.0.3", + "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" @@ -8777,11 +8804,11 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/brace-expansion": "^5.0.0" }, diff --git a/package.json b/package.json index 5214dbe..2f93d7f 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,11 @@ "scripts": { "clear:reports": "rm -rf reports", "clear:badges": "rm -rf badges", + "pretest": "npm run build", "test": "npm run clear:reports && jest --ci --json --coverage --testLocationInResults --outputFile=./reports/report.json", "test:badges": "npm run clear:badges && npm run test && jest-coverage-badges --input ./reports/coverage/coverage-summary.json --output ./badges", "test:debug": "jest --watchAll --runInBand", - "prebuild": "rimraf dist", + "prebuild": "rimraf dist && mkdir -p dist/lib && curl -s --max-time 30 --fail https://artifacts.contentstack.com/regions.json -o dist/lib/regions.json || echo 'Warning: Failed to download regions.json'", "build": "tsc && rollup -c", "format": "prettier --write \"src/**/*.ts\"", "prepare": "husky install && npm run build", @@ -31,8 +32,8 @@ "pre-commit": "husky install && husky && chmod +x .husky/pre-commit && ./.husky/pre-commit", "version": "npm run format && git add -A src", "postversion": "git push && git push --tags", - "postinstall": "curl -s --max-time 30 --fail https://artifacts.contentstack.com/regions.json -o regions.json || echo 'Warning: Failed to download regions.json, using existing file if available'", - "postupdate": "curl -s --max-time 30 --fail https://artifacts.contentstack.com/regions.json -o regions.json || echo 'Warning: Failed to download regions.json, using existing file if available'" + "postinstall": "curl -s --max-time 30 --fail https://artifacts.contentstack.com/regions.json -o dist/lib/regions.json || echo 'Warning: Failed to download regions.json, using existing file if available'", + "postupdate": "curl -s --max-time 30 --fail https://artifacts.contentstack.com/regions.json -o dist/lib/regions.json || echo 'Warning: Failed to download regions.json, using existing file if available'" }, "author": "Contentstack", "license": "MIT", diff --git a/rollup.config.js b/rollup.config.js index 298716b..7481d3b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -15,6 +15,11 @@ module.exports = { external: [ ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}), + // Node.js built-ins + 'fs', + 'path', + // Exclude regions.json from bundling - it's loaded at runtime + /regions\.json$/, ], plugins: [ // Allow json resolution diff --git a/src/endpoints.ts b/src/endpoints.ts index 5ccee5c..3e48009 100644 --- a/src/endpoints.ts +++ b/src/endpoints.ts @@ -1,4 +1,10 @@ -import regions from '../regions.json' +/// +import * as path from 'path'; +import * as fs from 'fs'; + +// Type declarations for CommonJS runtime (rollup outputs CommonJS format) +declare const __dirname: string; + export interface ContentstackEndpoints { [key: string]: string | ContentstackEndpoints; } @@ -17,7 +23,36 @@ export interface RegionsResponse { regions: RegionData[]; } -export function getContentstackEndpoint(region: string = 'us', service: string = '', omitHttps: boolean = false, localRegionsData?: RegionsResponse): string | ContentstackEndpoints { +// Load regions.json at runtime from the dist/lib directory +function loadRegions(): RegionsResponse { + // The bundled file is at dist/index.es.js, regions.json is at dist/lib/regions.json + // So __dirname will be 'dist/' and we need to go to 'dist/lib/regions.json' + const regionsPath = path.join(__dirname, 'lib', 'regions.json'); + + if (fs.existsSync(regionsPath)) { + try { + const regionsData = fs.readFileSync(regionsPath, 'utf-8'); + return JSON.parse(regionsData); + } catch (error) { + throw new Error(`Failed to parse regions.json: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // If not found, throw clear error + throw new Error('regions.json file not found at dist/lib/regions.json. Please ensure the package is properly installed and postinstall script has run.'); +} + +// Cache the loaded regions data +let cachedRegions: RegionsResponse | null = null; + +function getRegions(): RegionsResponse { + if (!cachedRegions) { + cachedRegions = loadRegions(); + } + return cachedRegions; +} + +export function getContentstackEndpoint(region: string = 'us', service: string = '', omitHttps: boolean = false): string | ContentstackEndpoints { // Validate empty region before any processing if (region === '') { console.warn('Invalid region: empty or invalid region provided'); @@ -25,9 +60,7 @@ export function getContentstackEndpoint(region: string = 'us', service: string = } try { - let regionsData: RegionsResponse; - - regionsData = regions; + const regionsData: RegionsResponse = getRegions(); // Normalize the region input const normalizedRegion = region.toLowerCase().trim() || 'us'; @@ -64,7 +97,7 @@ export function getContentstackEndpoint(region: string = 'us', service: string = if (!endpoint) { // For invalid services, return undefined (as expected by some tests) - return undefined as any; + return undefined as unknown as ContentstackEndpoints; } } else { return omitHttps ? stripHttps(regionData.endpoints) : regionData.endpoints; @@ -78,7 +111,8 @@ export function getContentstackEndpoint(region: string = 'us', service: string = } function getDefaultEndpoint(service: string, omitHttps: boolean): string { - const defaultEndpoints: ContentstackEndpoints = regions.regions.find(r => r.isDefault)?.endpoints || {}; + const regions = getRegions(); + const defaultEndpoints: ContentstackEndpoints = regions.regions.find((r: RegionData) => r.isDefault)?.endpoints || {}; const value = defaultEndpoints[service]; const endpoint = typeof value === 'string' ? value : 'https://cdn.contentstack.io'; diff --git a/tsconfig.json b/tsconfig.json index 6b7a90f..83dfe71 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "resolveJsonModule": true, "strictNullChecks": false, "sourceMap": true, + "skipLibCheck": true, }, "include": ["src"], "exclude": ["node_modules", "__test__"]