Skip to content

Commit 8ab1f0f

Browse files
authored
Merge pull request #2659 from contentstack/v2-dev
DX | 27-07-2026 | Release | CLI Beta
2 parents 2f9dcdb + 3170e51 commit 8ab1f0f

39 files changed

Lines changed: 1453 additions & 1990 deletions

‎.talismanrc‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
fileignoreconfig:
22
- filename: pnpm-lock.yaml
3-
checksum: 64ca5ee7000d70e76ba367e3cacc2158e5cd33e41c7844fb6af79837a9469bb6
3+
checksum: 117b66122d3f3043f48724ff9755ec2d4a9dff3c156ceedef753df3014cafee6
4+
- filename: packages/contentstack/src/hooks/init/opt-in-plugin-guide.ts
5+
checksum: 2b079a93988e2439a03c401f75695fcdbfeb138a75c84707e4bc892536680e9f
46
version: '1.0'

‎MIGRATION.md‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,45 @@ csdx cm:entries:publish --content-types blog --environments prod --locales en-us
189189
csdx cm:stacks:bulk-entries --operation publish --content-types blog --environments prod --locales en-us -k blt123
190190
```
191191

192+
### 7. 🚀 Launch Plugin Now Opt-In
193+
194+
**What Changed:**
195+
- The `launch` plugin (`@contentstack/cli-launch`) is no longer bundled with the CLI
196+
- All `launch:*` commands now require installing the plugin explicitly before use
197+
- Brings `launch` in line with the opt-in, modular plugin model used elsewhere in 2.x
198+
199+
> ⚠️ **This is a change relative to the 2.x beta — not only relative to 1.x.** `@contentstack/cli-launch` was bundled throughout the 1.x releases **and** the entire 2.x beta period, so `launch:*` worked out of the box for beta users too. Starting with 2.x GA it becomes opt-in, so beta users who rely on `launch:*` are also affected when they upgrade.
200+
201+
**Before (1.x.x and 2.x.x-beta):**
202+
- `launch:*` commands were bundled with the CLI and available by default after installation
203+
204+
**After (2.x.x GA):**
205+
- `launch:*` commands are provided by a separate, opt-in plugin that must be installed explicitly
206+
- Running any `launch:*` command without the plugin installed fails with a `command not found` error where it previously worked out of the box
207+
208+
**Affected commands:** `launch`, `launch:deployments`, `launch:environments`, `launch:functions`, `launch:logs`, `launch:open`, `launch:rollback`
209+
210+
**Installation Methods:**
211+
212+
213+
**Option 1: Using CLI Plugin Manager**
214+
```bash
215+
csdx plugins:install @contentstack/cli-launch
216+
```
217+
218+
**Option 2: Using npm**
219+
```bash
220+
npm install -g @contentstack/cli-launch
221+
```
222+
223+
**Usage:**
224+
After installation, launch commands will be available through the CLI:
225+
```bash
226+
csdx launch --help
227+
```
228+
229+
**Migration Action:** If you use any `launch:*` command, install the `@contentstack/cli-launch` plugin before (or immediately after) upgrading to 2.x GA to avoid `command not found` errors. This applies to 2.x beta users as well, since `launch` was bundled during the beta.
230+
192231
## Troubleshooting
193232

194233
### Common Issues
@@ -209,6 +248,11 @@ csdx cm:stacks:bulk-entries --operation publish --content-types blog --environme
209248
- The 2.x.x-beta version should be faster due to TypeScript modules
210249
- If you are experiencing issues, switch to console log mode for debugging
211250

251+
**5. `launch:*` command not found:**
252+
- In 2.x GA, `launch` is an opt-in plugin and is no longer bundled (it was bundled in 1.x and during the 2.x beta)
253+
- Install it with `csdx plugins:install @contentstack/cli-launch`, then re-run your `launch:*` command
254+
- Verify it is installed with `csdx plugins`
255+
212256
### Getting Help
213257

214258
**Documentation:**

‎packages/contentstack-auth/package.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@contentstack/cli-auth",
33
"description": "Contentstack CLI plugin for authentication activities",
4-
"version": "2.0.0-beta.16",
4+
"version": "2.0.0-beta.17",
55
"author": "Contentstack",
66
"bugs": "https://github.com/contentstack/cli/issues",
77
"scripts": {
@@ -15,8 +15,8 @@
1515
"lint": "eslint src/**/*.ts"
1616
},
1717
"dependencies": {
18-
"@contentstack/cli-command": "~2.0.0-beta.10",
19-
"@contentstack/cli-utilities": "~2.0.0-beta.11",
18+
"@contentstack/cli-command": "~2.0.0-beta.11",
19+
"@contentstack/cli-utilities": "~2.0.0-beta.12",
2020
"@oclif/core": "^4.11.4",
2121
"otplib": "^12.0.1"
2222
},

‎packages/contentstack-command/package.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@contentstack/cli-command",
33
"description": "Contentstack CLI plugin for configuration",
4-
"version": "2.0.0-beta.10",
4+
"version": "2.0.0-beta.11",
55
"author": "Contentstack",
66
"main": "lib/index.js",
77
"types": "lib/index.d.ts",
@@ -14,7 +14,7 @@
1414
"lint": "eslint src/**/*.ts"
1515
},
1616
"dependencies": {
17-
"@contentstack/cli-utilities": "~2.0.0-beta.11",
17+
"@contentstack/cli-utilities": "~2.0.0-beta.12",
1818
"contentstack": "^3.27.0",
1919
"@oclif/core": "^4.11.4"
2020
},
@@ -66,4 +66,4 @@
6666
"url": "git+https://github.com/contentstack/cli.git",
6767
"directory": "packages/contentstack-command"
6868
}
69-
}
69+
}

‎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/package.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@contentstack/cli-config",
33
"description": "Contentstack CLI plugin for configuration",
4-
"version": "2.0.0-beta.14",
4+
"version": "2.0.0-beta.15",
55
"author": "Contentstack",
66
"scripts": {
77
"build": "pnpm compile && oclif manifest && oclif readme",
@@ -14,8 +14,8 @@
1414
"lint": "eslint src/**/*.ts"
1515
},
1616
"dependencies": {
17-
"@contentstack/cli-command": "~2.0.0-beta.10",
18-
"@contentstack/cli-utilities": "~2.0.0-beta.11",
17+
"@contentstack/cli-command": "~2.0.0-beta.11",
18+
"@contentstack/cli-utilities": "~2.0.0-beta.12",
1919
"@contentstack/utils": "~1.9.1",
2020
"@oclif/core": "^4.11.4"
2121
},
@@ -95,4 +95,4 @@
9595
"url": "git+https://github.com/contentstack/cli.git",
9696
"directory": "packages/contentstack-config"
9797
}
98-
}
98+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
4848
'cs-assets': _flags.string({
4949
description: 'Custom host to set for Contentstack Assets API',
5050
}),
51+
'auth-api': _flags.string({
52+
description: 'Custom host to set for Auth API',
53+
}),
5154
};
5255
static examples = [
5356
'$ csdx config:set:region',
@@ -82,6 +85,7 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
8285
let launchHubUrl = regionSetFlags['launch'];
8386
let composableStudioUrl = regionSetFlags['studio'];
8487
let csAssetsUrl = regionSetFlags['cs-assets'];
88+
let authUrl = regionSetFlags['auth-api'];
8589
let selectedRegion = args.region;
8690
if (!(cda && cma && uiHost && name) && !selectedRegion) {
8791
selectedRegion = await interactive.askRegions();
@@ -115,6 +119,9 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
115119
if (!csAssetsUrl) {
116120
csAssetsUrl = this.transformUrl(cma, 'am-api');
117121
}
122+
if (!authUrl) {
123+
authUrl = this.transformUrl(cma, 'auth-api');
124+
}
118125
let customRegion: Region = {
119126
cda,
120127
cma,
@@ -125,6 +132,17 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
125132
launchHubUrl,
126133
composableStudioUrl,
127134
csAssetsUrl,
135+
endpoints: {
136+
contentManagement: cma,
137+
contentDelivery: cda,
138+
application: uiHost,
139+
developerHub: developerHubUrl,
140+
launch: launchHubUrl,
141+
personalizeManagement: personalizeUrl,
142+
composableStudio: composableStudioUrl,
143+
assetManagement: csAssetsUrl,
144+
auth: authUrl,
145+
},
128146
};
129147
customRegion = regionHandler.setCustomRegion(customRegion);
130148
await authHandler.setConfigData('logout'); //Todo: Handle this logout flow well through logout command call
@@ -137,6 +155,7 @@ export default class RegionSetCommand extends BaseCommand<typeof RegionSetComman
137155
cliux.success(`Launch URL: ${customRegion.launchHubUrl}`);
138156
cliux.success(`Studio URL: ${customRegion.composableStudioUrl}`);
139157
cliux.success(`Contentstack Assets URL: ${customRegion.csAssetsUrl}`);
158+
cliux.success(`Auth API URL: ${customRegion.endpoints?.auth}`);
140159
} catch (error) {
141160
handleAndLogError(error, { ...this.contextDetails, module: 'config-set-region' });
142161
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface Region {
2020
launchHubUrl: string;
2121
composableStudioUrl: string;
2222
csAssetsUrl?: string;
23+
endpoints?: Record<string, string>;
2324
}
2425

2526
export interface Limit {

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

Lines changed: 20 additions & 35 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,28 +22,10 @@ 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-
csAssetsUrl: endpoints.assetManagement,
44-
};
45-
} catch {
46-
return null;
47-
}
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;
4829
}
4930

5031
/**
@@ -147,19 +128,23 @@ class UserConfig {
147128
* @returns { object } JSON object with only valid keys for region
148129
*/
149130
sanitizeRegionObject(regionObject) {
150-
const sanitizedRegion = {
151-
cma: regionObject.cma,
152-
cda: regionObject.cda,
153-
uiHost: regionObject.uiHost,
154-
name: regionObject.name,
155-
developerHubUrl: regionObject['developerHubUrl'],
156-
personalizeUrl: regionObject['personalizeUrl'],
157-
launchHubUrl: regionObject['launchHubUrl'],
158-
composableStudioUrl: regionObject['composableStudioUrl'],
159-
csAssetsUrl: regionObject['csAssetsUrl'],
131+
// endpoints is the single source of truth — every friendly field (cma, cda, ...,
132+
// auth, csAssetsUrl) is derived from it below via buildRegionFromEndpoints, same
133+
// as named regions. Falls back to synthesizing endpoints from the individual raw
134+
// fields 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+
assetManagement: regionObject['csAssetsUrl'],
144+
auth: regionObject['authUrl'],
160145
};
161146

162-
return sanitizedRegion;
147+
return buildRegionFromEndpoints(regionObject.name, endpoints);
163148
}
164149
}
165150

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

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,87 @@ describe('Region command', function () {
313313
csAssetsUrl: 'https://custom-asset-management.com',
314314
};
315315
const result = UserConfig.setCustomRegion(customRegion);
316-
expect(result).to.deep.equal(customRegion);
316+
expect(result.cma).to.equal(customRegion.cma);
317+
expect(result.cda).to.equal(customRegion.cda);
318+
expect(result.uiHost).to.equal(customRegion.uiHost);
319+
expect(result.name).to.equal(customRegion.name);
320+
expect(result.developerHubUrl).to.equal(customRegion.developerHubUrl);
321+
expect(result.personalizeUrl).to.equal(customRegion.personalizeUrl);
322+
expect(result.launchHubUrl).to.equal(customRegion.launchHubUrl);
323+
expect(result.composableStudioUrl).to.equal(customRegion.composableStudioUrl);
324+
});
325+
326+
it('should include a matching endpoints object for a custom region without an auth host', function () {
327+
const customRegion = {
328+
cma: 'https://custom-cma.com',
329+
cda: 'https://custom-cda.com',
330+
uiHost: 'https://custom-ui.com',
331+
name: 'Custom Region',
332+
developerHubUrl: 'https://custom-developer-hub.com',
333+
personalizeUrl: 'https://custom-personalize.com',
334+
launchHubUrl: 'https://custom-launch.com',
335+
composableStudioUrl: 'https://custom-composable-studio.com',
336+
};
337+
const result = UserConfig.setCustomRegion(customRegion);
338+
expect(result.endpoints).to.deep.equal({
339+
contentManagement: customRegion.cma,
340+
contentDelivery: customRegion.cda,
341+
application: customRegion.uiHost,
342+
developerHub: customRegion.developerHubUrl,
343+
launch: customRegion.launchHubUrl,
344+
personalizeManagement: customRegion.personalizeUrl,
345+
composableStudio: customRegion.composableStudioUrl,
346+
assetManagement: undefined,
347+
auth: undefined,
348+
});
349+
});
350+
351+
it('should preserve an explicitly supplied endpoints object for a custom region', function () {
352+
const endpoints = {
353+
contentManagement: 'https://custom-cma.com',
354+
contentDelivery: 'https://custom-cda.com',
355+
application: 'https://custom-ui.com',
356+
auth: 'https://custom-auth.com',
357+
};
358+
const customRegion = {
359+
cma: 'https://custom-cma.com',
360+
cda: 'https://custom-cda.com',
361+
uiHost: 'https://custom-ui.com',
362+
name: 'Custom Region',
363+
endpoints,
364+
};
365+
const result = UserConfig.setCustomRegion(customRegion);
366+
expect(result.cma).to.equal(endpoints.contentManagement);
367+
expect(result.cda).to.equal(endpoints.contentDelivery);
368+
expect(result.uiHost).to.equal(endpoints.application);
369+
expect(result.endpoints).to.deep.equal(endpoints);
370+
});
371+
372+
it('should not fall back to a stale legacy authUrl input once endpoints.auth is supplied', function () {
373+
// endpoints is the single source of truth: a stale/mismatched top-level
374+
// authUrl on the raw input is never used — only endpoints.auth matters.
375+
const customRegion = {
376+
cma: 'https://custom-cma.com',
377+
cda: 'https://custom-cda.com',
378+
uiHost: 'https://custom-ui.com',
379+
name: 'Custom Region',
380+
authUrl: 'https://stale-auth.com',
381+
endpoints: {
382+
contentManagement: 'https://custom-cma.com',
383+
contentDelivery: 'https://custom-cda.com',
384+
application: 'https://custom-ui.com',
385+
auth: 'https://correct-auth.com',
386+
},
387+
};
388+
const result = UserConfig.setCustomRegion(customRegion);
389+
expect(result.endpoints.auth).to.equal('https://correct-auth.com');
390+
});
391+
392+
it('should include the full endpoints passthrough (incl. auth) for named regions', function () {
393+
const result = UserConfig.setRegion('NA');
394+
expect(result.endpoints).to.be.an('object');
395+
expect(result.endpoints.auth).to.equal('https://auth-api.contentstack.com');
396+
expect(result.endpoints.contentManagement).to.equal(result.cma);
317397
});
318398

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

0 commit comments

Comments
 (0)