@sap/cli-core 2025.24.0 → 2026.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 2026.3.0
9
+
10
+ ### Fixed
11
+
12
+ - Fixed Windows compatibility issue where browser discovery would fail with "win32 is not supported" error when encountering platform-specific browsers like Safari.
13
+
14
+ ## 2026.1.0
15
+
16
+ ### Changed
17
+
18
+ - **TLS 1.3 is now the default protocol** for all HTTPS connections to meet NS2/SC compliance requirements
19
+ - **Automatic TLS fallback mechanism** retries with TLS 1.2 when servers don't support TLS 1.3
20
+
8
21
  ## 2025.23.0
9
22
 
10
23
  ### Added
@@ -1,11 +1,12 @@
1
1
  import { set as setConfig, get as getConfig } from "../../config/index.js";
2
- import { OPTION_HOST, OPTION_OPTIONS_FILE, OPTION_VERBOSE, } from "../../constants.js";
2
+ import { OPTION_HOST, OPTION_OPTIONS_FILE, OPTION_TLS_VERSION, OPTION_VERBOSE, } from "../../constants.js";
3
3
  import { getInfoFromTenant, parseTenant } from "../../utils/utils.js";
4
4
  import { create as createNextHandler } from "./next.js";
5
5
  import { create as createOptionsHandler } from "./options/index.js";
6
6
  const MANDATORY_OPTIONS = [
7
7
  OPTION_VERBOSE,
8
8
  OPTION_OPTIONS_FILE,
9
+ OPTION_TLS_VERSION,
9
10
  { ...OPTION_HOST, required: true },
10
11
  ];
11
12
  const post = async () => async () => {
@@ -1,7 +1,7 @@
1
1
  import { kebabCase } from "lodash-es";
2
2
  import { set as setConfig } from "../../config/index.js";
3
3
  import { getBooleanOption, parseOption } from "./utils.js";
4
- import { OPTION_VERBOSE } from "../../constants.js";
4
+ import { OPTION_TLS_VERSION, OPTION_VERBOSE } from "../../constants.js";
5
5
  import { get as getLogger } from "../../logger/index.js";
6
6
  export const create = (handlerArgs) => async () => async (...args) => {
7
7
  const { debug } = getLogger("commands.handler.parseArguments");
@@ -13,6 +13,7 @@ export const create = (handlerArgs) => async () => async (...args) => {
13
13
  ? handlerArgs.reduce((p, c, i) => ({ ...p, [c.argument]: args[i] }), {})
14
14
  : {};
15
15
  const verbose = getBooleanOption(options[OPTION_VERBOSE.longName]);
16
- debug("setting command: %s, options: %s, arguments: %s, verbose: %s", command, JSON.stringify(options), JSON.stringify(commandArgs), verbose);
16
+ const tlsVersion = parseOption(options[OPTION_TLS_VERSION.longName]);
17
+ debug("setting command: %s, options: %s, arguments: %s, verbose: %s, tlsVersion: %s", command, JSON.stringify(options), JSON.stringify(commandArgs), verbose, tlsVersion);
17
18
  setConfig({ command, options, arguments: commandArgs, verbose });
18
19
  };
package/constants.js CHANGED
@@ -2,7 +2,7 @@ import path from "path";
2
2
  import { GrantType } from "./types.js";
3
3
  import { getVersion } from "./utils/utils.js";
4
4
  export const VERSION = getVersion();
5
- export const DEFAULT_TLS_VERSION = "TLSv1.2";
5
+ export const DEFAULT_TLS_VERSION = "TLSv1.3";
6
6
  export const DISCOVERY_DOCUMENT_PREFIX = "discovery-";
7
7
  export var AuthenticationMethod;
8
8
  (function (AuthenticationMethod) {
@@ -226,7 +226,7 @@ export const OPTION_TLS_VERSION = {
226
226
  longName: "tls-version",
227
227
  description: "specifies the TLS version to use for HTTPS connections",
228
228
  args: [{ name: "version" }],
229
- choices: [DEFAULT_TLS_VERSION, "TLSv1.3"],
229
+ choices: [DEFAULT_TLS_VERSION, "TLSv1.2"],
230
230
  default: DEFAULT_TLS_VERSION,
231
231
  hidden: true,
232
232
  };
package/dwc/dwc.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "path";
2
2
  import { get } from "../logger/index.js";
3
- import { CLI_DESCRIPTION, CLI_NAME, CLI_SAP_HELP, OPTION_FILE_PATH, OPTION_FORCE, OPTION_HELP, OPTION_HOST, OPTION_INPUT, OPTION_OPTIONS_FILE, OPTION_OUTPUT, OPTION_VERBOSE, OPTION_VERSION, ROOT_COMMAND, } from "../constants.js";
3
+ import { CLI_DESCRIPTION, CLI_NAME, CLI_SAP_HELP, OPTION_FILE_PATH, OPTION_FORCE, OPTION_HELP, OPTION_HOST, OPTION_INPUT, OPTION_OPTIONS_FILE, OPTION_OUTPUT, OPTION_TLS_VERSION, OPTION_VERBOSE, OPTION_VERSION, ROOT_COMMAND, } from "../constants.js";
4
4
  import { init as initCache } from "../cache/index.js";
5
5
  import { get as getConfig, set as setConfig, setCustomConfig, initialize as initializeConfig, } from "../config/index.js";
6
6
  import { buildOption, createCommand, registerLongName, } from "../utils/commands.js";
@@ -25,6 +25,7 @@ const MANDATORY_OPTIONS = [
25
25
  OPTION_FORCE,
26
26
  OPTION_OUTPUT,
27
27
  OPTION_VERBOSE,
28
+ OPTION_TLS_VERSION,
28
29
  OPTION_INPUT,
29
30
  OPTION_OPTIONS_FILE,
30
31
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cli-core",
3
- "version": "2025.24.0",
3
+ "version": "2026.2.1",
4
4
  "description": "Command-Line Interface (CLI) Core Module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "SAP SE",
@@ -19,18 +19,18 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "ajv": "8.17.1",
22
- "axios": "1.13.1",
22
+ "axios": "1.13.2",
23
23
  "commander": "12.1.0",
24
24
  "compare-versions": "6.1.1",
25
25
  "config": "4.1.1",
26
26
  "dotenv": "17.2.3",
27
- "form-data": "4.0.4",
27
+ "form-data": "4.0.5",
28
28
  "fs-extra": "11.3.2",
29
29
  "https": "1.0.0",
30
30
  "https-proxy-agent": "7.0.6",
31
31
  "jszip": "3.10.1",
32
32
  "lodash-es": "4.17.21",
33
- "open": "10.2.0",
33
+ "open": "11.0.0",
34
34
  "path": "0.12.7",
35
35
  "prompts": "2.4.2",
36
36
  "qs": "6.14.0"
package/types.js CHANGED
@@ -37,7 +37,7 @@ export const CHARACTERS = [
37
37
  "e",
38
38
  "f",
39
39
  "g",
40
- "h",
40
+ // "h", skip 'h' to avoid confusion with '--help' option
41
41
  "i",
42
42
  "j",
43
43
  "k",
@@ -3,4 +3,5 @@ import tls from "tls";
3
3
  export declare const getTlsVersion: () => tls.SecureVersion;
4
4
  export declare const isLegacyTlsDetectionEnabled: () => boolean;
5
5
  export declare const createTlsAgentOptions: () => https.AgentOptions;
6
+ export declare const isTlsProtocolError: (error: unknown) => boolean;
6
7
  export declare const createHttpsAgent: (proxy?: string) => https.Agent;
@@ -28,6 +28,17 @@ export const createTlsAgentOptions = () => {
28
28
  debug("HTTPS agent options:", JSON.stringify(agentOptions, null, 2));
29
29
  return agentOptions;
30
30
  };
31
+ export const isTlsProtocolError = (error) => {
32
+ // Check for TLS protocol errors that indicate unsupported TLS version
33
+ const message = error?.message?.toLowerCase() || "";
34
+ const code = error?.code || "";
35
+ return (code === "EPROTO" ||
36
+ code === "ERR_SSL_UNSUPPORTED_PROTOCOL" ||
37
+ message.includes("unsupported protocol") ||
38
+ message.includes("wrong version number") ||
39
+ message.includes("ssl routines") ||
40
+ message.includes("tlsv1 alert protocol version"));
41
+ };
31
42
  export const createHttpsAgent = (proxy) => {
32
43
  const { debug } = getLogger();
33
44
  const agentOptions = createTlsAgentOptions();
@@ -1,10 +1,12 @@
1
1
  import axios from "axios";
2
+ import https from "https";
3
+ import { HttpsProxyAgent } from "https-proxy-agent";
2
4
  import { getPackageName, getVersion } from "../../config/core.js";
3
5
  import { get as getConfig, set as setConfig } from "../../config/index.js";
4
- import { X_CSRF_TOKEN } from "../../constants.js";
6
+ import { DEFAULT_TLS_VERSION, X_CSRF_TOKEN } from "../../constants.js";
5
7
  import { get } from "../../logger/index.js";
6
8
  import { logVerbose } from "../../logger/utils.js";
7
- import { createHttpsAgent, getTlsVersion, isLegacyTlsDetectionEnabled, } from "./httpsAgent.js";
9
+ import { createHttpsAgent, getTlsVersion, isLegacyTlsDetectionEnabled, isTlsProtocolError, } from "./httpsAgent.js";
8
10
  import { printCorrelationId, printError } from "./utils.js";
9
11
  export const DEFAULTS = {
10
12
  maxBodyLength: -1,
@@ -26,51 +28,121 @@ const getCsrfTokenFromConfig = () => {
26
28
  const cnfg = getConfig();
27
29
  return cnfg[X_CSRF_TOKEN] ? { [X_CSRF_TOKEN]: cnfg[X_CSRF_TOKEN] } : {};
28
30
  };
29
- export const fetch = async (config) => {
31
+ const createHttpsAgentWithTlsVersion = (proxy, tlsVersion) => {
32
+ const agentOptions = {
33
+ minVersion: tlsVersion,
34
+ maxVersion: tlsVersion,
35
+ rejectUnauthorized: true,
36
+ };
37
+ if (proxy) {
38
+ return new HttpsProxyAgent(proxy, agentOptions);
39
+ }
40
+ return new https.Agent(agentOptions);
41
+ };
42
+ const logProxyConfiguration = (proxy) => {
43
+ const logger = getLogger();
44
+ if (proxy) {
45
+ logVerbose(logger, "using https proxy agent for https proxy");
46
+ logger.debug(`using https proxy agent for https proxy ${proxy}`);
47
+ }
48
+ else {
49
+ logger.debug("no https proxy defined via environment variable https_proxy");
50
+ }
51
+ };
52
+ const logTlsConfiguration = () => {
53
+ const logger = getLogger();
54
+ const tlsVersion = getTlsVersion();
55
+ const useLegacyTls = isLegacyTlsDetectionEnabled();
56
+ logger.debug(`TLS configuration - version: ${tlsVersion}, legacy detection: ${useLegacyTls}`);
57
+ };
58
+ const buildAxiosConfig = (config, httpsAgent) => {
59
+ return {
60
+ httpsAgent,
61
+ proxy: false,
62
+ ...config,
63
+ ...DEFAULTS,
64
+ headers: {
65
+ ...getCsrfTokenFromConfig(),
66
+ ...config.headers,
67
+ "User-Agent": `${getPackageName()}+${getVersion()}`,
68
+ },
69
+ };
70
+ };
71
+ const logSuccessResponse = (response) => {
30
72
  const cnfg = getConfig();
31
73
  const logger = getLogger();
32
- const { debug, trace, error } = logger;
74
+ if (cnfg.verbose) {
75
+ logVerbose(logger, "%d %s", response.status, response.statusText);
76
+ printCorrelationId(response);
77
+ }
78
+ logger.trace("response", response);
79
+ };
80
+ const logErrorResponse = (err) => {
81
+ const cnfg = getConfig();
82
+ const logger = getLogger();
83
+ logger.error("error while executing http request", err.toString(), err.response);
84
+ if (cnfg.verbose) {
85
+ printError(err);
86
+ printCorrelationId(err.response);
87
+ }
88
+ };
89
+ const shouldAttemptTlsFallback = (error) => {
90
+ const tlsVersion = getTlsVersion();
91
+ const isUsingDefaultTls = tlsVersion === DEFAULT_TLS_VERSION;
92
+ const isTlsError = isTlsProtocolError(error);
93
+ return isUsingDefaultTls && isTlsError && !isLegacyTlsDetectionEnabled();
94
+ };
95
+ const executeRequest = async (config, httpsAgent) => {
96
+ const axiosConfig = buildAxiosConfig(config, httpsAgent);
97
+ return axios(axiosConfig);
98
+ };
99
+ const attemptTlsFallback = async (config, originalError) => {
100
+ const logger = getLogger();
101
+ const { debug, warn, error } = logger;
102
+ warn("TLSv1.3 connection failed with protocol error, automatically retrying with TLSv1.2");
103
+ debug("original error:", originalError?.message ??
104
+ JSON.stringify(originalError));
33
105
  try {
34
- logVerbose(logger, "%s %s", config.method.toUpperCase(), config.url);
35
- debug("http config: %s", JSON.stringify(config));
36
106
  const HTTPS_PROXY = process.env.https_proxy ?? process.env.HTTPS_PROXY;
37
- const httpsAgent = createHttpsAgent(HTTPS_PROXY);
38
- if (HTTPS_PROXY) {
39
- logVerbose(logger, "using https proxy agent for https proxy");
40
- debug(`using https proxy agent for https proxy ${HTTPS_PROXY}`);
41
- }
42
- else {
43
- debug("no https proxy defined via environment variable https_proxy");
44
- }
45
- const tlsVersion = getTlsVersion();
46
- const useLegacyTls = isLegacyTlsDetectionEnabled();
47
- debug(`TLS configuration - version: ${tlsVersion}, legacy detection: ${useLegacyTls}`);
48
- const res = await axios({
49
- httpsAgent,
50
- proxy: false,
51
- ...config,
52
- ...DEFAULTS,
53
- headers: {
54
- ...getCsrfTokenFromConfig(),
55
- ...config.headers,
56
- "User-Agent": `${getPackageName()}+${getVersion()}`,
57
- },
58
- });
107
+ const fallbackAgent = createHttpsAgentWithTlsVersion(HTTPS_PROXY, "TLSv1.2");
108
+ debug("retrying request with TLSv1.2");
109
+ const res = await executeRequest(config, fallbackAgent);
110
+ const cnfg = getConfig();
59
111
  if (cnfg.verbose) {
60
- logVerbose(logger, "%d %s", res.status, res.statusText);
112
+ logVerbose(logger, "%d %s (fallback to TLSv1.2)", res.status, res.statusText);
61
113
  printCorrelationId(res);
62
114
  }
63
- trace("response", res);
115
+ debug("TLSv1.2 fallback successful");
116
+ logger.trace("response", res);
117
+ return res;
118
+ }
119
+ catch (fallbackErr) {
120
+ error("TLSv1.2 fallback also failed", fallbackErr.toString(), fallbackErr.response);
121
+ throw fallbackErr;
122
+ }
123
+ };
124
+ export const fetch = async (config) => {
125
+ const logger = getLogger();
126
+ try {
127
+ logVerbose(logger, "%s %s", config.method.toUpperCase(), config.url);
128
+ logger.debug("http config: %s", JSON.stringify(config));
129
+ const HTTPS_PROXY = process.env.https_proxy ?? process.env.HTTPS_PROXY;
130
+ const httpsAgent = createHttpsAgent(HTTPS_PROXY);
131
+ logProxyConfiguration(HTTPS_PROXY);
132
+ logTlsConfiguration();
133
+ const res = await executeRequest(config, httpsAgent);
134
+ logSuccessResponse(res);
64
135
  setEtag(res.headers);
65
136
  return res;
66
137
  }
67
138
  catch (err) {
68
- error("error while executing http request", err.toString(), err.response);
69
- if (cnfg.verbose) {
70
- printError(err);
71
- printCorrelationId(err.response);
139
+ logErrorResponse(err);
140
+ setEtag(err?.response?.headers);
141
+ if (shouldAttemptTlsFallback(err)) {
142
+ const fallbackResponse = await attemptTlsFallback(config, err);
143
+ setEtag(fallbackResponse.headers);
144
+ return fallbackResponse;
72
145
  }
73
- setEtag(err.response?.headers);
74
146
  throw err;
75
147
  }
76
148
  };
@@ -4,13 +4,26 @@ import { getOptionValueFromConfig } from "./options.js";
4
4
  const getLogger = () => getLoggerOrig("utils.open");
5
5
  export async function getSupportedBrowsers() {
6
6
  const { apps } = await import("open");
7
- return (Object.entries(apps)
8
- // Private browser is not supported
9
- .filter(([name]) => name !== "browserPrivate")
10
- .map(([key, value]) => ({
11
- name: key,
12
- app: value,
13
- })));
7
+ const browsers = [];
8
+ // Iterate over app keys and safely access each value
9
+ // Some apps (like safari) may only be available on certain platforms
10
+ for (const key of Object.keys(apps)) {
11
+ const name = key;
12
+ // Skip private browser
13
+ if (name !== "browserPrivate") {
14
+ try {
15
+ // Access the lazy property - may throw if not supported on this platform
16
+ const value = apps[name];
17
+ if (value !== undefined) {
18
+ browsers.push({ name, app: value });
19
+ }
20
+ }
21
+ catch {
22
+ // Silently skip apps not supported on this platform (e.g., safari on Windows)
23
+ }
24
+ }
25
+ }
26
+ return browsers;
14
27
  }
15
28
  export async function getDefaultBrowser() {
16
29
  const { apps } = await import("open");
package/utils/options.js CHANGED
@@ -14,7 +14,7 @@ export const getOptionValueFromConfigGracefully = (option) => {
14
14
  try {
15
15
  return getOptionValueFromConfig(option);
16
16
  }
17
- catch (err) {
17
+ catch {
18
18
  return undefined;
19
19
  }
20
20
  };