@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/README.md ADDED
@@ -0,0 +1,257 @@
1
+ ![Aikido Safe Chain](https://raw.githubusercontent.com/AikidoSec/safe-chain/main/docs/banner.svg)
2
+
3
+ # Aikido Safe Chain
4
+
5
+ [![NPM Version](https://img.shields.io/npm/v/%40aikidosec%2Fsafe-chain?style=flat-square)](https://www.npmjs.com/package/@aikidosec/safe-chain)
6
+ [![NPM Downloads](https://img.shields.io/npm/dw/%40aikidosec%2Fsafe-chain?style=flat-square)](https://www.npmjs.com/package/@aikidosec/safe-chain)
7
+
8
+ - ✅ **Block malware on developer laptops and CI/CD**
9
+ - ✅ **Supports npm and PyPI** more package managers coming
10
+ - ✅ **Blocks packages newer than 24 hours** without breaking your build
11
+ - ✅ **Tokenless, free, no build data shared**
12
+
13
+ Aikido Safe Chain supports the following package managers:
14
+
15
+ - 📦 **npm**
16
+ - 📦 **npx**
17
+ - 📦 **yarn**
18
+ - 📦 **pnpm**
19
+ - 📦 **pnpx**
20
+ - 📦 **bun**
21
+ - 📦 **bunx**
22
+ - 📦 **pip** (beta)
23
+ - 📦 **pip3** (beta)
24
+ - 📦 **uv** (beta)
25
+
26
+ # Usage
27
+
28
+ ## Installation
29
+
30
+ Installing the Aikido Safe Chain is easy with our one-line installer.
31
+
32
+ > ⚠️ **Already installed via npm?** See the [migration guide](https://github.com/AikidoSec/safe-chain/blob/main/docs/npm-to-binary-migration.md) to switch to the binary version.
33
+
34
+ ### Unix/Linux/macOS
35
+
36
+ **Default installation (JavaScript packages only):**
37
+
38
+ ```shell
39
+ curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh
40
+ ```
41
+
42
+ **Include Python support (pip/pip3/uv):**
43
+
44
+ ```shell
45
+ curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --include-python
46
+ ```
47
+
48
+ ### Windows (PowerShell)
49
+
50
+ **Default installation (JavaScript packages only):**
51
+
52
+ ```powershell
53
+ iex (iwr "https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.ps1" -UseBasicParsing)
54
+ ```
55
+
56
+ **Include Python support (pip/pip3/uv):**
57
+
58
+ ```powershell
59
+ iex "& { $(iwr 'https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.ps1' -UseBasicParsing) } -includepython"
60
+ ```
61
+
62
+ ### Verify the installation
63
+
64
+ 1. **❗Restart your terminal** to start using the Aikido Safe Chain.
65
+
66
+ - This step is crucial as it ensures that the shell aliases for npm, npx, yarn, pnpm, pnpx, bun, bunx, and pip/pip3 are loaded correctly. If you do not restart your terminal, the aliases will not be available.
67
+
68
+ 2. **Verify the installation** by running one of the following commands:
69
+
70
+ For JavaScript/Node.js:
71
+
72
+ ```shell
73
+ npm install safe-chain-test
74
+ ```
75
+
76
+ For Python (if you enabled Python support):
77
+
78
+ ```shell
79
+ pip3 install safe-chain-pi-test
80
+ ```
81
+
82
+ - The output should show that Aikido Safe Chain is blocking the installation of these test packages as they are flagged as malware.
83
+
84
+ When running `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `uv`, `pip`, or `pip3` commands, the Aikido Safe Chain will automatically check for malware in the packages you are trying to install. It also intercepts Python module invocations for pip when available (e.g., `python -m pip install ...`, `python3 -m pip download ...`). If any malware is detected, it will prompt you to exit the command.
85
+
86
+ You can check the installed version by running:
87
+
88
+ ```shell
89
+ safe-chain --version
90
+ ```
91
+
92
+ ## How it works
93
+
94
+ ### Malware Blocking
95
+
96
+ The Aikido Safe Chain works by running a lightweight proxy server that intercepts package downloads from the npm registry and PyPI. When you run npm, npx, yarn, pnpm, pnpx, bun, bunx, uv, `pip`, or `pip3` commands, all package downloads are routed through this local proxy, which verifies packages in real-time against **[Aikido Intel - Open Sources Threat Intelligence](https://intel.aikido.dev/?tab=malware)**. If malware is detected in any package (including deep dependencies), the proxy blocks the download before the malicious code reaches your machine.
97
+
98
+ ### Minimum package age (npm only)
99
+
100
+ For npm packages, Safe Chain temporarily suppresses packages published within the last 24 hours (by default) until they have been validated against malware. This provides an additional security layer during the critical period when newly published packages are most vulnerable to containing undetected threats. You can configure this threshold or bypass this protection entirely - see the [Minimum Package Age Configuration](#minimum-package-age) section below.
101
+
102
+ ⚠️ This feature **only applies to npm-based package managers** (npm, npx, yarn, pnpm, pnpx, bun, bunx) and does not apply to Python package managers (uv, pip, pip3).
103
+
104
+ ### Shell Integration
105
+
106
+ The Aikido Safe Chain integrates with your shell to provide a seamless experience when using npm, npx, yarn, pnpm, pnpx, bun, bunx, and Python package managers (uv, pip). It sets up aliases for these commands so that they are wrapped by the Aikido Safe Chain commands, which manage the proxy server before executing the original commands. We currently support:
107
+
108
+ - ✅ **Bash**
109
+ - ✅ **Zsh**
110
+ - ✅ **Fish**
111
+ - ✅ **PowerShell**
112
+ - ✅ **PowerShell Core**
113
+
114
+ More information about the shell integration can be found in the [shell integration documentation](https://github.com/AikidoSec/safe-chain/blob/main/docs/shell-integration.md).
115
+
116
+ ## Uninstallation
117
+
118
+ To uninstall the Aikido Safe Chain, you can run the following command:
119
+
120
+ 1. **Remove all aliases from your shell** by running:
121
+ ```shell
122
+ safe-chain teardown
123
+ ```
124
+ 2. **Uninstall the Aikido Safe Chain package** using npm:
125
+ ```shell
126
+ npm uninstall -g @aikidosec/safe-chain
127
+ ```
128
+ 3. **❗Restart your terminal** to remove the aliases.
129
+
130
+ # Configuration
131
+
132
+ ## Logging
133
+
134
+ You can control the output from Aikido Safe Chain using the `--safe-chain-logging` flag:
135
+
136
+ - `--safe-chain-logging=silent` - Suppresses all Aikido Safe Chain output except when malware is blocked. The package manager output is written to stdout as normal, and Safe Chain only writes a short message if it has blocked malware and causes the process to exit.
137
+
138
+ Example usage:
139
+
140
+ ```shell
141
+ npm install express --safe-chain-logging=silent
142
+ ```
143
+
144
+ - `--safe-chain-logging=verbose` - Enables detailed diagnostic output from Aikido Safe Chain. Useful for troubleshooting issues or understanding what Safe Chain is doing behind the scenes.
145
+
146
+ Example usage:
147
+
148
+ ```shell
149
+ npm install express --safe-chain-logging=verbose
150
+ ```
151
+
152
+ ## Minimum Package Age
153
+
154
+ You can configure how long packages must exist before Safe Chain allows their installation. By default, packages must be at least 24 hours old before they can be installed through npm-based package managers.
155
+
156
+ ### Configuration Options
157
+
158
+ You can set the minimum package age through multiple sources (in order of priority):
159
+
160
+ 1. **CLI Argument** (highest priority):
161
+
162
+ ```shell
163
+ npm install express --safe-chain-minimum-package-age-hours=48
164
+ ```
165
+
166
+ 2. **Environment Variable**:
167
+
168
+ ```shell
169
+ export SAFE_CHAIN_MINIMUM_PACKAGE_AGE_HOURS=48
170
+ npm install express
171
+ ```
172
+
173
+ 3. **Config File** (`~/.aikido/config.json`):
174
+
175
+ ```json
176
+ {
177
+ "minimumPackageAgeHours": 48
178
+ }
179
+ ```
180
+
181
+ # Usage in CI/CD
182
+
183
+ You can protect your CI/CD pipelines from malicious packages by integrating Aikido Safe Chain into your build process. This ensures that any packages installed during your automated builds are checked for malware before installation.
184
+
185
+ ## Installation for CI/CD
186
+
187
+ Use the `--ci` flag to automatically configure Aikido Safe Chain for CI/CD environments. This sets up executable shims in the PATH instead of shell aliases.
188
+
189
+ ### Unix/Linux/macOS (GitHub Actions, Azure Pipelines, etc.)
190
+
191
+ **JavaScript only:**
192
+
193
+ ```shell
194
+ curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci
195
+ ```
196
+
197
+ **With Python support:**
198
+
199
+ ```shell
200
+ curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci --include-python
201
+ ```
202
+
203
+ ### Windows (Azure Pipelines, etc.)
204
+
205
+ **JavaScript only:**
206
+
207
+ ```powershell
208
+ iex "& { $(iwr 'https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.ps1' -UseBasicParsing) } -ci"
209
+ ```
210
+
211
+ **With Python support:**
212
+
213
+ ```powershell
214
+ iex "& { $(iwr 'https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.ps1' -UseBasicParsing) } -ci -includepython"
215
+ ```
216
+
217
+ ## Supported Platforms
218
+
219
+ - ✅ **GitHub Actions**
220
+ - ✅ **Azure Pipelines**
221
+
222
+ ## GitHub Actions Example
223
+
224
+ ```yaml
225
+ - name: Setup Node.js
226
+ uses: actions/setup-node@v4
227
+ with:
228
+ node-version: "22"
229
+ cache: "npm"
230
+
231
+ - name: Install safe-chain
232
+ run: curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci --include-python
233
+
234
+ - name: Install dependencies
235
+ run: npm ci
236
+ ```
237
+
238
+ > **Note:** Remove `--include-python` if you don't need Python (pip/pip3/uv) support.
239
+
240
+ ## Azure DevOps Example
241
+
242
+ ```yaml
243
+ - task: NodeTool@0
244
+ inputs:
245
+ versionSpec: "22.x"
246
+ displayName: "Install Node.js"
247
+
248
+ - script: curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci --include-python
249
+ displayName: "Install safe-chain"
250
+
251
+ - script: npm ci
252
+ displayName: "Install dependencies"
253
+ ```
254
+
255
+ > **Note:** Remove `--include-python` if you don't need Python (pip/pip3/uv) support.
256
+
257
+ After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
6
+
7
+ setEcoSystem(ECOSYSTEM_JS);
8
+ const packageManagerName = "bun";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
6
+
7
+ setEcoSystem(ECOSYSTEM_JS);
8
+ const packageManagerName = "bunx";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
6
+
7
+ setEcoSystem(ECOSYSTEM_JS);
8
+ const packageManagerName = "npm";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
6
+
7
+ setEcoSystem(ECOSYSTEM_JS);
8
+ const packageManagerName = "npx";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
6
+ import { setCurrentPipInvocation, PIP_INVOCATIONS, PIP_PACKAGE_MANAGER } from "../src/packagemanager/pip/pipSettings.js";
7
+
8
+ // Set eco system
9
+ setEcoSystem(ECOSYSTEM_PY);
10
+
11
+ // Set current invocation
12
+ setCurrentPipInvocation(PIP_INVOCATIONS.PIP);
13
+
14
+ initializePackageManager(PIP_PACKAGE_MANAGER);
15
+
16
+ (async () => {
17
+ // Pass through only user-supplied pip args
18
+ var exitCode = await main(process.argv.slice(2));
19
+ process.exit(exitCode);
20
+ })();
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
6
+ import { setCurrentPipInvocation, PIP_INVOCATIONS, PIP_PACKAGE_MANAGER } from "../src/packagemanager/pip/pipSettings.js";
7
+
8
+ // Set eco system
9
+ setEcoSystem(ECOSYSTEM_PY);
10
+
11
+ // Set current invocation
12
+ setCurrentPipInvocation(PIP_INVOCATIONS.PIP3);
13
+
14
+ // Create package manager
15
+ initializePackageManager(PIP_PACKAGE_MANAGER);
16
+
17
+ (async () => {
18
+ // Pass through only user-supplied pip args
19
+ var exitCode = await main(process.argv.slice(2));
20
+ process.exit(exitCode);
21
+ })();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
6
+
7
+ setEcoSystem(ECOSYSTEM_JS);
8
+ const packageManagerName = "pnpm";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
6
+
7
+ setEcoSystem(ECOSYSTEM_JS);
8
+ const packageManagerName = "pnpx";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
4
+ import { setCurrentPipInvocation, PIP_INVOCATIONS, PIP_PACKAGE_MANAGER } from "../src/packagemanager/pip/pipSettings.js";
5
+ import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
6
+ import { main } from "../src/main.js";
7
+
8
+ // Set eco system
9
+ setEcoSystem(ECOSYSTEM_PY);
10
+
11
+ // Strip nodejs and wrapper script from args
12
+ let argv = process.argv.slice(2);
13
+
14
+ (async () => {
15
+ if (argv[0] === '-m' && (argv[1] === 'pip' || argv[1] === 'pip3')) {
16
+ setEcoSystem(ECOSYSTEM_PY);
17
+ setCurrentPipInvocation(argv[1] === 'pip3' ? PIP_INVOCATIONS.PY_PIP3 : PIP_INVOCATIONS.PY_PIP);
18
+ initializePackageManager(PIP_PACKAGE_MANAGER);
19
+
20
+ // Strip off the '-m pip' or '-m pip3' from the args
21
+ argv = argv.slice(2);
22
+
23
+ var exitCode = await main(argv);
24
+ process.exit(exitCode);
25
+ } else {
26
+ // Forward to real python binary for non-pip flows
27
+ const { spawn } = await import('child_process');
28
+ spawn('python', argv, { stdio: 'inherit' });
29
+ }
30
+ })();
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
4
+ import { setCurrentPipInvocation, PIP_INVOCATIONS, PIP_PACKAGE_MANAGER } from "../src/packagemanager/pip/pipSettings.js";
5
+ import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
6
+ import { main } from "../src/main.js";
7
+
8
+ // Set eco system
9
+ setEcoSystem(ECOSYSTEM_PY);
10
+
11
+ // Strip nodejs and wrapper script from args
12
+ let argv = process.argv.slice(2);
13
+
14
+ (async () => {
15
+ if (argv[0] === '-m' && (argv[1] === 'pip' || argv[1] === 'pip3')) {
16
+ setEcoSystem(ECOSYSTEM_PY);
17
+ setCurrentPipInvocation(argv[1] === 'pip3' ? PIP_INVOCATIONS.PY3_PIP3 : PIP_INVOCATIONS.PY3_PIP);
18
+ initializePackageManager(PIP_PACKAGE_MANAGER);
19
+
20
+ // Strip off the '-m pip' or '-m pip3' from the args
21
+ argv = argv.slice(2);
22
+
23
+ var exitCode = await main(argv);
24
+ process.exit(exitCode);
25
+ } else {
26
+ // Forward to real python3 binary for non-pip flows
27
+ const { spawn } = await import('child_process');
28
+ spawn('python3', argv, { stdio: 'inherit' });
29
+ }
30
+ })();
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
6
+
7
+ // Set eco system
8
+ setEcoSystem(ECOSYSTEM_PY);
9
+
10
+ initializePackageManager("uv");
11
+
12
+ (async () => {
13
+ // Pass through only user-supplied uv args
14
+ var exitCode = await main(process.argv.slice(2));
15
+ process.exit(exitCode);
16
+ })();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/main.js";
4
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
5
+ import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
6
+
7
+ setEcoSystem(ECOSYSTEM_JS);
8
+ const packageManagerName = "yarn";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ import chalk from "chalk";
4
+ import { ui } from "../src/environment/userInteraction.js";
5
+ import { setup } from "../src/shell-integration/setup.js";
6
+ import { teardown } from "../src/shell-integration/teardown.js";
7
+ import { setupCi } from "../src/shell-integration/setup-ci.js";
8
+ import { initializeCliArguments } from "../src/config/cliArguments.js";
9
+ import { setEcoSystem } from "../src/config/settings.js";
10
+ import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
11
+ import { main } from "../src/main.js";
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ import fs from "fs";
15
+ import { knownAikidoTools } from "../src/shell-integration/helpers.js";
16
+ import {
17
+ PIP_INVOCATIONS,
18
+ PIP_PACKAGE_MANAGER,
19
+ setCurrentPipInvocation,
20
+ } from "../src/packagemanager/pip/pipSettings.js";
21
+
22
+ /** @type {string} */
23
+ // This checks the current file's dirname in a way that's compatible with:
24
+ // - Modulejs (import.meta.url)
25
+ // - ES modules (__dirname)
26
+ // This is needed because safe-chain's npm package is built using ES modules,
27
+ // but building the binaries requires commonjs.
28
+ let dirname;
29
+ if (import.meta.url) {
30
+ const filename = fileURLToPath(import.meta.url);
31
+ dirname = path.dirname(filename);
32
+ } else {
33
+ dirname = __dirname;
34
+ }
35
+
36
+ if (process.argv.length < 3) {
37
+ ui.writeError("No command provided. Please provide a command to execute.");
38
+ ui.emptyLine();
39
+ writeHelp();
40
+ process.exit(1);
41
+ }
42
+
43
+ initializeCliArguments(process.argv);
44
+
45
+ const command = process.argv[2];
46
+
47
+ const tool = knownAikidoTools.find((tool) => tool.tool === command);
48
+
49
+ if (tool && tool.internalPackageManagerName === PIP_PACKAGE_MANAGER) {
50
+ (async function () {
51
+ await executePip(tool);
52
+ })();
53
+ } else if (tool) {
54
+ const args = process.argv.slice(3);
55
+
56
+ setEcoSystem(tool.ecoSystem);
57
+ initializePackageManager(tool.internalPackageManagerName);
58
+
59
+ (async () => {
60
+ var exitCode = await main(args);
61
+ process.exit(exitCode);
62
+ })();
63
+ } else if (command === "help" || command === "--help" || command === "-h") {
64
+ writeHelp();
65
+ process.exit(0);
66
+ } else if (command === "setup") {
67
+ setup();
68
+ } else if (command === "teardown") {
69
+ teardown();
70
+ } else if (command === "setup-ci") {
71
+ setupCi();
72
+ } else if (command === "--version" || command === "-v" || command === "-v") {
73
+ (async () => {
74
+ ui.writeInformation(`Current safe-chain version: ${await getVersion()}`);
75
+ })();
76
+ } else {
77
+ ui.writeError(`Unknown command: ${command}.`);
78
+ ui.emptyLine();
79
+
80
+ writeHelp();
81
+
82
+ process.exit(1);
83
+ }
84
+
85
+ function writeHelp() {
86
+ ui.writeInformation(
87
+ chalk.bold("Usage: ") + chalk.cyan("safe-chain <command>")
88
+ );
89
+ ui.emptyLine();
90
+ ui.writeInformation(
91
+ `Available commands: ${chalk.cyan("setup")}, ${chalk.cyan(
92
+ "teardown"
93
+ )}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("help")}, ${chalk.cyan(
94
+ "--version"
95
+ )}`
96
+ );
97
+ ui.emptyLine();
98
+ ui.writeInformation(
99
+ `- ${chalk.cyan(
100
+ "safe-chain setup"
101
+ )}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.`
102
+ );
103
+ ui.writeInformation(
104
+ ` ${chalk.yellow(
105
+ "--include-python"
106
+ )}: Experimental: include Python package managers (pip, pip3) in the setup.`
107
+ );
108
+ ui.writeInformation(
109
+ `- ${chalk.cyan(
110
+ "safe-chain teardown"
111
+ )}: This will remove safe-chain aliases from your shell configuration.`
112
+ );
113
+ ui.writeInformation(
114
+ `- ${chalk.cyan(
115
+ "safe-chain setup-ci"
116
+ )}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.`
117
+ );
118
+ ui.writeInformation(
119
+ ` ${chalk.yellow(
120
+ "--include-python"
121
+ )}: Experimental: include Python package managers (pip, pip3) in the setup.`
122
+ );
123
+ ui.writeInformation(
124
+ `- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan(
125
+ "-v"
126
+ )}): Display the current version of safe-chain.`
127
+ );
128
+ ui.emptyLine();
129
+ }
130
+
131
+ async function getVersion() {
132
+ const packageJsonPath = path.join(dirname, "..", "package.json");
133
+
134
+ const data = await fs.promises.readFile(packageJsonPath);
135
+ const json = JSON.parse(data.toString("utf8"));
136
+
137
+ if (json && json.version) {
138
+ return json.version;
139
+ }
140
+
141
+ return "0.0.0";
142
+ }
143
+
144
+ /**
145
+ * @param {import("../src/shell-integration/helpers.js").AikidoTool} tool
146
+ */
147
+ async function executePip(tool) {
148
+ // Scanners for pip / pip3 / python / python3 use a slightly different approach:
149
+ // - They all use the same PIP_PACKAGE_MANAGER internally, but need some setup to be able to do so
150
+ // - It needs to set which tool to run (pip / pip3 / python / python3)
151
+ // - For python and python3, the -m pip/pip3 args are removed and later added again by the package manager
152
+ // - Python / python3 skips safe-chain if not being run with -m pip or -m pip3
153
+
154
+ let args = process.argv.slice(3);
155
+ setEcoSystem(tool.ecoSystem);
156
+ initializePackageManager(PIP_PACKAGE_MANAGER);
157
+
158
+ let shouldSkip = false;
159
+ if (tool.tool === "pip") {
160
+ setCurrentPipInvocation(PIP_INVOCATIONS.PIP);
161
+ } else if (tool.tool === "pip3") {
162
+ setCurrentPipInvocation(PIP_INVOCATIONS.PIP3);
163
+ } else if (tool.tool === "python") {
164
+ if (args[0] === "-m" && (args[1] === "pip" || args[1] === "pip3")) {
165
+ setCurrentPipInvocation(
166
+ args[1] === "pip3" ? PIP_INVOCATIONS.PY_PIP3 : PIP_INVOCATIONS.PY_PIP
167
+ );
168
+ args = args.slice(2);
169
+ } else {
170
+ shouldSkip = true;
171
+ }
172
+ } else if (tool.tool === "python3") {
173
+ if (args[0] === "-m" && (args[1] === "pip" || args[1] === "pip3")) {
174
+ setCurrentPipInvocation(
175
+ args[1] === "pip3" ? PIP_INVOCATIONS.PY3_PIP3 : PIP_INVOCATIONS.PY3_PIP
176
+ );
177
+ args = args.slice(2);
178
+ } else {
179
+ shouldSkip = true;
180
+ }
181
+ }
182
+
183
+ if (shouldSkip) {
184
+ const { spawn } = await import("child_process");
185
+ spawn(tool.tool, args, { stdio: "inherit" });
186
+ } else {
187
+ var exitCode = await main(args);
188
+ process.exit(exitCode);
189
+ }
190
+ }