@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,140 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { ui } from "../environment/userInteraction.js";
|
|
3
|
+
import { detectShells } from "./shellDetection.js";
|
|
4
|
+
import { getAliases } from "./helpers.js";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import { EOL } from "os";
|
|
7
|
+
|
|
8
|
+
export async function removeShell() {
|
|
9
|
+
ui.writeInformation(
|
|
10
|
+
chalk.bold("Removing shell aliases.") +
|
|
11
|
+
" This will remove safe-chain aliases for npm, npx, and yarn commands."
|
|
12
|
+
);
|
|
13
|
+
ui.emptyLine();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const shells = detectShells();
|
|
17
|
+
if (shells.length === 0) {
|
|
18
|
+
ui.writeError("No supported shells detected. Cannot remove aliases.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
ui.writeInformation(
|
|
23
|
+
`Detected ${shells.length} supported shell(s): ${shells
|
|
24
|
+
.map((shell) => chalk.bold(shell.name))
|
|
25
|
+
.join(", ")}.`
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
let updatedCount = 0;
|
|
29
|
+
for (const shell of shells) {
|
|
30
|
+
if (removeAliasesForShell(shell)) {
|
|
31
|
+
updatedCount++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (updatedCount > 0) {
|
|
36
|
+
ui.emptyLine();
|
|
37
|
+
ui.writeInformation(`Please restart your terminal to apply the changes.`);
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
ui.writeError(
|
|
41
|
+
`Failed to remove shell aliases: ${error.message}. Please check your shell configuration.`
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* This function removes aliases for the given shell.
|
|
49
|
+
* It reads the shell's startup file (eg ~/.bashrc, ~/.zshrc, etc.),
|
|
50
|
+
* and then removes the aliases for npm, npx, and yarn commands.
|
|
51
|
+
* If the aliases don't exist, it will report that they were not found.
|
|
52
|
+
* If the startup file does not exist, it will report that no aliases need to be removed.
|
|
53
|
+
*
|
|
54
|
+
* The shell startup script is loaded by the respective shell when it starts.
|
|
55
|
+
* This means that the aliases will be removed from the shell after it is restarted.
|
|
56
|
+
*/
|
|
57
|
+
function removeAliasesForShell(shell) {
|
|
58
|
+
if (!shell.startupFile) {
|
|
59
|
+
ui.writeError(
|
|
60
|
+
`- ${chalk.bold(
|
|
61
|
+
shell.name
|
|
62
|
+
)}: no startup file found. Cannot remove aliases.`
|
|
63
|
+
);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(shell.startupFile)) {
|
|
68
|
+
ui.writeInformation(
|
|
69
|
+
`- ${chalk.bold(
|
|
70
|
+
shell.name
|
|
71
|
+
)}: startup file does not exist. No aliases to remove.`
|
|
72
|
+
);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const aliases = getAliases(shell.startupFile);
|
|
77
|
+
const fileContent = fs.readFileSync(shell.startupFile, "utf-8");
|
|
78
|
+
const { removedCount, notFoundCount } = removeAliasesFromFile(
|
|
79
|
+
aliases,
|
|
80
|
+
fileContent,
|
|
81
|
+
shell.startupFile
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
let summary = "- " + chalk.bold(shell.name) + ": ";
|
|
85
|
+
|
|
86
|
+
if (removedCount > 0) {
|
|
87
|
+
summary += chalk.green(`${removedCount} aliases were removed`);
|
|
88
|
+
}
|
|
89
|
+
if (notFoundCount > 0) {
|
|
90
|
+
if (removedCount > 0) {
|
|
91
|
+
summary += ", ";
|
|
92
|
+
}
|
|
93
|
+
summary += chalk.yellow(`${notFoundCount} aliases were not found`);
|
|
94
|
+
}
|
|
95
|
+
if (removedCount === 0 && notFoundCount === 0) {
|
|
96
|
+
summary += chalk.yellow("no aliases found to remove");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ui.writeInformation(summary);
|
|
100
|
+
return removedCount > 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* This function removes the aliases from the startup file.
|
|
105
|
+
* It searches for exact matches of each alias line and removes them.
|
|
106
|
+
* eg: for bash it will remove 'alias npm="aikido-npm"' for npm from ~/.bashrc
|
|
107
|
+
* @returns an object with the counts of removed and not found aliases.
|
|
108
|
+
*/
|
|
109
|
+
export function removeAliasesFromFile(aliases, fileContent, startupFilePath) {
|
|
110
|
+
let removedCount = 0;
|
|
111
|
+
let notFoundCount = 0;
|
|
112
|
+
let updatedContent = fileContent;
|
|
113
|
+
|
|
114
|
+
for (const alias of aliases) {
|
|
115
|
+
const lines = updatedContent.split(EOL);
|
|
116
|
+
let aliasLineIndex = lines.findIndex((line) => line.trim() === alias);
|
|
117
|
+
|
|
118
|
+
if (aliasLineIndex !== -1) {
|
|
119
|
+
removedCount++;
|
|
120
|
+
|
|
121
|
+
// Remove all occurrences of the alias line, in case it appears multiple times
|
|
122
|
+
while (aliasLineIndex !== -1) {
|
|
123
|
+
lines.splice(aliasLineIndex, 1);
|
|
124
|
+
aliasLineIndex = lines.findIndex((line) => line.trim() === alias);
|
|
125
|
+
}
|
|
126
|
+
updatedContent = lines.join(EOL);
|
|
127
|
+
} else {
|
|
128
|
+
notFoundCount++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (removedCount > 0) {
|
|
133
|
+
fs.writeFileSync(startupFilePath, updatedContent, "utf-8");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
removedCount,
|
|
138
|
+
notFoundCount,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { EOL, tmpdir } from "node:os";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { getAliases } from "./helpers.js";
|
|
6
|
+
import { removeAliasesFromFile } from "./removeShell.js";
|
|
7
|
+
|
|
8
|
+
describe("removeShell", () => {
|
|
9
|
+
function runRemovalTestsForEnvironment(shell, startupExtension, expectedAliases) {
|
|
10
|
+
describe(`${shell} shell removal`, () => {
|
|
11
|
+
it(`should remove aliases from ${shell} file`, () => {
|
|
12
|
+
const lines = [`#!/usr/bin/env ${shell}`, "", ...expectedAliases, ""];
|
|
13
|
+
const filePath = createShellStartupScript(lines, startupExtension);
|
|
14
|
+
|
|
15
|
+
// Test the removeAliasesFromFile function directly
|
|
16
|
+
const aliases = getAliases(filePath);
|
|
17
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
18
|
+
|
|
19
|
+
const result = removeAliasesFromFile(aliases, fileContent, filePath);
|
|
20
|
+
|
|
21
|
+
assert.strictEqual(result.removedCount, 3, "Should remove 3 aliases");
|
|
22
|
+
assert.strictEqual(result.notFoundCount, 0, "Should find all aliases");
|
|
23
|
+
|
|
24
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
25
|
+
for (const alias of expectedAliases) {
|
|
26
|
+
assert.ok(!updatedContent.includes(alias), `Alias "${alias}" should be removed`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it(`should handle file with no aliases for ${shell}`, () => {
|
|
31
|
+
const lines = [`#!/usr/bin/env ${shell}`, "", "alias other='command'", ""];
|
|
32
|
+
const filePath = createShellStartupScript(lines, startupExtension);
|
|
33
|
+
|
|
34
|
+
const aliases = getAliases(filePath);
|
|
35
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
36
|
+
|
|
37
|
+
const result = removeAliasesFromFile(aliases, fileContent, filePath);
|
|
38
|
+
|
|
39
|
+
assert.strictEqual(result.removedCount, 0, "Should remove 0 aliases");
|
|
40
|
+
assert.strictEqual(result.notFoundCount, 3, "Should report 3 aliases not found");
|
|
41
|
+
|
|
42
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
43
|
+
assert.ok(updatedContent.includes("alias other='command'"), "Other aliases should remain unchanged");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it(`should remove duplicate aliases from ${shell} file`, () => {
|
|
47
|
+
const lines = [
|
|
48
|
+
`#!/usr/bin/env ${shell}`,
|
|
49
|
+
"",
|
|
50
|
+
...expectedAliases,
|
|
51
|
+
"alias other='command'",
|
|
52
|
+
...expectedAliases, // duplicates
|
|
53
|
+
""
|
|
54
|
+
];
|
|
55
|
+
const filePath = createShellStartupScript(lines, startupExtension);
|
|
56
|
+
|
|
57
|
+
const aliases = getAliases(filePath);
|
|
58
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
59
|
+
|
|
60
|
+
const result = removeAliasesFromFile(aliases, fileContent, filePath);
|
|
61
|
+
|
|
62
|
+
assert.strictEqual(result.removedCount, 3, "Should remove 3 aliases (counting duplicates as single removal)");
|
|
63
|
+
assert.strictEqual(result.notFoundCount, 0, "Should find all aliases");
|
|
64
|
+
|
|
65
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
66
|
+
for (const alias of expectedAliases) {
|
|
67
|
+
assert.ok(!updatedContent.includes(alias), `Alias "${alias}" should be completely removed`);
|
|
68
|
+
}
|
|
69
|
+
assert.ok(updatedContent.includes("alias other='command'"), "Other aliases should remain");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it(`should use real getAliases() for ${shell} file`, () => {
|
|
73
|
+
const filePath = `${tmpdir()}/test${startupExtension}`;
|
|
74
|
+
const aliases = getAliases(filePath);
|
|
75
|
+
|
|
76
|
+
// Verify we get the expected aliases for this shell type
|
|
77
|
+
assert.strictEqual(aliases.length, 3, "Should get 3 aliases (npm, npx, yarn)");
|
|
78
|
+
for (let i = 0; i < aliases.length; i++) {
|
|
79
|
+
assert.strictEqual(aliases[i], expectedAliases[i], `Alias ${i} should match expected format`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it(`should handle partial alias matches for ${shell}`, () => {
|
|
84
|
+
const lines = [
|
|
85
|
+
`#!/usr/bin/env ${shell}`,
|
|
86
|
+
"",
|
|
87
|
+
expectedAliases[0], // Only first alias
|
|
88
|
+
"alias other='command'",
|
|
89
|
+
""
|
|
90
|
+
];
|
|
91
|
+
const filePath = createShellStartupScript(lines, startupExtension);
|
|
92
|
+
|
|
93
|
+
const aliases = getAliases(filePath);
|
|
94
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
95
|
+
|
|
96
|
+
const result = removeAliasesFromFile(aliases, fileContent, filePath);
|
|
97
|
+
|
|
98
|
+
assert.strictEqual(result.removedCount, 1, "Should remove 1 alias");
|
|
99
|
+
assert.strictEqual(result.notFoundCount, 2, "Should report 2 aliases not found");
|
|
100
|
+
|
|
101
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
102
|
+
assert.ok(!updatedContent.includes(expectedAliases[0]), "First alias should be removed");
|
|
103
|
+
assert.ok(updatedContent.includes("alias other='command'"), "Other aliases should remain");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Test for each shell type using real getAliases() output
|
|
109
|
+
runRemovalTestsForEnvironment("bash", ".bashrc", [
|
|
110
|
+
"alias npm='aikido-npm'",
|
|
111
|
+
"alias npx='aikido-npx'",
|
|
112
|
+
"alias yarn='aikido-yarn'"
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
runRemovalTestsForEnvironment("zsh", ".zshrc", [
|
|
116
|
+
"alias npm='aikido-npm'",
|
|
117
|
+
"alias npx='aikido-npx'",
|
|
118
|
+
"alias yarn='aikido-yarn'"
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
runRemovalTestsForEnvironment("fish", ".fish", [
|
|
122
|
+
'alias npm "aikido-npm"',
|
|
123
|
+
'alias npx "aikido-npx"',
|
|
124
|
+
'alias yarn "aikido-yarn"'
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
runRemovalTestsForEnvironment("pwsh", ".ps1", [
|
|
128
|
+
"Set-Alias npm aikido-npm",
|
|
129
|
+
"Set-Alias npx aikido-npx",
|
|
130
|
+
"Set-Alias yarn aikido-yarn"
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
describe("removeAliasesFromFile edge cases", () => {
|
|
134
|
+
it("should handle empty file", () => {
|
|
135
|
+
const aliases = ["alias npm='aikido-npm'"];
|
|
136
|
+
const fileContent = "";
|
|
137
|
+
const filePath = `${tmpdir()}/test-${Math.random().toString(36).substring(2, 15)}.bashrc`;
|
|
138
|
+
fs.writeFileSync(filePath, fileContent, "utf-8");
|
|
139
|
+
|
|
140
|
+
const result = removeAliasesFromFile(aliases, fileContent, filePath);
|
|
141
|
+
|
|
142
|
+
assert.strictEqual(result.removedCount, 0, "Should remove 0 aliases from empty file");
|
|
143
|
+
assert.strictEqual(result.notFoundCount, 1, "Should report 1 alias not found");
|
|
144
|
+
|
|
145
|
+
// Cleanup
|
|
146
|
+
fs.rmSync(filePath, { force: true });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should handle file with only whitespace", () => {
|
|
150
|
+
const aliases = ["alias npm='aikido-npm'"];
|
|
151
|
+
const fileContent = `${EOL}${EOL} ${EOL}`;
|
|
152
|
+
const filePath = `${tmpdir()}/test-${Math.random().toString(36).substring(2, 15)}.bashrc`;
|
|
153
|
+
fs.writeFileSync(filePath, fileContent, "utf-8");
|
|
154
|
+
|
|
155
|
+
const result = removeAliasesFromFile(aliases, fileContent, filePath);
|
|
156
|
+
|
|
157
|
+
assert.strictEqual(result.removedCount, 0, "Should remove 0 aliases from whitespace-only file");
|
|
158
|
+
assert.strictEqual(result.notFoundCount, 1, "Should report 1 alias not found");
|
|
159
|
+
|
|
160
|
+
// Cleanup
|
|
161
|
+
fs.rmSync(filePath, { force: true });
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
function createShellStartupScript(lines, fileExtension) {
|
|
167
|
+
const randomFileName = Math.random().toString(36).substring(2, 15);
|
|
168
|
+
const filePath = `${tmpdir()}/${randomFileName}${fileExtension}`;
|
|
169
|
+
fs.writeFileSync(filePath, lines.join(EOL), "utf-8");
|
|
170
|
+
return filePath;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function readAndDeleteFile(filePath) {
|
|
174
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
175
|
+
fs.rmSync(filePath, { force: true });
|
|
176
|
+
return fileContent.split(EOL);
|
|
177
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { ui } from "../environment/userInteraction.js";
|
|
3
|
+
import { detectShells } from "./shellDetection.js";
|
|
4
|
+
import { getAliases } from "./helpers.js";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import { EOL } from "os";
|
|
7
|
+
|
|
8
|
+
export async function setupShell() {
|
|
9
|
+
ui.writeInformation(
|
|
10
|
+
chalk.bold("Setting up shell aliases.") +
|
|
11
|
+
" This will wrap safe-chain around npm, npx, and yarn commands."
|
|
12
|
+
);
|
|
13
|
+
ui.emptyLine();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const shells = detectShells();
|
|
17
|
+
if (shells.length === 0) {
|
|
18
|
+
ui.writeError("No supported shells detected. Cannot set up aliases.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
ui.writeInformation(
|
|
23
|
+
`Detected ${shells.length} supported shell(s): ${shells
|
|
24
|
+
.map((shell) => chalk.bold(shell.name))
|
|
25
|
+
.join(", ")}.`
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
let updatedCount = 0;
|
|
29
|
+
for (const shell of shells) {
|
|
30
|
+
if (setupAliasesForShell(shell)) {
|
|
31
|
+
updatedCount++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (updatedCount > 0) {
|
|
36
|
+
ui.emptyLine();
|
|
37
|
+
ui.writeInformation(`Please restart your terminal to apply the changes.`);
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
ui.writeError(
|
|
41
|
+
`Failed to set up shell aliases: ${error.message}. Please check your shell configuration.`
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* This function sets up aliases for the given shell.
|
|
49
|
+
* It reads the shell's startup file (eg ~/.bashrc, ~/.zshrc, etc.),
|
|
50
|
+
* and then appends the aliases for npm, npx, and yarn commands.
|
|
51
|
+
* If the aliases already exist, it will not add them again.
|
|
52
|
+
* If the startup file does not exist, it will create it.
|
|
53
|
+
*
|
|
54
|
+
* The shell startup script is loaded by the respective shell when it starts.
|
|
55
|
+
* This means that the aliases will be available in the shell after it is restarted.
|
|
56
|
+
*/
|
|
57
|
+
function setupAliasesForShell(shell) {
|
|
58
|
+
if (!shell.startupFile) {
|
|
59
|
+
ui.writeError(
|
|
60
|
+
`- ${chalk.bold(
|
|
61
|
+
shell.name
|
|
62
|
+
)}: no startup file found. Cannot set up aliases.`
|
|
63
|
+
);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const aliases = getAliases(shell.startupFile);
|
|
68
|
+
|
|
69
|
+
if (aliases.length === 0) {
|
|
70
|
+
ui.writeError(`- ${chalk.bold(shell.name)}: could not generate aliases.`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const fileContent = readOrCreateStartupFile(shell.startupFile);
|
|
75
|
+
const { addedCount, existingCount, failedCount } = appendAliasesToFile(
|
|
76
|
+
aliases,
|
|
77
|
+
fileContent,
|
|
78
|
+
shell.startupFile
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
let summary = "- " + chalk.bold(shell.name) + ": ";
|
|
82
|
+
|
|
83
|
+
if (addedCount > 0) {
|
|
84
|
+
summary += chalk.green(`${addedCount} aliases were added`);
|
|
85
|
+
}
|
|
86
|
+
if (existingCount > 0) {
|
|
87
|
+
if (addedCount > 0) {
|
|
88
|
+
summary += ", ";
|
|
89
|
+
}
|
|
90
|
+
summary += chalk.yellow(`${existingCount} aliases were already present`);
|
|
91
|
+
}
|
|
92
|
+
if (failedCount > 0) {
|
|
93
|
+
if (addedCount > 0 || existingCount > 0) {
|
|
94
|
+
summary += ", ";
|
|
95
|
+
}
|
|
96
|
+
summary += chalk.red(`${failedCount} aliases failed to add`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// write summary in a single line
|
|
100
|
+
ui.writeInformation(summary);
|
|
101
|
+
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* This reads the content of the startup file.
|
|
107
|
+
* If the file does not exist, it creates an empty file and returns an empty string.
|
|
108
|
+
* The startup file is the shell's startup script (eg: ~/.bashrc, ~/.zshrc, etc.).
|
|
109
|
+
* It is used to set up the shell environment when it starts.
|
|
110
|
+
* Some shells may not have a startup file, in which case this function will create one.
|
|
111
|
+
*/
|
|
112
|
+
export function readOrCreateStartupFile(filePath) {
|
|
113
|
+
if (!fs.existsSync(filePath)) {
|
|
114
|
+
fs.writeFileSync(filePath, "", "utf-8");
|
|
115
|
+
ui.writeInformation(`File ${filePath} created.`);
|
|
116
|
+
}
|
|
117
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* This function appends the aliases to the startup file.
|
|
122
|
+
* eg: for bash it will append 'alias npm="aikido-npm"' for npm to ~/.bashrc
|
|
123
|
+
* @returns an object with the counts of added, existing, and failed aliases.
|
|
124
|
+
*/
|
|
125
|
+
export function appendAliasesToFile(aliases, fileContent, startupFilePath) {
|
|
126
|
+
let addedCount = 0;
|
|
127
|
+
let existingCount = 0;
|
|
128
|
+
let failedCount = 0;
|
|
129
|
+
|
|
130
|
+
for (const alias of aliases) {
|
|
131
|
+
try {
|
|
132
|
+
if (fileContent.includes(alias)) {
|
|
133
|
+
existingCount++;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fs.appendFileSync(startupFilePath, `${EOL}${alias}`, "utf-8");
|
|
138
|
+
|
|
139
|
+
addedCount++;
|
|
140
|
+
} catch {
|
|
141
|
+
failedCount++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
addedCount,
|
|
148
|
+
existingCount,
|
|
149
|
+
failedCount,
|
|
150
|
+
};
|
|
151
|
+
}
|