@aikidosec/safe-chain 1.0.0 → 1.0.10
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/.editorconfig +8 -0
- package/.github/workflows/build-and-release.yml +41 -0
- package/.github/workflows/test-on-pr.yml +28 -0
- package/README.md +55 -0
- package/bin/aikido-npm.js +8 -0
- package/bin/aikido-npx.js +8 -0
- package/bin/aikido-yarn.js +8 -0
- package/eslint.config.js +25 -0
- package/package.json +27 -5
- package/safe-package-manager-demo.gif +0 -0
- package/src/api/aikido.js +31 -0
- package/src/api/npmApi.js +46 -0
- package/src/config/configFile.js +91 -0
- package/src/environment/environment.js +14 -0
- package/src/environment/userInteraction.js +79 -0
- package/src/main.js +31 -0
- package/src/packagemanager/currentPackageManager.js +28 -0
- package/src/packagemanager/npm/createPackageManager.js +83 -0
- package/src/packagemanager/npm/dependencyScanner/commandArgumentScanner.js +37 -0
- package/src/packagemanager/npm/dependencyScanner/dryRunScanner.js +50 -0
- package/src/packagemanager/npm/dependencyScanner/nullScanner.js +6 -0
- package/src/packagemanager/npm/parsing/parseNpmInstallDryRunOutput.js +57 -0
- package/src/packagemanager/npm/parsing/parseNpmInstallDryRunOutput.spec.js +134 -0
- package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.js +109 -0
- package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.spec.js +176 -0
- package/src/packagemanager/npm/runNpmCommand.js +33 -0
- package/src/packagemanager/npm/utils/cmd-list.js +171 -0
- package/src/packagemanager/npm/utils/npmCommands.js +26 -0
- package/src/packagemanager/npx/createPackageManager.js +13 -0
- package/src/packagemanager/npx/dependencyScanner/commandArgumentScanner.js +31 -0
- package/src/packagemanager/npx/parsing/parsePackagesFromArguments.js +106 -0
- package/src/packagemanager/npx/parsing/parsePackagesFromArguments.spec.js +147 -0
- package/src/packagemanager/npx/runNpxCommand.js +17 -0
- package/src/packagemanager/yarn/createPackageManager.js +34 -0
- package/src/packagemanager/yarn/dependencyScanner/commandArgumentScanner.js +28 -0
- package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.js +102 -0
- package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.spec.js +126 -0
- package/src/packagemanager/yarn/runYarnCommand.js +17 -0
- package/src/scanning/audit/index.js +56 -0
- package/src/scanning/index.js +94 -0
- package/src/scanning/index.scanCommand.spec.js +180 -0
- package/src/scanning/index.shouldScanCommand.spec.js +47 -0
- package/src/scanning/malwareDatabase.js +62 -0
- package/src/shell-integration/addAlias.js +63 -0
- package/src/shell-integration/helpers.js +44 -0
- package/src/shell-integration/removeAlias.js +61 -0
- package/src/shell-integration/shellIntegration.spec.js +172 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolvePackageVersion } from "../../../api/npmApi.js";
|
|
2
|
+
import { parsePackagesFromInstallArgs } from "../parsing/parsePackagesFromInstallArgs.js";
|
|
3
|
+
import { hasDryRunArg } from "../utils/npmCommands.js";
|
|
4
|
+
|
|
5
|
+
export function commandArgumentScanner(opts) {
|
|
6
|
+
const ignoreDryRun = opts?.ignoreDryRun ?? false;
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
scan: (args) => scanDependencies(args),
|
|
10
|
+
shouldScan: (args) => shouldScanDependencies(args, ignoreDryRun),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function scanDependencies(args) {
|
|
14
|
+
return checkChangesFromArgs(args);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function shouldScanDependencies(args, ignoreDryRun) {
|
|
18
|
+
return ignoreDryRun || !hasDryRunArg(args);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function checkChangesFromArgs(args) {
|
|
22
|
+
const changes = [];
|
|
23
|
+
const packageUpdates = parsePackagesFromInstallArgs(args);
|
|
24
|
+
|
|
25
|
+
for (const packageUpdate of packageUpdates) {
|
|
26
|
+
var exactVersion = await resolvePackageVersion(
|
|
27
|
+
packageUpdate.name,
|
|
28
|
+
packageUpdate.version
|
|
29
|
+
);
|
|
30
|
+
if (exactVersion) {
|
|
31
|
+
packageUpdate.version = exactVersion;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
changes.push({ ...packageUpdate, type: "add" });
|
|
35
|
+
}
|
|
36
|
+
return changes;
|
|
37
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ui } from "../../../environment/userInteraction.js";
|
|
2
|
+
import { parseDryRunOutput } from "../parsing/parseNpmInstallDryRunOutput.js";
|
|
3
|
+
import { dryRunNpmCommandAndOutput } from "../runNpmCommand.js";
|
|
4
|
+
import { hasDryRunArg } from "../utils/npmCommands.js";
|
|
5
|
+
|
|
6
|
+
export function dryRunScanner(scannerOptions) {
|
|
7
|
+
return {
|
|
8
|
+
scan: (args) => scanDependencies(scannerOptions, args),
|
|
9
|
+
shouldScan: (args) => shouldScanDependencies(scannerOptions, args),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function scanDependencies(scannerOptions, args) {
|
|
13
|
+
let dryRunArgs = args;
|
|
14
|
+
|
|
15
|
+
if (scannerOptions?.dryRunCommand) {
|
|
16
|
+
// Replace the first argument with the dryRunCommand (eg: "install" instead of "install-test")
|
|
17
|
+
dryRunArgs = [scannerOptions.dryRunCommand, ...args.slice(1)];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return checkChangesWithDryRun(dryRunArgs);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function shouldScanDependencies(scannerOptions, args) {
|
|
24
|
+
if (hasDryRunArg(args)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (scannerOptions?.skipScanWhen && scannerOptions.skipScanWhen(args)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function checkChangesWithDryRun(args) {
|
|
36
|
+
const dryRunOutput = dryRunNpmCommandAndOutput(args);
|
|
37
|
+
|
|
38
|
+
// Dry-run can return a non-zero status code in some cases
|
|
39
|
+
// e.g., when running "npm audit fix --dry-run", it returns exit code 1
|
|
40
|
+
// when there are vulnurabilities that can be fixed.
|
|
41
|
+
if (dryRunOutput.status !== 0 && !dryRunOutput.output) {
|
|
42
|
+
ui.writeError("Detecting changes failed.");
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const parsedOutput = parseDryRunOutput(dryRunOutput.output);
|
|
47
|
+
|
|
48
|
+
// reverse the array to have the top-level packages first
|
|
49
|
+
return parsedOutput.reverse();
|
|
50
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function parseDryRunOutput(output) {
|
|
2
|
+
const lines = output.split(/\r?\n/);
|
|
3
|
+
const packageChanges = [];
|
|
4
|
+
|
|
5
|
+
for (const line of lines) {
|
|
6
|
+
if (line.startsWith("add ")) {
|
|
7
|
+
packageChanges.push(parseAdd(line));
|
|
8
|
+
} else if (line.startsWith("remove ")) {
|
|
9
|
+
packageChanges.push(parseRemove(line));
|
|
10
|
+
} else if (line.startsWith("change ")) {
|
|
11
|
+
packageChanges.push(parseChange(line));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return packageChanges;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseAdd(line) {
|
|
19
|
+
const splitLine = getLineParts(line);
|
|
20
|
+
const packageName = splitLine[1];
|
|
21
|
+
const packageVersion = splitLine[splitLine.length - 1];
|
|
22
|
+
return addedPackage(packageName, packageVersion);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function addedPackage(name, version) {
|
|
26
|
+
return { type: "add", name, version };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseRemove(line) {
|
|
30
|
+
const splitLine = getLineParts(line);
|
|
31
|
+
const packageName = splitLine[1];
|
|
32
|
+
const packageVersion = splitLine[splitLine.length - 1];
|
|
33
|
+
return removedPackage(packageName, packageVersion);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function removedPackage(name, version) {
|
|
37
|
+
return { type: "remove", name, version };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseChange(line) {
|
|
41
|
+
const splitLine = getLineParts(line);
|
|
42
|
+
const packageName = splitLine[1];
|
|
43
|
+
const packageVersion = splitLine[splitLine.length - 1];
|
|
44
|
+
const oldVersion = splitLine[2];
|
|
45
|
+
return changedPackage(packageName, packageVersion, oldVersion);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getLineParts(line) {
|
|
49
|
+
return line
|
|
50
|
+
.split(" ")
|
|
51
|
+
.map((part) => part.trim())
|
|
52
|
+
.filter((part) => part !== "");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function changedPackage(name, version, oldVersion) {
|
|
56
|
+
return { type: "change", name, version, oldVersion };
|
|
57
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { parseDryRunOutput } from "./parseNpmInstallDryRunOutput.js";
|
|
4
|
+
|
|
5
|
+
describe("parseNpmInstallDryRunOutput", () => {
|
|
6
|
+
it("should parse added packages", () => {
|
|
7
|
+
const output = `
|
|
8
|
+
add @jest/transform 29.7.0
|
|
9
|
+
add @jest/test-result 29.7.0
|
|
10
|
+
add @jest/reporters 29.7.0
|
|
11
|
+
add @jest/console 29.7.0
|
|
12
|
+
add jest-cli 29.7.0
|
|
13
|
+
add import-local 3.2.0
|
|
14
|
+
add @jest/types 29.6.3
|
|
15
|
+
add @jest/core 29.7.0
|
|
16
|
+
add jest 29.7.0
|
|
17
|
+
|
|
18
|
+
added 267 packages in 831ms
|
|
19
|
+
|
|
20
|
+
32 packages are looking for funding
|
|
21
|
+
run \`npm fund\` for details`;
|
|
22
|
+
|
|
23
|
+
const expected = [
|
|
24
|
+
{ name: "@jest/transform", version: "29.7.0", type: "add" },
|
|
25
|
+
{ name: "@jest/test-result", version: "29.7.0", type: "add" },
|
|
26
|
+
{ name: "@jest/reporters", version: "29.7.0", type: "add" },
|
|
27
|
+
{ name: "@jest/console", version: "29.7.0", type: "add" },
|
|
28
|
+
{ name: "jest-cli", version: "29.7.0", type: "add" },
|
|
29
|
+
{ name: "import-local", version: "3.2.0", type: "add" },
|
|
30
|
+
{ name: "@jest/types", version: "29.6.3", type: "add" },
|
|
31
|
+
{ name: "@jest/core", version: "29.7.0", type: "add" },
|
|
32
|
+
{ name: "jest", version: "29.7.0", type: "add" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const result = parseDryRunOutput(output);
|
|
36
|
+
|
|
37
|
+
assert.deepEqual(result, expected);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should parse removed packages", () => {
|
|
41
|
+
const output = `
|
|
42
|
+
remove react 19.1.0
|
|
43
|
+
|
|
44
|
+
removed 1 package in 115ms`;
|
|
45
|
+
|
|
46
|
+
const expected = [{ name: "react", version: "19.1.0", type: "remove" }];
|
|
47
|
+
|
|
48
|
+
const result = parseDryRunOutput(output);
|
|
49
|
+
|
|
50
|
+
assert.deepEqual(result, expected);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should parse changed packages", () => {
|
|
54
|
+
const output = `
|
|
55
|
+
change react 19.0.0 => 19.1.0
|
|
56
|
+
|
|
57
|
+
changed 1 package in 204ms`;
|
|
58
|
+
|
|
59
|
+
const expected = [
|
|
60
|
+
{
|
|
61
|
+
name: "react",
|
|
62
|
+
version: "19.1.0",
|
|
63
|
+
oldVersion: "19.0.0",
|
|
64
|
+
type: "change",
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const result = parseDryRunOutput(output);
|
|
69
|
+
|
|
70
|
+
assert.deepEqual(result, expected);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should parse mixed package changes", () => {
|
|
74
|
+
const output = `
|
|
75
|
+
add @jest/transform 29.7.0
|
|
76
|
+
add @jest/test-result 29.7.0
|
|
77
|
+
add @jest/reporters 29.7.0
|
|
78
|
+
add @jest/console 29.7.0
|
|
79
|
+
add jest-cli 29.7.0
|
|
80
|
+
add import-local 3.2.0
|
|
81
|
+
add @jest/types 29.6.3
|
|
82
|
+
add @jest/core 29.7.0
|
|
83
|
+
add jest 29.7.0
|
|
84
|
+
remove react 19.1.0
|
|
85
|
+
change lodash 4.17.0 => 4.18.0
|
|
86
|
+
|
|
87
|
+
removed 1 package in 115ms`;
|
|
88
|
+
|
|
89
|
+
const expected = [
|
|
90
|
+
{ name: "@jest/transform", version: "29.7.0", type: "add" },
|
|
91
|
+
{ name: "@jest/test-result", version: "29.7.0", type: "add" },
|
|
92
|
+
{ name: "@jest/reporters", version: "29.7.0", type: "add" },
|
|
93
|
+
{ name: "@jest/console", version: "29.7.0", type: "add" },
|
|
94
|
+
{ name: "jest-cli", version: "29.7.0", type: "add" },
|
|
95
|
+
{ name: "import-local", version: "3.2.0", type: "add" },
|
|
96
|
+
{ name: "@jest/types", version: "29.6.3", type: "add" },
|
|
97
|
+
{ name: "@jest/core", version: "29.7.0", type: "add" },
|
|
98
|
+
{ name: "jest", version: "29.7.0", type: "add" },
|
|
99
|
+
{ name: "react", version: "19.1.0", type: "remove" },
|
|
100
|
+
{
|
|
101
|
+
name: "lodash",
|
|
102
|
+
version: "4.18.0",
|
|
103
|
+
oldVersion: "4.17.0",
|
|
104
|
+
type: "change",
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const result = parseDryRunOutput(output);
|
|
109
|
+
|
|
110
|
+
assert.deepEqual(result, expected);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should work with npm v22.0.0", () => {
|
|
114
|
+
const output = `
|
|
115
|
+
add @jest/types 29.6.3
|
|
116
|
+
add @jest/core 29.7.0
|
|
117
|
+
add jest 29.7.0
|
|
118
|
+
|
|
119
|
+
added 257 packages in 791ms
|
|
120
|
+
|
|
121
|
+
44 packages are looking for funding
|
|
122
|
+
run \`npm fund\` for details`;
|
|
123
|
+
|
|
124
|
+
const expected = [
|
|
125
|
+
{ name: "@jest/types", version: "29.6.3", type: "add" },
|
|
126
|
+
{ name: "@jest/core", version: "29.7.0", type: "add" },
|
|
127
|
+
{ name: "jest", version: "29.7.0", type: "add" },
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
const result = parseDryRunOutput(output);
|
|
131
|
+
|
|
132
|
+
assert.deepEqual(result, expected);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export function parsePackagesFromInstallArgs(args) {
|
|
2
|
+
const changes = [];
|
|
3
|
+
let defaultTag = "latest";
|
|
4
|
+
|
|
5
|
+
// Skip first argument (install command)
|
|
6
|
+
for (let i = 1; i < args.length; i++) {
|
|
7
|
+
const arg = args[i];
|
|
8
|
+
const npmOption = getNpmOption(arg);
|
|
9
|
+
|
|
10
|
+
if (npmOption) {
|
|
11
|
+
// If the option has a parameter, skip the next argument as well
|
|
12
|
+
i += npmOption.numberOfParameters;
|
|
13
|
+
|
|
14
|
+
// it a tag is specified, set the default tag
|
|
15
|
+
if (npmOption.name === "--tag") {
|
|
16
|
+
defaultTag = args[i];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const packageDetails = parsePackagename(arg);
|
|
23
|
+
if (packageDetails) {
|
|
24
|
+
changes.push(packageDetails);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const change of changes) {
|
|
30
|
+
if (!change.version) {
|
|
31
|
+
change.version = defaultTag;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return changes;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getNpmOption(arg) {
|
|
39
|
+
if (isNpmOptionWithParameter(arg)) {
|
|
40
|
+
return {
|
|
41
|
+
name: arg,
|
|
42
|
+
numberOfParameters: 1,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Arguments starting with "-" or "--" are considered npm options
|
|
47
|
+
if (arg.startsWith("-")) {
|
|
48
|
+
return {
|
|
49
|
+
name: arg,
|
|
50
|
+
numberOfParameters: 0,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isNpmOptionWithParameter(arg) {
|
|
58
|
+
const optionsWithParameters = [
|
|
59
|
+
"--access",
|
|
60
|
+
"--auth-type",
|
|
61
|
+
"--cache",
|
|
62
|
+
"--fetch-retries",
|
|
63
|
+
"--fetch-retry-mintimeout",
|
|
64
|
+
"--fetch-retry-maxtimeout",
|
|
65
|
+
"--fetch-retry-factor",
|
|
66
|
+
"--fetch-timeout",
|
|
67
|
+
"--https-proxy",
|
|
68
|
+
"--include",
|
|
69
|
+
"--location",
|
|
70
|
+
"--lockfile-version",
|
|
71
|
+
"--loglevel",
|
|
72
|
+
"--omit",
|
|
73
|
+
"--proxy",
|
|
74
|
+
"--registry",
|
|
75
|
+
"--replace-registry-host",
|
|
76
|
+
"--tag",
|
|
77
|
+
"--user-config",
|
|
78
|
+
"--workspace",
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
return optionsWithParameters.includes(arg);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parsePackagename(arg) {
|
|
85
|
+
arg = removeAlias(arg);
|
|
86
|
+
const lastAtIndex = arg.lastIndexOf("@");
|
|
87
|
+
|
|
88
|
+
let name, version;
|
|
89
|
+
if (lastAtIndex !== -1) {
|
|
90
|
+
name = arg.slice(0, lastAtIndex);
|
|
91
|
+
version = arg.slice(lastAtIndex + 1);
|
|
92
|
+
} else {
|
|
93
|
+
name = arg;
|
|
94
|
+
version = null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
name,
|
|
99
|
+
version,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function removeAlias(arg) {
|
|
104
|
+
const aliasIndex = arg.indexOf("@npm:");
|
|
105
|
+
if (aliasIndex !== -1) {
|
|
106
|
+
return arg.slice(aliasIndex + 5);
|
|
107
|
+
}
|
|
108
|
+
return arg;
|
|
109
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { parsePackagesFromInstallArgs } from "./parsePackagesFromInstallArgs.js";
|
|
4
|
+
|
|
5
|
+
describe("parsePackagesFromInstallArgs", () => {
|
|
6
|
+
it("should return an empty array for no changes", () => {
|
|
7
|
+
const args = ["install"];
|
|
8
|
+
|
|
9
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
10
|
+
|
|
11
|
+
assert.deepEqual(result, []);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should return an array of changes for one package", () => {
|
|
15
|
+
const args = ["install", "@jest/transform@29.7.0"];
|
|
16
|
+
|
|
17
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
18
|
+
|
|
19
|
+
assert.deepEqual(result, [{ name: "@jest/transform", version: "29.7.0" }]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should return an array of changes for multiple packages", () => {
|
|
23
|
+
const args = ["install", "express@4.17.1", "lodash@4.17.21"];
|
|
24
|
+
|
|
25
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
26
|
+
|
|
27
|
+
assert.deepEqual(result, [
|
|
28
|
+
{ name: "express", version: "4.17.1" },
|
|
29
|
+
{ name: "lodash", version: "4.17.21" },
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should ignore options and return an array of changes", () => {
|
|
34
|
+
const args = [
|
|
35
|
+
"install",
|
|
36
|
+
"--save-dev",
|
|
37
|
+
"express@4.17.1",
|
|
38
|
+
"--save-exact",
|
|
39
|
+
"lodash@4.17.21",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
43
|
+
|
|
44
|
+
assert.deepEqual(result, [
|
|
45
|
+
{ name: "express", version: "4.17.1" },
|
|
46
|
+
{ name: "lodash", version: "4.17.21" },
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should ignore options with parameters and return an array of changes", () => {
|
|
51
|
+
const args = [
|
|
52
|
+
"install",
|
|
53
|
+
"--save-dev",
|
|
54
|
+
"express@4.17.1",
|
|
55
|
+
"--loglevel",
|
|
56
|
+
"error",
|
|
57
|
+
"lodash@4.17.21",
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
61
|
+
|
|
62
|
+
assert.deepEqual(result, [
|
|
63
|
+
{ name: "express", version: "4.17.1" },
|
|
64
|
+
{ name: "lodash", version: "4.17.21" },
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should not ignore the next argument if it is passed directly with the option", () => {
|
|
69
|
+
const args = [
|
|
70
|
+
"install",
|
|
71
|
+
"--save-dev",
|
|
72
|
+
"express@4.17.1",
|
|
73
|
+
"--loglevel=error",
|
|
74
|
+
"lodash@4.17.21",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
78
|
+
|
|
79
|
+
assert.deepEqual(result, [
|
|
80
|
+
{ name: "express", version: "4.17.1" },
|
|
81
|
+
{ name: "lodash", version: "4.17.21" },
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should set the default tag for packages", () => {
|
|
86
|
+
const args = ["install", "express", "lodash@4.17.21"];
|
|
87
|
+
|
|
88
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
89
|
+
|
|
90
|
+
assert.deepEqual(result, [
|
|
91
|
+
{ name: "express", version: "latest" },
|
|
92
|
+
{ name: "lodash", version: "4.17.21" },
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should set the default tag for packages with a specific tag", () => {
|
|
97
|
+
const args = ["install", "express", "lodash@4.17.21", "--tag", "beta"];
|
|
98
|
+
|
|
99
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
100
|
+
|
|
101
|
+
assert.deepEqual(result, [
|
|
102
|
+
{ name: "express", version: "beta" },
|
|
103
|
+
{ name: "lodash", version: "4.17.21" },
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should ignore alias", () => {
|
|
108
|
+
const args = ["install", "express@npm:express@4.17.1"];
|
|
109
|
+
|
|
110
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
111
|
+
|
|
112
|
+
assert.deepEqual(result, [{ name: "express", version: "4.17.1" }]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should parse version even for aliased packages", () => {
|
|
116
|
+
const args = ["install", "express@npm:express@4.17.1"];
|
|
117
|
+
|
|
118
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
119
|
+
|
|
120
|
+
assert.deepEqual(result, [{ name: "express", version: "4.17.1" }]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should parse scoped packages", () => {
|
|
124
|
+
const args = ["install", "@scope/package@1.0.0"];
|
|
125
|
+
|
|
126
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
127
|
+
|
|
128
|
+
assert.deepEqual(result, [{ name: "@scope/package", version: "1.0.0" }]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should parse packages with version ranges", () => {
|
|
132
|
+
const args = ["install", "express@^4.17.1"];
|
|
133
|
+
|
|
134
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
135
|
+
|
|
136
|
+
assert.deepEqual(result, [{ name: "express", version: "^4.17.1" }]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should parse package folders", () => {
|
|
140
|
+
const args = ["install", "./local-package"];
|
|
141
|
+
|
|
142
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
143
|
+
|
|
144
|
+
assert.deepEqual(result, [{ name: "./local-package", version: "latest" }]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should parse tarballs", () => {
|
|
148
|
+
const args = ["install", "file:./local-package.tgz"];
|
|
149
|
+
|
|
150
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
151
|
+
|
|
152
|
+
assert.deepEqual(result, [
|
|
153
|
+
{ name: "file:./local-package.tgz", version: "latest" },
|
|
154
|
+
]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should parse tarball URLs", () => {
|
|
158
|
+
const args = ["install", "https://example.com/local-package.tgz"];
|
|
159
|
+
|
|
160
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
161
|
+
|
|
162
|
+
assert.deepEqual(result, [
|
|
163
|
+
{ name: "https://example.com/local-package.tgz", version: "latest" },
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should parse git URLs", () => {
|
|
168
|
+
const args = ["install", "git://github.com/npm/cli.git"];
|
|
169
|
+
|
|
170
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
171
|
+
|
|
172
|
+
assert.deepEqual(result, [
|
|
173
|
+
{ name: "git://github.com/npm/cli.git", version: "latest" },
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { ui } from "../../environment/userInteraction.js";
|
|
3
|
+
|
|
4
|
+
export function runNpm(args) {
|
|
5
|
+
try {
|
|
6
|
+
const npmCommand = `npm ${args.join(" ")}`;
|
|
7
|
+
execSync(npmCommand, { stdio: "inherit" });
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error.status) {
|
|
10
|
+
return { status: error.status };
|
|
11
|
+
} else {
|
|
12
|
+
ui.writeError("Error executing command:", error.message);
|
|
13
|
+
return { status: 1 };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return { status: 0 };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function dryRunNpmCommandAndOutput(args) {
|
|
20
|
+
try {
|
|
21
|
+
const npmCommand = `npm ${args.join(" ")} --dry-run`;
|
|
22
|
+
const output = execSync(npmCommand, { stdio: "pipe" });
|
|
23
|
+
return { status: 0, output: output.toString() };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error.status) {
|
|
26
|
+
const output = error.stdout ? error.stdout.toString() : "";
|
|
27
|
+
return { status: error.status, output };
|
|
28
|
+
} else {
|
|
29
|
+
ui.writeError("Error executing command:", error.message);
|
|
30
|
+
return { status: 1 };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|