@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,122 @@
|
|
|
1
|
+
// oxlint-disable no-console
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { isCi } from "./environment.js";
|
|
4
|
+
import {
|
|
5
|
+
getLoggingLevel,
|
|
6
|
+
LOGGING_SILENT,
|
|
7
|
+
LOGGING_VERBOSE,
|
|
8
|
+
} from "../config/settings.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @type {{ bufferOutput: boolean, bufferedMessages:(() => void)[]}}
|
|
12
|
+
*/
|
|
13
|
+
const state = {
|
|
14
|
+
bufferOutput: false,
|
|
15
|
+
bufferedMessages: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function isSilentMode() {
|
|
19
|
+
return getLoggingLevel() === LOGGING_SILENT;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isVerboseMode() {
|
|
23
|
+
return getLoggingLevel() === LOGGING_VERBOSE;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function emptyLine() {
|
|
27
|
+
if (isSilentMode()) return;
|
|
28
|
+
|
|
29
|
+
writeInformation("");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} message
|
|
34
|
+
* @param {...any} optionalParams
|
|
35
|
+
* @returns {void}
|
|
36
|
+
*/
|
|
37
|
+
function writeInformation(message, ...optionalParams) {
|
|
38
|
+
if (isSilentMode()) return;
|
|
39
|
+
|
|
40
|
+
writeOrBuffer(() => console.log(message, ...optionalParams));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} message
|
|
45
|
+
* @param {...any} optionalParams
|
|
46
|
+
* @returns {void}
|
|
47
|
+
*/
|
|
48
|
+
function writeWarning(message, ...optionalParams) {
|
|
49
|
+
if (isSilentMode()) return;
|
|
50
|
+
|
|
51
|
+
if (!isCi()) {
|
|
52
|
+
message = chalk.yellow(message);
|
|
53
|
+
}
|
|
54
|
+
writeOrBuffer(() => console.warn(message, ...optionalParams));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} message
|
|
59
|
+
* @param {...any} optionalParams
|
|
60
|
+
* @returns {void}
|
|
61
|
+
*/
|
|
62
|
+
function writeError(message, ...optionalParams) {
|
|
63
|
+
if (!isCi()) {
|
|
64
|
+
message = chalk.red(message);
|
|
65
|
+
}
|
|
66
|
+
writeOrBuffer(() => console.error(message, ...optionalParams));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writeExitWithoutInstallingMaliciousPackages() {
|
|
70
|
+
let message = "Safe-chain: Exiting without installing malicious packages.";
|
|
71
|
+
if (!isCi()) {
|
|
72
|
+
message = chalk.red(message);
|
|
73
|
+
}
|
|
74
|
+
writeOrBuffer(() => console.error(message));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} message
|
|
79
|
+
* @param {...any} optionalParams
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
function writeVerbose(message, ...optionalParams) {
|
|
83
|
+
if (!isVerboseMode()) return;
|
|
84
|
+
|
|
85
|
+
writeOrBuffer(() => console.log(message, ...optionalParams));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
*
|
|
90
|
+
* @param {() => void} messageFunction
|
|
91
|
+
*/
|
|
92
|
+
function writeOrBuffer(messageFunction) {
|
|
93
|
+
if (state.bufferOutput) {
|
|
94
|
+
state.bufferedMessages.push(messageFunction);
|
|
95
|
+
} else {
|
|
96
|
+
messageFunction();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function startBufferingLogs() {
|
|
101
|
+
state.bufferOutput = true;
|
|
102
|
+
state.bufferedMessages = [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function writeBufferedLogsAndStopBuffering() {
|
|
106
|
+
state.bufferOutput = false;
|
|
107
|
+
for (const log of state.bufferedMessages) {
|
|
108
|
+
log();
|
|
109
|
+
}
|
|
110
|
+
state.bufferedMessages = [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const ui = {
|
|
114
|
+
writeVerbose,
|
|
115
|
+
writeInformation,
|
|
116
|
+
writeWarning,
|
|
117
|
+
writeError,
|
|
118
|
+
writeExitWithoutInstallingMaliciousPackages,
|
|
119
|
+
emptyLine,
|
|
120
|
+
startBufferingLogs,
|
|
121
|
+
writeBufferedLogsAndStopBuffering,
|
|
122
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
/** @type {NodeJS.Process & { pkg?: unknown }} */
|
|
4
|
+
const processWithPkg = process;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} executablePath
|
|
8
|
+
* @returns {string | undefined}
|
|
9
|
+
*/
|
|
10
|
+
export function deriveInstallDirFromExecutablePath(executablePath) {
|
|
11
|
+
if (!executablePath) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const pathLibrary = executablePath.includes("\\") ? path.win32 : path.posix;
|
|
16
|
+
const executableDir = pathLibrary.dirname(executablePath);
|
|
17
|
+
if (pathLibrary.basename(executableDir) !== "bin") {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return pathLibrary.dirname(executableDir);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns the install directory for a packaged safe-chain binary.
|
|
26
|
+
* Custom installation directories only apply to packaged binary installs.
|
|
27
|
+
* For npm/global/dev-script executions this intentionally returns undefined,
|
|
28
|
+
* which causes callers to fall back to the default ~/.safe-chain layout.
|
|
29
|
+
*
|
|
30
|
+
* @param {{ isPackaged?: boolean, executablePath?: string }} [options]
|
|
31
|
+
* @returns {string | undefined}
|
|
32
|
+
*/
|
|
33
|
+
export function getInstalledSafeChainDir(options = {}) {
|
|
34
|
+
const isPackaged = options.isPackaged ?? Boolean(processWithPkg.pkg);
|
|
35
|
+
if (!isPackaged) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return deriveInstallDirFromExecutablePath(
|
|
40
|
+
options.executablePath ?? process.execPath,
|
|
41
|
+
);
|
|
42
|
+
}
|
package/src/main.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { scanCommand, shouldScanCommand } from "./scanning/index.js";
|
|
4
|
+
import { ui } from "./environment/userInteraction.js";
|
|
5
|
+
import { getPackageManager } from "./packagemanager/currentPackageManager.js";
|
|
6
|
+
import { initializeCliArguments } from "./config/cliArguments.js";
|
|
7
|
+
import { createSafeChainProxy } from "./registryProxy/registryProxy.js";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { getAuditStats } from "./scanning/audit/index.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string[]} args
|
|
13
|
+
* @returns {Promise<number>}
|
|
14
|
+
*/
|
|
15
|
+
export async function main(args) {
|
|
16
|
+
if (isSafeChainVerify(args)) {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
process.on("SIGINT", handleProcessTermination);
|
|
21
|
+
process.on("SIGTERM", handleProcessTermination);
|
|
22
|
+
|
|
23
|
+
const proxy = createSafeChainProxy();
|
|
24
|
+
await proxy.startServer();
|
|
25
|
+
|
|
26
|
+
// Global error handlers to log unhandled errors
|
|
27
|
+
process.on("uncaughtException", (error) => {
|
|
28
|
+
ui.writeError(`Safe-chain: Uncaught exception: ${error.message}`);
|
|
29
|
+
ui.writeVerbose(`Stack trace: ${error.stack}`);
|
|
30
|
+
ui.writeBufferedLogsAndStopBuffering();
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
process.on("unhandledRejection", (reason) => {
|
|
35
|
+
ui.writeError(`Safe-chain: Unhandled promise rejection: ${reason}`);
|
|
36
|
+
if (reason instanceof Error) {
|
|
37
|
+
ui.writeVerbose(`Stack trace: ${reason.stack}`);
|
|
38
|
+
}
|
|
39
|
+
ui.writeBufferedLogsAndStopBuffering();
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// This parses all the --safe-chain arguments and removes them from the args array
|
|
45
|
+
args = initializeCliArguments(args);
|
|
46
|
+
|
|
47
|
+
if (shouldScanCommand(args)) {
|
|
48
|
+
const commandScanResult = await scanCommand(args);
|
|
49
|
+
|
|
50
|
+
// Returning the exit code back to the caller allows the promise
|
|
51
|
+
// to be awaited in the bin files and return the correct exit code
|
|
52
|
+
if (commandScanResult !== 0) {
|
|
53
|
+
return commandScanResult;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Buffer logs during package manager execution, this avoids interleaving
|
|
58
|
+
// of logs from the package manager and safe-chain
|
|
59
|
+
// Not doing this could cause bugs to disappear when cursor movement codes
|
|
60
|
+
// are written by the package manager while safe-chain is writing logs
|
|
61
|
+
ui.startBufferingLogs();
|
|
62
|
+
const packageManagerResult = await getPackageManager().runCommand(args);
|
|
63
|
+
|
|
64
|
+
// Write all buffered logs
|
|
65
|
+
ui.writeBufferedLogsAndStopBuffering();
|
|
66
|
+
|
|
67
|
+
if (proxy.hasBlockedMaliciousPackages()) {
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (proxy.hasBlockedMinimumAgeRequests()) {
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const auditStats = getAuditStats();
|
|
76
|
+
if (auditStats.totalPackages > 0) {
|
|
77
|
+
ui.writeVerbose(
|
|
78
|
+
`${chalk.green("✔")} Safe-chain: Scanned ${
|
|
79
|
+
auditStats.totalPackages
|
|
80
|
+
} packages, no malware found.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (proxy.hasSuppressedVersions()) {
|
|
85
|
+
ui.writeInformation(
|
|
86
|
+
`${chalk.yellow(
|
|
87
|
+
"ℹ",
|
|
88
|
+
)} Safe-chain: Some package versions were suppressed during package metadata resolution due to minimum package age.`,
|
|
89
|
+
);
|
|
90
|
+
ui.writeInformation(
|
|
91
|
+
` To disable this check, use: ${chalk.cyan(
|
|
92
|
+
"--safe-chain-skip-minimum-package-age",
|
|
93
|
+
)}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Returning the exit code back to the caller allows the promise
|
|
98
|
+
// to be awaited in the bin files and return the correct exit code
|
|
99
|
+
return packageManagerResult.status;
|
|
100
|
+
} catch (/** @type any */ error) {
|
|
101
|
+
ui.writeError("Failed to check for malicious packages:", error.message);
|
|
102
|
+
ui.writeBufferedLogsAndStopBuffering();
|
|
103
|
+
|
|
104
|
+
// Returning the exit code back to the caller allows the promise
|
|
105
|
+
// to be awaited in the bin files and return the correct exit code
|
|
106
|
+
return 1;
|
|
107
|
+
} finally {
|
|
108
|
+
await proxy.stopServer();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function handleProcessTermination() {
|
|
113
|
+
ui.writeBufferedLogsAndStopBuffering();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** @param {string[]} args */
|
|
117
|
+
function isSafeChainVerify(args) {
|
|
118
|
+
const safeChainCheckCommand = "safe-chain-verify";
|
|
119
|
+
if (args.length > 0 && args[0] === safeChainCheckCommand) {
|
|
120
|
+
ui.writeInformation("OK: Safe-chain works!");
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ui } from "../../environment/userInteraction.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Centralized logging for package-manager command launch failures.
|
|
5
|
+
*
|
|
6
|
+
* @param {any} error - Error thrown by safeSpawn while preparing/running the command.
|
|
7
|
+
* @param {string} command - Command name that failed to execute.
|
|
8
|
+
* @returns {{status: number}}
|
|
9
|
+
*/
|
|
10
|
+
export function reportCommandExecutionFailure(error, command) {
|
|
11
|
+
const message = typeof error?.message === "string" ? error.message : "Unknown error";
|
|
12
|
+
ui.writeError(`Error executing command: ${message}`);
|
|
13
|
+
|
|
14
|
+
ui.writeError(`Is '${command}' installed and available on your system?`);
|
|
15
|
+
|
|
16
|
+
return { status: typeof error?.status === "number" ? error.status : 1 };
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string[]} args
|
|
3
|
+
* @param {...string} commandArgs
|
|
4
|
+
* @returns {boolean}
|
|
5
|
+
*/
|
|
6
|
+
export function matchesCommand(args, ...commandArgs) {
|
|
7
|
+
if (args.length < commandArgs.length) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
for (var i = 0; i < commandArgs.length; i++) {
|
|
12
|
+
if (args[i].toLowerCase() !== commandArgs[i].toLowerCase()) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { safeSpawn } from "../../utils/safeSpawn.js";
|
|
2
|
+
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
|
3
|
+
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @returns {import("../currentPackageManager.js").PackageManager}
|
|
7
|
+
*/
|
|
8
|
+
export function createBunPackageManager() {
|
|
9
|
+
return {
|
|
10
|
+
runCommand: (args) => runBunCommand("bun", args),
|
|
11
|
+
|
|
12
|
+
// For bun, we use the proxy-only approach to block package downloads,
|
|
13
|
+
// so we don't need to analyze commands.
|
|
14
|
+
isSupportedCommand: () => false,
|
|
15
|
+
getDependencyUpdatesForCommand: () => [],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @returns {import("../currentPackageManager.js").PackageManager}
|
|
21
|
+
*/
|
|
22
|
+
export function createBunxPackageManager() {
|
|
23
|
+
return {
|
|
24
|
+
runCommand: (args) => runBunCommand("bunx", args),
|
|
25
|
+
|
|
26
|
+
// For bunx, we use the proxy-only approach to block package downloads,
|
|
27
|
+
// so we don't need to analyze commands.
|
|
28
|
+
isSupportedCommand: () => false,
|
|
29
|
+
getDependencyUpdatesForCommand: () => [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} command
|
|
35
|
+
* @param {string[]} args
|
|
36
|
+
* @returns {Promise<{status: number}>}
|
|
37
|
+
*/
|
|
38
|
+
async function runBunCommand(command, args) {
|
|
39
|
+
try {
|
|
40
|
+
const result = await safeSpawn(command, args, {
|
|
41
|
+
stdio: "inherit",
|
|
42
|
+
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
|
43
|
+
});
|
|
44
|
+
return { status: result.status };
|
|
45
|
+
} catch (/** @type any */ error) {
|
|
46
|
+
return reportCommandExecutionFailure(error, command);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBunPackageManager,
|
|
3
|
+
createBunxPackageManager,
|
|
4
|
+
} from "./bun/createBunPackageManager.js";
|
|
5
|
+
import { createNpmPackageManager } from "./npm/createPackageManager.js";
|
|
6
|
+
import { createNpxPackageManager } from "./npx/createPackageManager.js";
|
|
7
|
+
import {
|
|
8
|
+
createPnpmPackageManager,
|
|
9
|
+
createPnpxPackageManager,
|
|
10
|
+
} from "./pnpm/createPackageManager.js";
|
|
11
|
+
import { createYarnPackageManager } from "./yarn/createPackageManager.js";
|
|
12
|
+
import { createPipPackageManager } from "./pip/createPackageManager.js";
|
|
13
|
+
import { createUvPackageManager } from "./uv/createUvPackageManager.js";
|
|
14
|
+
import { createPoetryPackageManager } from "./poetry/createPoetryPackageManager.js";
|
|
15
|
+
import { createPipXPackageManager } from "./pipx/createPipXPackageManager.js";
|
|
16
|
+
import { createUvxPackageManager } from "./uvx/createUvxPackageManager.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @type {{packageManagerName: PackageManager | null}}
|
|
20
|
+
*/
|
|
21
|
+
const state = {
|
|
22
|
+
packageManagerName: null,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} GetDependencyUpdatesResult
|
|
27
|
+
* @property {string} name
|
|
28
|
+
* @property {string} version
|
|
29
|
+
* @property {string} type
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} PackageManager
|
|
34
|
+
* @property {(args: string[]) => Promise<{ status: number }>} runCommand
|
|
35
|
+
* @property {(args: string[]) => boolean} isSupportedCommand
|
|
36
|
+
* @property {(args: string[]) => Promise<GetDependencyUpdatesResult[]> | GetDependencyUpdatesResult[]} getDependencyUpdatesForCommand
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} packageManagerName
|
|
41
|
+
* @param {{ tool: string, args: string[] }} [context] - Optional tool context for package managers like pip
|
|
42
|
+
*
|
|
43
|
+
* @return {PackageManager}
|
|
44
|
+
*/
|
|
45
|
+
export function initializePackageManager(packageManagerName, context) {
|
|
46
|
+
if (packageManagerName === "npm") {
|
|
47
|
+
state.packageManagerName = createNpmPackageManager();
|
|
48
|
+
} else if (packageManagerName === "npx") {
|
|
49
|
+
state.packageManagerName = createNpxPackageManager();
|
|
50
|
+
} else if (packageManagerName === "yarn") {
|
|
51
|
+
state.packageManagerName = createYarnPackageManager();
|
|
52
|
+
} else if (packageManagerName === "pnpm") {
|
|
53
|
+
state.packageManagerName = createPnpmPackageManager();
|
|
54
|
+
} else if (packageManagerName === "pnpx") {
|
|
55
|
+
state.packageManagerName = createPnpxPackageManager();
|
|
56
|
+
} else if (packageManagerName === "bun") {
|
|
57
|
+
state.packageManagerName = createBunPackageManager();
|
|
58
|
+
} else if (packageManagerName === "bunx") {
|
|
59
|
+
state.packageManagerName = createBunxPackageManager();
|
|
60
|
+
} else if (packageManagerName === "pip") {
|
|
61
|
+
state.packageManagerName = createPipPackageManager(context);
|
|
62
|
+
} else if (packageManagerName === "uv") {
|
|
63
|
+
state.packageManagerName = createUvPackageManager();
|
|
64
|
+
} else if (packageManagerName === "uvx") {
|
|
65
|
+
state.packageManagerName = createUvxPackageManager();
|
|
66
|
+
} else if (packageManagerName === "poetry") {
|
|
67
|
+
state.packageManagerName = createPoetryPackageManager();
|
|
68
|
+
} else if (packageManagerName === "pipx") {
|
|
69
|
+
state.packageManagerName = createPipXPackageManager();
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error("Unsupported package manager: " + packageManagerName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return state.packageManagerName;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getPackageManager() {
|
|
78
|
+
if (!state.packageManagerName) {
|
|
79
|
+
throw new Error("Package manager not initialized.");
|
|
80
|
+
}
|
|
81
|
+
return state.packageManagerName;
|
|
82
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { commandArgumentScanner } from "./dependencyScanner/commandArgumentScanner.js";
|
|
2
|
+
import { nullScanner } from "./dependencyScanner/nullScanner.js";
|
|
3
|
+
import { runNpm } from "./runNpmCommand.js";
|
|
4
|
+
import {
|
|
5
|
+
getNpmCommandForArgs,
|
|
6
|
+
npmInstallCommand,
|
|
7
|
+
npmUpdateCommand,
|
|
8
|
+
npmExecCommand,
|
|
9
|
+
} from "./utils/npmCommands.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @returns {import("../currentPackageManager.js").PackageManager}
|
|
13
|
+
*/
|
|
14
|
+
export function createNpmPackageManager() {
|
|
15
|
+
/**
|
|
16
|
+
* @param {string[]} args
|
|
17
|
+
*
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
function isSupportedCommand(args) {
|
|
21
|
+
const scanner = findDependencyScannerForCommand(
|
|
22
|
+
commandScannerMapping,
|
|
23
|
+
args
|
|
24
|
+
);
|
|
25
|
+
return scanner.shouldScan(args);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string[]} args
|
|
30
|
+
*
|
|
31
|
+
* @returns {ReturnType<import("../currentPackageManager.js").PackageManager["getDependencyUpdatesForCommand"]>}
|
|
32
|
+
*/
|
|
33
|
+
function getDependencyUpdatesForCommand(args) {
|
|
34
|
+
const scanner = findDependencyScannerForCommand(
|
|
35
|
+
commandScannerMapping,
|
|
36
|
+
args
|
|
37
|
+
);
|
|
38
|
+
return scanner.scan(args);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
runCommand: runNpm,
|
|
43
|
+
isSupportedCommand,
|
|
44
|
+
getDependencyUpdatesForCommand,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @type {Record<string, import("./dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner>}
|
|
50
|
+
*/
|
|
51
|
+
const commandScannerMapping = {
|
|
52
|
+
[npmInstallCommand]: commandArgumentScanner(),
|
|
53
|
+
[npmUpdateCommand]: commandArgumentScanner(),
|
|
54
|
+
[npmExecCommand]: commandArgumentScanner({ ignoreDryRun: true }), // exec command doesn't support dry-run
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @param {Record<string, import("./dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner>} scanners
|
|
60
|
+
* @param {string[]} args
|
|
61
|
+
*
|
|
62
|
+
* @returns {import("./dependencyScanner/commandArgumentScanner.js").CommandArgumentScanner}
|
|
63
|
+
*/
|
|
64
|
+
function findDependencyScannerForCommand(scanners, args) {
|
|
65
|
+
const command = getNpmCommandForArgs(args);
|
|
66
|
+
if (!command) {
|
|
67
|
+
return nullScanner();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const scanner = scanners[command];
|
|
71
|
+
return scanner ? scanner : nullScanner();
|
|
72
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
|
2
|
+
import { parsePackagesFromInstallArgs } from "../parsing/parsePackagesFromInstallArgs.js";
|
|
3
|
+
import { hasDryRunArg } from "../utils/npmCommands.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} ScanResult
|
|
7
|
+
* @property {string} name
|
|
8
|
+
* @property {string} version
|
|
9
|
+
* @property {string} type
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} ScannerOptions
|
|
14
|
+
* @property {boolean} [ignoreDryRun]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} CommandArgumentScanner
|
|
19
|
+
* @property {(args: string[]) => Promise<ScanResult[]> | ScanResult[]} scan
|
|
20
|
+
* @property {(args: string[]) => boolean} shouldScan
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {ScannerOptions} [opts]
|
|
25
|
+
*
|
|
26
|
+
* @returns {CommandArgumentScanner}
|
|
27
|
+
*/
|
|
28
|
+
export function commandArgumentScanner(opts) {
|
|
29
|
+
const ignoreDryRun = opts?.ignoreDryRun ?? false;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
scan: (args) => scanDependencies(args),
|
|
33
|
+
shouldScan: (args) => shouldScanDependencies(args, ignoreDryRun),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string[]} args
|
|
39
|
+
* @returns {Promise<ScanResult[]>}
|
|
40
|
+
*/
|
|
41
|
+
function scanDependencies(args) {
|
|
42
|
+
return checkChangesFromArgs(args);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {string[]} args
|
|
47
|
+
* @param {boolean} ignoreDryRun
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
function shouldScanDependencies(args, ignoreDryRun) {
|
|
51
|
+
return ignoreDryRun || !hasDryRunArg(args);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string[]} args
|
|
56
|
+
* @returns {Promise<ScanResult[]>}
|
|
57
|
+
*/
|
|
58
|
+
export async function checkChangesFromArgs(args) {
|
|
59
|
+
const changes = [];
|
|
60
|
+
const packageUpdates = parsePackagesFromInstallArgs(args);
|
|
61
|
+
|
|
62
|
+
for (const packageUpdate of packageUpdates) {
|
|
63
|
+
var exactVersion = await resolvePackageVersion(
|
|
64
|
+
packageUpdate.name,
|
|
65
|
+
packageUpdate.version
|
|
66
|
+
);
|
|
67
|
+
if (exactVersion) {
|
|
68
|
+
packageUpdate.version = exactVersion;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
changes.push({ ...packageUpdate, type: "add" });
|
|
72
|
+
}
|
|
73
|
+
return changes;
|
|
74
|
+
}
|