@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.
- package/LICENSE +674 -0
- package/README.md +537 -0
- package/bin/aikido-bun.js +14 -0
- package/bin/aikido-bunx.js +14 -0
- package/bin/aikido-npm.js +14 -0
- package/bin/aikido-npx.js +14 -0
- package/bin/aikido-pip.js +17 -0
- package/bin/aikido-pip3.js +17 -0
- package/bin/aikido-pipx.js +16 -0
- package/bin/aikido-pnpm.js +14 -0
- package/bin/aikido-pnpx.js +14 -0
- package/bin/aikido-poetry.js +13 -0
- package/bin/aikido-python.js +19 -0
- package/bin/aikido-python3.js +19 -0
- package/bin/aikido-uv.js +16 -0
- package/bin/aikido-uvx.js +16 -0
- package/bin/aikido-yarn.js +14 -0
- package/bin/safe-chain.js +147 -0
- package/docs/Release.md +25 -0
- package/docs/banner.svg +151 -0
- package/docs/safe-package-manager-demo.gif +0 -0
- package/docs/safe-package-manager-demo.png +0 -0
- package/docs/shell-integration.md +149 -0
- package/docs/troubleshooting.md +321 -0
- package/npm-shrinkwrap.json +3180 -0
- package/package.json +71 -0
- package/src/api/aikido.js +187 -0
- package/src/api/npmApi.js +71 -0
- package/src/config/cliArguments.js +161 -0
- package/src/config/configFile.js +327 -0
- package/src/config/environmentVariables.js +57 -0
- package/src/config/safeChainDir.js +71 -0
- package/src/config/settings.js +247 -0
- package/src/environment/environment.js +14 -0
- package/src/environment/userInteraction.js +122 -0
- package/src/installLocation.js +42 -0
- package/src/main.js +123 -0
- package/src/packagemanager/_shared/commandErrors.js +17 -0
- package/src/packagemanager/_shared/matchesCommand.js +18 -0
- package/src/packagemanager/bun/createBunPackageManager.js +48 -0
- package/src/packagemanager/currentPackageManager.js +82 -0
- package/src/packagemanager/npm/createPackageManager.js +72 -0
- package/src/packagemanager/npm/dependencyScanner/commandArgumentScanner.js +74 -0
- package/src/packagemanager/npm/dependencyScanner/nullScanner.js +9 -0
- package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.js +144 -0
- package/src/packagemanager/npm/runNpmCommand.js +20 -0
- package/src/packagemanager/npm/utils/abbrevs-generated.js +359 -0
- package/src/packagemanager/npm/utils/cmd-list.js +174 -0
- package/src/packagemanager/npm/utils/npmCommands.js +34 -0
- package/src/packagemanager/npx/createPackageManager.js +15 -0
- package/src/packagemanager/npx/dependencyScanner/commandArgumentScanner.js +43 -0
- package/src/packagemanager/npx/parsing/parsePackagesFromArguments.js +130 -0
- package/src/packagemanager/npx/runNpxCommand.js +20 -0
- package/src/packagemanager/pip/createPackageManager.js +25 -0
- package/src/packagemanager/pip/pipSettings.js +6 -0
- package/src/packagemanager/pip/runPipCommand.js +209 -0
- package/src/packagemanager/pipx/createPipXPackageManager.js +18 -0
- package/src/packagemanager/pipx/runPipXCommand.js +60 -0
- package/src/packagemanager/pnpm/createPackageManager.js +57 -0
- package/src/packagemanager/pnpm/dependencyScanner/commandArgumentScanner.js +35 -0
- package/src/packagemanager/pnpm/parsing/parsePackagesFromArguments.js +109 -0
- package/src/packagemanager/pnpm/runPnpmCommand.js +32 -0
- package/src/packagemanager/poetry/createPoetryPackageManager.js +72 -0
- package/src/packagemanager/uv/createUvPackageManager.js +18 -0
- package/src/packagemanager/uv/runUvCommand.js +66 -0
- package/src/packagemanager/uvx/createUvxPackageManager.js +18 -0
- package/src/packagemanager/yarn/createPackageManager.js +41 -0
- package/src/packagemanager/yarn/dependencyScanner/commandArgumentScanner.js +35 -0
- package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.js +128 -0
- package/src/packagemanager/yarn/runYarnCommand.js +36 -0
- package/src/registryProxy/certBundle.js +203 -0
- package/src/registryProxy/certUtils.js +178 -0
- package/src/registryProxy/getConnectTimeout.js +13 -0
- package/src/registryProxy/http-utils.js +80 -0
- package/src/registryProxy/interceptors/createInterceptorForEcoSystem.js +25 -0
- package/src/registryProxy/interceptors/interceptorBuilder.js +179 -0
- package/src/registryProxy/interceptors/minimumPackageAgeExclusions.js +33 -0
- package/src/registryProxy/interceptors/npm/modifyNpmInfo.js +180 -0
- package/src/registryProxy/interceptors/npm/npmInterceptor.js +101 -0
- package/src/registryProxy/interceptors/npm/parseNpmPackageUrl.js +60 -0
- package/src/registryProxy/interceptors/pip/modifyPipInfo.js +167 -0
- package/src/registryProxy/interceptors/pip/modifyPipJsonResponse.js +176 -0
- package/src/registryProxy/interceptors/pip/parsePipPackageUrl.js +162 -0
- package/src/registryProxy/interceptors/pip/pipInterceptor.js +122 -0
- package/src/registryProxy/interceptors/pip/pipMetadataResponseUtils.js +27 -0
- package/src/registryProxy/interceptors/pip/pipMetadataVersionUtils.js +131 -0
- package/src/registryProxy/interceptors/suppressedVersionsState.js +21 -0
- package/src/registryProxy/isImdsEndpoint.js +13 -0
- package/src/registryProxy/mitmRequestHandler.js +240 -0
- package/src/registryProxy/plainHttpProxy.js +95 -0
- package/src/registryProxy/registryProxy.js +255 -0
- package/src/registryProxy/tunnelRequestHandler.js +213 -0
- package/src/scanning/audit/index.js +129 -0
- package/src/scanning/index.js +82 -0
- package/src/scanning/malwareDatabase.js +131 -0
- package/src/scanning/newPackagesDatabaseBuilder.js +71 -0
- package/src/scanning/newPackagesDatabaseWarnings.js +17 -0
- package/src/scanning/newPackagesListCache.js +126 -0
- package/src/scanning/packageNameVariants.js +29 -0
- package/src/shell-integration/helpers.js +296 -0
- package/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh +37 -0
- package/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd +25 -0
- package/src/shell-integration/setup-ci.js +152 -0
- package/src/shell-integration/setup.js +110 -0
- package/src/shell-integration/shellDetection.js +39 -0
- package/src/shell-integration/startup-scripts/init-fish.fish +122 -0
- package/src/shell-integration/startup-scripts/init-posix.sh +112 -0
- package/src/shell-integration/startup-scripts/init-pwsh.ps1 +176 -0
- package/src/shell-integration/supported-shells/bash.js +222 -0
- package/src/shell-integration/supported-shells/fish.js +97 -0
- package/src/shell-integration/supported-shells/powershell.js +102 -0
- package/src/shell-integration/supported-shells/windowsPowershell.js +102 -0
- package/src/shell-integration/supported-shells/zsh.js +94 -0
- package/src/shell-integration/teardown.js +114 -0
- package/src/utils/safeSpawn.js +153 -0
- 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
|
+
}
|