@aikidosec/safe-chain 0.0.1-custom-install-dir

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.
Files changed (116) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +537 -0
  3. package/bin/aikido-bun.js +14 -0
  4. package/bin/aikido-bunx.js +14 -0
  5. package/bin/aikido-npm.js +14 -0
  6. package/bin/aikido-npx.js +14 -0
  7. package/bin/aikido-pip.js +17 -0
  8. package/bin/aikido-pip3.js +17 -0
  9. package/bin/aikido-pipx.js +16 -0
  10. package/bin/aikido-pnpm.js +14 -0
  11. package/bin/aikido-pnpx.js +14 -0
  12. package/bin/aikido-poetry.js +13 -0
  13. package/bin/aikido-python.js +19 -0
  14. package/bin/aikido-python3.js +19 -0
  15. package/bin/aikido-uv.js +16 -0
  16. package/bin/aikido-uvx.js +16 -0
  17. package/bin/aikido-yarn.js +14 -0
  18. package/bin/safe-chain.js +147 -0
  19. package/docs/Release.md +25 -0
  20. package/docs/banner.svg +151 -0
  21. package/docs/safe-package-manager-demo.gif +0 -0
  22. package/docs/safe-package-manager-demo.png +0 -0
  23. package/docs/shell-integration.md +149 -0
  24. package/docs/troubleshooting.md +321 -0
  25. package/npm-shrinkwrap.json +3180 -0
  26. package/package.json +71 -0
  27. package/src/api/aikido.js +187 -0
  28. package/src/api/npmApi.js +71 -0
  29. package/src/config/cliArguments.js +161 -0
  30. package/src/config/configFile.js +327 -0
  31. package/src/config/environmentVariables.js +57 -0
  32. package/src/config/safeChainDir.js +71 -0
  33. package/src/config/settings.js +247 -0
  34. package/src/environment/environment.js +14 -0
  35. package/src/environment/userInteraction.js +122 -0
  36. package/src/installLocation.js +42 -0
  37. package/src/main.js +123 -0
  38. package/src/packagemanager/_shared/commandErrors.js +17 -0
  39. package/src/packagemanager/_shared/matchesCommand.js +18 -0
  40. package/src/packagemanager/bun/createBunPackageManager.js +48 -0
  41. package/src/packagemanager/currentPackageManager.js +82 -0
  42. package/src/packagemanager/npm/createPackageManager.js +72 -0
  43. package/src/packagemanager/npm/dependencyScanner/commandArgumentScanner.js +74 -0
  44. package/src/packagemanager/npm/dependencyScanner/nullScanner.js +9 -0
  45. package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.js +144 -0
  46. package/src/packagemanager/npm/runNpmCommand.js +20 -0
  47. package/src/packagemanager/npm/utils/abbrevs-generated.js +359 -0
  48. package/src/packagemanager/npm/utils/cmd-list.js +174 -0
  49. package/src/packagemanager/npm/utils/npmCommands.js +34 -0
  50. package/src/packagemanager/npx/createPackageManager.js +15 -0
  51. package/src/packagemanager/npx/dependencyScanner/commandArgumentScanner.js +43 -0
  52. package/src/packagemanager/npx/parsing/parsePackagesFromArguments.js +130 -0
  53. package/src/packagemanager/npx/runNpxCommand.js +20 -0
  54. package/src/packagemanager/pip/createPackageManager.js +25 -0
  55. package/src/packagemanager/pip/pipSettings.js +6 -0
  56. package/src/packagemanager/pip/runPipCommand.js +209 -0
  57. package/src/packagemanager/pipx/createPipXPackageManager.js +18 -0
  58. package/src/packagemanager/pipx/runPipXCommand.js +60 -0
  59. package/src/packagemanager/pnpm/createPackageManager.js +57 -0
  60. package/src/packagemanager/pnpm/dependencyScanner/commandArgumentScanner.js +35 -0
  61. package/src/packagemanager/pnpm/parsing/parsePackagesFromArguments.js +109 -0
  62. package/src/packagemanager/pnpm/runPnpmCommand.js +32 -0
  63. package/src/packagemanager/poetry/createPoetryPackageManager.js +72 -0
  64. package/src/packagemanager/uv/createUvPackageManager.js +18 -0
  65. package/src/packagemanager/uv/runUvCommand.js +66 -0
  66. package/src/packagemanager/uvx/createUvxPackageManager.js +18 -0
  67. package/src/packagemanager/yarn/createPackageManager.js +41 -0
  68. package/src/packagemanager/yarn/dependencyScanner/commandArgumentScanner.js +35 -0
  69. package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.js +128 -0
  70. package/src/packagemanager/yarn/runYarnCommand.js +36 -0
  71. package/src/registryProxy/certBundle.js +203 -0
  72. package/src/registryProxy/certUtils.js +178 -0
  73. package/src/registryProxy/getConnectTimeout.js +13 -0
  74. package/src/registryProxy/http-utils.js +80 -0
  75. package/src/registryProxy/interceptors/createInterceptorForEcoSystem.js +25 -0
  76. package/src/registryProxy/interceptors/interceptorBuilder.js +179 -0
  77. package/src/registryProxy/interceptors/minimumPackageAgeExclusions.js +33 -0
  78. package/src/registryProxy/interceptors/npm/modifyNpmInfo.js +180 -0
  79. package/src/registryProxy/interceptors/npm/npmInterceptor.js +101 -0
  80. package/src/registryProxy/interceptors/npm/parseNpmPackageUrl.js +60 -0
  81. package/src/registryProxy/interceptors/pip/modifyPipInfo.js +167 -0
  82. package/src/registryProxy/interceptors/pip/modifyPipJsonResponse.js +176 -0
  83. package/src/registryProxy/interceptors/pip/parsePipPackageUrl.js +162 -0
  84. package/src/registryProxy/interceptors/pip/pipInterceptor.js +122 -0
  85. package/src/registryProxy/interceptors/pip/pipMetadataResponseUtils.js +27 -0
  86. package/src/registryProxy/interceptors/pip/pipMetadataVersionUtils.js +131 -0
  87. package/src/registryProxy/interceptors/suppressedVersionsState.js +21 -0
  88. package/src/registryProxy/isImdsEndpoint.js +13 -0
  89. package/src/registryProxy/mitmRequestHandler.js +240 -0
  90. package/src/registryProxy/plainHttpProxy.js +95 -0
  91. package/src/registryProxy/registryProxy.js +255 -0
  92. package/src/registryProxy/tunnelRequestHandler.js +213 -0
  93. package/src/scanning/audit/index.js +129 -0
  94. package/src/scanning/index.js +82 -0
  95. package/src/scanning/malwareDatabase.js +131 -0
  96. package/src/scanning/newPackagesDatabaseBuilder.js +71 -0
  97. package/src/scanning/newPackagesDatabaseWarnings.js +17 -0
  98. package/src/scanning/newPackagesListCache.js +126 -0
  99. package/src/scanning/packageNameVariants.js +29 -0
  100. package/src/shell-integration/helpers.js +296 -0
  101. package/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh +37 -0
  102. package/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd +25 -0
  103. package/src/shell-integration/setup-ci.js +152 -0
  104. package/src/shell-integration/setup.js +110 -0
  105. package/src/shell-integration/shellDetection.js +39 -0
  106. package/src/shell-integration/startup-scripts/init-fish.fish +122 -0
  107. package/src/shell-integration/startup-scripts/init-posix.sh +112 -0
  108. package/src/shell-integration/startup-scripts/init-pwsh.ps1 +176 -0
  109. package/src/shell-integration/supported-shells/bash.js +222 -0
  110. package/src/shell-integration/supported-shells/fish.js +97 -0
  111. package/src/shell-integration/supported-shells/powershell.js +102 -0
  112. package/src/shell-integration/supported-shells/windowsPowershell.js +102 -0
  113. package/src/shell-integration/supported-shells/zsh.js +94 -0
  114. package/src/shell-integration/teardown.js +114 -0
  115. package/src/utils/safeSpawn.js +153 -0
  116. package/tsconfig.json +21 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @param {string[]} args
3
+ * @returns {{name: string, version: string}[]}
4
+ */
5
+ export function parsePackagesFromArguments(args) {
6
+ const changes = [];
7
+ let defaultTag = "latest";
8
+
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+ const option = getOption(arg);
12
+
13
+ if (option) {
14
+ // If the option has a parameter, skip the next argument as well
15
+ i += option.numberOfParameters;
16
+
17
+ continue;
18
+ }
19
+
20
+ const packageDetails = parsePackagename(arg, defaultTag);
21
+ if (packageDetails) {
22
+ changes.push(packageDetails);
23
+ }
24
+ }
25
+
26
+ return changes;
27
+ }
28
+
29
+ /**
30
+ * @param {string} arg
31
+ * @returns {{name: string, numberOfParameters: number} | undefined}
32
+ */
33
+ function getOption(arg) {
34
+ if (isOptionWithParameter(arg)) {
35
+ return {
36
+ name: arg,
37
+ numberOfParameters: 1,
38
+ };
39
+ }
40
+
41
+ // Arguments starting with "-" or "--" are considered options
42
+ // except for "--package=" which contains the package name
43
+ if (arg.startsWith("-") && !arg.startsWith("--package=")) {
44
+ return {
45
+ name: arg,
46
+ numberOfParameters: 0,
47
+ };
48
+ }
49
+
50
+ return undefined;
51
+ }
52
+
53
+ /**
54
+ * @param {string} arg
55
+ * @returns {boolean}
56
+ */
57
+ function isOptionWithParameter(arg) {
58
+ const optionsWithParameters = ["--C", "--dir"];
59
+
60
+ return optionsWithParameters.includes(arg);
61
+ }
62
+
63
+ /**
64
+ * @param {string} arg
65
+ * @param {string} defaultTag
66
+ * @returns {{name: string, version: string}}
67
+ */
68
+ function parsePackagename(arg, defaultTag) {
69
+ // format can be --package=name@version
70
+ // in that case, we need to remove the --package= part
71
+ if (arg.startsWith("--package=")) {
72
+ arg = arg.slice(10);
73
+ }
74
+
75
+ arg = removeAlias(arg);
76
+
77
+ // Split at the last "@" to separate the package name and version
78
+ const lastAtIndex = arg.lastIndexOf("@");
79
+
80
+ let name, version;
81
+ // The index of the last "@" should be greater than 0
82
+ // If the index is 0, it means the package name starts with "@" (eg: "@aikidosec/package-name")
83
+ if (lastAtIndex > 0) {
84
+ name = arg.slice(0, lastAtIndex);
85
+ version = arg.slice(lastAtIndex + 1);
86
+ } else {
87
+ name = arg;
88
+ version = defaultTag; // No tag specified (eg: "http-server"), use the default tag
89
+ }
90
+
91
+ return {
92
+ name,
93
+ version,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * @param {string} arg
99
+ * @returns {string}
100
+ */
101
+ function removeAlias(arg) {
102
+ // removes the alias.
103
+ // Eg.: server@npm:http-server@latest becomes http-server@latest
104
+ const aliasIndex = arg.indexOf("@npm:");
105
+ if (aliasIndex !== -1) {
106
+ return arg.slice(aliasIndex + 5);
107
+ }
108
+ return arg;
109
+ }
@@ -0,0 +1,32 @@
1
+ import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
2
+ import { safeSpawn } from "../../utils/safeSpawn.js";
3
+ import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
4
+
5
+ /**
6
+ * @param {string[]} args
7
+ * @param {string} [toolName]
8
+ * @returns {Promise<{status: number}>}
9
+ */
10
+ export async function runPnpmCommand(args, toolName = "pnpm") {
11
+ try {
12
+ let result;
13
+ if (toolName === "pnpm") {
14
+ result = await safeSpawn("pnpm", args, {
15
+ stdio: "inherit",
16
+ env: mergeSafeChainProxyEnvironmentVariables(process.env),
17
+ });
18
+ } else if (toolName === "pnpx") {
19
+ result = await safeSpawn("pnpx", args, {
20
+ stdio: "inherit",
21
+ env: mergeSafeChainProxyEnvironmentVariables(process.env),
22
+ });
23
+ } else {
24
+ throw new Error(`Unsupported tool name for aikido-pnpm: ${toolName}`);
25
+ }
26
+
27
+ return { status: result.status };
28
+ } catch (/** @type any */ error) {
29
+ const target = toolName === "pnpm" ? "pnpm" : "pnpx";
30
+ return reportCommandExecutionFailure(error, target);
31
+ }
32
+ }
@@ -0,0 +1,72 @@
1
+ import { ui } from "../../environment/userInteraction.js";
2
+ import { safeSpawn } from "../../utils/safeSpawn.js";
3
+ import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
4
+ import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
5
+ import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
6
+
7
+ /**
8
+ * @returns {import("../currentPackageManager.js").PackageManager}
9
+ */
10
+ export function createPoetryPackageManager() {
11
+ return {
12
+ runCommand: (args) => runPoetryCommand(args),
13
+
14
+ // MITM only approach for Poetry
15
+ isSupportedCommand: () => false,
16
+ getDependencyUpdatesForCommand: () => [],
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Sets CA bundle environment variables used by Poetry and Python libraries.
22
+ * Poetry uses the Python requests library which respects these environment variables.
23
+ *
24
+ * @param {NodeJS.ProcessEnv} env - Environment object to modify
25
+ * @param {string} combinedCaPath - Path to the combined CA bundle
26
+ */
27
+ function setPoetryCaBundleEnvironmentVariables(env, combinedCaPath) {
28
+ // SSL_CERT_FILE: Used by Python SSL libraries and requests
29
+ if (env.SSL_CERT_FILE) {
30
+ ui.writeWarning("Safe-chain: User defined SSL_CERT_FILE found in environment. It will be overwritten.");
31
+ }
32
+ env.SSL_CERT_FILE = combinedCaPath;
33
+
34
+ // REQUESTS_CA_BUNDLE: Used by the requests library (which Poetry uses)
35
+ if (env.REQUESTS_CA_BUNDLE) {
36
+ ui.writeWarning("Safe-chain: User defined REQUESTS_CA_BUNDLE found in environment. It will be overwritten.");
37
+ }
38
+ env.REQUESTS_CA_BUNDLE = combinedCaPath;
39
+
40
+ // PIP_CERT: Poetry may use pip internally
41
+ if (env.PIP_CERT) {
42
+ ui.writeWarning("Safe-chain: User defined PIP_CERT found in environment. It will be overwritten.");
43
+ }
44
+ env.PIP_CERT = combinedCaPath;
45
+ }
46
+
47
+ /**
48
+ * Runs a poetry command with safe-chain's certificate bundle and proxy configuration.
49
+ *
50
+ * Poetry respects standard HTTP_PROXY/HTTPS_PROXY environment variables through
51
+ * the Python requests library.
52
+ *
53
+ * @param {string[]} args - Command line arguments to pass to poetry
54
+ * @returns {Promise<{status: number}>} Exit status of the poetry command
55
+ */
56
+ async function runPoetryCommand(args) {
57
+ try {
58
+ const env = mergeSafeChainProxyEnvironmentVariables(process.env);
59
+
60
+ const combinedCaPath = getCombinedCaBundlePath();
61
+ setPoetryCaBundleEnvironmentVariables(env, combinedCaPath);
62
+
63
+ const result = await safeSpawn("poetry", args, {
64
+ stdio: "inherit",
65
+ env,
66
+ });
67
+
68
+ return { status: result.status };
69
+ } catch (/** @type any */ error) {
70
+ return reportCommandExecutionFailure(error, "poetry");
71
+ }
72
+ }
@@ -0,0 +1,18 @@
1
+ import { runUv } from "./runUvCommand.js";
2
+
3
+ /**
4
+ * @returns {import("../currentPackageManager.js").PackageManager}
5
+ */
6
+ export function createUvPackageManager() {
7
+ return {
8
+ /**
9
+ * @param {string[]} args
10
+ */
11
+ runCommand: (args) => {
12
+ return runUv("uv", args);
13
+ },
14
+ // For uv, rely solely on MITM
15
+ isSupportedCommand: () => false,
16
+ getDependencyUpdatesForCommand: () => [],
17
+ };
18
+ }
@@ -0,0 +1,66 @@
1
+ import { ui } from "../../environment/userInteraction.js";
2
+ import { safeSpawn } from "../../utils/safeSpawn.js";
3
+ import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
4
+ import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
5
+ import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
6
+
7
+ /**
8
+ * Sets CA bundle environment variables used by Python libraries and uv.
9
+ *
10
+ * @param {NodeJS.ProcessEnv} env - Env object
11
+ * @param {string} combinedCaPath - Path to the combined CA bundle
12
+ */
13
+ function setUvCaBundleEnvironmentVariables(env, combinedCaPath) {
14
+ // SSL_CERT_FILE: Used by Python SSL libraries and underlying HTTP clients
15
+ if (env.SSL_CERT_FILE) {
16
+ ui.writeWarning("Safe-chain: User defined SSL_CERT_FILE found in environment. It will be overwritten.");
17
+ }
18
+ env.SSL_CERT_FILE = combinedCaPath;
19
+
20
+ // REQUESTS_CA_BUNDLE: Used by the requests library (which uv may use internally)
21
+ if (env.REQUESTS_CA_BUNDLE) {
22
+ ui.writeWarning("Safe-chain: User defined REQUESTS_CA_BUNDLE found in environment. It will be overwritten.");
23
+ }
24
+ env.REQUESTS_CA_BUNDLE = combinedCaPath;
25
+
26
+ // PIP_CERT: Some underlying pip operations may respect this
27
+ if (env.PIP_CERT) {
28
+ ui.writeWarning("Safe-chain: User defined PIP_CERT found in environment. It will be overwritten.");
29
+ }
30
+ env.PIP_CERT = combinedCaPath;
31
+ }
32
+
33
+ /**
34
+ * Runs a uv command with safe-chain's certificate bundle and proxy configuration.
35
+ *
36
+ * uv respects standard environment variables for proxy and TLS configuration:
37
+ * - HTTP_PROXY / HTTPS_PROXY: Proxy settings
38
+ * - SSL_CERT_FILE / REQUESTS_CA_BUNDLE: CA bundle for TLS verification
39
+ *
40
+ * Unlike pip (which requires a temporary config file for cert configuration), uv directly
41
+ * honors environment variables, so no config/ini file is needed.
42
+ *
43
+ * @param {string} command - The uv command to execute (typically 'uv')
44
+ * @param {string[]} args - Command line arguments to pass to uv
45
+ * @returns {Promise<{status: number}>} Exit status of the uv command
46
+ */
47
+ export async function runUv(command, args) {
48
+ try {
49
+ const env = mergeSafeChainProxyEnvironmentVariables(process.env);
50
+
51
+ const combinedCaPath = getCombinedCaBundlePath();
52
+ setUvCaBundleEnvironmentVariables(env, combinedCaPath);
53
+
54
+ // Note: uv uses HTTPS_PROXY and HTTP_PROXY environment variables for proxy configuration
55
+ // These are already set by mergeSafeChainProxyEnvironmentVariables
56
+
57
+ const result = await safeSpawn(command, args, {
58
+ stdio: "inherit",
59
+ env,
60
+ });
61
+
62
+ return { status: result.status };
63
+ } catch (/** @type any */ error) {
64
+ return reportCommandExecutionFailure(error, command);
65
+ }
66
+ }
@@ -0,0 +1,18 @@
1
+ import { runUv } from "../uv/runUvCommand.js";
2
+
3
+ /**
4
+ * @returns {import("../currentPackageManager.js").PackageManager}
5
+ */
6
+ export function createUvxPackageManager() {
7
+ return {
8
+ /**
9
+ * @param {string[]} args
10
+ */
11
+ runCommand: (args) => {
12
+ return runUv("uvx", args);
13
+ },
14
+ // For uvx, rely solely on MITM
15
+ isSupportedCommand: () => false,
16
+ getDependencyUpdatesForCommand: () => [],
17
+ };
18
+ }
@@ -0,0 +1,41 @@
1
+ import { commandArgumentScanner } from "./dependencyScanner/commandArgumentScanner.js";
2
+ import { runYarnCommand } from "./runYarnCommand.js";
3
+
4
+ const scanner = commandArgumentScanner();
5
+
6
+ /**
7
+ * @returns {import("../currentPackageManager.js").PackageManager}
8
+ */
9
+ export function createYarnPackageManager() {
10
+ return {
11
+ runCommand: runYarnCommand,
12
+ isSupportedCommand: (args) =>
13
+ matchesCommand(args, "add") ||
14
+ matchesCommand(args, "global", "add") ||
15
+ matchesCommand(args, "install") ||
16
+ matchesCommand(args, "up") ||
17
+ matchesCommand(args, "upgrade") ||
18
+ matchesCommand(args, "global", "upgrade") ||
19
+ matchesCommand(args, "dlx"),
20
+ getDependencyUpdatesForCommand: (args) => scanner.scan(args),
21
+ };
22
+ }
23
+
24
+ /**
25
+ * @param {string[]} args
26
+ * @param {...string} commandArgs
27
+ * @returns {boolean}
28
+ */
29
+ function matchesCommand(args, ...commandArgs) {
30
+ if (args.length < commandArgs.length) {
31
+ return false;
32
+ }
33
+
34
+ for (var i = 0; i < commandArgs.length; i++) {
35
+ if (args[i].toLowerCase() !== commandArgs[i].toLowerCase()) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ return true;
41
+ }
@@ -0,0 +1,35 @@
1
+ import { resolvePackageVersion } from "../../../api/npmApi.js";
2
+ import { parsePackagesFromArguments } from "../parsing/parsePackagesFromArguments.js";
3
+
4
+ /**
5
+ * @returns {import("../../npm/dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner}
6
+ */
7
+ export function commandArgumentScanner() {
8
+ return {
9
+ scan: (args) => scanDependencies(args),
10
+ shouldScan: () => true, // There's no dry run for yarn, so we always scan
11
+ };
12
+ }
13
+
14
+ /**
15
+ * @param {string[]} args
16
+ * @returns {Promise<import("../../npm/dependencyScanner/commandArgumentScanner.js").ScanResult[]>}
17
+ */
18
+ async function scanDependencies(args) {
19
+ const changes = [];
20
+ const packageUpdates = parsePackagesFromArguments(args);
21
+
22
+ for (const packageUpdate of packageUpdates) {
23
+ var exactVersion = await resolvePackageVersion(
24
+ packageUpdate.name,
25
+ packageUpdate.version
26
+ );
27
+ if (exactVersion) {
28
+ packageUpdate.version = exactVersion;
29
+ }
30
+
31
+ changes.push({ ...packageUpdate, type: "add" });
32
+ }
33
+
34
+ return changes;
35
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @param {string[]} args
3
+ * @returns {{name: string, version: string}[]}
4
+ */
5
+ export function parsePackagesFromArguments(args) {
6
+ const changes = [];
7
+ let defaultTag = "latest";
8
+
9
+ for (let i = 1; i < args.length; i++) {
10
+ const arg = args[i];
11
+ const option = getOption(arg);
12
+
13
+ if (option) {
14
+ // If the option has a parameter, skip the next argument as well
15
+ i += option.numberOfParameters;
16
+
17
+ continue;
18
+ }
19
+
20
+ const packageDetails = parsePackagename(arg, defaultTag);
21
+ if (packageDetails) {
22
+ changes.push(packageDetails);
23
+ }
24
+ }
25
+
26
+ return changes;
27
+ }
28
+
29
+ /**
30
+ * @param {string} arg
31
+ *
32
+ * @returns {{name: string, numberOfParameters: number} | undefined}
33
+ */
34
+ function getOption(arg) {
35
+ if (isOptionWithParameter(arg)) {
36
+ return {
37
+ name: arg,
38
+ numberOfParameters: 1,
39
+ };
40
+ }
41
+
42
+ // Arguments starting with "-" or "--" are considered options
43
+ // except for "--package=" which contains the package name
44
+ if (arg.startsWith("-")) {
45
+ return {
46
+ name: arg,
47
+ numberOfParameters: 0,
48
+ };
49
+ }
50
+
51
+ return undefined;
52
+ }
53
+
54
+ /**
55
+ * @param {string} arg
56
+ *
57
+ * @returns {boolean}
58
+ */
59
+ function isOptionWithParameter(arg) {
60
+ const optionsWithParameters = [
61
+ "--use-yarnrc",
62
+ "--link-folder",
63
+ "--global-folder",
64
+ "--modules-folder",
65
+ "--preferred-cache-folder",
66
+ "--cache-folder",
67
+ "--mutex",
68
+ "--cwd",
69
+ "--proxy",
70
+ "--https-proxy",
71
+ "--registry",
72
+ "--network-concurrency",
73
+ "--network-timeout",
74
+ "--scripts-prepend-node-path",
75
+ "--otp",
76
+ ];
77
+
78
+ return optionsWithParameters.includes(arg);
79
+ }
80
+
81
+ /**
82
+ * @param {string} arg
83
+ * @param {string} defaultTag
84
+ *
85
+ * @returns {{name: string, version: string}}
86
+ */
87
+ function parsePackagename(arg, defaultTag) {
88
+ // format can be --package=name@version
89
+ // in that case, we need to remove the --package= part
90
+ if (arg.startsWith("--package=")) {
91
+ arg = arg.slice(10);
92
+ }
93
+
94
+ arg = removeAlias(arg);
95
+
96
+ // Split at the last "@" to separate the package name and version
97
+ const lastAtIndex = arg.lastIndexOf("@");
98
+
99
+ let name, version;
100
+ // The index of the last "@" should be greater than 0
101
+ // If the index is 0, it means the package name starts with "@" (eg: "@vercel/otel")
102
+ if (lastAtIndex > 0) {
103
+ name = arg.slice(0, lastAtIndex);
104
+ version = arg.slice(lastAtIndex + 1);
105
+ } else {
106
+ name = arg;
107
+ version = defaultTag; // No tag specified (eg: "http-server"), use the default tag
108
+ }
109
+
110
+ return {
111
+ name,
112
+ version,
113
+ };
114
+ }
115
+
116
+ /**
117
+ * @param {string} arg
118
+ * @returns {string}
119
+ */
120
+ function removeAlias(arg) {
121
+ // removes the alias.
122
+ // Eg.: server@npm:http-server@latest becomes http-server@latest
123
+ const aliasIndex = arg.indexOf("@npm:");
124
+ if (aliasIndex !== -1) {
125
+ return arg.slice(aliasIndex + 5);
126
+ }
127
+ return arg;
128
+ }
@@ -0,0 +1,36 @@
1
+ import { safeSpawn } from "../../utils/safeSpawn.js";
2
+ import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
3
+ import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
4
+
5
+ /**
6
+ * @param {string[]} args
7
+ *
8
+ * @returns {Promise<{status: number}>}
9
+ */
10
+ export async function runYarnCommand(args) {
11
+ try {
12
+ const env = mergeSafeChainProxyEnvironmentVariables(process.env);
13
+ await fixYarnProxyEnvironmentVariables(env);
14
+
15
+ const result = await safeSpawn("yarn", args, {
16
+ stdio: "inherit",
17
+ env,
18
+ });
19
+ return { status: result.status };
20
+ } catch (/** @type any */ error) {
21
+ return reportCommandExecutionFailure(error, "yarn");
22
+ }
23
+ }
24
+
25
+ /**
26
+ * @param {Record<string, string>} env
27
+ *
28
+ * @returns {Promise<void>}
29
+ */
30
+ async function fixYarnProxyEnvironmentVariables(env) {
31
+ // Yarn ignores standard proxy environment variable HTTPS_PROXY
32
+ // It does respect NODE_EXTRA_CA_CERTS for custom CA certificates though.
33
+ // Don't use YARN_HTTPS_CA_FILE_PATH or YARN_CA_FILE_PATH though, it causes yarn to ignore all system CAs
34
+
35
+ env.YARN_HTTPS_PROXY = env.HTTPS_PROXY;
36
+ }