@aikidosec/safe-chain 1.0.14 → 1.0.16
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/README.md +10 -8
- package/bin/aikido-pnpm.js +8 -0
- package/bin/aikido-pnpx.js +8 -0
- package/docs/shell-integration.md +4 -4
- package/package.json +3 -1
- package/src/packagemanager/_shared/matchesCommand.js +13 -0
- package/src/packagemanager/currentPackageManager.js +8 -0
- package/src/packagemanager/pnpm/createPackageManager.js +46 -0
- package/src/packagemanager/pnpm/dependencyScanner/commandArgumentScanner.js +28 -0
- package/src/packagemanager/pnpm/parsing/parsePackagesFromArguments.js +88 -0
- package/src/packagemanager/pnpm/parsing/parsePackagesFromArguments.spec.js +138 -0
- package/src/packagemanager/pnpm/runPnpmCommand.js +24 -0
- package/src/shell-integration/helpers.js +31 -31
- package/src/shell-integration/setup.js +26 -102
- package/src/shell-integration/shellDetection.js +17 -66
- package/src/shell-integration/supported-shells/bash.js +58 -0
- package/src/shell-integration/supported-shells/bash.spec.js +199 -0
- package/src/shell-integration/supported-shells/fish.js +61 -0
- package/src/shell-integration/supported-shells/fish.spec.js +199 -0
- package/src/shell-integration/supported-shells/powershell.js +61 -0
- package/src/shell-integration/supported-shells/powershell.spec.js +204 -0
- package/src/shell-integration/supported-shells/windowsPowershell.js +61 -0
- package/src/shell-integration/supported-shells/windowsPowershell.spec.js +204 -0
- package/src/shell-integration/supported-shells/zsh.js +58 -0
- package/src/shell-integration/supported-shells/zsh.spec.js +199 -0
- package/src/shell-integration/teardown.js +20 -99
- package/src/shell-integration/setup.spec.js +0 -304
- package/src/shell-integration/teardown.spec.js +0 -177
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Aikido Safe Chain
|
|
2
2
|
|
|
3
|
-
The Aikido Safe Chain **prevents developers from installing malware** on their workstations through npm, npx,
|
|
3
|
+
The Aikido Safe Chain **prevents developers from installing malware** on their workstations through npm, npx, yarn, pnpm and pnpx.
|
|
4
4
|
|
|
5
|
-
The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md),
|
|
5
|
+
The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), and [pnpx](https://pnpm.io/cli/dlx) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm or pnpx from downloading or running the malware.
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -11,7 +11,9 @@ Aikido Safe Chain works on Node.js version 18 and above and supports the followi
|
|
|
11
11
|
- ✅ **npm**
|
|
12
12
|
- ✅ **npx**
|
|
13
13
|
- ✅ **yarn**
|
|
14
|
-
-
|
|
14
|
+
- ✅ **pnpm**
|
|
15
|
+
- ✅ **pnpx**
|
|
16
|
+
- 🚧 **bun** Coming soon
|
|
15
17
|
|
|
16
18
|
# Usage
|
|
17
19
|
|
|
@@ -28,20 +30,20 @@ Installing the Aikido Safe Chain is easy. You just need 3 simple steps:
|
|
|
28
30
|
safe-chain setup
|
|
29
31
|
```
|
|
30
32
|
3. **❗Restart your terminal** to start using the Aikido Safe Chain.
|
|
31
|
-
- This step is crucial as it ensures that the shell aliases for npm, npx, and
|
|
33
|
+
- This step is crucial as it ensures that the shell aliases for npm, npx, yarn, pnpm and pnpx are loaded correctly. If you do not restart your terminal, the aliases will not be available.
|
|
32
34
|
4. **Verify the installation** by running:
|
|
33
35
|
```shell
|
|
34
|
-
npm install
|
|
36
|
+
npm install safe-chain-test
|
|
35
37
|
```
|
|
36
38
|
- The output should show that Aikido Safe Chain is blocking the installation of this package as it is flagged as malware.
|
|
37
39
|
|
|
38
|
-
When running `npm`, `npx`, or `
|
|
40
|
+
When running `npm`, `npx`, `yarn`, `pnpm` or `pnpx` commands, the Aikido Safe Chain will automatically check for malware in the packages you are trying to install. If any malware is detected, it will prompt you to exit the command.
|
|
39
41
|
|
|
40
42
|
## How it works
|
|
41
43
|
|
|
42
|
-
The Aikido Safe Chain works by intercepting the npm, npx, and
|
|
44
|
+
The Aikido Safe Chain works by intercepting the npm, npx, yarn, pnpm and pnpx commands and verifying the packages against **[Aikido Intel - Open Sources Threat Intelligence](https://intel.aikido.dev/?tab=malware)**.
|
|
43
45
|
|
|
44
|
-
The Aikido Safe Chain integrates with your shell to provide a seamless experience when using npm, npx, and
|
|
46
|
+
The Aikido Safe Chain integrates with your shell to provide a seamless experience when using npm, npx, yarn, pnpm and pnpx commands. It sets up aliases for these commands so that they are wrapped by the Aikido Safe Chain commands, which perform malware checks before executing the original commands. We currently support:
|
|
45
47
|
|
|
46
48
|
- ✅ **Bash**
|
|
47
49
|
- ✅ **Zsh**
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from "../src/main.js";
|
|
4
|
+
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
|
5
|
+
|
|
6
|
+
const packageManagerName = "pnpm";
|
|
7
|
+
initializePackageManager(packageManagerName, process.versions.node);
|
|
8
|
+
await main(process.argv.slice(2));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from "../src/main.js";
|
|
4
|
+
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
|
5
|
+
|
|
6
|
+
const packageManagerName = "pnpx";
|
|
7
|
+
initializePackageManager(packageManagerName, process.versions.node);
|
|
8
|
+
await main(process.argv.slice(2));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The shell integration automatically wraps common package manager commands (`npm`, `npx`, `yarn`) with Aikido's security scanning functionality. This is achieved by adding shell aliases that redirect these commands to their Aikido-wrapped equivalents.
|
|
5
|
+
The shell integration automatically wraps common package manager commands (`npm`, `npx`, `yarn`, `pnpm`, `pnpx`) with Aikido's security scanning functionality. This is achieved by adding shell aliases that redirect these commands to their Aikido-wrapped equivalents.
|
|
6
6
|
|
|
7
7
|
## Supported Shells
|
|
8
8
|
|
|
@@ -27,7 +27,7 @@ safe-chain setup
|
|
|
27
27
|
This command:
|
|
28
28
|
|
|
29
29
|
- Detects all supported shells on your system
|
|
30
|
-
- Adds aliases for `npm`, `npx`, and `
|
|
30
|
+
- Adds aliases for `npm`, `npx`, `yarn`, `pnpm` and `pnpx` to each shell's startup file
|
|
31
31
|
|
|
32
32
|
❗ After running this command, **you must restart your terminal** for the changes to take effect. This ensures that the aliases are loaded correctly.
|
|
33
33
|
|
|
@@ -75,7 +75,7 @@ The system modifies the following files based on your shell configuration:
|
|
|
75
75
|
This means the aliases are working but the Aikido commands aren't installed or available in your PATH:
|
|
76
76
|
|
|
77
77
|
- Make sure Aikido Safe Chain is properly installed on your system
|
|
78
|
-
- Verify the `aikido-npm`, `aikido-npx`, and `aikido-
|
|
78
|
+
- Verify the `aikido-npm`, `aikido-npx`, `aikido-yarn`, `aikido-pnpm` and `aikido-pnpx` commands exist
|
|
79
79
|
- Check that these commands are in your system's PATH
|
|
80
80
|
|
|
81
81
|
### Manual Verification
|
|
@@ -105,4 +105,4 @@ To verify the integration is working, follow these steps:
|
|
|
105
105
|
|
|
106
106
|
3. **If you need to remove aliases manually:**
|
|
107
107
|
|
|
108
|
-
Edit the same startup file from step 1 and delete any lines containing `aikido-npm`, `aikido-npx`, or `aikido-
|
|
108
|
+
Edit the same startup file from step 1 and delete any lines containing `aikido-npm`, `aikido-npx`, `aikido-yarn`, `aikido-pnpm` or `aikido-pnpx`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikidosec/safe-chain",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"test": "node --test --experimental-test-module-mocks **/*.spec.js",
|
|
6
6
|
"test:watch": "node --test --watch --experimental-test-module-mocks **/*.spec.js",
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"aikido-npm": "bin/aikido-npm.js",
|
|
15
15
|
"aikido-npx": "bin/aikido-npx.js",
|
|
16
16
|
"aikido-yarn": "bin/aikido-yarn.js",
|
|
17
|
+
"aikido-pnpm": "bin/aikido-pnpm.js",
|
|
18
|
+
"aikido-pnpx": "bin/aikido-pnpx.js",
|
|
17
19
|
"safe-chain": "bin/safe-chain.js"
|
|
18
20
|
},
|
|
19
21
|
"type": "module",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function matchesCommand(args, ...commandArgs) {
|
|
2
|
+
if (args.length < commandArgs.length) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
for (var i = 0; i < commandArgs.length; i++) {
|
|
7
|
+
if (args[i].toLowerCase() !== commandArgs[i].toLowerCase()) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { createNpmPackageManager } from "./npm/createPackageManager.js";
|
|
2
2
|
import { createNpxPackageManager } from "./npx/createPackageManager.js";
|
|
3
|
+
import {
|
|
4
|
+
createPnpmPackageManager,
|
|
5
|
+
createPnpxPackageManager,
|
|
6
|
+
} from "./pnpm/createPackageManager.js";
|
|
3
7
|
import { createYarnPackageManager } from "./yarn/createPackageManager.js";
|
|
4
8
|
|
|
5
9
|
const state = {
|
|
@@ -13,6 +17,10 @@ export function initializePackageManager(packageManagerName, version) {
|
|
|
13
17
|
state.packageManagerName = createNpxPackageManager();
|
|
14
18
|
} else if (packageManagerName === "yarn") {
|
|
15
19
|
state.packageManagerName = createYarnPackageManager();
|
|
20
|
+
} else if (packageManagerName === "pnpm") {
|
|
21
|
+
state.packageManagerName = createPnpmPackageManager();
|
|
22
|
+
} else if (packageManagerName === "pnpx") {
|
|
23
|
+
state.packageManagerName = createPnpxPackageManager();
|
|
16
24
|
} else {
|
|
17
25
|
throw new Error("Unsupported package manager: " + packageManagerName);
|
|
18
26
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { matchesCommand } from "../_shared/matchesCommand.js";
|
|
2
|
+
import { commandArgumentScanner } from "./dependencyScanner/commandArgumentScanner.js";
|
|
3
|
+
import { runPnpmCommand } from "./runPnpmCommand.js";
|
|
4
|
+
|
|
5
|
+
const scanner = commandArgumentScanner();
|
|
6
|
+
|
|
7
|
+
export function createPnpmPackageManager() {
|
|
8
|
+
return {
|
|
9
|
+
getWarningMessage: () => null,
|
|
10
|
+
runCommand: (args) => runPnpmCommand(args, "pnpm"),
|
|
11
|
+
isSupportedCommand: (args) =>
|
|
12
|
+
matchesCommand(args, "add") ||
|
|
13
|
+
matchesCommand(args, "update") ||
|
|
14
|
+
matchesCommand(args, "upgrade") ||
|
|
15
|
+
matchesCommand(args, "up") ||
|
|
16
|
+
// dlx does not always come in the first position
|
|
17
|
+
// eg: pnpm --package=yo --package=generator-webapp dlx yo webapp
|
|
18
|
+
// documentation: https://pnpm.io/cli/dlx#--package-name
|
|
19
|
+
args.includes("dlx"),
|
|
20
|
+
getDependencyUpdatesForCommand: (args) =>
|
|
21
|
+
getDependencyUpdatesForCommand(args, false),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createPnpxPackageManager() {
|
|
26
|
+
return {
|
|
27
|
+
getWarningMessage: () => null,
|
|
28
|
+
runCommand: (args) => runPnpmCommand(args, "pnpx"),
|
|
29
|
+
isSupportedCommand: () => true,
|
|
30
|
+
getDependencyUpdatesForCommand: (args) =>
|
|
31
|
+
getDependencyUpdatesForCommand(args, true),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getDependencyUpdatesForCommand(args, isPnpx) {
|
|
36
|
+
if (isPnpx) {
|
|
37
|
+
return scanner.scan(args);
|
|
38
|
+
}
|
|
39
|
+
if (args.includes("dlx")) {
|
|
40
|
+
// dlx is not always the first argument (eg: `pnpm --package=yo --package=generator-webapp dlx yo webapp`)
|
|
41
|
+
// so we need to filter it out instead of slicing the array
|
|
42
|
+
// documentation: https://pnpm.io/cli/dlx#--package-name
|
|
43
|
+
return scanner.scan(args.filter((arg) => arg !== "dlx"));
|
|
44
|
+
}
|
|
45
|
+
return scanner.scan(args.slice(1));
|
|
46
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
|
2
|
+
import { parsePackagesFromArguments } from "../parsing/parsePackagesFromArguments.js";
|
|
3
|
+
|
|
4
|
+
export function commandArgumentScanner() {
|
|
5
|
+
return {
|
|
6
|
+
scan: (args) => scanDependencies(args),
|
|
7
|
+
shouldScan: () => true, // There's no dry run for pnpm, so we always scan
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function scanDependencies(args) {
|
|
12
|
+
const changes = [];
|
|
13
|
+
const packageUpdates = parsePackagesFromArguments(args);
|
|
14
|
+
|
|
15
|
+
for (const packageUpdate of packageUpdates) {
|
|
16
|
+
var exactVersion = await resolvePackageVersion(
|
|
17
|
+
packageUpdate.name,
|
|
18
|
+
packageUpdate.version
|
|
19
|
+
);
|
|
20
|
+
if (exactVersion) {
|
|
21
|
+
packageUpdate.version = exactVersion;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
changes.push({ ...packageUpdate, type: "add" });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return changes;
|
|
28
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export function parsePackagesFromArguments(args) {
|
|
2
|
+
const changes = [];
|
|
3
|
+
let defaultTag = "latest";
|
|
4
|
+
|
|
5
|
+
for (let i = 0; i < args.length; i++) {
|
|
6
|
+
const arg = args[i];
|
|
7
|
+
const option = getOption(arg);
|
|
8
|
+
|
|
9
|
+
if (option) {
|
|
10
|
+
// If the option has a parameter, skip the next argument as well
|
|
11
|
+
i += option.numberOfParameters;
|
|
12
|
+
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const packageDetails = parsePackagename(arg, defaultTag);
|
|
17
|
+
if (packageDetails) {
|
|
18
|
+
changes.push(packageDetails);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return changes;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getOption(arg) {
|
|
26
|
+
if (isOptionWithParameter(arg)) {
|
|
27
|
+
return {
|
|
28
|
+
name: arg,
|
|
29
|
+
numberOfParameters: 1,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Arguments starting with "-" or "--" are considered options
|
|
34
|
+
// except for "--package=" which contains the package name
|
|
35
|
+
if (arg.startsWith("-") && !arg.startsWith("--package=")) {
|
|
36
|
+
return {
|
|
37
|
+
name: arg,
|
|
38
|
+
numberOfParameters: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isOptionWithParameter(arg) {
|
|
46
|
+
const optionsWithParameters = ["--C", "--dir"];
|
|
47
|
+
|
|
48
|
+
return optionsWithParameters.includes(arg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parsePackagename(arg, defaultTag) {
|
|
52
|
+
// format can be --package=name@version
|
|
53
|
+
// in that case, we need to remove the --package= part
|
|
54
|
+
if (arg.startsWith("--package=")) {
|
|
55
|
+
arg = arg.slice(10);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
arg = removeAlias(arg);
|
|
59
|
+
|
|
60
|
+
// Split at the last "@" to separate the package name and version
|
|
61
|
+
const lastAtIndex = arg.lastIndexOf("@");
|
|
62
|
+
|
|
63
|
+
let name, version;
|
|
64
|
+
// The index of the last "@" should be greater than 0
|
|
65
|
+
// If the index is 0, it means the package name starts with "@" (eg: "@aikidosec/package-name")
|
|
66
|
+
if (lastAtIndex > 0) {
|
|
67
|
+
name = arg.slice(0, lastAtIndex);
|
|
68
|
+
version = arg.slice(lastAtIndex + 1);
|
|
69
|
+
} else {
|
|
70
|
+
name = arg;
|
|
71
|
+
version = defaultTag; // No tag specified (eg: "http-server"), use the default tag
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
version,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function removeAlias(arg) {
|
|
81
|
+
// removes the alias.
|
|
82
|
+
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
|
83
|
+
const aliasIndex = arg.indexOf("@npm:");
|
|
84
|
+
if (aliasIndex !== -1) {
|
|
85
|
+
return arg.slice(aliasIndex + 5);
|
|
86
|
+
}
|
|
87
|
+
return arg;
|
|
88
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { parsePackagesFromArguments } from "./parsePackagesFromArguments.js";
|
|
4
|
+
|
|
5
|
+
describe("standardPnpmArgumentParser", () => {
|
|
6
|
+
it("should return an empty array for no changes", () => {
|
|
7
|
+
const args = [];
|
|
8
|
+
|
|
9
|
+
const result = parsePackagesFromArguments(args);
|
|
10
|
+
|
|
11
|
+
assert.deepEqual(result, []);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should return an array of changes for one package", () => {
|
|
15
|
+
const args = ["axios@1.9.0"];
|
|
16
|
+
|
|
17
|
+
const result = parsePackagesFromArguments(args);
|
|
18
|
+
|
|
19
|
+
assert.deepEqual(result, [{ name: "axios", version: "1.9.0" }]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should return the package with latest tag if absent", () => {
|
|
23
|
+
const args = ["axios"];
|
|
24
|
+
|
|
25
|
+
const result = parsePackagesFromArguments(args);
|
|
26
|
+
|
|
27
|
+
assert.deepEqual(result, [{ name: "axios", version: "latest" }]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should return the package with latest tag if the version is absent and package starts with @", () => {
|
|
31
|
+
const args = ["@aikidosec/package-name"];
|
|
32
|
+
|
|
33
|
+
const result = parsePackagesFromArguments(args);
|
|
34
|
+
|
|
35
|
+
assert.deepEqual(result, [
|
|
36
|
+
{ name: "@aikidosec/package-name", version: "latest" },
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should return the package with the specified tag if the package starts with @ and includes the version", () => {
|
|
41
|
+
const args = ["@aikidosec/package-name@1.0.0"];
|
|
42
|
+
|
|
43
|
+
const result = parsePackagesFromArguments(args);
|
|
44
|
+
|
|
45
|
+
assert.deepEqual(result, [
|
|
46
|
+
{ name: "@aikidosec/package-name", version: "1.0.0" },
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should only return all packages", () => {
|
|
51
|
+
const args = ["axios", "jest"];
|
|
52
|
+
|
|
53
|
+
const result = parsePackagesFromArguments(args);
|
|
54
|
+
|
|
55
|
+
assert.deepEqual(result, [
|
|
56
|
+
{ name: "axios", version: "latest" },
|
|
57
|
+
{ name: "jest", version: "latest" },
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should ignore options with parameters and return an array of changes", () => {
|
|
62
|
+
const args = ["--C", "/Users/johnsmith/dev/project", "axios@1.9.0"];
|
|
63
|
+
|
|
64
|
+
const result = parsePackagesFromArguments(args);
|
|
65
|
+
|
|
66
|
+
assert.deepEqual(result, [{ name: "axios", version: "1.9.0" }]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should parse version even for aliased packages", () => {
|
|
70
|
+
const args = ["server@npm:axios@1.9.0"];
|
|
71
|
+
|
|
72
|
+
const result = parsePackagesFromArguments(args);
|
|
73
|
+
|
|
74
|
+
assert.deepEqual(result, [{ name: "axios", version: "1.9.0" }]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should parse scoped packages", () => {
|
|
78
|
+
const args = ["@scope/package@1.0.0"];
|
|
79
|
+
|
|
80
|
+
const result = parsePackagesFromArguments(args);
|
|
81
|
+
|
|
82
|
+
assert.deepEqual(result, [{ name: "@scope/package", version: "1.0.0" }]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should parse packages with version ranges", () => {
|
|
86
|
+
const args = ["axios@^1.9.0"];
|
|
87
|
+
|
|
88
|
+
const result = parsePackagesFromArguments(args);
|
|
89
|
+
|
|
90
|
+
assert.deepEqual(result, [{ name: "axios", version: "^1.9.0" }]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should parse package folders", () => {
|
|
94
|
+
const args = ["./local-package"];
|
|
95
|
+
|
|
96
|
+
const result = parsePackagesFromArguments(args);
|
|
97
|
+
|
|
98
|
+
assert.deepEqual(result, [{ name: "./local-package", version: "latest" }]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should parse tarballs", () => {
|
|
102
|
+
const args = ["file:./local-package.tgz"];
|
|
103
|
+
|
|
104
|
+
const result = parsePackagesFromArguments(args);
|
|
105
|
+
|
|
106
|
+
assert.deepEqual(result, [
|
|
107
|
+
{ name: "file:./local-package.tgz", version: "latest" },
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should parse tarball URLs", () => {
|
|
112
|
+
const args = ["https://example.com/local-package.tgz"];
|
|
113
|
+
|
|
114
|
+
const result = parsePackagesFromArguments(args);
|
|
115
|
+
|
|
116
|
+
assert.deepEqual(result, [
|
|
117
|
+
{ name: "https://example.com/local-package.tgz", version: "latest" },
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should parse git URLs", () => {
|
|
122
|
+
const args = ["git://github.com/http-party/http-server"];
|
|
123
|
+
|
|
124
|
+
const result = parsePackagesFromArguments(args);
|
|
125
|
+
|
|
126
|
+
assert.deepEqual(result, [
|
|
127
|
+
{ name: "git://github.com/http-party/http-server", version: "latest" },
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should parse packages with --package={packageName}", () => {
|
|
132
|
+
const args = ["--package=axios@1.9.0"];
|
|
133
|
+
|
|
134
|
+
const result = parsePackagesFromArguments(args);
|
|
135
|
+
|
|
136
|
+
assert.deepEqual(result, [{ name: "axios", version: "1.9.0" }]);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import { ui } from "../../environment/userInteraction.js";
|
|
3
|
+
|
|
4
|
+
export function runPnpmCommand(args, toolName = "pnpm") {
|
|
5
|
+
try {
|
|
6
|
+
let result;
|
|
7
|
+
|
|
8
|
+
if (toolName === "pnpm") {
|
|
9
|
+
result = spawnSync("pnpm", args, { stdio: "inherit" });
|
|
10
|
+
} else if (toolName === "pnpx") {
|
|
11
|
+
result = spawnSync("pnpx", args, { stdio: "inherit" });
|
|
12
|
+
} else {
|
|
13
|
+
throw new Error(`Unsupported tool name for aikido-pnpm: ${toolName}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (result.status !== null) {
|
|
17
|
+
return { status: result.status };
|
|
18
|
+
}
|
|
19
|
+
} catch (error) {
|
|
20
|
+
ui.writeError("Error executing command:", error.message);
|
|
21
|
+
return { status: 1 };
|
|
22
|
+
}
|
|
23
|
+
return { status: 0 };
|
|
24
|
+
}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
export const knownAikidoTools = [
|
|
2
6
|
{ tool: "npm", aikidoCommand: "aikido-npm" },
|
|
3
7
|
{ tool: "npx", aikidoCommand: "aikido-npx" },
|
|
4
8
|
{ tool: "yarn", aikidoCommand: "aikido-yarn" },
|
|
5
|
-
|
|
9
|
+
{ tool: "pnpm", aikidoCommand: "aikido-pnpm" },
|
|
10
|
+
{ tool: "pnpx", aikidoCommand: "aikido-pnpx" },
|
|
11
|
+
// When adding a new tool here, also update the expected alias in the tests (setup.spec.js, teardown.spec.js)
|
|
6
12
|
// and add the documentation for the new tool in the README.md
|
|
7
13
|
];
|
|
8
14
|
|
|
9
|
-
export function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
export function doesExecutableExistOnSystem(executableName) {
|
|
16
|
+
if (os.platform() === "win32") {
|
|
17
|
+
const result = spawnSync("where", [executableName], { stdio: "ignore" });
|
|
18
|
+
return result.status === 0;
|
|
19
|
+
} else {
|
|
20
|
+
const result = spawnSync("which", [executableName], { stdio: "ignore" });
|
|
21
|
+
return result.status === 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
13
24
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
export function removeLinesMatchingPattern(filePath, pattern) {
|
|
26
|
+
if (!fs.existsSync(filePath)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
17
29
|
|
|
18
|
-
|
|
30
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
31
|
+
const lines = fileContent.split(os.EOL);
|
|
32
|
+
const updatedLines = lines.filter((line) => !pattern.test(line));
|
|
33
|
+
fs.writeFileSync(filePath, updatedLines.join(os.EOL), "utf-8");
|
|
19
34
|
}
|
|
20
35
|
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
case "ps1":
|
|
25
|
-
createAlias = createGeneralPowershellAlias;
|
|
26
|
-
break;
|
|
27
|
-
case "fish":
|
|
28
|
-
createAlias = createGeneralFishAlias;
|
|
29
|
-
break;
|
|
30
|
-
default:
|
|
31
|
-
createAlias = createGeneralPosixAlias;
|
|
36
|
+
export function addLineToFile(filePath, line) {
|
|
37
|
+
if (!fs.existsSync(filePath)) {
|
|
38
|
+
fs.writeFileSync(filePath, "", "utf-8");
|
|
32
39
|
}
|
|
33
|
-
return createAlias;
|
|
34
|
-
}
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
function createGeneralPowershellAlias(tool, aikidoCommand) {
|
|
40
|
-
return `Set-Alias ${tool} ${aikidoCommand}`;
|
|
41
|
-
}
|
|
42
|
-
function createGeneralFishAlias(tool, aikidoCommand) {
|
|
43
|
-
return `alias ${tool} "${aikidoCommand}"`;
|
|
41
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
42
|
+
const updatedContent = fileContent + os.EOL + line;
|
|
43
|
+
fs.writeFileSync(filePath, updatedContent, "utf-8");
|
|
44
44
|
}
|