@aikidosec/safe-chain 1.0.10 → 1.0.12
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 +34 -32
- package/bin/safe-chain.js +57 -0
- package/package.json +3 -2
- package/src/main.js +0 -13
- package/src/scanning/index.js +1 -3
- package/src/shell-integration/setup.js +151 -0
- package/src/shell-integration/setup.spec.js +304 -0
- package/src/shell-integration/shellDetection.js +75 -0
- package/src/shell-integration/teardown.js +140 -0
- package/src/shell-integration/teardown.spec.js +177 -0
- package/src/shell-integration/addAlias.js +0 -63
- package/src/shell-integration/removeAlias.js +0 -61
- package/src/shell-integration/shellIntegration.spec.js +0 -172
package/README.md
CHANGED
|
@@ -6,50 +6,52 @@ The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [n
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Installing the Aikido Safe Chain is easy. You just need 3 simple steps:
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
1. **Install the Aikido Safe Chain package globally** using npm:
|
|
12
|
+
```shell
|
|
13
|
+
npm install -g @aikidosec/safe-chain
|
|
14
|
+
```
|
|
15
|
+
2. **Setup the shell integration** by running:
|
|
16
|
+
```shell
|
|
17
|
+
safe-chain setup
|
|
18
|
+
```
|
|
19
|
+
3. **Restart your terminal** to start using the Aikido Safe Chain.
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
When running `npm`, `npx`, or `yarn` 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.
|
|
16
22
|
|
|
17
23
|
## Aliases in shell
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
The Aikido Safe Chain will setup aliases in your shell for the commands **npm**, **npx**, and **yarn**. This means that when you run these commands, they will be replaced with the Aikido Safe Chain commands **aikido-npm**, **aikido-npx**, and **aikido-yarn** respectively. The Aikido Safe Chain will do a pre-install check for malware before running the original command.
|
|
20
26
|
|
|
21
|
-
### Creating
|
|
27
|
+
### Creating shell aliases
|
|
22
28
|
|
|
23
|
-
The `
|
|
29
|
+
The `setup` command will detect which shells are installed and add the aliases for **npm**, **npx**, and **yarn** to your shell startup scripts.
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
Supported shells include:
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
- ✅ **Bash**: `~/.bashrc`
|
|
34
|
+
- ✅ **Zsh**: `~/.zshrc`
|
|
35
|
+
- ✅ **Fish**: `~/.config/fish/config.fish`
|
|
36
|
+
- ✅ **Powershell**: `$PROFILE`
|
|
37
|
+
- ✅ **PowerShell Core**: `$PROFILE`
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
aikido-npm add-aikido-aliases ~/.zshrc
|
|
33
|
-
|
|
34
|
-
# Example for powershell
|
|
35
|
-
aikido-npm add-aikido-aliases $PROFILE
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
This will create the aliases. The following table shows the aliases that will be created in the shell startup script:
|
|
39
|
+
After adding the alias, **the shell needs to restart in order to load the alias**.
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
| -------------- | -------------------------- | --------------------------------------- | --------------------------------------- | ----------------------------------------- |
|
|
42
|
-
| **Bash** | ~/.bashrc | `alias npm='aikido-npm'` | `alias npx='aikido-npx'` | `alias yarn='aikido-yarn'` |
|
|
43
|
-
| **Zsh** | ~/.zshrc | `alias npm='aikido-npm'` | `alias npx='aikido-npx'` | `alias yarn='aikido-yarn'` |
|
|
44
|
-
| **Ash** | ~/.profile, ~/.ashrc | `alias npm='aikido-npm'` | `alias npx='aikido-npx'` | `alias yarn='aikido-yarn'` |
|
|
45
|
-
| **Fish** | ~/.config/fish/config.fish | `alias npm "aikido-npm"` | `alias npx "aikido-npx"` | `alias yarn "aikido-yarn"` |
|
|
46
|
-
| **Powershell** | $PROFILE | `Set-Alias -Name npm -Value aikido-npm` | `Set-Alias -Name npx -Value aikido-npx` | `Set-Alias -Name yarn -Value aikido-yarn` |
|
|
41
|
+
### Removing shell aliases
|
|
47
42
|
|
|
48
|
-
|
|
43
|
+
The `teardown` command will remove the aliases for **npm**, **npx**, and **yarn** from your shell startup scripts. This is useful if you want to stop using the Aikido Safe Chain or if you want to switch to a different package manager.
|
|
49
44
|
|
|
50
|
-
|
|
45
|
+
## Uninstallation
|
|
51
46
|
|
|
52
|
-
To
|
|
47
|
+
To uninstall the Aikido Safe Chain, you can run the following command:
|
|
53
48
|
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
1. **Remove all aliases from your shell** by running:
|
|
50
|
+
```shell
|
|
51
|
+
safe-chain teardown
|
|
52
|
+
```
|
|
53
|
+
2. **Uninstall the Aikido Safe Chain package** using npm:
|
|
54
|
+
```shell
|
|
55
|
+
npm uninstall -g @aikidosec/safe-chain
|
|
56
|
+
```
|
|
57
|
+
3. **Restart your terminal** to remove the aliases.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { ui } from "../src/environment/userInteraction.js";
|
|
5
|
+
import { setup } from "../src/shell-integration/setup.js";
|
|
6
|
+
import { teardown } from "../src/shell-integration/teardown.js";
|
|
7
|
+
|
|
8
|
+
if (process.argv.length < 3) {
|
|
9
|
+
ui.writeError("No command provided. Please provide a command to execute.");
|
|
10
|
+
ui.emptyLine();
|
|
11
|
+
writeHelp();
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const command = process.argv[2];
|
|
16
|
+
|
|
17
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
18
|
+
writeHelp();
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (command === "setup") {
|
|
23
|
+
setup();
|
|
24
|
+
} else if (command === "teardown") {
|
|
25
|
+
teardown();
|
|
26
|
+
} else {
|
|
27
|
+
ui.writeError(`Unknown command: ${command}.`);
|
|
28
|
+
ui.emptyLine();
|
|
29
|
+
|
|
30
|
+
writeHelp();
|
|
31
|
+
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeHelp() {
|
|
36
|
+
ui.writeInformation(
|
|
37
|
+
chalk.bold("Usage: ") + chalk.cyan("safe-chain <command>")
|
|
38
|
+
);
|
|
39
|
+
ui.emptyLine();
|
|
40
|
+
ui.writeInformation(
|
|
41
|
+
`Available commands: ${chalk.cyan("setup")}, ${chalk.cyan(
|
|
42
|
+
"teardown"
|
|
43
|
+
)}, ${chalk.cyan("help")}`
|
|
44
|
+
);
|
|
45
|
+
ui.emptyLine();
|
|
46
|
+
ui.writeInformation(
|
|
47
|
+
`- ${chalk.cyan(
|
|
48
|
+
"safe-chain setup"
|
|
49
|
+
)}: This will setup your shell to wrap safe-chain around npm, npx and yarn.`
|
|
50
|
+
);
|
|
51
|
+
ui.writeInformation(
|
|
52
|
+
`- ${chalk.cyan(
|
|
53
|
+
"safe-chain teardown"
|
|
54
|
+
)}: This will remove safe-chain aliases from your shell configuration.`
|
|
55
|
+
);
|
|
56
|
+
ui.emptyLine();
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikidosec/safe-chain",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
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",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"bin": {
|
|
14
14
|
"aikido-npm": "bin/aikido-npm.js",
|
|
15
15
|
"aikido-npx": "bin/aikido-npx.js",
|
|
16
|
-
"aikido-yarn": "bin/aikido-yarn.js"
|
|
16
|
+
"aikido-yarn": "bin/aikido-yarn.js",
|
|
17
|
+
"safe-chain": "bin/safe-chain.js"
|
|
17
18
|
},
|
|
18
19
|
"type": "module",
|
|
19
20
|
"keywords": [],
|
package/src/main.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { scanCommand, shouldScanCommand } from "./scanning/index.js";
|
|
4
|
-
import { isAddAliasCommand, addAlias } from "./shell-integration/addAlias.js";
|
|
5
|
-
import {
|
|
6
|
-
removeAlias,
|
|
7
|
-
isRemoveAliasCommand,
|
|
8
|
-
} from "./shell-integration/removeAlias.js";
|
|
9
4
|
import { ui } from "./environment/userInteraction.js";
|
|
10
5
|
import { getPackageManager } from "./packagemanager/currentPackageManager.js";
|
|
11
6
|
|
|
12
7
|
export async function main(args) {
|
|
13
8
|
try {
|
|
14
|
-
if (isAddAliasCommand(args)) {
|
|
15
|
-
addAlias(args);
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
if (isRemoveAliasCommand(args)) {
|
|
19
|
-
removeAlias(args);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
9
|
if (shouldScanCommand(args)) {
|
|
23
10
|
await scanCommand(args);
|
|
24
11
|
}
|
package/src/scanning/index.js
CHANGED
|
@@ -86,9 +86,7 @@ async function acceptRiskOrExit(message, defaultValue) {
|
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
ui.writeInformation(
|
|
90
|
-
"Exiting without installing packages. Please check the output."
|
|
91
|
-
);
|
|
89
|
+
ui.writeInformation("Exiting without installing packages.");
|
|
92
90
|
ui.emptyLine();
|
|
93
91
|
process.exit(1);
|
|
94
92
|
}
|
|
@@ -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 setup() {
|
|
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
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
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 { readOrCreateStartupFile, appendAliasesToFile } from "./setup.js";
|
|
7
|
+
|
|
8
|
+
describe("setupShell", () => {
|
|
9
|
+
function runSetupTestsForEnvironment(shell, startupExtension, expectedAliases) {
|
|
10
|
+
describe(`${shell} shell setup`, () => {
|
|
11
|
+
it(`should add aliases to ${shell} file`, () => {
|
|
12
|
+
const lines = [`#!/usr/bin/env ${shell}`, "", "alias cls='clear'"];
|
|
13
|
+
const filePath = createShellStartupScript(lines, startupExtension);
|
|
14
|
+
|
|
15
|
+
const aliases = getAliases(filePath);
|
|
16
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
17
|
+
|
|
18
|
+
const result = appendAliasesToFile(aliases, fileContent, filePath);
|
|
19
|
+
|
|
20
|
+
assert.strictEqual(result.addedCount, 3, "Should add 3 aliases");
|
|
21
|
+
assert.strictEqual(result.existingCount, 0, "Should find no existing aliases");
|
|
22
|
+
assert.strictEqual(result.failedCount, 0, "Should have no failed aliases");
|
|
23
|
+
|
|
24
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
25
|
+
for (const alias of expectedAliases) {
|
|
26
|
+
assert.ok(updatedContent.includes(alias), `Alias "${alias}" should be added`);
|
|
27
|
+
}
|
|
28
|
+
assert.ok(updatedContent.includes("alias cls='clear'"), "Original aliases should remain");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it(`should not add aliases if they already exist in ${shell} file`, () => {
|
|
32
|
+
const lines = [`#!/usr/bin/env ${shell}`, "", ...expectedAliases];
|
|
33
|
+
const filePath = createShellStartupScript(lines, startupExtension);
|
|
34
|
+
|
|
35
|
+
const aliases = getAliases(filePath);
|
|
36
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
37
|
+
|
|
38
|
+
const result = appendAliasesToFile(aliases, fileContent, filePath);
|
|
39
|
+
|
|
40
|
+
assert.strictEqual(result.addedCount, 0, "Should add 0 aliases");
|
|
41
|
+
assert.strictEqual(result.existingCount, 3, "Should find 3 existing aliases");
|
|
42
|
+
assert.strictEqual(result.failedCount, 0, "Should have no failed aliases");
|
|
43
|
+
|
|
44
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
45
|
+
// Count occurrences to ensure no duplicates were added
|
|
46
|
+
for (const alias of expectedAliases) {
|
|
47
|
+
assert.strictEqual(countOccurrences(updatedContent, alias), 1, `Alias "${alias}" should appear exactly once`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it(`should create file and add aliases if file does not exist for ${shell}`, () => {
|
|
52
|
+
const randomName = Math.random().toString(36).substring(2, 15);
|
|
53
|
+
const filePath = `${tmpdir()}/nonexistent-${randomName}${startupExtension}`;
|
|
54
|
+
if (fs.existsSync(filePath)) {
|
|
55
|
+
fs.rmSync(filePath, { force: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Test readOrCreateStartupFile function
|
|
59
|
+
const fileContent = readOrCreateStartupFile(filePath);
|
|
60
|
+
assert.strictEqual(fileContent, "", "Should return empty string for new file");
|
|
61
|
+
assert.ok(fs.existsSync(filePath), "File should be created");
|
|
62
|
+
|
|
63
|
+
// Test adding aliases to the newly created file
|
|
64
|
+
const aliases = getAliases(filePath);
|
|
65
|
+
const result = appendAliasesToFile(aliases, fileContent, filePath);
|
|
66
|
+
|
|
67
|
+
assert.strictEqual(result.addedCount, 3, "Should add 3 aliases");
|
|
68
|
+
assert.strictEqual(result.existingCount, 0, "Should find no existing aliases");
|
|
69
|
+
assert.strictEqual(result.failedCount, 0, "Should have no failed aliases");
|
|
70
|
+
|
|
71
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
72
|
+
for (const alias of expectedAliases) {
|
|
73
|
+
assert.ok(updatedContent.includes(alias), `Alias "${alias}" should be added`);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it(`should add aliases only once when called multiple times for ${shell}`, () => {
|
|
78
|
+
const lines = [`#!/usr/bin/env ${shell}`, ""];
|
|
79
|
+
const filePath = createShellStartupScript(lines, startupExtension);
|
|
80
|
+
|
|
81
|
+
const aliases = getAliases(filePath);
|
|
82
|
+
|
|
83
|
+
// First call - should add aliases
|
|
84
|
+
let fileContent = fs.readFileSync(filePath, "utf-8");
|
|
85
|
+
const result1 = appendAliasesToFile(aliases, fileContent, filePath);
|
|
86
|
+
assert.strictEqual(result1.addedCount, 3, "First call should add 3 aliases");
|
|
87
|
+
|
|
88
|
+
// Second call - should detect existing aliases
|
|
89
|
+
fileContent = fs.readFileSync(filePath, "utf-8");
|
|
90
|
+
const result2 = appendAliasesToFile(aliases, fileContent, filePath);
|
|
91
|
+
assert.strictEqual(result2.addedCount, 0, "Second call should add 0 aliases");
|
|
92
|
+
assert.strictEqual(result2.existingCount, 3, "Second call should find 3 existing aliases");
|
|
93
|
+
|
|
94
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
95
|
+
for (const alias of expectedAliases) {
|
|
96
|
+
assert.strictEqual(countOccurrences(updatedContent, alias), 1, `Alias "${alias}" should appear exactly once`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it(`should use real getAliases() for ${shell} file`, () => {
|
|
101
|
+
const filePath = `${tmpdir()}/test${startupExtension}`;
|
|
102
|
+
const aliases = getAliases(filePath);
|
|
103
|
+
|
|
104
|
+
// Verify we get the expected aliases for this shell type
|
|
105
|
+
assert.strictEqual(aliases.length, 3, "Should get 3 aliases (npm, npx, yarn)");
|
|
106
|
+
for (let i = 0; i < aliases.length; i++) {
|
|
107
|
+
assert.strictEqual(aliases[i], expectedAliases[i], `Alias ${i} should match expected format`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it(`should handle mixed scenario - some existing, some new for ${shell}`, () => {
|
|
112
|
+
const lines = [`#!/usr/bin/env ${shell}`, "", expectedAliases[0], "alias other='command'"];
|
|
113
|
+
const filePath = createShellStartupScript(lines, startupExtension);
|
|
114
|
+
|
|
115
|
+
const aliases = getAliases(filePath);
|
|
116
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
117
|
+
|
|
118
|
+
const result = appendAliasesToFile(aliases, fileContent, filePath);
|
|
119
|
+
|
|
120
|
+
assert.strictEqual(result.addedCount, 2, "Should add 2 new aliases");
|
|
121
|
+
assert.strictEqual(result.existingCount, 1, "Should find 1 existing alias");
|
|
122
|
+
assert.strictEqual(result.failedCount, 0, "Should have no failed aliases");
|
|
123
|
+
|
|
124
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
125
|
+
for (const alias of expectedAliases) {
|
|
126
|
+
assert.ok(updatedContent.includes(alias), `Alias "${alias}" should be present`);
|
|
127
|
+
}
|
|
128
|
+
assert.ok(updatedContent.includes("alias other='command'"), "Other aliases should remain");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Test for each shell type using real getAliases() output
|
|
134
|
+
runSetupTestsForEnvironment("bash", ".bashrc", [
|
|
135
|
+
"alias npm='aikido-npm'",
|
|
136
|
+
"alias npx='aikido-npx'",
|
|
137
|
+
"alias yarn='aikido-yarn'"
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
runSetupTestsForEnvironment("zsh", ".zshrc", [
|
|
141
|
+
"alias npm='aikido-npm'",
|
|
142
|
+
"alias npx='aikido-npx'",
|
|
143
|
+
"alias yarn='aikido-yarn'"
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
runSetupTestsForEnvironment("fish", ".fish", [
|
|
147
|
+
'alias npm "aikido-npm"',
|
|
148
|
+
'alias npx "aikido-npx"',
|
|
149
|
+
'alias yarn "aikido-yarn"'
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
runSetupTestsForEnvironment("pwsh", ".ps1", [
|
|
153
|
+
"Set-Alias npm aikido-npm",
|
|
154
|
+
"Set-Alias npx aikido-npx",
|
|
155
|
+
"Set-Alias yarn aikido-yarn"
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
describe("readOrCreateStartupFile", () => {
|
|
159
|
+
it("should read existing file content", () => {
|
|
160
|
+
const lines = ["#!/usr/bin/env bash", "", "alias test='echo test'"];
|
|
161
|
+
const filePath = createShellStartupScript(lines, ".bashrc");
|
|
162
|
+
|
|
163
|
+
const content = readOrCreateStartupFile(filePath);
|
|
164
|
+
|
|
165
|
+
assert.ok(content.includes("#!/usr/bin/env bash"), "Should contain shebang");
|
|
166
|
+
assert.ok(content.includes("alias test='echo test'"), "Should contain existing aliases");
|
|
167
|
+
|
|
168
|
+
// Cleanup
|
|
169
|
+
fs.rmSync(filePath, { force: true });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should create file if it doesn't exist", () => {
|
|
173
|
+
const filePath = `${tmpdir()}/test-${Math.random().toString(36).substring(2, 15)}.bashrc`;
|
|
174
|
+
if (fs.existsSync(filePath)) {
|
|
175
|
+
fs.rmSync(filePath, { force: true });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const content = readOrCreateStartupFile(filePath);
|
|
179
|
+
|
|
180
|
+
assert.strictEqual(content, "", "Should return empty string for new file");
|
|
181
|
+
assert.ok(fs.existsSync(filePath), "File should be created");
|
|
182
|
+
|
|
183
|
+
// Cleanup
|
|
184
|
+
fs.rmSync(filePath, { force: true });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should handle empty existing file", () => {
|
|
188
|
+
const filePath = `${tmpdir()}/test-${Math.random().toString(36).substring(2, 15)}.bashrc`;
|
|
189
|
+
fs.writeFileSync(filePath, "", "utf-8");
|
|
190
|
+
|
|
191
|
+
const content = readOrCreateStartupFile(filePath);
|
|
192
|
+
|
|
193
|
+
assert.strictEqual(content, "", "Should return empty string for empty file");
|
|
194
|
+
assert.ok(fs.existsSync(filePath), "File should still exist");
|
|
195
|
+
|
|
196
|
+
// Cleanup
|
|
197
|
+
fs.rmSync(filePath, { force: true });
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("appendAliasesToFile edge cases", () => {
|
|
202
|
+
it("should handle empty aliases array", () => {
|
|
203
|
+
const lines = ["#!/usr/bin/env bash", "", "alias test='echo test'"];
|
|
204
|
+
const filePath = createShellStartupScript(lines, ".bashrc");
|
|
205
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
206
|
+
|
|
207
|
+
const result = appendAliasesToFile([], fileContent, filePath);
|
|
208
|
+
|
|
209
|
+
assert.strictEqual(result.addedCount, 0, "Should add 0 aliases");
|
|
210
|
+
assert.strictEqual(result.existingCount, 0, "Should find 0 existing aliases");
|
|
211
|
+
assert.strictEqual(result.failedCount, 0, "Should have 0 failed aliases");
|
|
212
|
+
|
|
213
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
214
|
+
assert.ok(updatedContent.includes("alias test='echo test'"), "Original content should remain");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should handle partial substring matches correctly", () => {
|
|
218
|
+
const lines = [
|
|
219
|
+
"#!/usr/bin/env bash",
|
|
220
|
+
"",
|
|
221
|
+
"alias npmx='some-other-command'", // Contains 'npm' but shouldn't match 'alias npm='
|
|
222
|
+
"alias test='echo test'"
|
|
223
|
+
];
|
|
224
|
+
const filePath = createShellStartupScript(lines, ".bashrc");
|
|
225
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
226
|
+
|
|
227
|
+
const aliases = ["alias npm='aikido-npm'"];
|
|
228
|
+
const result = appendAliasesToFile(aliases, fileContent, filePath);
|
|
229
|
+
|
|
230
|
+
assert.strictEqual(result.addedCount, 1, "Should add 1 alias (npm)");
|
|
231
|
+
assert.strictEqual(result.existingCount, 0, "Should find 0 existing aliases");
|
|
232
|
+
assert.strictEqual(result.failedCount, 0, "Should have 0 failed aliases");
|
|
233
|
+
|
|
234
|
+
const updatedContent = readAndDeleteFile(filePath);
|
|
235
|
+
assert.ok(updatedContent.includes("alias npm='aikido-npm'"), "npm alias should be added");
|
|
236
|
+
assert.ok(updatedContent.includes("alias npmx='some-other-command'"), "npmx alias should remain");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should handle file with only whitespace", () => {
|
|
240
|
+
const filePath = `${tmpdir()}/test-${Math.random().toString(36).substring(2, 15)}.bashrc`;
|
|
241
|
+
const fileContent = `${EOL}${EOL} ${EOL}`;
|
|
242
|
+
fs.writeFileSync(filePath, fileContent, "utf-8");
|
|
243
|
+
|
|
244
|
+
const aliases = ["alias npm='aikido-npm'"];
|
|
245
|
+
const result = appendAliasesToFile(aliases, fileContent, filePath);
|
|
246
|
+
|
|
247
|
+
assert.strictEqual(result.addedCount, 1, "Should add 1 alias");
|
|
248
|
+
assert.strictEqual(result.existingCount, 0, "Should find 0 existing aliases");
|
|
249
|
+
assert.strictEqual(result.failedCount, 0, "Should have 0 failed aliases");
|
|
250
|
+
|
|
251
|
+
const updatedContent = fs.readFileSync(filePath, "utf-8");
|
|
252
|
+
assert.ok(updatedContent.includes("alias npm='aikido-npm'"), "Alias should be added");
|
|
253
|
+
|
|
254
|
+
// Cleanup
|
|
255
|
+
fs.rmSync(filePath, { force: true });
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe("appendAliasesToFile error handling", () => {
|
|
260
|
+
it("should handle file permission errors gracefully", () => {
|
|
261
|
+
const filePath = `${tmpdir()}/test-${Math.random().toString(36).substring(2, 15)}.bashrc`;
|
|
262
|
+
fs.writeFileSync(filePath, "#!/usr/bin/env bash", "utf-8");
|
|
263
|
+
|
|
264
|
+
// Make file read-only to simulate permission error
|
|
265
|
+
fs.chmodSync(filePath, 0o444);
|
|
266
|
+
|
|
267
|
+
const aliases = ["alias npm='aikido-npm'"];
|
|
268
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
269
|
+
|
|
270
|
+
const result = appendAliasesToFile(aliases, fileContent, filePath);
|
|
271
|
+
|
|
272
|
+
assert.strictEqual(result.addedCount, 0, "Should add 0 aliases due to permission error");
|
|
273
|
+
assert.strictEqual(result.existingCount, 0, "Should find 0 existing aliases");
|
|
274
|
+
assert.strictEqual(result.failedCount, 1, "Should have 1 failed alias");
|
|
275
|
+
|
|
276
|
+
// Restore permissions and cleanup
|
|
277
|
+
fs.chmodSync(filePath, 0o644);
|
|
278
|
+
fs.rmSync(filePath, { force: true });
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
function createShellStartupScript(lines, fileExtension) {
|
|
284
|
+
const randomFileName = Math.random().toString(36).substring(2, 15);
|
|
285
|
+
const filePath = `${tmpdir()}/${randomFileName}${fileExtension}`;
|
|
286
|
+
fs.writeFileSync(filePath, lines.join(EOL), "utf-8");
|
|
287
|
+
return filePath;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function readAndDeleteFile(filePath) {
|
|
291
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
292
|
+
fs.rmSync(filePath, { force: true });
|
|
293
|
+
return fileContent.split(EOL);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function countOccurrences(lines, searchString) {
|
|
297
|
+
let count = 0;
|
|
298
|
+
for (const line of lines) {
|
|
299
|
+
if (line.includes(searchString)) {
|
|
300
|
+
count++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return count;
|
|
304
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as os from "os";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
|
|
4
|
+
const shellList = {
|
|
5
|
+
bash: {
|
|
6
|
+
name: "Bash",
|
|
7
|
+
executable: "bash",
|
|
8
|
+
getStartupFileCommand: "echo ~/.bashrc",
|
|
9
|
+
},
|
|
10
|
+
zsh: {
|
|
11
|
+
name: "Zsh",
|
|
12
|
+
executable: "zsh",
|
|
13
|
+
getStartupFileCommand: "echo ${ZDOTDIR:-$HOME}/.zshrc",
|
|
14
|
+
},
|
|
15
|
+
fish: {
|
|
16
|
+
name: "Fish",
|
|
17
|
+
executable: "fish",
|
|
18
|
+
getStartupFileCommand: "echo ~/.config/fish/config.fish",
|
|
19
|
+
},
|
|
20
|
+
powershell: {
|
|
21
|
+
name: "PowerShell Core",
|
|
22
|
+
executable: "pwsh",
|
|
23
|
+
getStartupFileCommand: "echo $PROFILE",
|
|
24
|
+
},
|
|
25
|
+
windowsPowerShell: {
|
|
26
|
+
name: "Windows PowerShell",
|
|
27
|
+
executable: "powershell",
|
|
28
|
+
getStartupFileCommand: "echo $PROFILE",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function detectShells() {
|
|
33
|
+
let availableShells = [];
|
|
34
|
+
|
|
35
|
+
for (const shellName of Object.keys(shellList)) {
|
|
36
|
+
const shell = shellList[shellName];
|
|
37
|
+
|
|
38
|
+
if (isShellAvailable(shell)) {
|
|
39
|
+
const startupFile = getShellStartupFile(shell);
|
|
40
|
+
availableShells.push({
|
|
41
|
+
name: shell.name,
|
|
42
|
+
executable: shell.executable,
|
|
43
|
+
startupFile: startupFile || null,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return availableShells;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isShellAvailable(shell) {
|
|
52
|
+
try {
|
|
53
|
+
if (os.platform() === "win32") {
|
|
54
|
+
execSync(`where ${shell.executable}`, { stdio: "ignore" });
|
|
55
|
+
} else {
|
|
56
|
+
execSync(`which ${shell.executable}`, { stdio: "ignore" });
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getShellStartupFile(shell) {
|
|
65
|
+
try {
|
|
66
|
+
const command = shell.getStartupFileCommand;
|
|
67
|
+
const output = execSync(command, {
|
|
68
|
+
encoding: "utf8",
|
|
69
|
+
shell: shell.executable,
|
|
70
|
+
}).trim();
|
|
71
|
+
return output;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -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 teardown() {
|
|
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 "./teardown.js";
|
|
7
|
+
|
|
8
|
+
describe("teardown", () => {
|
|
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
|
+
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { EOL } from "os";
|
|
4
|
-
import { getAliases } from "./helpers.js";
|
|
5
|
-
import { ui } from "../environment/userInteraction.js";
|
|
6
|
-
|
|
7
|
-
export function isAddAliasCommand(args) {
|
|
8
|
-
if (args[0] === "add-aikido-aliases") {
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (args[0] === "add-aikido-npm-alias") {
|
|
13
|
-
// not in the documenation anymore, but still here for backwards compatibility
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function addAlias(args) {
|
|
21
|
-
if (!isAddAliasCommand(args)) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (args.length < 2) {
|
|
26
|
-
ui.writeError("Please specify the file to add the alias to.");
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const filePath = path.resolve(args[1]);
|
|
31
|
-
const aliases = getAliases(filePath);
|
|
32
|
-
|
|
33
|
-
if (!fs.existsSync(filePath)) {
|
|
34
|
-
fs.writeFileSync(filePath, "", "utf-8");
|
|
35
|
-
ui.writeInformation(`File ${filePath} created.`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
39
|
-
|
|
40
|
-
let missingAliases = [];
|
|
41
|
-
for (const alias of aliases) {
|
|
42
|
-
if (fileContent.includes(alias)) {
|
|
43
|
-
ui.writeInformation(`Alias "${alias}" already exists in ${filePath}`);
|
|
44
|
-
} else {
|
|
45
|
-
missingAliases.push(alias);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (missingAliases.length === 0) {
|
|
50
|
-
ui.writeInformation(`The aliases are already present in ${filePath}`);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const aliasLines = missingAliases.join(EOL);
|
|
55
|
-
ui.writeInformation(`Adding alias "${aliasLines}" to ${filePath}...`);
|
|
56
|
-
|
|
57
|
-
// Append the alias to the file
|
|
58
|
-
fs.appendFileSync(filePath, `${EOL}${aliasLines}${EOL}`, "utf-8");
|
|
59
|
-
ui.writeInformation(`Alias added to ${filePath}`);
|
|
60
|
-
ui.writeInformation(
|
|
61
|
-
`Please restart your terminal for the changes to take effect.`
|
|
62
|
-
);
|
|
63
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { getAliases } from "./helpers.js";
|
|
4
|
-
import { ui } from "../environment/userInteraction.js";
|
|
5
|
-
import { EOL } from "os";
|
|
6
|
-
|
|
7
|
-
export function isRemoveAliasCommand(args) {
|
|
8
|
-
if (args[0] === "remove-aikido-aliases") {
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (args[0] === "remove-aikido-npm-alias") {
|
|
13
|
-
// not in the documenation anymore, but still here for backwards compatibility
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function removeAlias(args) {
|
|
21
|
-
if (!isRemoveAliasCommand(args)) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (args.length < 2) {
|
|
26
|
-
ui.writeError("Please specify the file to remove the alias from.");
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const filePath = path.resolve(args[1]);
|
|
31
|
-
const aliases = getAliases(filePath);
|
|
32
|
-
|
|
33
|
-
if (!fs.existsSync(filePath)) {
|
|
34
|
-
ui.writeError(`File ${filePath} does not exist.`);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
for (const alias of aliases) {
|
|
39
|
-
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
40
|
-
if (!fileContent.includes(alias)) {
|
|
41
|
-
ui.writeInformation(`Alias "${alias}" does not exist in ${filePath}`);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
ui.writeInformation(`Removing alias "${alias}" from ${filePath}...`);
|
|
46
|
-
|
|
47
|
-
// Remove the alias from the file.
|
|
48
|
-
// Make sure to remove the added newlines as well, but also make sure to remove aliases if they don't have newlines
|
|
49
|
-
const updatedContent = fileContent
|
|
50
|
-
.replaceAll(`${EOL}${alias}${EOL}`, "")
|
|
51
|
-
.replaceAll(`${alias}${EOL}`, "")
|
|
52
|
-
.replaceAll(`${EOL}${alias}`, "")
|
|
53
|
-
.replaceAll(alias, "");
|
|
54
|
-
|
|
55
|
-
fs.writeFileSync(filePath, updatedContent, "utf-8");
|
|
56
|
-
ui.writeInformation(`Alias "${alias}" removed from ${filePath}`);
|
|
57
|
-
}
|
|
58
|
-
ui.writeInformation(
|
|
59
|
-
`Please restart your terminal for the changes to take effect.`
|
|
60
|
-
);
|
|
61
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
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 { addAlias } from "./addAlias.js";
|
|
6
|
-
import { removeAlias } from "./removeAlias.js";
|
|
7
|
-
|
|
8
|
-
describe("Add alias", () => {
|
|
9
|
-
function runTestsForEnvironment(shell, startupExtension, aliases) {
|
|
10
|
-
it(`should add alias to ${shell} file`, () => {
|
|
11
|
-
const lines = [`#!/usr/bin/env ${shell}`, "", "alias cls='clear'"];
|
|
12
|
-
const filePath = createShellStartupScript(lines, startupExtension);
|
|
13
|
-
|
|
14
|
-
addAlias(["add-aikido-aliases", filePath]);
|
|
15
|
-
|
|
16
|
-
const fileContent = readAndDeleteFile(filePath);
|
|
17
|
-
for (const alias of aliases) {
|
|
18
|
-
assert.ok(fileContent.includes(alias));
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it(`should not add alias if it already exists in ${shell} file`, () => {
|
|
23
|
-
const lines = [`#!/usr/bin/env ${shell}`, "", ...aliases];
|
|
24
|
-
const filePath = createShellStartupScript(lines, startupExtension);
|
|
25
|
-
|
|
26
|
-
addAlias(["add-aikido-aliases", filePath]);
|
|
27
|
-
|
|
28
|
-
const fileContent = readAndDeleteFile(filePath);
|
|
29
|
-
assert.strictEqual(fileContent.length, lines.length);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it(`should create file and add alias if file does not exist`, () => {
|
|
33
|
-
const filePath = `${tmpdir()}/nonexistent-file${startupExtension}`;
|
|
34
|
-
if (fs.existsSync(filePath)) {
|
|
35
|
-
fs.rmSync(filePath, { force: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
addAlias(["add-aikido-aliases", filePath]);
|
|
39
|
-
|
|
40
|
-
assert.ok(fs.existsSync(filePath));
|
|
41
|
-
const fileContent = readAndDeleteFile(filePath);
|
|
42
|
-
for (const alias of aliases) {
|
|
43
|
-
assert.ok(fileContent.includes(alias));
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it(`should add the alias only once in ${shell} file`, () => {
|
|
48
|
-
const lines = [`#!/usr/bin/env ${shell}`, ""];
|
|
49
|
-
const filePath = createShellStartupScript(lines, startupExtension);
|
|
50
|
-
|
|
51
|
-
addAlias(["add-aikido-aliases", filePath]);
|
|
52
|
-
addAlias(["add-aikido-aliases", filePath]);
|
|
53
|
-
|
|
54
|
-
const fileContent = readAndDeleteFile(filePath);
|
|
55
|
-
for (const alias of aliases) {
|
|
56
|
-
assert.strictEqual(countOccurences(fileContent, alias), 1);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it(`should remove one alias from ${shell} file`, () => {
|
|
61
|
-
const alias = aliases[Math.floor(Math.random() * aliases.length)];
|
|
62
|
-
const lines = [`#!/usr/bin/env ${shell}`, "", alias];
|
|
63
|
-
const filePath = createShellStartupScript(lines, startupExtension);
|
|
64
|
-
|
|
65
|
-
removeAlias(["remove-aikido-npm-alias", filePath]);
|
|
66
|
-
|
|
67
|
-
const fileContent = readAndDeleteFile(filePath);
|
|
68
|
-
assert.ok(!fileContent.includes(alias));
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it(`should remove all aliases from ${shell} file`, () => {
|
|
72
|
-
const lines = [`#!/usr/bin/env ${shell}`, "", ...aliases, ""];
|
|
73
|
-
const filePath = createShellStartupScript(lines, startupExtension);
|
|
74
|
-
|
|
75
|
-
removeAlias(["remove-aikido-npm-alias", filePath]);
|
|
76
|
-
|
|
77
|
-
const fileContent = readAndDeleteFile(filePath);
|
|
78
|
-
for (const alias of aliases) {
|
|
79
|
-
assert.ok(!fileContent.includes(alias));
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it(`should not remove alias if it does not exist in ${shell} file`, () => {
|
|
84
|
-
const lines = [`#!/usr/bin/env ${shell}`, "", "alias cls='clear'"];
|
|
85
|
-
const filePath = createShellStartupScript(lines, startupExtension);
|
|
86
|
-
|
|
87
|
-
removeAlias(["remove-aikido-npm-alias", filePath]);
|
|
88
|
-
|
|
89
|
-
const fileContent = readAndDeleteFile(filePath);
|
|
90
|
-
assert.ok(fileContent.includes("alias cls='clear'"));
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it(`should not remove alias if file does not exist`, () => {
|
|
94
|
-
const filePath = `${tmpdir()}/nonexistent-file${startupExtension}`;
|
|
95
|
-
if (fs.existsSync(filePath)) {
|
|
96
|
-
fs.rmSync(filePath, { force: true });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
removeAlias(["remove-aikido-npm-alias", filePath]);
|
|
100
|
-
|
|
101
|
-
assert.ok(!fs.existsSync(filePath));
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it(`should remove aliases from ${shell} file if duplicated`, () => {
|
|
105
|
-
const lines = [
|
|
106
|
-
`#!/usr/bin/env ${shell}`,
|
|
107
|
-
"",
|
|
108
|
-
...aliases,
|
|
109
|
-
"cls='clear'",
|
|
110
|
-
...aliases,
|
|
111
|
-
...aliases,
|
|
112
|
-
"",
|
|
113
|
-
];
|
|
114
|
-
const filePath = createShellStartupScript(lines, startupExtension);
|
|
115
|
-
|
|
116
|
-
removeAlias(["remove-aikido-npm-alias", filePath]);
|
|
117
|
-
|
|
118
|
-
const fileContent = readAndDeleteFile(filePath);
|
|
119
|
-
for (const alias of aliases) {
|
|
120
|
-
assert.ok(!fileContent.includes(alias));
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
runTestsForEnvironment("bash", ".bashrc", [
|
|
126
|
-
"alias npm='aikido-npm'",
|
|
127
|
-
"alias npx='aikido-npx'",
|
|
128
|
-
"alias yarn='aikido-yarn'",
|
|
129
|
-
]);
|
|
130
|
-
runTestsForEnvironment("zsh", ".zshrc", [
|
|
131
|
-
"alias npm='aikido-npm'",
|
|
132
|
-
"alias npx='aikido-npx'",
|
|
133
|
-
"alias yarn='aikido-yarn'",
|
|
134
|
-
]);
|
|
135
|
-
runTestsForEnvironment("fish", ".fish", [
|
|
136
|
-
'alias npm "aikido-npm"',
|
|
137
|
-
'alias npx "aikido-npx"',
|
|
138
|
-
'alias yarn "aikido-yarn"',
|
|
139
|
-
]);
|
|
140
|
-
runTestsForEnvironment("pwsh", ".ps1", [
|
|
141
|
-
"Set-Alias npm aikido-npm",
|
|
142
|
-
"Set-Alias npx aikido-npx",
|
|
143
|
-
"Set-Alias yarn aikido-yarn",
|
|
144
|
-
]);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
function createShellStartupScript(lines, fileExtension) {
|
|
148
|
-
var randomFileName = Math.random().toString(36).substring(2, 15);
|
|
149
|
-
var filePath = `${tmpdir()}/${randomFileName}${fileExtension}`;
|
|
150
|
-
fs.writeFileSync(filePath, lines.join(EOL), "utf-8");
|
|
151
|
-
return filePath;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function readAndDeleteFile(filePath) {
|
|
155
|
-
var fileContent = fs.readFileSync(filePath, "utf-8");
|
|
156
|
-
fs.rm(filePath, { force: true }, (err) => {
|
|
157
|
-
if (err) {
|
|
158
|
-
console.error(`Error deleting file: ${err}`);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
return fileContent.split(EOL);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function countOccurences(lines, alias) {
|
|
165
|
-
let count = 0;
|
|
166
|
-
for (const line of lines) {
|
|
167
|
-
if (line.includes(alias)) {
|
|
168
|
-
count++;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return count;
|
|
172
|
-
}
|