@aikidosec/safe-chain 0.0.4-connect-timeout-beta

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 (94) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +257 -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 +20 -0
  8. package/bin/aikido-pip3.js +21 -0
  9. package/bin/aikido-pnpm.js +14 -0
  10. package/bin/aikido-pnpx.js +14 -0
  11. package/bin/aikido-python.js +30 -0
  12. package/bin/aikido-python3.js +30 -0
  13. package/bin/aikido-uv.js +16 -0
  14. package/bin/aikido-yarn.js +14 -0
  15. package/bin/safe-chain.js +190 -0
  16. package/docs/banner.svg +151 -0
  17. package/docs/npm-to-binary-migration.md +89 -0
  18. package/docs/safe-package-manager-demo.gif +0 -0
  19. package/docs/safe-package-manager-demo.png +0 -0
  20. package/docs/shell-integration.md +149 -0
  21. package/package.json +68 -0
  22. package/src/api/aikido.js +54 -0
  23. package/src/api/npmApi.js +71 -0
  24. package/src/config/cliArguments.js +138 -0
  25. package/src/config/configFile.js +192 -0
  26. package/src/config/environmentVariables.js +7 -0
  27. package/src/config/settings.js +100 -0
  28. package/src/environment/environment.js +14 -0
  29. package/src/environment/userInteraction.js +122 -0
  30. package/src/main.js +104 -0
  31. package/src/packagemanager/_shared/matchesCommand.js +18 -0
  32. package/src/packagemanager/bun/createBunPackageManager.js +53 -0
  33. package/src/packagemanager/currentPackageManager.js +72 -0
  34. package/src/packagemanager/npm/createPackageManager.js +72 -0
  35. package/src/packagemanager/npm/dependencyScanner/commandArgumentScanner.js +74 -0
  36. package/src/packagemanager/npm/dependencyScanner/nullScanner.js +9 -0
  37. package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.js +144 -0
  38. package/src/packagemanager/npm/runNpmCommand.js +25 -0
  39. package/src/packagemanager/npm/utils/abbrevs-generated.js +359 -0
  40. package/src/packagemanager/npm/utils/cmd-list.js +174 -0
  41. package/src/packagemanager/npm/utils/npmCommands.js +34 -0
  42. package/src/packagemanager/npx/createPackageManager.js +15 -0
  43. package/src/packagemanager/npx/dependencyScanner/commandArgumentScanner.js +43 -0
  44. package/src/packagemanager/npx/parsing/parsePackagesFromArguments.js +130 -0
  45. package/src/packagemanager/npx/runNpxCommand.js +25 -0
  46. package/src/packagemanager/pip/createPackageManager.js +21 -0
  47. package/src/packagemanager/pip/pipSettings.js +30 -0
  48. package/src/packagemanager/pip/runPipCommand.js +175 -0
  49. package/src/packagemanager/pnpm/createPackageManager.js +57 -0
  50. package/src/packagemanager/pnpm/dependencyScanner/commandArgumentScanner.js +35 -0
  51. package/src/packagemanager/pnpm/parsing/parsePackagesFromArguments.js +109 -0
  52. package/src/packagemanager/pnpm/runPnpmCommand.js +36 -0
  53. package/src/packagemanager/uv/createUvPackageManager.js +18 -0
  54. package/src/packagemanager/uv/runUvCommand.js +71 -0
  55. package/src/packagemanager/yarn/createPackageManager.js +41 -0
  56. package/src/packagemanager/yarn/dependencyScanner/commandArgumentScanner.js +35 -0
  57. package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.js +128 -0
  58. package/src/packagemanager/yarn/runYarnCommand.js +41 -0
  59. package/src/registryProxy/certBundle.js +95 -0
  60. package/src/registryProxy/certUtils.js +128 -0
  61. package/src/registryProxy/http-utils.js +17 -0
  62. package/src/registryProxy/interceptors/createInterceptorForEcoSystem.js +25 -0
  63. package/src/registryProxy/interceptors/interceptorBuilder.js +140 -0
  64. package/src/registryProxy/interceptors/npm/modifyNpmInfo.js +177 -0
  65. package/src/registryProxy/interceptors/npm/npmInterceptor.js +47 -0
  66. package/src/registryProxy/interceptors/npm/parseNpmPackageUrl.js +43 -0
  67. package/src/registryProxy/interceptors/pipInterceptor.js +115 -0
  68. package/src/registryProxy/mitmRequestHandler.js +231 -0
  69. package/src/registryProxy/plainHttpProxy.js +95 -0
  70. package/src/registryProxy/registryProxy.js +184 -0
  71. package/src/registryProxy/tunnelRequestHandler.js +180 -0
  72. package/src/scanning/audit/index.js +129 -0
  73. package/src/scanning/index.js +82 -0
  74. package/src/scanning/malwareDatabase.js +131 -0
  75. package/src/shell-integration/helpers.js +213 -0
  76. package/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh +22 -0
  77. package/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd +24 -0
  78. package/src/shell-integration/setup-ci.js +170 -0
  79. package/src/shell-integration/setup.js +127 -0
  80. package/src/shell-integration/shellDetection.js +37 -0
  81. package/src/shell-integration/startup-scripts/include-python/init-fish.fish +94 -0
  82. package/src/shell-integration/startup-scripts/include-python/init-posix.sh +81 -0
  83. package/src/shell-integration/startup-scripts/include-python/init-pwsh.ps1 +115 -0
  84. package/src/shell-integration/startup-scripts/init-fish.fish +71 -0
  85. package/src/shell-integration/startup-scripts/init-posix.sh +58 -0
  86. package/src/shell-integration/startup-scripts/init-pwsh.ps1 +92 -0
  87. package/src/shell-integration/supported-shells/bash.js +134 -0
  88. package/src/shell-integration/supported-shells/fish.js +77 -0
  89. package/src/shell-integration/supported-shells/powershell.js +73 -0
  90. package/src/shell-integration/supported-shells/windowsPowershell.js +73 -0
  91. package/src/shell-integration/supported-shells/zsh.js +74 -0
  92. package/src/shell-integration/teardown.js +64 -0
  93. package/src/utils/safeSpawn.js +137 -0
  94. package/tsconfig.json +21 -0
package/src/main.js ADDED
@@ -0,0 +1,104 @@
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
+ process.on("SIGINT", handleProcessTermination);
17
+ process.on("SIGTERM", handleProcessTermination);
18
+
19
+ const proxy = createSafeChainProxy();
20
+ await proxy.startServer();
21
+
22
+ // Global error handlers to log unhandled errors
23
+ process.on("uncaughtException", (error) => {
24
+ ui.writeError(`Safe-chain: Uncaught exception: ${error.message}`);
25
+ ui.writeVerbose(`Stack trace: ${error.stack}`);
26
+ process.exit(1);
27
+ });
28
+
29
+ process.on("unhandledRejection", (reason) => {
30
+ ui.writeError(`Safe-chain: Unhandled promise rejection: ${reason}`);
31
+ if (reason instanceof Error) {
32
+ ui.writeVerbose(`Stack trace: ${reason.stack}`);
33
+ }
34
+ process.exit(1);
35
+ });
36
+
37
+ try {
38
+ // This parses all the --safe-chain arguments and removes them from the args array
39
+ args = initializeCliArguments(args);
40
+
41
+ if (shouldScanCommand(args)) {
42
+ const commandScanResult = await scanCommand(args);
43
+
44
+ // Returning the exit code back to the caller allows the promise
45
+ // to be awaited in the bin files and return the correct exit code
46
+ if (commandScanResult !== 0) {
47
+ return commandScanResult;
48
+ }
49
+ }
50
+
51
+ // Buffer logs during package manager execution, this avoids interleaving
52
+ // of logs from the package manager and safe-chain
53
+ // Not doing this could cause bugs to disappear when cursor movement codes
54
+ // are written by the package manager while safe-chain is writing logs
55
+ ui.startBufferingLogs();
56
+ const packageManagerResult = await getPackageManager().runCommand(args);
57
+
58
+ // Write all buffered logs
59
+ ui.writeBufferedLogsAndStopBuffering();
60
+
61
+ if (!proxy.verifyNoMaliciousPackages()) {
62
+ return 1;
63
+ }
64
+
65
+ const auditStats = getAuditStats();
66
+ if (auditStats.totalPackages > 0) {
67
+ ui.emptyLine();
68
+ ui.writeInformation(
69
+ `${chalk.green("✔")} Safe-chain: Scanned ${
70
+ auditStats.totalPackages
71
+ } packages, no malware found.`
72
+ );
73
+ }
74
+
75
+ if (proxy.hasSuppressedVersions()) {
76
+ ui.writeInformation(
77
+ `${chalk.yellow(
78
+ "ℹ"
79
+ )} Safe-chain: Some package versions were suppressed due to minimum age requirement.`
80
+ );
81
+ ui.writeInformation(
82
+ ` To disable this check, use: ${chalk.cyan(
83
+ "--safe-chain-skip-minimum-package-age"
84
+ )}`
85
+ );
86
+ }
87
+
88
+ // Returning the exit code back to the caller allows the promise
89
+ // to be awaited in the bin files and return the correct exit code
90
+ return packageManagerResult.status;
91
+ } catch (/** @type any */ error) {
92
+ ui.writeError("Failed to check for malicious packages:", error.message);
93
+
94
+ // Returning the exit code back to the caller allows the promise
95
+ // to be awaited in the bin files and return the correct exit code
96
+ return 1;
97
+ } finally {
98
+ await proxy.stopServer();
99
+ }
100
+ }
101
+
102
+ function handleProcessTermination() {
103
+ ui.writeBufferedLogsAndStopBuffering();
104
+ }
@@ -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,53 @@
1
+ import { ui } from "../../environment/userInteraction.js";
2
+ import { safeSpawn } from "../../utils/safeSpawn.js";
3
+ import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.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
+ if (error.status) {
47
+ return { status: error.status };
48
+ } else {
49
+ ui.writeError("Error executing command:", error.message);
50
+ return { status: 1 };
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,72 @@
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
+
15
+ /**
16
+ * @type {{packageManagerName: PackageManager | null}}
17
+ */
18
+ const state = {
19
+ packageManagerName: null,
20
+ };
21
+
22
+ /**
23
+ * @typedef {Object} GetDependencyUpdatesResult
24
+ * @property {string} name
25
+ * @property {string} version
26
+ * @property {string} type
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} PackageManager
31
+ * @property {(args: string[]) => Promise<{ status: number }>} runCommand
32
+ * @property {(args: string[]) => boolean} isSupportedCommand
33
+ * @property {(args: string[]) => Promise<GetDependencyUpdatesResult[]> | GetDependencyUpdatesResult[]} getDependencyUpdatesForCommand
34
+ */
35
+
36
+ /**
37
+ * @param {string} packageManagerName
38
+ *
39
+ * @return {PackageManager}
40
+ */
41
+ export function initializePackageManager(packageManagerName) {
42
+ if (packageManagerName === "npm") {
43
+ state.packageManagerName = createNpmPackageManager();
44
+ } else if (packageManagerName === "npx") {
45
+ state.packageManagerName = createNpxPackageManager();
46
+ } else if (packageManagerName === "yarn") {
47
+ state.packageManagerName = createYarnPackageManager();
48
+ } else if (packageManagerName === "pnpm") {
49
+ state.packageManagerName = createPnpmPackageManager();
50
+ } else if (packageManagerName === "pnpx") {
51
+ state.packageManagerName = createPnpxPackageManager();
52
+ } else if (packageManagerName === "bun") {
53
+ state.packageManagerName = createBunPackageManager();
54
+ } else if (packageManagerName === "bunx") {
55
+ state.packageManagerName = createBunxPackageManager();
56
+ } else if (packageManagerName === "pip") {
57
+ state.packageManagerName = createPipPackageManager();
58
+ } else if (packageManagerName === "uv") {
59
+ state.packageManagerName = createUvPackageManager();
60
+ } else {
61
+ throw new Error("Unsupported package manager: " + packageManagerName);
62
+ }
63
+
64
+ return state.packageManagerName;
65
+ }
66
+
67
+ export function getPackageManager() {
68
+ if (!state.packageManagerName) {
69
+ throw new Error("Package manager not initialized.");
70
+ }
71
+ return state.packageManagerName;
72
+ }
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @returns {import("./commandArgumentScanner.js").CommandArgumentScanner}
3
+ */
4
+ export function nullScanner() {
5
+ return {
6
+ scan: () => [],
7
+ shouldScan: () => false,
8
+ };
9
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @typedef {Object} PackageDetail
3
+ * @property {string} name
4
+ * @property {string} version
5
+ */
6
+
7
+ /**
8
+ * @typedef {Object} NpmOption
9
+ * @property {string} name
10
+ * @property {number} numberOfParameters
11
+ */
12
+
13
+ /**
14
+ * @param {string[]} args
15
+ * @returns {PackageDetail[]}
16
+ */
17
+ export function parsePackagesFromInstallArgs(args) {
18
+ /** @type {{name: string, version: string | null}[]} */
19
+ const changes = [];
20
+ let defaultTag = "latest";
21
+
22
+ // Skip first argument (install command)
23
+ for (let i = 1; i < args.length; i++) {
24
+ const arg = args[i];
25
+ const npmOption = getNpmOption(arg);
26
+
27
+ if (npmOption) {
28
+ // If the option has a parameter, skip the next argument as well
29
+ i += npmOption.numberOfParameters;
30
+
31
+ // it a tag is specified, set the default tag
32
+ if (npmOption.name === "--tag") {
33
+ defaultTag = args[i];
34
+ }
35
+
36
+ continue;
37
+ }
38
+
39
+ const packageDetails = parsePackagename(arg);
40
+ if (packageDetails) {
41
+ changes.push(packageDetails);
42
+ continue;
43
+ }
44
+ }
45
+
46
+ for (const change of changes) {
47
+ if (!change.version) {
48
+ change.version = defaultTag;
49
+ }
50
+ }
51
+
52
+ return /** @type {PackageDetail[]} */ (changes);
53
+ }
54
+
55
+ /**
56
+ * @param {string} arg
57
+ * @returns {NpmOption | undefined}
58
+ */
59
+ function getNpmOption(arg) {
60
+ if (isNpmOptionWithParameter(arg)) {
61
+ return {
62
+ name: arg,
63
+ numberOfParameters: 1,
64
+ };
65
+ }
66
+
67
+ // Arguments starting with "-" or "--" are considered npm options
68
+ if (arg.startsWith("-")) {
69
+ return {
70
+ name: arg,
71
+ numberOfParameters: 0,
72
+ };
73
+ }
74
+
75
+ return undefined;
76
+ }
77
+
78
+ /**
79
+ * @param {string} arg
80
+ * @returns {boolean}
81
+ */
82
+ function isNpmOptionWithParameter(arg) {
83
+ const optionsWithParameters = [
84
+ "--access",
85
+ "--auth-type",
86
+ "--cache",
87
+ "--fetch-retries",
88
+ "--fetch-retry-mintimeout",
89
+ "--fetch-retry-maxtimeout",
90
+ "--fetch-retry-factor",
91
+ "--fetch-timeout",
92
+ "--https-proxy",
93
+ "--include",
94
+ "--location",
95
+ "--lockfile-version",
96
+ "--loglevel",
97
+ "--omit",
98
+ "--proxy",
99
+ "--registry",
100
+ "--replace-registry-host",
101
+ "--tag",
102
+ "--user-config",
103
+ "--workspace",
104
+ ];
105
+
106
+ return optionsWithParameters.includes(arg);
107
+ }
108
+
109
+ /**
110
+ * @param {string} arg
111
+ * @returns {{name: string, version: string | null}}
112
+ */
113
+ function parsePackagename(arg) {
114
+ arg = removeAlias(arg);
115
+ const lastAtIndex = arg.lastIndexOf("@");
116
+
117
+ let name, version;
118
+ // The index of the last "@" should be greater than 0
119
+ // If the index is 0, it means the package name starts with "@" (eg: "@vercel/otel")
120
+ if (lastAtIndex > 0) {
121
+ name = arg.slice(0, lastAtIndex);
122
+ version = arg.slice(lastAtIndex + 1);
123
+ } else {
124
+ name = arg;
125
+ version = null;
126
+ }
127
+
128
+ return {
129
+ name,
130
+ version,
131
+ };
132
+ }
133
+
134
+ /**
135
+ * @param {string} arg
136
+ * @returns {string}
137
+ */
138
+ function removeAlias(arg) {
139
+ const aliasIndex = arg.indexOf("@npm:");
140
+ if (aliasIndex !== -1) {
141
+ return arg.slice(aliasIndex + 5);
142
+ }
143
+ return arg;
144
+ }
@@ -0,0 +1,25 @@
1
+ import { ui } from "../../environment/userInteraction.js";
2
+ import { safeSpawn } from "../../utils/safeSpawn.js";
3
+ import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
4
+
5
+ /**
6
+ * @param {string[]} args
7
+ *
8
+ * @returns {Promise<{status: number}>}
9
+ */
10
+ export async function runNpm(args) {
11
+ try {
12
+ const result = await safeSpawn("npm", args, {
13
+ stdio: "inherit",
14
+ env: mergeSafeChainProxyEnvironmentVariables(process.env),
15
+ });
16
+ return { status: result.status };
17
+ } catch (/** @type any */ error) {
18
+ if (error.status) {
19
+ return { status: error.status };
20
+ } else {
21
+ ui.writeError("Error executing command:", error.message);
22
+ return { status: 1 };
23
+ }
24
+ }
25
+ }