@aikidosec/safe-chain 1.0.0 → 1.0.11
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 +57 -0
- package/bin/aikido-npm.js +8 -0
- package/bin/aikido-npx.js +8 -0
- package/bin/aikido-yarn.js +8 -0
- package/bin/safe-chain.js +57 -0
- package/eslint.config.js +25 -0
- package/package.json +28 -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 +18 -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 +92 -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/helpers.js +44 -0
- package/src/shell-integration/removeShell.js +140 -0
- package/src/shell-integration/removeShell.spec.js +177 -0
- package/src/shell-integration/setupShell.js +151 -0
- package/src/shell-integration/setupShell.spec.js +304 -0
- package/src/shell-integration/shellDetection.js +75 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Based on https://github.com/npm/cli/blob/latest/lib/utils/cmd-list.js
|
|
2
|
+
|
|
3
|
+
import abbrev from "abbrev";
|
|
4
|
+
|
|
5
|
+
const commands = [
|
|
6
|
+
"access",
|
|
7
|
+
"adduser",
|
|
8
|
+
"audit",
|
|
9
|
+
"bugs",
|
|
10
|
+
"cache",
|
|
11
|
+
"ci",
|
|
12
|
+
"completion",
|
|
13
|
+
"config",
|
|
14
|
+
"dedupe",
|
|
15
|
+
"deprecate",
|
|
16
|
+
"diff",
|
|
17
|
+
"dist-tag",
|
|
18
|
+
"docs",
|
|
19
|
+
"doctor",
|
|
20
|
+
"edit",
|
|
21
|
+
"exec",
|
|
22
|
+
"explain",
|
|
23
|
+
"explore",
|
|
24
|
+
"find-dupes",
|
|
25
|
+
"fund",
|
|
26
|
+
"get",
|
|
27
|
+
"help",
|
|
28
|
+
"help-search",
|
|
29
|
+
"init",
|
|
30
|
+
"install",
|
|
31
|
+
"install-ci-test",
|
|
32
|
+
"install-test",
|
|
33
|
+
"link",
|
|
34
|
+
"ll",
|
|
35
|
+
"login",
|
|
36
|
+
"logout",
|
|
37
|
+
"ls",
|
|
38
|
+
"org",
|
|
39
|
+
"outdated",
|
|
40
|
+
"owner",
|
|
41
|
+
"pack",
|
|
42
|
+
"ping",
|
|
43
|
+
"pkg",
|
|
44
|
+
"prefix",
|
|
45
|
+
"profile",
|
|
46
|
+
"prune",
|
|
47
|
+
"publish",
|
|
48
|
+
"query",
|
|
49
|
+
"rebuild",
|
|
50
|
+
"repo",
|
|
51
|
+
"restart",
|
|
52
|
+
"root",
|
|
53
|
+
"run",
|
|
54
|
+
"sbom",
|
|
55
|
+
"search",
|
|
56
|
+
"set",
|
|
57
|
+
"shrinkwrap",
|
|
58
|
+
"star",
|
|
59
|
+
"stars",
|
|
60
|
+
"start",
|
|
61
|
+
"stop",
|
|
62
|
+
"team",
|
|
63
|
+
"test",
|
|
64
|
+
"token",
|
|
65
|
+
"undeprecate",
|
|
66
|
+
"uninstall",
|
|
67
|
+
"unpublish",
|
|
68
|
+
"unstar",
|
|
69
|
+
"update",
|
|
70
|
+
"version",
|
|
71
|
+
"view",
|
|
72
|
+
"whoami",
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// These must resolve to an entry in commands
|
|
76
|
+
const aliases = {
|
|
77
|
+
// aliases
|
|
78
|
+
author: "owner",
|
|
79
|
+
home: "docs",
|
|
80
|
+
issues: "bugs",
|
|
81
|
+
info: "view",
|
|
82
|
+
show: "view",
|
|
83
|
+
find: "search",
|
|
84
|
+
add: "install",
|
|
85
|
+
unlink: "uninstall",
|
|
86
|
+
remove: "uninstall",
|
|
87
|
+
rm: "uninstall",
|
|
88
|
+
r: "uninstall",
|
|
89
|
+
|
|
90
|
+
// short names for common things
|
|
91
|
+
un: "uninstall",
|
|
92
|
+
rb: "rebuild",
|
|
93
|
+
list: "ls",
|
|
94
|
+
ln: "link",
|
|
95
|
+
create: "init",
|
|
96
|
+
i: "install",
|
|
97
|
+
it: "install-test",
|
|
98
|
+
cit: "install-ci-test",
|
|
99
|
+
up: "update",
|
|
100
|
+
c: "config",
|
|
101
|
+
s: "search",
|
|
102
|
+
se: "search",
|
|
103
|
+
tst: "test",
|
|
104
|
+
t: "test",
|
|
105
|
+
ddp: "dedupe",
|
|
106
|
+
v: "view",
|
|
107
|
+
"run-script": "run",
|
|
108
|
+
"clean-install": "ci",
|
|
109
|
+
"clean-install-test": "install-ci-test",
|
|
110
|
+
x: "exec",
|
|
111
|
+
why: "explain",
|
|
112
|
+
la: "ll",
|
|
113
|
+
verison: "version",
|
|
114
|
+
ic: "ci",
|
|
115
|
+
|
|
116
|
+
// typos
|
|
117
|
+
innit: "init",
|
|
118
|
+
// manually abbrev so that install-test doesn't make insta stop working
|
|
119
|
+
in: "install",
|
|
120
|
+
ins: "install",
|
|
121
|
+
inst: "install",
|
|
122
|
+
insta: "install",
|
|
123
|
+
instal: "install",
|
|
124
|
+
isnt: "install",
|
|
125
|
+
isnta: "install",
|
|
126
|
+
isntal: "install",
|
|
127
|
+
isntall: "install",
|
|
128
|
+
"install-clean": "ci",
|
|
129
|
+
"isntall-clean": "ci",
|
|
130
|
+
hlep: "help",
|
|
131
|
+
"dist-tags": "dist-tag",
|
|
132
|
+
upgrade: "update",
|
|
133
|
+
udpate: "update",
|
|
134
|
+
rum: "run",
|
|
135
|
+
sit: "install-ci-test",
|
|
136
|
+
urn: "run",
|
|
137
|
+
ogr: "org",
|
|
138
|
+
"add-user": "adduser",
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export function deref(c) {
|
|
142
|
+
if (!c) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Translate camelCase to snake-case (i.e. installTest to install-test)
|
|
147
|
+
if (c.match(/[A-Z]/)) {
|
|
148
|
+
c = c.replace(/([A-Z])/g, (m) => "-" + m.toLowerCase());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// if they asked for something exactly we are done
|
|
152
|
+
if (commands.includes(c)) {
|
|
153
|
+
return c;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// if they asked for a direct alias
|
|
157
|
+
if (aliases[c]) {
|
|
158
|
+
return aliases[c];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const abbrevs = abbrev(commands.concat(Object.keys(aliases)));
|
|
162
|
+
|
|
163
|
+
// first deref the abbrev, if there is one
|
|
164
|
+
// then resolve any aliases
|
|
165
|
+
// so `npm install-cl` will resolve to `install-clean` then to `ci`
|
|
166
|
+
let a = abbrevs[c];
|
|
167
|
+
while (aliases[a]) {
|
|
168
|
+
a = aliases[a];
|
|
169
|
+
}
|
|
170
|
+
return a;
|
|
171
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { deref } from "./cmd-list.js";
|
|
2
|
+
|
|
3
|
+
export function getNpmCommandForArgs(args) {
|
|
4
|
+
if (args.length === 0) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const argCommand = deref(args[0]);
|
|
9
|
+
if (!argCommand) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return argCommand;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function hasDryRunArg(args) {
|
|
17
|
+
return args.some((arg) => arg === "--dry-run");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const npmInstallCommand = "install";
|
|
21
|
+
export const npmCiCommand = "ci";
|
|
22
|
+
export const npmInstallTestCommand = "install-test";
|
|
23
|
+
export const npmInstallCiTestCommand = "install-ci-test";
|
|
24
|
+
export const npmUpdateCommand = "update";
|
|
25
|
+
export const npmAuditCommand = "audit";
|
|
26
|
+
export const npmExecCommand = "exec";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { commandArgumentScanner } from "./dependencyScanner/commandArgumentScanner.js";
|
|
2
|
+
import { runNpx } from "./runNpxCommand.js";
|
|
3
|
+
|
|
4
|
+
export function createNpxPackageManager() {
|
|
5
|
+
const scanner = commandArgumentScanner();
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
getWarningMessage: () => null,
|
|
9
|
+
runCommand: runNpx,
|
|
10
|
+
isSupportedCommand: (args) => scanner.shouldScan(args),
|
|
11
|
+
getDependencyUpdatesForCommand: (args) => scanner.scan(args),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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, // all npx commands need to be scanned, npx doesn't have dry-run
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function scanDependencies(args) {
|
|
11
|
+
return checkChangesFromArgs(args);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function checkChangesFromArgs(args) {
|
|
15
|
+
const changes = [];
|
|
16
|
+
const packageUpdates = parsePackagesFromArguments(args);
|
|
17
|
+
|
|
18
|
+
for (const packageUpdate of packageUpdates) {
|
|
19
|
+
var exactVersion = await resolvePackageVersion(
|
|
20
|
+
packageUpdate.name,
|
|
21
|
+
packageUpdate.version
|
|
22
|
+
);
|
|
23
|
+
if (exactVersion) {
|
|
24
|
+
packageUpdate.version = exactVersion;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
changes.push({ ...packageUpdate, type: "add" });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return changes;
|
|
31
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export function parsePackagesFromArguments(args) {
|
|
2
|
+
let defaultTag = "latest";
|
|
3
|
+
|
|
4
|
+
for (let i = 0; i < args.length; i++) {
|
|
5
|
+
const arg = args[i];
|
|
6
|
+
const option = getOption(arg);
|
|
7
|
+
|
|
8
|
+
if (option) {
|
|
9
|
+
// If the option has a parameter, skip the next argument as well
|
|
10
|
+
i += option.numberOfParameters;
|
|
11
|
+
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const packageDetails = parsePackagename(arg, defaultTag);
|
|
16
|
+
if (packageDetails) {
|
|
17
|
+
return [packageDetails];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getOption(arg) {
|
|
25
|
+
if (isOptionWithParameter(arg)) {
|
|
26
|
+
return {
|
|
27
|
+
name: arg,
|
|
28
|
+
numberOfParameters: 1,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Arguments starting with "-" or "--" are considered options
|
|
33
|
+
// except for "--package=" which contains the package name
|
|
34
|
+
if (arg.startsWith("-") && !arg.startsWith("--package=")) {
|
|
35
|
+
return {
|
|
36
|
+
name: arg,
|
|
37
|
+
numberOfParameters: 0,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isOptionWithParameter(arg) {
|
|
45
|
+
const optionsWithParameters = [
|
|
46
|
+
"--access",
|
|
47
|
+
"--auth-type",
|
|
48
|
+
"--cache",
|
|
49
|
+
"--fetch-retries",
|
|
50
|
+
"--fetch-retry-mintimeout",
|
|
51
|
+
"--fetch-retry-maxtimeout",
|
|
52
|
+
"--fetch-retry-factor",
|
|
53
|
+
"--fetch-timeout",
|
|
54
|
+
"--https-proxy",
|
|
55
|
+
"--include",
|
|
56
|
+
"--location",
|
|
57
|
+
"--lockfile-version",
|
|
58
|
+
"--loglevel",
|
|
59
|
+
"--omit",
|
|
60
|
+
"--proxy",
|
|
61
|
+
"--registry",
|
|
62
|
+
"--replace-registry-host",
|
|
63
|
+
"--tag",
|
|
64
|
+
"--user-config",
|
|
65
|
+
"--workspace",
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
return optionsWithParameters.includes(arg);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parsePackagename(arg, defaultTag) {
|
|
72
|
+
// format can be --package=name@version
|
|
73
|
+
// in that case, we need to remove the --package= part
|
|
74
|
+
if (arg.startsWith("--package=")) {
|
|
75
|
+
arg = arg.slice(10);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
arg = removeAlias(arg);
|
|
79
|
+
|
|
80
|
+
// Split at the last "@" to separate the package name and version
|
|
81
|
+
const lastAtIndex = arg.lastIndexOf("@");
|
|
82
|
+
|
|
83
|
+
let name, version;
|
|
84
|
+
if (lastAtIndex !== -1) {
|
|
85
|
+
name = arg.slice(0, lastAtIndex);
|
|
86
|
+
version = arg.slice(lastAtIndex + 1);
|
|
87
|
+
} else {
|
|
88
|
+
name = arg;
|
|
89
|
+
version = defaultTag; // No tag specified (eg: "http-server"), use the default tag
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
name,
|
|
94
|
+
version,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function removeAlias(arg) {
|
|
99
|
+
// removes the alias.
|
|
100
|
+
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
|
101
|
+
const aliasIndex = arg.indexOf("@npm:");
|
|
102
|
+
if (aliasIndex !== -1) {
|
|
103
|
+
return arg.slice(aliasIndex + 5);
|
|
104
|
+
}
|
|
105
|
+
return arg;
|
|
106
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { parsePackagesFromArguments } from "./parsePackagesFromArguments.js";
|
|
4
|
+
|
|
5
|
+
describe("parsePackagesFromArguments", () => {
|
|
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 = ["http-server@14.1.1"];
|
|
16
|
+
|
|
17
|
+
const result = parsePackagesFromArguments(args);
|
|
18
|
+
|
|
19
|
+
assert.deepEqual(result, [{ name: "http-server", version: "14.1.1" }]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should return the package with latest tag if absent", () => {
|
|
23
|
+
const args = ["http-server"];
|
|
24
|
+
|
|
25
|
+
const result = parsePackagesFromArguments(args);
|
|
26
|
+
|
|
27
|
+
assert.deepEqual(result, [{ name: "http-server", version: "latest" }]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should ignore double --", () => {
|
|
31
|
+
const args = ["--", "http-server"];
|
|
32
|
+
|
|
33
|
+
const result = parsePackagesFromArguments(args);
|
|
34
|
+
|
|
35
|
+
assert.deepEqual(result, [{ name: "http-server", version: "latest" }]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should only return the first package", () => {
|
|
39
|
+
const args = ["http-server", "jest"];
|
|
40
|
+
|
|
41
|
+
const result = parsePackagesFromArguments(args);
|
|
42
|
+
|
|
43
|
+
assert.deepEqual(result, [{ name: "http-server", version: "latest" }]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return package with -p option", () => {
|
|
47
|
+
const args = ["-p", "http-server"];
|
|
48
|
+
|
|
49
|
+
const result = parsePackagesFromArguments(args);
|
|
50
|
+
|
|
51
|
+
assert.deepEqual(result, [{ name: "http-server", version: "latest" }]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should return package with --package option", () => {
|
|
55
|
+
const args = ["--package", "http-server"];
|
|
56
|
+
|
|
57
|
+
const result = parsePackagesFromArguments(args);
|
|
58
|
+
|
|
59
|
+
assert.deepEqual(result, [{ name: "http-server", version: "latest" }]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should return package with --package=x option", () => {
|
|
63
|
+
const args = ["--package=http-server"];
|
|
64
|
+
|
|
65
|
+
const result = parsePackagesFromArguments(args);
|
|
66
|
+
|
|
67
|
+
assert.deepEqual(result, [{ name: "http-server", version: "latest" }]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should return package with --package=x@version option", () => {
|
|
71
|
+
const args = ["--package=http-server@1.0.0"];
|
|
72
|
+
|
|
73
|
+
const result = parsePackagesFromArguments(args);
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(result, [{ name: "http-server", version: "1.0.0" }]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should ignore options with parameters and return an array of changes", () => {
|
|
79
|
+
const args = ["--loglevel", "error", "http-server@14.1.1"];
|
|
80
|
+
|
|
81
|
+
const result = parsePackagesFromArguments(args);
|
|
82
|
+
|
|
83
|
+
assert.deepEqual(result, [{ name: "http-server", version: "14.1.1" }]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should parse version even for aliased packages", () => {
|
|
87
|
+
const args = ["server@npm:http-server@14.1.1"];
|
|
88
|
+
|
|
89
|
+
const result = parsePackagesFromArguments(args);
|
|
90
|
+
|
|
91
|
+
assert.deepEqual(result, [{ name: "http-server", version: "14.1.1" }]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should parse scoped packages", () => {
|
|
95
|
+
const args = ["@scope/package@1.0.0"];
|
|
96
|
+
|
|
97
|
+
const result = parsePackagesFromArguments(args);
|
|
98
|
+
|
|
99
|
+
assert.deepEqual(result, [{ name: "@scope/package", version: "1.0.0" }]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should parse packages with version ranges", () => {
|
|
103
|
+
const args = ["http-server@^14.1.1"];
|
|
104
|
+
|
|
105
|
+
const result = parsePackagesFromArguments(args);
|
|
106
|
+
|
|
107
|
+
assert.deepEqual(result, [{ name: "http-server", version: "^14.1.1" }]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should parse package folders", () => {
|
|
111
|
+
const args = ["./local-package"];
|
|
112
|
+
|
|
113
|
+
const result = parsePackagesFromArguments(args);
|
|
114
|
+
|
|
115
|
+
assert.deepEqual(result, [{ name: "./local-package", version: "latest" }]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should parse tarballs", () => {
|
|
119
|
+
const args = ["file:./local-package.tgz"];
|
|
120
|
+
|
|
121
|
+
const result = parsePackagesFromArguments(args);
|
|
122
|
+
|
|
123
|
+
assert.deepEqual(result, [
|
|
124
|
+
{ name: "file:./local-package.tgz", version: "latest" },
|
|
125
|
+
]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should parse tarball URLs", () => {
|
|
129
|
+
const args = ["https://example.com/local-package.tgz"];
|
|
130
|
+
|
|
131
|
+
const result = parsePackagesFromArguments(args);
|
|
132
|
+
|
|
133
|
+
assert.deepEqual(result, [
|
|
134
|
+
{ name: "https://example.com/local-package.tgz", version: "latest" },
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should parse git URLs", () => {
|
|
139
|
+
const args = ["git://github.com/http-party/http-server"];
|
|
140
|
+
|
|
141
|
+
const result = parsePackagesFromArguments(args);
|
|
142
|
+
|
|
143
|
+
assert.deepEqual(result, [
|
|
144
|
+
{ name: "git://github.com/http-party/http-server", version: "latest" },
|
|
145
|
+
]);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { ui } from "../../environment/userInteraction.js";
|
|
3
|
+
|
|
4
|
+
export function runNpx(args) {
|
|
5
|
+
try {
|
|
6
|
+
const npxCommand = `npx ${args.join(" ")}`;
|
|
7
|
+
execSync(npxCommand, { 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
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { commandArgumentScanner } from "./dependencyScanner/commandArgumentScanner.js";
|
|
2
|
+
import { runYarnCommand } from "./runYarnCommand.js";
|
|
3
|
+
|
|
4
|
+
const scanner = commandArgumentScanner();
|
|
5
|
+
|
|
6
|
+
export function createYarnPackageManager() {
|
|
7
|
+
return {
|
|
8
|
+
getWarningMessage: () => null,
|
|
9
|
+
runCommand: runYarnCommand,
|
|
10
|
+
isSupportedCommand: (args) =>
|
|
11
|
+
matchesCommand(args, "add") ||
|
|
12
|
+
matchesCommand(args, "global", "add") ||
|
|
13
|
+
matchesCommand(args, "install") ||
|
|
14
|
+
matchesCommand(args, "up") ||
|
|
15
|
+
matchesCommand(args, "upgrade") ||
|
|
16
|
+
matchesCommand(args, "global", "upgrade") ||
|
|
17
|
+
matchesCommand(args, "dlx"),
|
|
18
|
+
getDependencyUpdatesForCommand: (args) => scanner.scan(args),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function matchesCommand(args, ...commandArgs) {
|
|
23
|
+
if (args.length < commandArgs.length) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (var i = 0; i < commandArgs.length; i++) {
|
|
28
|
+
if (args[i].toLowerCase() !== commandArgs[i].toLowerCase()) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
@@ -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 yarn, 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,102 @@
|
|
|
1
|
+
export function parsePackagesFromArguments(args) {
|
|
2
|
+
const changes = [];
|
|
3
|
+
let defaultTag = "latest";
|
|
4
|
+
|
|
5
|
+
for (let i = 1; 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("-")) {
|
|
36
|
+
return {
|
|
37
|
+
name: arg,
|
|
38
|
+
numberOfParameters: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isOptionWithParameter(arg) {
|
|
46
|
+
const optionsWithParameters = [
|
|
47
|
+
"--use-yarnrc",
|
|
48
|
+
"--link-folder",
|
|
49
|
+
"--global-folder",
|
|
50
|
+
"--modules-folder",
|
|
51
|
+
"--preferred-cache-folder",
|
|
52
|
+
"--cache-folder",
|
|
53
|
+
"--mutex",
|
|
54
|
+
"--cwd",
|
|
55
|
+
"--proxy",
|
|
56
|
+
"--https-proxy",
|
|
57
|
+
"--registry",
|
|
58
|
+
"--network-concurrency",
|
|
59
|
+
"--network-timeout",
|
|
60
|
+
"--scripts-prepend-node-path",
|
|
61
|
+
"--otp",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
return optionsWithParameters.includes(arg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parsePackagename(arg, defaultTag) {
|
|
68
|
+
// format can be --package=name@version
|
|
69
|
+
// in that case, we need to remove the --package= part
|
|
70
|
+
if (arg.startsWith("--package=")) {
|
|
71
|
+
arg = arg.slice(10);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
arg = removeAlias(arg);
|
|
75
|
+
|
|
76
|
+
// Split at the last "@" to separate the package name and version
|
|
77
|
+
const lastAtIndex = arg.lastIndexOf("@");
|
|
78
|
+
|
|
79
|
+
let name, version;
|
|
80
|
+
if (lastAtIndex !== -1) {
|
|
81
|
+
name = arg.slice(0, lastAtIndex);
|
|
82
|
+
version = arg.slice(lastAtIndex + 1);
|
|
83
|
+
} else {
|
|
84
|
+
name = arg;
|
|
85
|
+
version = defaultTag; // No tag specified (eg: "http-server"), use the default tag
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
name,
|
|
90
|
+
version,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function removeAlias(arg) {
|
|
95
|
+
// removes the alias.
|
|
96
|
+
// Eg.: server@npm:http-server@latest becomes http-server@latest
|
|
97
|
+
const aliasIndex = arg.indexOf("@npm:");
|
|
98
|
+
if (aliasIndex !== -1) {
|
|
99
|
+
return arg.slice(aliasIndex + 5);
|
|
100
|
+
}
|
|
101
|
+
return arg;
|
|
102
|
+
}
|