diff --git a/packages/contentstack-utilities/src/contentstack-management-sdk.ts b/packages/contentstack-utilities/src/contentstack-management-sdk.ts index 77600cc31f..468996114c 100644 --- a/packages/contentstack-utilities/src/contentstack-management-sdk.ts +++ b/packages/contentstack-utilities/src/contentstack-management-sdk.ts @@ -2,7 +2,12 @@ import { client, ContentstackClient, ContentstackConfig } from '@contentstack/ma import authHandler from './auth-handler'; import { Agent } from 'node:https'; import configHandler, { default as configStore } from './config-handler'; -import { getProxyConfigForHost, resolveRequestHost, clearProxyEnv } from './proxy-helper'; +import { + getProxyConfigForHost, + resolveRequestHost, + clearProxyEnv, + shouldBypassProxy, +} from './proxy-helper'; import dotenv from 'dotenv'; dotenv.config(); @@ -22,13 +27,13 @@ class ManagementSDKInitiator { // NO_PROXY has priority over HTTP_PROXY/HTTPS_PROXY and config-set proxy const proxyConfig = getProxyConfigForHost(host); - // When bypassing, clear proxy env immediately so SDK never see it (they may read at init or first request). - if (!proxyConfig) { + // When NO_PROXY matches, strip proxy env so SDK/axios cannot pick up HTTP_PROXY for this process. + if (host && shouldBypassProxy(host)) { clearProxyEnv(); } const option: ContentstackConfig = { - host: config.host, + host: config.host || host || undefined, maxContentLength: config.maxContentLength || 100000000, maxBodyLength: config.maxBodyLength || 1000000000, maxRequests: 10, @@ -118,7 +123,10 @@ class ManagementSDKInitiator { if (proxyConfig) { option.proxy = proxyConfig; + } else if (host && shouldBypassProxy(host)) { + option.proxy = false; } + // When host is in NO_PROXY, do not add proxy to option at all if (config.endpoint) { option.endpoint = config.endpoint; } diff --git a/packages/contentstack-utilities/src/http-client/client.ts b/packages/contentstack-utilities/src/http-client/client.ts index 2fd213d2ed..c96a890152 100644 --- a/packages/contentstack-utilities/src/http-client/client.ts +++ b/packages/contentstack-utilities/src/http-client/client.ts @@ -3,19 +3,25 @@ import { IHttpClient } from './client-interface'; import { HttpResponse } from './http-response'; import configStore from '../config-handler'; import authHandler from '../auth-handler'; -import { hasProxy, getProxyUrl, getProxyConfig, getProxyConfigForHost } from '../proxy-helper'; +import { + hasProxy, + getProxyUrl, + getProxyConfigForHost, + resolveRequestHost, + shouldBypassProxy, +} from '../proxy-helper'; /** * Derive request host from baseURL or url for NO_PROXY checks. */ function getRequestHost(baseURL?: string, url?: string): string | undefined { const toTry = [baseURL, url].filter(Boolean) as string[]; - for (const candidateUrl of toTry) { + for (const u of toTry) { try { - const parsed = new URL(candidateUrl.startsWith('http') ? candidateUrl : `https://${candidateUrl}`); + const parsed = new URL(u.startsWith('http') ? u : `https://${u}`); return parsed.hostname || undefined; } catch { - // Invalid URL; try next candidate (baseURL or url) + // ignore } } return undefined; @@ -427,12 +433,14 @@ export class HttpClient implements IHttpClient { } } - // Configure proxy if available. NO_PROXY has priority: hosts in NO_PROXY never use proxy. + // Configure proxy if available. NO_PROXY has priority; fall back to region CMA for host resolution. if (!this.request.proxy) { - const host = getRequestHost(this.request.baseURL, url); - const proxyConfig = host ? getProxyConfigForHost(host) : getProxyConfig(); + const host = getRequestHost(this.request.baseURL, url) || resolveRequestHost({}); + const proxyConfig = getProxyConfigForHost(host); if (proxyConfig) { this.request.proxy = proxyConfig; + } else if (host && shouldBypassProxy(host)) { + this.request.proxy = false; } } diff --git a/packages/contentstack-utilities/src/proxy-helper.ts b/packages/contentstack-utilities/src/proxy-helper.ts index 68de79fc8d..7abd8eda74 100644 --- a/packages/contentstack-utilities/src/proxy-helper.ts +++ b/packages/contentstack-utilities/src/proxy-helper.ts @@ -82,44 +82,14 @@ export function shouldBypassProxy(host: string): boolean { } /** - * Get proxy configuration. Sources (in order): env (HTTP_PROXY/HTTPS_PROXY), then global config - * from `csdx config:set:proxy --host --port --protocol `. + * Get proxy configuration. Priority order (per spec): + * 1. Global CLI config from `csdx config:set:proxy --host --port --protocol ` + * 2. Environment variables (HTTPS_PROXY or HTTP_PROXY) * For per-request use, prefer getProxyConfigForHost(host) so NO_PROXY overrides both sources. * @returns ProxyConfig object or undefined if no proxy is configured */ export function getProxyConfig(): ProxyConfig | undefined { - // Priority 1: Environment variables (HTTPS_PROXY or HTTP_PROXY) - const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; - - if (proxyUrl) { - try { - const url = new URL(proxyUrl); - const defaultPort = url.protocol === 'https:' ? 443 : 80; - const port = url.port ? Number.parseInt(url.port, 10) : defaultPort; - - if (!Number.isNaN(port) && port >= 1 && port <= 65535) { - const protocol = url.protocol.replace(':', '') as 'http' | 'https'; - const proxyConfig: ProxyConfig = { - protocol: protocol, - host: url.hostname, - port: port, - }; - - if (url.username || url.password) { - proxyConfig.auth = { - username: url.username, - password: url.password, - }; - } - - return proxyConfig; - } - } catch { - // Invalid URL, continue to check global config - } - } - - // Priority 2: Global config (csdx config:set:proxy) + // Priority 1: Global config (csdx config:set:proxy) const globalProxyConfig = configStore.get('proxy'); if (globalProxyConfig) { if (typeof globalProxyConfig === 'object') { @@ -151,11 +121,42 @@ export function getProxyConfig(): ProxyConfig | undefined { return proxyConfig; } } catch { - // Invalid URL, return undefined + // Invalid URL, continue to check environment } } } + // Priority 2: Environment variables (HTTPS_PROXY or HTTP_PROXY) + const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; + + if (proxyUrl) { + try { + const url = new URL(proxyUrl); + const defaultPort = url.protocol === 'https:' ? 443 : 80; + const port = url.port ? Number.parseInt(url.port, 10) : defaultPort; + + if (!Number.isNaN(port) && port >= 1 && port <= 65535) { + const protocol = url.protocol.replace(':', '') as 'http' | 'https'; + const proxyConfig: ProxyConfig = { + protocol: protocol, + host: url.hostname, + port: port, + }; + + if (url.username || url.password) { + proxyConfig.auth = { + username: url.username, + password: url.password, + }; + } + + return proxyConfig; + } + } catch { + // Invalid URL, return undefined + } + } + return undefined; } @@ -172,27 +173,38 @@ export function getProxyConfigForHost(host: string): ProxyConfig | undefined { return getProxyConfig(); } +function regionCmaHostname(): string { + const cma = configStore.get('region')?.cma; + if (!cma || typeof cma !== 'string') { + return ''; + } + if (cma.startsWith('http')) { + try { + const u = new URL(cma); + return u.hostname || cma; + } catch { + return cma; + } + } + return cma; +} + /** - * Resolve request host for proxy/NO_PROXY checks: config.host or default CMA from region. - * Use when the caller may omit host so NO_PROXY still applies (e.g. from region.cma). - * @param config - Object with optional host (e.g. API client config) - * @returns Host string (hostname or empty) + * Hostname for NO_PROXY / proxy. Prefer `region.cma` when set so callers that pass a + * default SDK host (e.g. bulk-entries -> api.contentstack.io) still match rules like + * `.csnonprod.com` against the real API host (e.g. dev11-api.csnonprod.com). */ export function resolveRequestHost(config: { host?: string }): string { - if (config.host) return config.host; - const cma = configStore.get('region')?.cma; - if (cma && typeof cma === 'string') { - if (cma.startsWith('http')) { - try { - const u = new URL(cma); - return u.hostname || cma; - } catch { - return cma; - } - } - return cma; + const fromRegion = regionCmaHostname(); + if (fromRegion) { + return normalizeHost(fromRegion) || fromRegion; + } + + const raw = config.host?.trim() || ''; + if (!raw) { + return ''; } - return ''; + return normalizeHost(raw) || raw; } /**