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
20 changes: 19 additions & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
fileignoreconfig:
- filename: pnpm-lock.yaml
checksum: ba838353c1277083cb35b5f69cbbe00f0dc5d7d69e49b44c71c3a7a79e95ee73
checksum: 0729e56858f7c98eda5c3021f92e003642fafa28b085dcbc150231c67df7e9e9
- filename: packages/contentstack-utilities/src/feature-status/build-auth-headers.ts
checksum: 9360dfbce41aa9c8a62889f4821c37fc222b04b8b1c552d1dfbcd8f2854d49e0
- filename: packages/contentstack/src/hooks/prerun/plan-guard.ts
checksum: 74eeb5c98e9ae48bf6f2918e75a7cfca23915396ffdd177b8716acba35c287da
- filename: packages/contentstack-config/src/utils/region-handler.ts
checksum: cfa791c43bda0e297d655e159b8d0e8a34c698d6062a2e5e864c199d8f953406
- filename: packages/contentstack-utilities/test/unit/region-endpoints.test.ts
checksum: fe37fa11a72d154e6bfeef38a18039c68cf3cc4446183cb012596ad8c7ea5083
- filename: packages/contentstack-config/test/unit/commands/region.test.ts
checksum: 1ef835ff21d46107d42ea331d95bb346df49bae830658ed3b57146fca3858802
- filename: packages/contentstack-utilities/src/region-endpoints.ts
checksum: a62589aacc21904ea8cf2af499a65cd7356dc76f0b2880e4caa42a25302ecc60
- filename: packages/contentstack-utilities/test/unit/resolve-auth-host.test.ts
checksum: 19557d2905561d3dda94ebd68af342ecc646d2b5bbce3c1552bb9aea90f4bbbc
- filename: packages/contentstack-utilities/test/unit/region-refresh.test.ts
checksum: 1edac51f260df3ce28b538146e607d77742daed2364c25b4a922853b99f6d912
- filename: packages/contentstack/README.md
checksum: f55c2719e1e10de809e89243809c11e2520d9f8a53429039435eb893c5fe03ec
version: '1.0'
2 changes: 1 addition & 1 deletion packages/contentstack-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-auth
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-auth/1.8.4 darwin-arm64 node-v22.21.1
@contentstack/cli-auth/1.8.4 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-command/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ abstract class ContentstackCommand extends Command {

get context() {
// @ts-ignore
return this.config.context || {};
return this.config.context || this.config.options?.context || {};
}

get email() {
Expand Down
5 changes: 3 additions & 2 deletions packages/contentstack-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-config
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-config/1.20.5 darwin-arm64 node-v22.21.1
@contentstack/cli-config/1.20.5 darwin-arm64 node-v22.13.1
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down Expand Up @@ -457,7 +457,7 @@ Set region for CLI
```
USAGE
$ csdx config:set:region [REGION] [-d <value> -m <value> --ui-host <value> -n <value>] [--developer-hub <value>]
[--personalize <value>] [--launch <value>] [--studio <value>]
[--personalize <value>] [--launch <value>] [--studio <value>] [--auth-api <value>]

ARGUMENTS
[REGION] Name for the region
Expand All @@ -468,6 +468,7 @@ FLAGS
-m, --cma=<value> Custom host to set for content management API, , if this flag is added then cda, ui-host
and name flags are required
-n, --name=<value> Name for the region, if this flag is added then cda, cma and ui-host flags are required
--auth-api=<value> Custom host to set for Auth API
--developer-hub=<value> Custom host to set for Developer hub API
--launch=<value> Custom host to set for Launch API
--personalize=<value> Custom host to set for Personalize API
Expand Down
18 changes: 18 additions & 0 deletions packages/contentstack-config/src/commands/config/set/region.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
studio: _flags.string({
description: 'Custom host to set for Studio API',
}),
'auth-api': _flags.string({
description: 'Custom host to set for Auth API',
}),
};
static examples = [
'$ csdx config:set:region',
Expand Down Expand Up @@ -83,6 +86,7 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
let personalizeUrl = regionSetFlags['personalize'];
let launchHubUrl = regionSetFlags['launch'];
let composableStudioUrl = regionSetFlags['studio'];
let authUrl = regionSetFlags['auth-api'];
let selectedRegion = args.region;
if (!(cda && cma && uiHost && name) && !selectedRegion) {
selectedRegion = await interactive.askRegions();
Expand Down Expand Up @@ -113,6 +117,9 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
if (!composableStudioUrl) {
composableStudioUrl = this.transformUrl(cma, 'composable-studio-api');
}
if (!authUrl) {
authUrl = this.transformUrl(cma, 'auth-api');
}
let customRegion: Region = {
cda,
cma,
Expand All @@ -122,6 +129,16 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
personalizeUrl,
launchHubUrl,
composableStudioUrl,
endpoints: {
contentManagement: cma,
contentDelivery: cda,
application: uiHost,
developerHub: developerHubUrl,
launch: launchHubUrl,
personalizeManagement: personalizeUrl,
composableStudio: composableStudioUrl,
auth: authUrl,
},
};
customRegion = regionHandler.setCustomRegion(customRegion);
await authHandler.setConfigData('logout'); //Todo: Handle this logout flow well through logout command call
Expand All @@ -133,6 +150,7 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
cliux.success(`Personalize URL: ${customRegion.personalizeUrl}`);
cliux.success(`Launch URL: ${customRegion.launchHubUrl}`);
cliux.success(`Studio URL: ${customRegion.composableStudioUrl}`);
cliux.success(`Auth API URL: ${customRegion.endpoints?.auth}`);
} catch (error) {
handleAndLogError(error, { ...this.contextDetails, module: 'config-set-region' });
}
Expand Down
3 changes: 2 additions & 1 deletion packages/contentstack-config/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface Region {
personalizeUrl: string;
launchHubUrl: string;
composableStudioUrl: string;
endpoints?: Record<string, string>;
}

export interface Limit {
Expand All @@ -42,4 +43,4 @@ export interface SetRateLimitConfig {

export interface RegionsMap {
[key: string]: Region;
}
}
67 changes: 21 additions & 46 deletions packages/contentstack-config/src/utils/region-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { configHandler } from '@contentstack/cli-utilities';
import { getContentstackEndpoint } from '@contentstack/utils';
import { configHandler, resolveCanonicalEndpoints, buildRegionFromEndpoints } from '@contentstack/cli-utilities';
import { Region, RegionsMap } from '../interfaces';

function validURL(str) {
Expand All @@ -23,49 +22,21 @@ function validURL(str) {
* @returns {object} Region object with all necessary URLs
*/
function getRegionObject(regionKey: string): Region {
try {
// getContentstackEndpoint handles all aliases defined in regions.json
const endpoints = getContentstackEndpoint(regionKey) as any;

if (typeof endpoints === 'string') {
throw new Error('Invalid endpoint response');
}

return {
name: regionKey,
cma: endpoints.contentManagement,
cda: endpoints.contentDelivery,
uiHost: endpoints.application,
developerHubUrl: endpoints.developerHub,
launchHubUrl: endpoints.launch,
personalizeUrl: endpoints.personalizeManagement,
composableStudioUrl: endpoints.composableStudio,
};
} catch {
return null;
}
// resolveCanonicalEndpoints handles all aliases defined in regions.json
const endpoints = resolveCanonicalEndpoints(regionKey);
if (!endpoints) return null;
return buildRegionFromEndpoints(regionKey, endpoints) as Region;
}

/**
* Get all available regions dynamically
* This creates a regions object similar to the old hardcoded one but using @contentstack/utils
*/
function getAvailableRegions() {
const regionKeys = [
'NA',
'AWS-NA',
'EU',
'AWS-EU',
'AU',
'AWS-AU',
'AZURE-NA',
'AZURE-EU',
'GCP-NA',
'GCP-EU',
];
const regionKeys = ['NA', 'AWS-NA', 'EU', 'AWS-EU', 'AU', 'AWS-AU', 'AZURE-NA', 'AZURE-EU', 'GCP-NA', 'GCP-EU'];

const regions: RegionsMap = {};

for (const key of regionKeys) {
const regionObj = getRegionObject(key);
if (regionObj) {
Expand Down Expand Up @@ -157,18 +128,22 @@ class UserConfig {
* @returns { object } JSON object with only valid keys for region
*/
sanitizeRegionObject(regionObject) {
const sanitizedRegion = {
cma: regionObject.cma,
cda: regionObject.cda,
uiHost: regionObject.uiHost,
name: regionObject.name,
developerHubUrl: regionObject['developerHubUrl'],
personalizeUrl: regionObject['personalizeUrl'],
launchHubUrl: regionObject['launchHubUrl'],
composableStudioUrl: regionObject['composableStudioUrl'],
// endpoints is the single source of truth — every friendly field (cma, cda, ...,
// authUrl) is derived from it below via buildRegionFromEndpoints, same as named
// regions. Falls back to synthesizing endpoints from the individual raw fields
// when the caller didn't supply one directly.
const endpoints = regionObject['endpoints'] ?? {
contentManagement: regionObject.cma,
contentDelivery: regionObject.cda,
application: regionObject.uiHost,
developerHub: regionObject['developerHubUrl'],
launch: regionObject['launchHubUrl'],
personalizeManagement: regionObject['personalizeUrl'],
composableStudio: regionObject['composableStudioUrl'],
auth: regionObject['authUrl'],
};

return sanitizedRegion;
return buildRegionFromEndpoints(regionObject.name, endpoints);
}
}

Expand Down
81 changes: 80 additions & 1 deletion packages/contentstack-config/test/unit/commands/region.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,86 @@ describe('Region command', function () {
composableStudioUrl: 'https://custom-composable-studio.com',
};
const result = UserConfig.setCustomRegion(customRegion);
expect(result).to.deep.equal(customRegion);
expect(result.cma).to.equal(customRegion.cma);
expect(result.cda).to.equal(customRegion.cda);
expect(result.uiHost).to.equal(customRegion.uiHost);
expect(result.name).to.equal(customRegion.name);
expect(result.developerHubUrl).to.equal(customRegion.developerHubUrl);
expect(result.personalizeUrl).to.equal(customRegion.personalizeUrl);
expect(result.launchHubUrl).to.equal(customRegion.launchHubUrl);
expect(result.composableStudioUrl).to.equal(customRegion.composableStudioUrl);
});

it('should include a matching endpoints object for a custom region without an auth host', function () {
const customRegion = {
cma: 'https://custom-cma.com',
cda: 'https://custom-cda.com',
uiHost: 'https://custom-ui.com',
name: 'Custom Region',
developerHubUrl: 'https://custom-developer-hub.com',
personalizeUrl: 'https://custom-personalize.com',
launchHubUrl: 'https://custom-launch.com',
composableStudioUrl: 'https://custom-composable-studio.com',
};
const result = UserConfig.setCustomRegion(customRegion);
expect(result.endpoints).to.deep.equal({
contentManagement: customRegion.cma,
contentDelivery: customRegion.cda,
application: customRegion.uiHost,
developerHub: customRegion.developerHubUrl,
launch: customRegion.launchHubUrl,
personalizeManagement: customRegion.personalizeUrl,
composableStudio: customRegion.composableStudioUrl,
auth: undefined,
});
});

it('should preserve an explicitly supplied endpoints object for a custom region', function () {
const endpoints = {
contentManagement: 'https://custom-cma.com',
contentDelivery: 'https://custom-cda.com',
application: 'https://custom-ui.com',
auth: 'https://custom-auth.com',
};
const customRegion = {
cma: 'https://custom-cma.com',
cda: 'https://custom-cda.com',
uiHost: 'https://custom-ui.com',
name: 'Custom Region',
endpoints,
};
const result = UserConfig.setCustomRegion(customRegion);
expect(result.cma).to.equal(endpoints.contentManagement);
expect(result.cda).to.equal(endpoints.contentDelivery);
expect(result.uiHost).to.equal(endpoints.application);
expect(result.endpoints).to.deep.equal(endpoints);
});

it('should not fall back to a stale legacy authUrl input once endpoints.auth is supplied', function () {
// endpoints is the single source of truth: a stale/mismatched top-level
// authUrl on the raw input is never used — only endpoints.auth matters.
const customRegion = {
cma: 'https://custom-cma.com',
cda: 'https://custom-cda.com',
uiHost: 'https://custom-ui.com',
name: 'Custom Region',
authUrl: 'https://stale-auth.com',
endpoints: {
contentManagement: 'https://custom-cma.com',
contentDelivery: 'https://custom-cda.com',
application: 'https://custom-ui.com',
auth: 'https://correct-auth.com',
},
};
const result = UserConfig.setCustomRegion(customRegion);
expect(result.endpoints.auth).to.equal('https://correct-auth.com');
});

it('should include the full endpoints passthrough (incl. auth) for named regions', function () {
const result = UserConfig.setRegion('NA');
expect(result.endpoints).to.be.an('object');
expect(result.endpoints.auth).to.equal('https://auth-api.contentstack.com');
expect(result.endpoints.contentManagement).to.equal(result.cma);
});

it('should sanitize region object to only include valid properties', function () {
Expand Down
1 change: 1 addition & 0 deletions packages/contentstack-utilities/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"dependencies": {
"@contentstack/management": "~1.30.1",
"@contentstack/marketplace-sdk": "^1.5.1",
"@contentstack/utils": "~1.9.1",
"@oclif/core": "^4.11.4",
"axios": "^1.16.1",
"chalk": "^4.1.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import configHandler from '../config-handler';

export interface AuthHeaders {
[key: string]: string;
}

export function buildAuthHeaders(ctx?: {
managementToken?: string;
apiKey?: string;
authToken?: string;
orgUid?: string;
}): AuthHeaders {
// 1. Management token takes priority (no 'Bearer' prefix — API contract)
if (ctx?.managementToken && ctx?.apiKey) {
return {
Authorization: ctx.managementToken,
api_key: ctx.apiKey,
};
}

// 2. OAuth
const oauthToken = configHandler.get('oauthAccessToken') as string | undefined;
if (oauthToken) {
return { Authorization: `Bearer ${oauthToken}` };
}

// 3. Authtoken + organization_uid
const authtoken = ctx?.authToken ?? (configHandler.get('authtoken') as string | undefined);
const orgUid = ctx?.orgUid ?? (configHandler.get('oauthOrgUid') as string | undefined);
if (authtoken && orgUid) {
return { authtoken, organization_uid: orgUid };
}

// 4. Authtoken + api_key
if (authtoken && ctx?.apiKey) {
return { authtoken, api_key: ctx.apiKey };
}

throw new Error(
'PLAN_CHECK: Cannot build auth headers — no valid credentials available. ' +
'Please log in or provide a management token alias.',
);
}
Loading
Loading