Skip to content

Commit 3c357c7

Browse files
authored
Merge pull request #2646 from contentstack/feat/org-plan-key-check-v1
Feat/org plan key check v1
2 parents 4a81da7 + 71acdbf commit 3c357c7

30 files changed

Lines changed: 2005 additions & 174 deletions

‎.talismanrc‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
fileignoreconfig:
22
- filename: pnpm-lock.yaml
3-
checksum: ba838353c1277083cb35b5f69cbbe00f0dc5d7d69e49b44c71c3a7a79e95ee73
3+
checksum: 0729e56858f7c98eda5c3021f92e003642fafa28b085dcbc150231c67df7e9e9
4+
- filename: packages/contentstack-utilities/src/feature-status/build-auth-headers.ts
5+
checksum: 9360dfbce41aa9c8a62889f4821c37fc222b04b8b1c552d1dfbcd8f2854d49e0
6+
- filename: packages/contentstack/src/hooks/prerun/plan-guard.ts
7+
checksum: 74eeb5c98e9ae48bf6f2918e75a7cfca23915396ffdd177b8716acba35c287da
8+
- filename: packages/contentstack-config/src/utils/region-handler.ts
9+
checksum: cfa791c43bda0e297d655e159b8d0e8a34c698d6062a2e5e864c199d8f953406
10+
- filename: packages/contentstack-utilities/test/unit/region-endpoints.test.ts
11+
checksum: fe37fa11a72d154e6bfeef38a18039c68cf3cc4446183cb012596ad8c7ea5083
12+
- filename: packages/contentstack-config/test/unit/commands/region.test.ts
13+
checksum: 1ef835ff21d46107d42ea331d95bb346df49bae830658ed3b57146fca3858802
14+
- filename: packages/contentstack-utilities/src/region-endpoints.ts
15+
checksum: a62589aacc21904ea8cf2af499a65cd7356dc76f0b2880e4caa42a25302ecc60
16+
- filename: packages/contentstack-utilities/test/unit/resolve-auth-host.test.ts
17+
checksum: 19557d2905561d3dda94ebd68af342ecc646d2b5bbce3c1552bb9aea90f4bbbc
18+
- filename: packages/contentstack-utilities/test/unit/region-refresh.test.ts
19+
checksum: 1edac51f260df3ce28b538146e607d77742daed2364c25b4a922853b99f6d912
20+
- filename: packages/contentstack/README.md
21+
checksum: f55c2719e1e10de809e89243809c11e2520d9f8a53429039435eb893c5fe03ec
422
version: '1.0'

‎packages/contentstack-auth/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-auth
1818
$ csdx COMMAND
1919
running command...
2020
$ csdx (--version)
21-
@contentstack/cli-auth/1.8.4 darwin-arm64 node-v22.21.1
21+
@contentstack/cli-auth/1.8.4 darwin-arm64 node-v22.13.1
2222
$ csdx --help [COMMAND]
2323
USAGE
2424
$ csdx COMMAND

‎packages/contentstack-command/src/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ abstract class ContentstackCommand extends Command {
1414

1515
get context() {
1616
// @ts-ignore
17-
return this.config.context || {};
17+
return this.config.context || this.config.options?.context || {};
1818
}
1919

2020
get email() {

‎packages/contentstack-config/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-config
1818
$ csdx COMMAND
1919
running command...
2020
$ csdx (--version)
21-
@contentstack/cli-config/1.20.5 darwin-arm64 node-v22.21.1
21+
@contentstack/cli-config/1.20.5 darwin-arm64 node-v22.13.1
2222
$ csdx --help [COMMAND]
2323
USAGE
2424
$ csdx COMMAND
@@ -457,7 +457,7 @@ Set region for CLI
457457
```
458458
USAGE
459459
$ csdx config:set:region [REGION] [-d <value> -m <value> --ui-host <value> -n <value>] [--developer-hub <value>]
460-
[--personalize <value>] [--launch <value>] [--studio <value>]
460+
[--personalize <value>] [--launch <value>] [--studio <value>] [--auth-api <value>]
461461
462462
ARGUMENTS
463463
[REGION] Name for the region
@@ -468,6 +468,7 @@ FLAGS
468468
-m, --cma=<value> Custom host to set for content management API, , if this flag is added then cda, ui-host
469469
and name flags are required
470470
-n, --name=<value> Name for the region, if this flag is added then cda, cma and ui-host flags are required
471+
--auth-api=<value> Custom host to set for Auth API
471472
--developer-hub=<value> Custom host to set for Developer hub API
472473
--launch=<value> Custom host to set for Launch API
473474
--personalize=<value> Custom host to set for Personalize API

‎packages/contentstack-config/src/commands/config/set/region.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
5151
studio: _flags.string({
5252
description: 'Custom host to set for Studio API',
5353
}),
54+
'auth-api': _flags.string({
55+
description: 'Custom host to set for Auth API',
56+
}),
5457
};
5558
static examples = [
5659
'$ csdx config:set:region',
@@ -83,6 +86,7 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
8386
let personalizeUrl = regionSetFlags['personalize'];
8487
let launchHubUrl = regionSetFlags['launch'];
8588
let composableStudioUrl = regionSetFlags['studio'];
89+
let authUrl = regionSetFlags['auth-api'];
8690
let selectedRegion = args.region;
8791
if (!(cda && cma && uiHost && name) && !selectedRegion) {
8892
selectedRegion = await interactive.askRegions();
@@ -113,6 +117,9 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
113117
if (!composableStudioUrl) {
114118
composableStudioUrl = this.transformUrl(cma, 'composable-studio-api');
115119
}
120+
if (!authUrl) {
121+
authUrl = this.transformUrl(cma, 'auth-api');
122+
}
116123
let customRegion: Region = {
117124
cda,
118125
cma,
@@ -122,6 +129,16 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
122129
personalizeUrl,
123130
launchHubUrl,
124131
composableStudioUrl,
132+
endpoints: {
133+
contentManagement: cma,
134+
contentDelivery: cda,
135+
application: uiHost,
136+
developerHub: developerHubUrl,
137+
launch: launchHubUrl,
138+
personalizeManagement: personalizeUrl,
139+
composableStudio: composableStudioUrl,
140+
auth: authUrl,
141+
},
125142
};
126143
customRegion = regionHandler.setCustomRegion(customRegion);
127144
await authHandler.setConfigData('logout'); //Todo: Handle this logout flow well through logout command call
@@ -133,6 +150,7 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
133150
cliux.success(`Personalize URL: ${customRegion.personalizeUrl}`);
134151
cliux.success(`Launch URL: ${customRegion.launchHubUrl}`);
135152
cliux.success(`Studio URL: ${customRegion.composableStudioUrl}`);
153+
cliux.success(`Auth API URL: ${customRegion.endpoints?.auth}`);
136154
} catch (error) {
137155
handleAndLogError(error, { ...this.contextDetails, module: 'config-set-region' });
138156
}

‎packages/contentstack-config/src/interfaces/index.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface Region {
1919
personalizeUrl: string;
2020
launchHubUrl: string;
2121
composableStudioUrl: string;
22+
endpoints?: Record<string, string>;
2223
}
2324

2425
export interface Limit {
@@ -42,4 +43,4 @@ export interface SetRateLimitConfig {
4243

4344
export interface RegionsMap {
4445
[key: string]: Region;
45-
}
46+
}

‎packages/contentstack-config/src/utils/region-handler.ts‎

Lines changed: 21 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { configHandler } from '@contentstack/cli-utilities';
2-
import { getContentstackEndpoint } from '@contentstack/utils';
1+
import { configHandler, resolveCanonicalEndpoints, buildRegionFromEndpoints } from '@contentstack/cli-utilities';
32
import { Region, RegionsMap } from '../interfaces';
43

54
function validURL(str) {
@@ -23,49 +22,21 @@ function validURL(str) {
2322
* @returns {object} Region object with all necessary URLs
2423
*/
2524
function getRegionObject(regionKey: string): Region {
26-
try {
27-
// getContentstackEndpoint handles all aliases defined in regions.json
28-
const endpoints = getContentstackEndpoint(regionKey) as any;
29-
30-
if (typeof endpoints === 'string') {
31-
throw new Error('Invalid endpoint response');
32-
}
33-
34-
return {
35-
name: regionKey,
36-
cma: endpoints.contentManagement,
37-
cda: endpoints.contentDelivery,
38-
uiHost: endpoints.application,
39-
developerHubUrl: endpoints.developerHub,
40-
launchHubUrl: endpoints.launch,
41-
personalizeUrl: endpoints.personalizeManagement,
42-
composableStudioUrl: endpoints.composableStudio,
43-
};
44-
} catch {
45-
return null;
46-
}
25+
// resolveCanonicalEndpoints handles all aliases defined in regions.json
26+
const endpoints = resolveCanonicalEndpoints(regionKey);
27+
if (!endpoints) return null;
28+
return buildRegionFromEndpoints(regionKey, endpoints) as Region;
4729
}
4830

4931
/**
5032
* Get all available regions dynamically
5133
* This creates a regions object similar to the old hardcoded one but using @contentstack/utils
5234
*/
5335
function getAvailableRegions() {
54-
const regionKeys = [
55-
'NA',
56-
'AWS-NA',
57-
'EU',
58-
'AWS-EU',
59-
'AU',
60-
'AWS-AU',
61-
'AZURE-NA',
62-
'AZURE-EU',
63-
'GCP-NA',
64-
'GCP-EU',
65-
];
36+
const regionKeys = ['NA', 'AWS-NA', 'EU', 'AWS-EU', 'AU', 'AWS-AU', 'AZURE-NA', 'AZURE-EU', 'GCP-NA', 'GCP-EU'];
6637

6738
const regions: RegionsMap = {};
68-
39+
6940
for (const key of regionKeys) {
7041
const regionObj = getRegionObject(key);
7142
if (regionObj) {
@@ -157,18 +128,22 @@ class UserConfig {
157128
* @returns { object } JSON object with only valid keys for region
158129
*/
159130
sanitizeRegionObject(regionObject) {
160-
const sanitizedRegion = {
161-
cma: regionObject.cma,
162-
cda: regionObject.cda,
163-
uiHost: regionObject.uiHost,
164-
name: regionObject.name,
165-
developerHubUrl: regionObject['developerHubUrl'],
166-
personalizeUrl: regionObject['personalizeUrl'],
167-
launchHubUrl: regionObject['launchHubUrl'],
168-
composableStudioUrl: regionObject['composableStudioUrl'],
131+
// endpoints is the single source of truth — every friendly field (cma, cda, ...,
132+
// authUrl) is derived from it below via buildRegionFromEndpoints, same as named
133+
// regions. Falls back to synthesizing endpoints from the individual raw fields
134+
// when the caller didn't supply one directly.
135+
const endpoints = regionObject['endpoints'] ?? {
136+
contentManagement: regionObject.cma,
137+
contentDelivery: regionObject.cda,
138+
application: regionObject.uiHost,
139+
developerHub: regionObject['developerHubUrl'],
140+
launch: regionObject['launchHubUrl'],
141+
personalizeManagement: regionObject['personalizeUrl'],
142+
composableStudio: regionObject['composableStudioUrl'],
143+
auth: regionObject['authUrl'],
169144
};
170145

171-
return sanitizedRegion;
146+
return buildRegionFromEndpoints(regionObject.name, endpoints);
172147
}
173148
}
174149

‎packages/contentstack-config/test/unit/commands/region.test.ts‎

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,86 @@ describe('Region command', function () {
299299
composableStudioUrl: 'https://custom-composable-studio.com',
300300
};
301301
const result = UserConfig.setCustomRegion(customRegion);
302-
expect(result).to.deep.equal(customRegion);
302+
expect(result.cma).to.equal(customRegion.cma);
303+
expect(result.cda).to.equal(customRegion.cda);
304+
expect(result.uiHost).to.equal(customRegion.uiHost);
305+
expect(result.name).to.equal(customRegion.name);
306+
expect(result.developerHubUrl).to.equal(customRegion.developerHubUrl);
307+
expect(result.personalizeUrl).to.equal(customRegion.personalizeUrl);
308+
expect(result.launchHubUrl).to.equal(customRegion.launchHubUrl);
309+
expect(result.composableStudioUrl).to.equal(customRegion.composableStudioUrl);
310+
});
311+
312+
it('should include a matching endpoints object for a custom region without an auth host', function () {
313+
const customRegion = {
314+
cma: 'https://custom-cma.com',
315+
cda: 'https://custom-cda.com',
316+
uiHost: 'https://custom-ui.com',
317+
name: 'Custom Region',
318+
developerHubUrl: 'https://custom-developer-hub.com',
319+
personalizeUrl: 'https://custom-personalize.com',
320+
launchHubUrl: 'https://custom-launch.com',
321+
composableStudioUrl: 'https://custom-composable-studio.com',
322+
};
323+
const result = UserConfig.setCustomRegion(customRegion);
324+
expect(result.endpoints).to.deep.equal({
325+
contentManagement: customRegion.cma,
326+
contentDelivery: customRegion.cda,
327+
application: customRegion.uiHost,
328+
developerHub: customRegion.developerHubUrl,
329+
launch: customRegion.launchHubUrl,
330+
personalizeManagement: customRegion.personalizeUrl,
331+
composableStudio: customRegion.composableStudioUrl,
332+
auth: undefined,
333+
});
334+
});
335+
336+
it('should preserve an explicitly supplied endpoints object for a custom region', function () {
337+
const endpoints = {
338+
contentManagement: 'https://custom-cma.com',
339+
contentDelivery: 'https://custom-cda.com',
340+
application: 'https://custom-ui.com',
341+
auth: 'https://custom-auth.com',
342+
};
343+
const customRegion = {
344+
cma: 'https://custom-cma.com',
345+
cda: 'https://custom-cda.com',
346+
uiHost: 'https://custom-ui.com',
347+
name: 'Custom Region',
348+
endpoints,
349+
};
350+
const result = UserConfig.setCustomRegion(customRegion);
351+
expect(result.cma).to.equal(endpoints.contentManagement);
352+
expect(result.cda).to.equal(endpoints.contentDelivery);
353+
expect(result.uiHost).to.equal(endpoints.application);
354+
expect(result.endpoints).to.deep.equal(endpoints);
355+
});
356+
357+
it('should not fall back to a stale legacy authUrl input once endpoints.auth is supplied', function () {
358+
// endpoints is the single source of truth: a stale/mismatched top-level
359+
// authUrl on the raw input is never used — only endpoints.auth matters.
360+
const customRegion = {
361+
cma: 'https://custom-cma.com',
362+
cda: 'https://custom-cda.com',
363+
uiHost: 'https://custom-ui.com',
364+
name: 'Custom Region',
365+
authUrl: 'https://stale-auth.com',
366+
endpoints: {
367+
contentManagement: 'https://custom-cma.com',
368+
contentDelivery: 'https://custom-cda.com',
369+
application: 'https://custom-ui.com',
370+
auth: 'https://correct-auth.com',
371+
},
372+
};
373+
const result = UserConfig.setCustomRegion(customRegion);
374+
expect(result.endpoints.auth).to.equal('https://correct-auth.com');
375+
});
376+
377+
it('should include the full endpoints passthrough (incl. auth) for named regions', function () {
378+
const result = UserConfig.setRegion('NA');
379+
expect(result.endpoints).to.be.an('object');
380+
expect(result.endpoints.auth).to.equal('https://auth-api.contentstack.com');
381+
expect(result.endpoints.contentManagement).to.equal(result.cma);
303382
});
304383

305384
it('should sanitize region object to only include valid properties', function () {

‎packages/contentstack-utilities/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"dependencies": {
3030
"@contentstack/management": "~1.30.1",
3131
"@contentstack/marketplace-sdk": "^1.5.1",
32+
"@contentstack/utils": "~1.9.1",
3233
"@oclif/core": "^4.11.4",
3334
"axios": "^1.16.1",
3435
"chalk": "^4.1.2",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import configHandler from '../config-handler';
2+
3+
export interface AuthHeaders {
4+
[key: string]: string;
5+
}
6+
7+
export function buildAuthHeaders(ctx?: {
8+
managementToken?: string;
9+
apiKey?: string;
10+
authToken?: string;
11+
orgUid?: string;
12+
}): AuthHeaders {
13+
// 1. Management token takes priority (no 'Bearer' prefix — API contract)
14+
if (ctx?.managementToken && ctx?.apiKey) {
15+
return {
16+
Authorization: ctx.managementToken,
17+
api_key: ctx.apiKey,
18+
};
19+
}
20+
21+
// 2. OAuth
22+
const oauthToken = configHandler.get('oauthAccessToken') as string | undefined;
23+
if (oauthToken) {
24+
return { Authorization: `Bearer ${oauthToken}` };
25+
}
26+
27+
// 3. Authtoken + organization_uid
28+
const authtoken = ctx?.authToken ?? (configHandler.get('authtoken') as string | undefined);
29+
const orgUid = ctx?.orgUid ?? (configHandler.get('oauthOrgUid') as string | undefined);
30+
if (authtoken && orgUid) {
31+
return { authtoken, organization_uid: orgUid };
32+
}
33+
34+
// 4. Authtoken + api_key
35+
if (authtoken && ctx?.apiKey) {
36+
return { authtoken, api_key: ctx.apiKey };
37+
}
38+
39+
throw new Error(
40+
'PLAN_CHECK: Cannot build auth headers — no valid credentials available. ' +
41+
'Please log in or provide a management token alias.',
42+
);
43+
}

0 commit comments

Comments
 (0)