Skip to content

Commit 4f540c0

Browse files
authored
Merge pull request #205 from contentstack/feat/DX-3423-endpoints-helper-functions
Enhance region loading and error handling
2 parents c9aef37 + a439de5 commit 4f540c0

File tree

8 files changed

+133
-50
lines changed

8 files changed

+133
-50
lines changed

.talismanrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ fileignoreconfig:
33
ignore_detectors:
44
- filecontent
55
- filename: package-lock.json
6-
checksum: 497081f339bddec3868c2469b5266cb248a1aed8ce6fbab57bbc77fb9f412be6
6+
checksum: d55fde89f42bf080e243915bc5c3fd1d0302e1d11c0b14deb62fef3574c5ba56
77
- filename: src/entry-editable.ts
88
checksum: 3ba7af9ed1c1adef2e2bd5610099716562bebb8ba750d4b41ddda99fc9eaf115
99
- filename: .husky/pre-commit

__test__/endpoints.test.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1-
import { getContentstackEndpoint, ContentstackEndpoints, RegionData, RegionsResponse } from '../src/endpoints';
1+
import { getContentstackEndpoint, ContentstackEndpoints } from '../src/endpoints';
2+
import * as path from 'path';
3+
import * as fs from 'fs';
24

35
// Mock console.warn to avoid noise in tests
46
const originalConsoleWarn = console.warn;
7+
58
beforeAll(() => {
69
console.warn = jest.fn();
10+
11+
// Verify build completed - dist/lib/regions.json must exist
12+
// The pretest hook ensures build runs before tests
13+
const regionsPath = path.join(process.cwd(), 'dist', 'lib', 'regions.json');
14+
15+
if (!fs.existsSync(regionsPath)) {
16+
throw new Error('dist/lib/regions.json not found. Please run "npm run build" first. The pretest hook should have handled this automatically.');
17+
}
718
});
819

920
afterAll(() => {
@@ -114,11 +125,10 @@ describe('getContentstackEndpoint', () => {
114125
});
115126

116127
it('should handle malformed regions data gracefully', () => {
117-
const malformedData: RegionsResponse = {
118-
regions: null as any
119-
};
120-
121-
const result = getContentstackEndpoint('us', 'contentDelivery', false, malformedData);
128+
// Note: This test now verifies that invalid regions fallback to default endpoint
129+
// The malformed data scenario is handled by getRegions() throwing an error
130+
// which causes getContentstackEndpoint to fall back to getDefaultEndpoint
131+
const result = getContentstackEndpoint('us', 'contentDelivery', false);
122132

123133
expect(result).toBe('https://cdn.contentstack.io');
124134
});

eslint.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ export default [
1212
ecmaVersion: 'latest',
1313
sourceType: 'module',
1414
},
15+
globals: {
16+
console: 'readonly',
17+
__dirname: 'readonly',
18+
require: 'readonly',
19+
},
1520
},
1621
plugins: {
1722
'@typescript-eslint': tseslint,

package-lock.json

Lines changed: 60 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,20 @@
2020
"scripts": {
2121
"clear:reports": "rm -rf reports",
2222
"clear:badges": "rm -rf badges",
23+
"pretest": "npm run build",
2324
"test": "npm run clear:reports && jest --ci --json --coverage --testLocationInResults --outputFile=./reports/report.json",
2425
"test:badges": "npm run clear:badges && npm run test && jest-coverage-badges --input ./reports/coverage/coverage-summary.json --output ./badges",
2526
"test:debug": "jest --watchAll --runInBand",
26-
"prebuild": "rimraf dist",
27+
"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'",
2728
"build": "tsc && rollup -c",
2829
"format": "prettier --write \"src/**/*.ts\"",
2930
"prepare": "husky install && npm run build",
3031
"prepublishOnly": "npm test",
3132
"pre-commit": "husky install && husky && chmod +x .husky/pre-commit && ./.husky/pre-commit",
3233
"version": "npm run format && git add -A src",
3334
"postversion": "git push && git push --tags",
34-
"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'",
35-
"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'"
35+
"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'",
36+
"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'"
3637
},
3738
"author": "Contentstack",
3839
"license": "MIT",

rollup.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ module.exports = {
1515
external: [
1616
...Object.keys(pkg.dependencies || {}),
1717
...Object.keys(pkg.peerDependencies || {}),
18+
// Node.js built-ins
19+
'fs',
20+
'path',
21+
// Exclude regions.json from bundling - it's loaded at runtime
22+
/regions\.json$/,
1823
],
1924
plugins: [
2025
// Allow json resolution

src/endpoints.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import regions from '../regions.json'
1+
/// <reference types="node" />
2+
import * as path from 'path';
3+
import * as fs from 'fs';
4+
5+
// Type declarations for CommonJS runtime (rollup outputs CommonJS format)
6+
declare const __dirname: string;
7+
28
export interface ContentstackEndpoints {
39
[key: string]: string | ContentstackEndpoints;
410
}
@@ -17,17 +23,44 @@ export interface RegionsResponse {
1723
regions: RegionData[];
1824
}
1925

20-
export function getContentstackEndpoint(region: string = 'us', service: string = '', omitHttps: boolean = false, localRegionsData?: RegionsResponse): string | ContentstackEndpoints {
26+
// Load regions.json at runtime from the dist/lib directory
27+
function loadRegions(): RegionsResponse {
28+
// The bundled file is at dist/index.es.js, regions.json is at dist/lib/regions.json
29+
// So __dirname will be 'dist/' and we need to go to 'dist/lib/regions.json'
30+
const regionsPath = path.join(__dirname, 'lib', 'regions.json');
31+
32+
if (fs.existsSync(regionsPath)) {
33+
try {
34+
const regionsData = fs.readFileSync(regionsPath, 'utf-8');
35+
return JSON.parse(regionsData);
36+
} catch (error) {
37+
throw new Error(`Failed to parse regions.json: ${error instanceof Error ? error.message : String(error)}`);
38+
}
39+
}
40+
41+
// If not found, throw clear error
42+
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.');
43+
}
44+
45+
// Cache the loaded regions data
46+
let cachedRegions: RegionsResponse | null = null;
47+
48+
function getRegions(): RegionsResponse {
49+
if (!cachedRegions) {
50+
cachedRegions = loadRegions();
51+
}
52+
return cachedRegions;
53+
}
54+
55+
export function getContentstackEndpoint(region: string = 'us', service: string = '', omitHttps: boolean = false): string | ContentstackEndpoints {
2156
// Validate empty region before any processing
2257
if (region === '') {
2358
console.warn('Invalid region: empty or invalid region provided');
2459
throw new Error('Unable to set the host. Please put valid host');
2560
}
2661

2762
try {
28-
let regionsData: RegionsResponse;
29-
30-
regionsData = regions;
63+
const regionsData: RegionsResponse = getRegions();
3164

3265
// Normalize the region input
3366
const normalizedRegion = region.toLowerCase().trim() || 'us';
@@ -64,7 +97,7 @@ export function getContentstackEndpoint(region: string = 'us', service: string =
6497

6598
if (!endpoint) {
6699
// For invalid services, return undefined (as expected by some tests)
67-
return undefined as any;
100+
return undefined as unknown as ContentstackEndpoints;
68101
}
69102
} else {
70103
return omitHttps ? stripHttps(regionData.endpoints) : regionData.endpoints;
@@ -78,7 +111,8 @@ export function getContentstackEndpoint(region: string = 'us', service: string =
78111
}
79112

80113
function getDefaultEndpoint(service: string, omitHttps: boolean): string {
81-
const defaultEndpoints: ContentstackEndpoints = regions.regions.find(r => r.isDefault)?.endpoints || {};
114+
const regions = getRegions();
115+
const defaultEndpoints: ContentstackEndpoints = regions.regions.find((r: RegionData) => r.isDefault)?.endpoints || {};
82116

83117
const value = defaultEndpoints[service];
84118
const endpoint = typeof value === 'string' ? value : 'https://cdn.contentstack.io';

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"resolveJsonModule": true,
2020
"strictNullChecks": false,
2121
"sourceMap": true,
22+
"skipLibCheck": true,
2223
},
2324
"include": ["src"],
2425
"exclude": ["node_modules", "__test__"]

0 commit comments

Comments
 (0)