@aikidosec/safe-chain 1.0.17 → 1.0.18
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/package.json +1 -1
- package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.js +3 -1
- package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.spec.js +8 -0
- package/src/packagemanager/npx/parsing/parsePackagesFromArguments.js +3 -1
- package/src/packagemanager/npx/parsing/parsePackagesFromArguments.spec.js +8 -0
- package/src/packagemanager/pnpm/parsing/parsePackagesFromArguments.spec.js +8 -0
- package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.js +3 -1
- package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.spec.js +8 -0
- package/src/shell-integration/helpers.js +26 -2
- package/src/shell-integration/helpers.spec.js +113 -0
package/package.json
CHANGED
|
@@ -86,7 +86,9 @@ function parsePackagename(arg) {
|
|
|
86
86
|
const lastAtIndex = arg.lastIndexOf("@");
|
|
87
87
|
|
|
88
88
|
let name, version;
|
|
89
|
-
|
|
89
|
+
// The index of the last "@" should be greater than 0
|
|
90
|
+
// If the index is 0, it means the package name starts with "@" (eg: "@vercel/otel")
|
|
91
|
+
if (lastAtIndex > 0) {
|
|
90
92
|
name = arg.slice(0, lastAtIndex);
|
|
91
93
|
version = arg.slice(lastAtIndex + 1);
|
|
92
94
|
} else {
|
|
@@ -19,6 +19,14 @@ describe("parsePackagesFromInstallArgs", () => {
|
|
|
19
19
|
assert.deepEqual(result, [{ name: "@jest/transform", version: "29.7.0" }]);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
it("should return the package in the format @vercel/otel", () => {
|
|
23
|
+
const args = ["install", "@vercel/otel"];
|
|
24
|
+
|
|
25
|
+
const result = parsePackagesFromInstallArgs(args);
|
|
26
|
+
|
|
27
|
+
assert.deepEqual(result, [{ name: "@vercel/otel", version: "latest" }]);
|
|
28
|
+
});
|
|
29
|
+
|
|
22
30
|
it("should return an array of changes for multiple packages", () => {
|
|
23
31
|
const args = ["install", "express@4.17.1", "lodash@4.17.21"];
|
|
24
32
|
|
|
@@ -81,7 +81,9 @@ function parsePackagename(arg, defaultTag) {
|
|
|
81
81
|
const lastAtIndex = arg.lastIndexOf("@");
|
|
82
82
|
|
|
83
83
|
let name, version;
|
|
84
|
-
|
|
84
|
+
// The index of the last "@" should be greater than 0
|
|
85
|
+
// If the index is 0, it means the package name starts with "@" (eg: "@vercel/otel")
|
|
86
|
+
if (lastAtIndex > 0) {
|
|
85
87
|
name = arg.slice(0, lastAtIndex);
|
|
86
88
|
version = arg.slice(lastAtIndex + 1);
|
|
87
89
|
} else {
|
|
@@ -19,6 +19,14 @@ describe("parsePackagesFromArguments", () => {
|
|
|
19
19
|
assert.deepEqual(result, [{ name: "http-server", version: "14.1.1" }]);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
it("should return the package in the format @vercel/otel", () => {
|
|
23
|
+
const args = ["@vercel/otel"];
|
|
24
|
+
|
|
25
|
+
const result = parsePackagesFromArguments(args);
|
|
26
|
+
|
|
27
|
+
assert.deepEqual(result, [{ name: "@vercel/otel", version: "latest" }]);
|
|
28
|
+
});
|
|
29
|
+
|
|
22
30
|
it("should return the package with latest tag if absent", () => {
|
|
23
31
|
const args = ["http-server"];
|
|
24
32
|
|
|
@@ -27,6 +27,14 @@ describe("standardPnpmArgumentParser", () => {
|
|
|
27
27
|
assert.deepEqual(result, [{ name: "axios", version: "latest" }]);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
it("should return the package in the format @vercel/otel", () => {
|
|
31
|
+
const args = ["@vercel/otel"];
|
|
32
|
+
|
|
33
|
+
const result = parsePackagesFromArguments(args);
|
|
34
|
+
|
|
35
|
+
assert.deepEqual(result, [{ name: "@vercel/otel", version: "latest" }]);
|
|
36
|
+
});
|
|
37
|
+
|
|
30
38
|
it("should return the package with latest tag if the version is absent and package starts with @", () => {
|
|
31
39
|
const args = ["@aikidosec/package-name"];
|
|
32
40
|
|
|
@@ -77,7 +77,9 @@ function parsePackagename(arg, defaultTag) {
|
|
|
77
77
|
const lastAtIndex = arg.lastIndexOf("@");
|
|
78
78
|
|
|
79
79
|
let name, version;
|
|
80
|
-
|
|
80
|
+
// The index of the last "@" should be greater than 0
|
|
81
|
+
// If the index is 0, it means the package name starts with "@" (eg: "@vercel/otel")
|
|
82
|
+
if (lastAtIndex > 0) {
|
|
81
83
|
name = arg.slice(0, lastAtIndex);
|
|
82
84
|
version = arg.slice(lastAtIndex + 1);
|
|
83
85
|
} else {
|
|
@@ -38,6 +38,14 @@ describe("standardYarnArgumentParser", () => {
|
|
|
38
38
|
]);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
it("should return the package in the format @vercel/otel", () => {
|
|
42
|
+
const args = ["add", "@vercel/otel"];
|
|
43
|
+
|
|
44
|
+
const result = parsePackagesFromArguments(args);
|
|
45
|
+
|
|
46
|
+
assert.deepEqual(result, [{ name: "@vercel/otel", version: "latest" }]);
|
|
47
|
+
});
|
|
48
|
+
|
|
41
49
|
it("should ignore options with parameters and return an array of changes", () => {
|
|
42
50
|
const args = ["add", "--proxy", "http://localhost", "axios@1.9.0"];
|
|
43
51
|
|
|
@@ -28,11 +28,35 @@ export function removeLinesMatchingPattern(filePath, pattern) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
31
|
-
const lines = fileContent.split(
|
|
32
|
-
const updatedLines = lines.filter((line) => !
|
|
31
|
+
const lines = fileContent.split(/[\r\n\u2028\u2029]+/);
|
|
32
|
+
const updatedLines = lines.filter((line) => !shouldRemoveLine(line, pattern));
|
|
33
33
|
fs.writeFileSync(filePath, updatedLines.join(os.EOL), "utf-8");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
const maxLineLength = 100;
|
|
37
|
+
function shouldRemoveLine(line, pattern) {
|
|
38
|
+
const isPatternMatch = pattern.test(line);
|
|
39
|
+
|
|
40
|
+
if (!isPatternMatch) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (line.length > maxLineLength) {
|
|
45
|
+
// safe-chain only adds lines shorter than maxLineLength
|
|
46
|
+
// so if the line is longer, it must be from a different
|
|
47
|
+
// source and could be dangerous to remove
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (line.includes("\n") || line.includes("\r") || line.includes("\u2028") || line.includes("\u2029")) {
|
|
52
|
+
// If the line contains newlines, something has gone wrong in splitting
|
|
53
|
+
// \u2028 and \u2029 are Unicode line separator characters (line and paragraph separators)
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
export function addLineToFile(filePath, line) {
|
|
37
61
|
if (!fs.existsSync(filePath)) {
|
|
38
62
|
fs.writeFileSync(filePath, "", "utf-8");
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
|
|
7
|
+
describe("removeLinesMatchingPatternTests", () => {
|
|
8
|
+
let testFile;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Create temporary test file
|
|
12
|
+
testFile = path.join(tmpdir(), `test-helpers-${Date.now()}.txt`);
|
|
13
|
+
|
|
14
|
+
// Mock the os module to override EOL
|
|
15
|
+
mock.module("node:os", {
|
|
16
|
+
namedExports: {
|
|
17
|
+
EOL: "\r\n", // Simulate Windows line endings
|
|
18
|
+
tmpdir: tmpdir,
|
|
19
|
+
platform: () => "linux"
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
// Clean up test files
|
|
26
|
+
if (fs.existsSync(testFile)) {
|
|
27
|
+
fs.unlinkSync(testFile);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Reset mocks
|
|
31
|
+
mock.reset();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
it("should handle mixed line endings without wiping entire file", async () => {
|
|
36
|
+
// Import helpers after setting up the mock
|
|
37
|
+
const { removeLinesMatchingPattern } = await import("./helpers.js");
|
|
38
|
+
|
|
39
|
+
// Create a file with Unix line endings but os.EOL expects Windows
|
|
40
|
+
const fileContent = [
|
|
41
|
+
"# keep this line",
|
|
42
|
+
"alias npm='remove-this'",
|
|
43
|
+
"# keep this line too",
|
|
44
|
+
"alias yarn='remove-this-too'",
|
|
45
|
+
"# final line to keep"
|
|
46
|
+
].join("\n"); // File has Unix line endings
|
|
47
|
+
|
|
48
|
+
fs.writeFileSync(testFile, fileContent, "utf-8");
|
|
49
|
+
|
|
50
|
+
// Try to remove lines containing 'alias'
|
|
51
|
+
const pattern = /alias.*=/;
|
|
52
|
+
removeLinesMatchingPattern(testFile, pattern);
|
|
53
|
+
|
|
54
|
+
const result = fs.readFileSync(testFile, "utf-8");
|
|
55
|
+
|
|
56
|
+
// This test will fail because the function splits on '\r\n' but file uses '\n'
|
|
57
|
+
// So it treats the entire content as one line and if any part matches, removes everything
|
|
58
|
+
assert.ok(result.includes("keep this line"), "Should preserve non-matching lines");
|
|
59
|
+
assert.ok(result.includes("final line to keep"), "Should preserve final line");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should handle mixed line endings with short matching content", async () => {
|
|
63
|
+
// Import helpers after setting up the mock
|
|
64
|
+
const { removeLinesMatchingPattern } = await import("./helpers.js");
|
|
65
|
+
|
|
66
|
+
// Create a file with Unix line endings, but make the entire content short
|
|
67
|
+
// to bypass the maxLineLength protection
|
|
68
|
+
const fileContent = [
|
|
69
|
+
"# keep1",
|
|
70
|
+
"alias x=y", // Short alias line that should be removed
|
|
71
|
+
"# keep2"
|
|
72
|
+
].join("\n"); // File has Unix line endings, total length < 100 chars
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(testFile, fileContent, "utf-8");
|
|
75
|
+
|
|
76
|
+
// Try to remove lines containing 'alias'
|
|
77
|
+
const pattern = /alias/;
|
|
78
|
+
removeLinesMatchingPattern(testFile, pattern);
|
|
79
|
+
|
|
80
|
+
const result = fs.readFileSync(testFile, "utf-8");
|
|
81
|
+
|
|
82
|
+
// This should now be protected by the newline detection
|
|
83
|
+
assert.ok(result.includes("keep1"), "Should preserve first line");
|
|
84
|
+
assert.ok(result.includes("keep2"), "Should preserve third line");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should handle Unicode line separators that bypass newline detection", async () => {
|
|
88
|
+
// Import helpers after setting up the mock
|
|
89
|
+
const { removeLinesMatchingPattern } = await import("./helpers.js");
|
|
90
|
+
|
|
91
|
+
// Use Unicode line separator (U+2028) and paragraph separator (U+2029)
|
|
92
|
+
// These are considered line breaks but aren't \n or \r
|
|
93
|
+
const fileContent = [
|
|
94
|
+
"keep this",
|
|
95
|
+
"alias test=value",
|
|
96
|
+
"keep that"
|
|
97
|
+
].join("\u2028"); // Unicode line separator
|
|
98
|
+
|
|
99
|
+
fs.writeFileSync(testFile, fileContent, "utf-8");
|
|
100
|
+
|
|
101
|
+
// Try to remove lines containing 'alias'
|
|
102
|
+
const pattern = /alias/;
|
|
103
|
+
removeLinesMatchingPattern(testFile, pattern);
|
|
104
|
+
|
|
105
|
+
const result = fs.readFileSync(testFile, "utf-8");
|
|
106
|
+
|
|
107
|
+
// This could still wipe everything if split() treats it as one line
|
|
108
|
+
// but the content doesn't contain \n or \r so passes the newline check
|
|
109
|
+
assert.ok(result.includes("keep this"), "Should preserve first part");
|
|
110
|
+
assert.ok(result.includes("keep that"), "Should preserve last part");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
});
|