Skip to content

Commit 24d7cda

Browse files
Merge pull request #2467 from contentstack/v1-dev
DX | 16-03-2026 | Release
2 parents a14f18f + 2b48315 commit 24d7cda

File tree

10 files changed

+189
-35
lines changed

10 files changed

+189
-35
lines changed

.github/workflows/sca-scan.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ jobs:
1515
args: --all-projects --fail-on=all
1616
json: true
1717
continue-on-error: true
18-
- uses: contentstack/sca-policy@main
18+
- uses: contentstack/sca-policy@main

packages/contentstack-auth/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-auth",
33
"description": "Contentstack CLI plugin for authentication activities",
4-
"version": "1.8.0-beta.0",
4+
"version": "1.8.0-beta.1",
55
"author": "Contentstack",
66
"bugs": "https://github.com/contentstack/cli/issues",
77
"scripts": {
@@ -22,8 +22,8 @@
2222
"test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\""
2323
},
2424
"dependencies": {
25-
"@contentstack/cli-command": "~1.8.0-beta.0",
26-
"@contentstack/cli-utilities": "~1.18.0-beta.0",
25+
"@contentstack/cli-command": "~1.8.0-beta.1",
26+
"@contentstack/cli-utilities": "~1.19.0-beta.0",
2727
"@oclif/core": "^4.3.0",
2828
"@oclif/plugin-help": "^6.2.28",
2929
"otplib": "^12.0.1"
@@ -81,4 +81,4 @@
8181
}
8282
},
8383
"repository": "contentstack/cli"
84-
}
84+
}

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": "1.8.0-beta.0",
4+
"version": "1.8.0-beta.1",
55
"author": "Contentstack",
66
"main": "lib/index.js",
77
"types": "lib/index.d.ts",
@@ -20,7 +20,7 @@
2020
"test:unit": "mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.ts\""
2121
},
2222
"dependencies": {
23-
"@contentstack/cli-utilities": "~1.18.0-beta.0",
23+
"@contentstack/cli-utilities": "~1.19.0-beta.0",
2424
"contentstack": "^3.25.3",
2525
"@oclif/core": "^4.3.0",
2626
"@oclif/plugin-help": "^6.2.28"
@@ -65,4 +65,4 @@
6565
"repositoryPrefix": "<%- repo %>/blob/main/packages/contentstack-command/<%- commandPath %>"
6666
},
6767
"repository": "contentstack/cli"
68-
}
68+
}

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": "1.20.0-beta.0",
4+
"version": "1.20.0-beta.1",
55
"author": "Contentstack",
66
"scripts": {
77
"build": "pnpm compile && oclif manifest && oclif readme",
@@ -21,8 +21,8 @@
2121
"test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\""
2222
},
2323
"dependencies": {
24-
"@contentstack/cli-command": "~1.8.0-beta.0",
25-
"@contentstack/cli-utilities": "~1.18.0-beta.0",
24+
"@contentstack/cli-command": "~1.8.0-beta.1",
25+
"@contentstack/cli-utilities": "~1.19.0-beta.0",
2626
"@contentstack/utils": "~1.7.0",
2727
"@oclif/core": "^4.3.0",
2828
"@oclif/plugin-help": "^6.2.28",
@@ -95,4 +95,4 @@
9595
}
9696
},
9797
"repository": "contentstack/cli"
98-
}
98+
}

packages/contentstack-utilities/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/cli-utilities",
3-
"version": "1.18.0-beta.0",
3+
"version": "1.19.0-beta.0",
44
"description": "Utilities for contentstack projects",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",
@@ -82,4 +82,4 @@
8282
"ts-node": "^10.9.2",
8383
"typescript": "^4.9.5"
8484
}
85-
}
85+
}

packages/contentstack-utilities/src/contentstack-management-sdk.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { client, ContentstackClient, ContentstackConfig } from '@contentstack/ma
22
import authHandler from './auth-handler';
33
import { Agent } from 'node:https';
44
import configHandler, { default as configStore } from './config-handler';
5-
import { getProxyConfig } from './proxy-helper';
5+
import { getProxyConfigForHost, resolveRequestHost, clearProxyEnv } from './proxy-helper';
66
import dotenv from 'dotenv';
77

88
dotenv.config();
@@ -17,8 +17,15 @@ class ManagementSDKInitiator {
1717
}
1818

1919
async createAPIClient(config): Promise<ContentstackClient> {
20-
// Get proxy configuration with priority: Environment variables > Global config
21-
const proxyConfig = getProxyConfig();
20+
// Resolve host so NO_PROXY applies even when config.host is omitted (e.g. from region.cma)
21+
const host = resolveRequestHost(config);
22+
// NO_PROXY has priority over HTTP_PROXY/HTTPS_PROXY and config-set proxy
23+
const proxyConfig = getProxyConfigForHost(host);
24+
25+
// When bypassing, clear proxy env immediately so SDK never see it (they may read at init or first request).
26+
if (!proxyConfig) {
27+
clearProxyEnv();
28+
}
2229

2330
const option: ContentstackConfig = {
2431
host: config.host,

packages/contentstack-utilities/src/http-client/client.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,23 @@ import { IHttpClient } from './client-interface';
33
import { HttpResponse } from './http-response';
44
import configStore from '../config-handler';
55
import authHandler from '../auth-handler';
6-
import { hasProxy, getProxyUrl, getProxyConfig } from '../proxy-helper';
6+
import { hasProxy, getProxyUrl, getProxyConfig, getProxyConfigForHost } from '../proxy-helper';
7+
8+
/**
9+
* Derive request host from baseURL or url for NO_PROXY checks.
10+
*/
11+
function getRequestHost(baseURL?: string, url?: string): string | undefined {
12+
const toTry = [baseURL, url].filter(Boolean) as string[];
13+
for (const candidateUrl of toTry) {
14+
try {
15+
const parsed = new URL(candidateUrl.startsWith('http') ? candidateUrl : `https://${candidateUrl}`);
16+
return parsed.hostname || undefined;
17+
} catch {
18+
// Invalid URL; try next candidate (baseURL or url)
19+
}
20+
}
21+
return undefined;
22+
}
723

824
export type HttpClientOptions = {
925
disableEarlyAccessHeaders?: boolean;
@@ -411,9 +427,10 @@ export class HttpClient implements IHttpClient {
411427
}
412428
}
413429

414-
// Configure proxy if available (priority: request.proxy > getProxyConfig())
430+
// Configure proxy if available. NO_PROXY has priority: hosts in NO_PROXY never use proxy.
415431
if (!this.request.proxy) {
416-
const proxyConfig = getProxyConfig();
432+
const host = getRequestHost(this.request.baseURL, url);
433+
const proxyConfig = host ? getProxyConfigForHost(host) : getProxyConfig();
417434
if (proxyConfig) {
418435
this.request.proxy = proxyConfig;
419436
}

packages/contentstack-utilities/src/proxy-helper.ts

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,84 @@ export interface ProxyConfig {
1111
}
1212

1313
/**
14-
* Get proxy configuration with priority: Environment variables > Global config
14+
* Parse NO_PROXY / no_proxy env (both uppercase and lowercase).
15+
* NO_PROXY has priority over HTTP_PROXY/HTTPS_PROXY: hosts in this list never use the proxy.
16+
* Values are hostnames only, comma-separated; leading dot matches subdomains (e.g. .contentstack.io).
17+
* The bypass list is fully dynamic: only env values are used (no hardcoded default).
18+
* @returns List of trimmed entries, or empty array when NO_PROXY/no_proxy is unset
19+
*/
20+
export function getNoProxyList(): string[] {
21+
const raw = process.env.NO_PROXY || process.env.no_proxy || '';
22+
return raw
23+
.split(',')
24+
.map((s) => s.trim())
25+
.filter(Boolean);
26+
}
27+
28+
/**
29+
* Normalize host for NO_PROXY matching: strip protocol/URL, port, lowercase, handle IPv6 brackets.
30+
* Accepts hostname, host:port, or full URL (e.g. https://api.contentstack.io).
31+
*/
32+
function normalizeHost(host: string): string {
33+
if (!host || typeof host !== 'string') return '';
34+
let h = host.trim().toLowerCase();
35+
// If it looks like a URL, extract hostname so NO_PROXY matching works (e.g. region.cma is full URL)
36+
if (h.includes('://')) {
37+
try {
38+
const u = new URL(h);
39+
h = u.hostname;
40+
} catch {
41+
// fall through to port stripping below
42+
}
43+
}
44+
const portIdx = h.lastIndexOf(':');
45+
if (h.startsWith('[')) {
46+
const close = h.indexOf(']');
47+
if (close !== -1 && h.length > close + 1 && h[close + 1] === ':') {
48+
h = h.slice(1, close);
49+
}
50+
} else if (portIdx !== -1) {
51+
const after = h.slice(portIdx + 1);
52+
if (/^\d+$/.test(after)) {
53+
h = h.slice(0, portIdx);
54+
}
55+
}
56+
return h;
57+
}
58+
59+
/**
60+
* Check if the given host should bypass the proxy based on NO_PROXY / no_proxy.
61+
* Supports: exact host, leading-dot subdomain match (e.g. .contentstack.io), and wildcard *.
62+
* @param host - Request hostname (with or without port; will be normalized)
63+
* @returns true if proxy should not be used for this host
64+
*/
65+
export function shouldBypassProxy(host: string): boolean {
66+
const normalized = normalizeHost(host);
67+
if (!normalized) return false;
68+
69+
const list = getNoProxyList();
70+
for (const entry of list) {
71+
const e = entry.trim().toLowerCase();
72+
if (!e) continue;
73+
if (e === '*') return true;
74+
if (e.startsWith('.')) {
75+
const domain = e.slice(1);
76+
if (normalized === domain || normalized.endsWith(e)) return true;
77+
} else {
78+
if (normalized === e) return true;
79+
}
80+
}
81+
return false;
82+
}
83+
84+
/**
85+
* Get proxy configuration. Sources (in order): env (HTTP_PROXY/HTTPS_PROXY), then global config
86+
* from `csdx config:set:proxy --host <host> --port <port> --protocol <protocol>`.
87+
* For per-request use, prefer getProxyConfigForHost(host) so NO_PROXY overrides both sources.
1588
* @returns ProxyConfig object or undefined if no proxy is configured
1689
*/
1790
export function getProxyConfig(): ProxyConfig | undefined {
18-
// Priority 1: Check environment variables (HTTPS_PROXY or HTTP_PROXY)
91+
// Priority 1: Environment variables (HTTPS_PROXY or HTTP_PROXY)
1992
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
2093

2194
if (proxyUrl) {
@@ -46,7 +119,7 @@ export function getProxyConfig(): ProxyConfig | undefined {
46119
}
47120
}
48121

49-
// Priority 2: Check global config store
122+
// Priority 2: Global config (csdx config:set:proxy)
50123
const globalProxyConfig = configStore.get('proxy');
51124
if (globalProxyConfig) {
52125
if (typeof globalProxyConfig === 'object') {
@@ -86,6 +159,63 @@ export function getProxyConfig(): ProxyConfig | undefined {
86159
return undefined;
87160
}
88161

162+
/**
163+
* Get proxy config only when the request host is not in NO_PROXY.
164+
* NO_PROXY has priority over both HTTP_PROXY/HTTPS_PROXY and over proxy set via
165+
* `csdx config:set:proxy` — if the host matches NO_PROXY, no proxy is used.
166+
* Use this for all outbound requests so Contentstack and localhost bypass the proxy when set.
167+
* @param host - Request hostname (e.g. api.contentstack.io or full URL like https://api.contentstack.io)
168+
* @returns ProxyConfig or undefined if proxy is disabled or host should bypass (NO_PROXY)
169+
*/
170+
export function getProxyConfigForHost(host: string): ProxyConfig | undefined {
171+
if (shouldBypassProxy(host)) return undefined;
172+
return getProxyConfig();
173+
}
174+
175+
/**
176+
* Resolve request host for proxy/NO_PROXY checks: config.host or default CMA from region.
177+
* Use when the caller may omit host so NO_PROXY still applies (e.g. from region.cma).
178+
* @param config - Object with optional host (e.g. API client config)
179+
* @returns Host string (hostname or empty)
180+
*/
181+
export function resolveRequestHost(config: { host?: string }): string {
182+
if (config.host) return config.host;
183+
const cma = configStore.get('region')?.cma;
184+
if (cma && typeof cma === 'string') {
185+
if (cma.startsWith('http')) {
186+
try {
187+
const u = new URL(cma);
188+
return u.hostname || cma;
189+
} catch {
190+
return cma;
191+
}
192+
}
193+
return cma;
194+
}
195+
return '';
196+
}
197+
198+
/**
199+
* Temporarily clear proxy-related env vars so SDK/axios cannot use them.
200+
* Call the returned function to restore. Use when creating a client for a host in NO_PROXY.
201+
* @returns Restore function (call to put env back)
202+
*/
203+
export function clearProxyEnv(): () => void {
204+
const saved: Record<string, string | undefined> = {};
205+
const keys = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy', 'ALL_PROXY', 'all_proxy'];
206+
for (const k of keys) {
207+
if (k in process.env) {
208+
saved[k] = process.env[k];
209+
delete process.env[k];
210+
}
211+
}
212+
return () => {
213+
for (const k of keys) {
214+
if (saved[k] !== undefined) process.env[k] = saved[k];
215+
}
216+
};
217+
}
218+
89219
/**
90220
* Check if proxy is configured (from any source)
91221
* @returns true if proxy is configured, false otherwise

packages/contentstack/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@
2424
"dependencies": {
2525
"@contentstack/cli-audit": "~1.19.0-beta.0",
2626
"@contentstack/cli-cm-export": "~1.24.0-beta.0",
27-
"@contentstack/cli-cm-import": "~1.32.0-beta.1",
27+
"@contentstack/cli-cm-import": "~1.32.0-beta.0",
2828
"@contentstack/cli-auth": "~1.8.0-beta.0",
29-
"@contentstack/cli-cm-bootstrap": "~1.19.0-beta.1",
29+
"@contentstack/cli-cm-bootstrap": "~1.19.0-beta.0",
3030
"@contentstack/cli-cm-branches": "~1.7.0-beta.0",
3131
"@contentstack/cli-cm-bulk-publish": "~1.11.0-beta.0",
32-
"@contentstack/cli-cm-clone": "~1.21.0-beta.1",
32+
"@contentstack/cli-cm-clone": "~1.21.0-beta.0",
3333
"@contentstack/cli-cm-export-to-csv": "~1.12.0-beta.0",
3434
"@contentstack/cli-cm-import-setup": "~1.8.0-beta.0",
3535
"@contentstack/cli-cm-migrate-rte": "~1.6.4",
36-
"@contentstack/cli-cm-seed": "~1.15.0-beta.2",
36+
"@contentstack/cli-cm-seed": "~1.15.0-beta.0",
3737
"@contentstack/cli-command": "~1.8.0-beta.0",
3838
"@contentstack/cli-config": "~1.20.0-beta.0",
3939
"@contentstack/cli-launch": "^1.9.6",
4040
"@contentstack/cli-migration": "~1.12.0-beta.0",
41-
"@contentstack/cli-utilities": "~1.18.0-beta.0",
41+
"@contentstack/cli-utilities": "~1.19.0-beta.0",
4242
"@contentstack/cli-variants": "~1.4.0-beta.0",
4343
"@contentstack/management": "~1.27.5",
4444
"@oclif/core": "^4.3.0",
@@ -168,4 +168,4 @@
168168
}
169169
},
170170
"repository": "https://github.com/contentstack/cli"
171-
}
171+
}

0 commit comments

Comments
 (0)