@sap/cli-core 2026.1.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 +13 -0
- package/commands/handler/mandatoryOptions.js +2 -1
- package/commands/handler/parseArguments.js +3 -2
- package/constants.js +2 -2
- package/dwc/dwc.js +2 -1
- package/package.json +3 -3
- package/types.js +1 -1
- package/utils/http/httpsAgent.d.ts +1 -0
- package/utils/http/httpsAgent.js +11 -0
- package/utils/http/index.js +107 -35
- package/utils/openUtils.js +20 -7
- package/utils/options.js +1 -1
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
|
-
|
|
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.
|
|
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.
|
|
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": "2026.1
|
|
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",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"compare-versions": "6.1.1",
|
|
25
25
|
"config": "4.1.1",
|
|
26
26
|
"dotenv": "17.2.3",
|
|
27
|
-
"form-data": "4.0.
|
|
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": "
|
|
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
|
@@ -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;
|
package/utils/http/httpsAgent.js
CHANGED
|
@@ -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();
|
package/utils/http/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
};
|
package/utils/openUtils.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
name
|
|
12
|
-
|
|
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");
|