@aikidosec/safe-chain 1.0.16 → 1.0.17

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/eslint.config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import js from "@eslint/js";
2
- import { defineConfig } from "@eslint/config-helpers";
2
+ import { defineConfig, globalIgnores } from "@eslint/config-helpers";
3
3
  import globals from "globals";
4
4
  import importPlugin from "eslint-plugin-import";
5
5
 
@@ -22,4 +22,5 @@ export default defineConfig([
22
22
  },
23
23
  rules: {},
24
24
  },
25
+ globalIgnores(['test/e2e']),
25
26
  ]);
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@aikidosec/safe-chain",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "scripts": {
5
- "test": "node --test --experimental-test-module-mocks **/*.spec.js",
6
- "test:watch": "node --test --watch --experimental-test-module-mocks **/*.spec.js",
5
+ "test": "node --test --experimental-test-module-mocks 'src/**/*.spec.js'",
6
+ "test:watch": "node --test --watch --experimental-test-module-mocks 'src/**/*.spec.js'",
7
7
  "lint": "eslint ."
8
8
  },
9
9
  "repository": {
@@ -42,5 +42,8 @@
42
42
  "bugs": {
43
43
  "url": "https://github.com/AikidoSec/safe-chain/issues"
44
44
  },
45
- "homepage": "https://github.com/AikidoSec/safe-chain#readme"
45
+ "homepage": "https://github.com/AikidoSec/safe-chain#readme",
46
+ "overrides": {
47
+ "brace-expansion@<=2.0.2": "2.0.2"
48
+ }
46
49
  }
@@ -2,6 +2,10 @@ import chalk from "chalk";
2
2
  import { ui } from "../environment/userInteraction.js";
3
3
  import { detectShells } from "./shellDetection.js";
4
4
  import { knownAikidoTools } from "./helpers.js";
5
+ import fs from "fs";
6
+ import os from "os";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
5
9
 
6
10
  /**
7
11
  * Loops over the detected shells and calls the setup function for each.
@@ -13,6 +17,8 @@ export async function setup() {
13
17
  );
14
18
  ui.emptyLine();
15
19
 
20
+ copyStartupFiles();
21
+
16
22
  try {
17
23
  const shells = detectShells();
18
24
  if (shells.length === 0) {
@@ -73,3 +79,22 @@ function setupShell(shell) {
73
79
 
74
80
  return success;
75
81
  }
82
+
83
+ function copyStartupFiles() {
84
+ const startupFiles = ["init-posix.sh", "init-pwsh.ps1", "init-fish.fish"];
85
+
86
+ for (const file of startupFiles) {
87
+ const targetDir = path.join(os.homedir(), ".safe-chain", "scripts");
88
+ const targetPath = path.join(os.homedir(), ".safe-chain", "scripts", file);
89
+
90
+ if (!fs.existsSync(targetDir)) {
91
+ fs.mkdirSync(targetDir, { recursive: true });
92
+ }
93
+
94
+ // Use absolute path for source
95
+ const __filename = fileURLToPath(import.meta.url);
96
+ const __dirname = path.dirname(__filename);
97
+ const sourcePath = path.resolve(__dirname, "startup-scripts", file);
98
+ fs.copyFileSync(sourcePath, targetPath);
99
+ }
100
+ }
@@ -0,0 +1,58 @@
1
+ function printSafeChainWarning
2
+ set original_cmd $argv[1]
3
+
4
+ # Fish equivalent of ANSI color codes: yellow background, black text for "Warning:"
5
+ set_color -b yellow black
6
+ printf "Warning:"
7
+ set_color normal
8
+ printf " safe-chain is not available to protect you from installing malware. %s will run without it.\n" $original_cmd
9
+
10
+ # Cyan text for the install command
11
+ printf "Install safe-chain by using "
12
+ set_color cyan
13
+ printf "npm install -g @aikidosec/safe-chain"
14
+ set_color normal
15
+ printf ".\n"
16
+ end
17
+
18
+ function wrapSafeChainCommand
19
+ set original_cmd $argv[1]
20
+ set aikido_cmd $argv[2]
21
+ set cmd_args $argv[3..-1]
22
+
23
+ if type -q $aikido_cmd
24
+ # If the aikido command is available, just run it with the provided arguments
25
+ $aikido_cmd $cmd_args
26
+ else
27
+ # If the aikido command is not available, print a warning and run the original command
28
+ printSafeChainWarning $original_cmd
29
+ command $original_cmd $cmd_args
30
+ end
31
+ end
32
+
33
+ function npx
34
+ wrapSafeChainCommand "npx" "aikido-npx" $argv
35
+ end
36
+
37
+ function yarn
38
+ wrapSafeChainCommand "yarn" "aikido-yarn" $argv
39
+ end
40
+
41
+ function pnpm
42
+ wrapSafeChainCommand "pnpm" "aikido-pnpm" $argv
43
+ end
44
+
45
+ function pnpx
46
+ wrapSafeChainCommand "pnpx" "aikido-pnpx" $argv
47
+ end
48
+
49
+ function npm
50
+ if test (count $argv) -eq 1 -a \( "$argv[1]" = "-v" -o "$argv[1]" = "--version" \)
51
+ # If args is just -v or --version and nothing else, just run the npm version command
52
+ # This is because nvm uses this to check the version of npm
53
+ command npm $argv
54
+ return
55
+ end
56
+
57
+ wrapSafeChainCommand "npm" "aikido-npm" $argv
58
+ end
@@ -0,0 +1,54 @@
1
+
2
+ function printSafeChainWarning() {
3
+ # \033[43;30m is used to set the background color to yellow and text color to black
4
+ # \033[0m is used to reset the text formatting
5
+ printf "\033[43;30mWarning:\033[0m safe-chain is not available to protect you from installing malware. %s will run without it.\n" "$1"
6
+ # \033[36m is used to set the text color to cyan
7
+ printf "Install safe-chain by using \033[36mnpm install -g @aikidosec/safe-chain\033[0m.\n"
8
+ }
9
+
10
+ function wrapSafeChainCommand() {
11
+ local original_cmd="$1"
12
+ local aikido_cmd="$2"
13
+
14
+ # Remove the first 2 arguments (original_cmd and aikido_cmd) from $@
15
+ # so that "$@" now contains only the arguments passed to the original command
16
+ shift 2
17
+
18
+ if command -v "$aikido_cmd" > /dev/null 2>&1; then
19
+ # If the aikido command is available, just run it with the provided arguments
20
+ "$aikido_cmd" "$@"
21
+ else
22
+ # If the aikido command is not available, print a warning and run the original command
23
+ printSafeChainWarning "$original_cmd"
24
+
25
+ command "$original_cmd" "$@"
26
+ fi
27
+ }
28
+
29
+ function npx() {
30
+ wrapSafeChainCommand "npx" "aikido-npx" "$@"
31
+ }
32
+
33
+ function yarn() {
34
+ wrapSafeChainCommand "yarn" "aikido-yarn" "$@"
35
+ }
36
+
37
+ function pnpm() {
38
+ wrapSafeChainCommand "pnpm" "aikido-pnpm" "$@"
39
+ }
40
+
41
+ function pnpx() {
42
+ wrapSafeChainCommand "pnpx" "aikido-pnpx" "$@"
43
+ }
44
+
45
+ function npm() {
46
+ if [[ "$1" == "-v" || "$1" == "--version" ]] && [[ $# -eq 1 ]]; then
47
+ # If args is just -v or --version and nothing else, just run the npm version command
48
+ # This is because nvm uses this to check the version of npm
49
+ command npm "$@"
50
+ return
51
+ fi
52
+
53
+ wrapSafeChainCommand "npm" "aikido-npm" "$@"
54
+ }
@@ -0,0 +1,80 @@
1
+ function Write-SafeChainWarning {
2
+ param([string]$Command)
3
+
4
+ # PowerShell equivalent of ANSI color codes: yellow background, black text for "Warning:"
5
+ Write-Host "Warning:" -BackgroundColor Yellow -ForegroundColor Black -NoNewline
6
+ Write-Host " safe-chain is not available to protect you from installing malware. $Command will run without it."
7
+
8
+ # Cyan text for the install command
9
+ Write-Host "Install safe-chain by using " -NoNewline
10
+ Write-Host "npm install -g @aikidosec/safe-chain" -ForegroundColor Cyan -NoNewline
11
+ Write-Host "."
12
+ }
13
+
14
+ function Test-CommandAvailable {
15
+ param([string]$Command)
16
+
17
+ try {
18
+ Get-Command $Command -ErrorAction Stop | Out-Null
19
+ return $true
20
+ }
21
+ catch {
22
+ return $false
23
+ }
24
+ }
25
+
26
+ function Invoke-RealCommand {
27
+ param(
28
+ [string]$Command,
29
+ [string[]]$Arguments
30
+ )
31
+
32
+ # Find the real executable to avoid calling our wrapped functions
33
+ $realCommand = Get-Command -Name $Command -CommandType Application | Select-Object -First 1
34
+ if ($realCommand) {
35
+ & $realCommand.Source @Arguments
36
+ }
37
+ }
38
+
39
+ function Invoke-WrappedCommand {
40
+ param(
41
+ [string]$OriginalCmd,
42
+ [string]$AikidoCmd,
43
+ [string[]]$Arguments
44
+ )
45
+
46
+ if (Test-CommandAvailable $AikidoCmd) {
47
+ & $AikidoCmd @Arguments
48
+ }
49
+ else {
50
+ Write-SafeChainWarning $OriginalCmd
51
+ Invoke-RealCommand $OriginalCmd $Arguments
52
+ }
53
+ }
54
+
55
+ function npx {
56
+ Invoke-WrappedCommand "npx" "aikido-npx" $args
57
+ }
58
+
59
+ function yarn {
60
+ Invoke-WrappedCommand "yarn" "aikido-yarn" $args
61
+ }
62
+
63
+ function pnpm {
64
+ Invoke-WrappedCommand "pnpm" "aikido-pnpm" $args
65
+ }
66
+
67
+ function pnpx {
68
+ Invoke-WrappedCommand "pnpx" "aikido-pnpx" $args
69
+ }
70
+
71
+ function npm {
72
+ # If args is just -v or --version and nothing else, just run the npm version command
73
+ # This is because nvm uses this to check the version of npm
74
+ if (($args.Length -eq 1) -and (($args[0] -eq "-v") -or ($args[0] -eq "--version"))) {
75
+ Invoke-RealCommand "npm" $args
76
+ return
77
+ }
78
+
79
+ Invoke-WrappedCommand "npm" "aikido-npm" $args
80
+ }
@@ -21,18 +21,22 @@ function teardown(tools) {
21
21
  removeLinesMatchingPattern(startupFile, new RegExp(`^alias\\s+${tool}=`));
22
22
  }
23
23
 
24
+ // Removes the line that sources the safe-chain bash initialization script (~/.aikido/scripts/init-posix.sh)
25
+ removeLinesMatchingPattern(
26
+ startupFile,
27
+ /^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh/
28
+ );
29
+
24
30
  return true;
25
31
  }
26
32
 
27
- function setup(tools) {
33
+ function setup() {
28
34
  const startupFile = getStartupFile();
29
35
 
30
- for (const { tool, aikidoCommand } of tools) {
31
- addLineToFile(
32
- startupFile,
33
- `alias ${tool}="${aikidoCommand}" # Safe-chain alias for ${tool}`
34
- );
35
- }
36
+ addLineToFile(
37
+ startupFile,
38
+ `source ~/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script`
39
+ );
36
40
 
37
41
  return true;
38
42
  }
@@ -66,38 +66,17 @@ describe("Bash shell integration", () => {
66
66
  });
67
67
 
68
68
  describe("setup", () => {
69
- it("should add aliases for all provided tools", () => {
70
- const tools = [
71
- { tool: "npm", aikidoCommand: "aikido-npm" },
72
- { tool: "npx", aikidoCommand: "aikido-npx" },
73
- { tool: "yarn", aikidoCommand: "aikido-yarn" },
74
- ];
75
-
76
- const result = bash.setup(tools);
69
+ it("should add source line for bash initialization script", () => {
70
+ const result = bash.setup();
77
71
  assert.strictEqual(result, true);
78
72
 
79
73
  const content = fs.readFileSync(mockStartupFile, "utf-8");
80
74
  assert.ok(
81
- content.includes('alias npm="aikido-npm" # Safe-chain alias for npm')
82
- );
83
- assert.ok(
84
- content.includes('alias npx="aikido-npx" # Safe-chain alias for npx')
85
- );
86
- assert.ok(
87
- content.includes('alias yarn="aikido-yarn" # Safe-chain alias for yarn')
75
+ content.includes(
76
+ "source ~/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script"
77
+ )
88
78
  );
89
79
  });
90
-
91
- it("should handle empty tools array", () => {
92
- const result = bash.setup([]);
93
- assert.strictEqual(result, true);
94
-
95
- // File should be created during teardown call even if no tools are provided
96
- if (fs.existsSync(mockStartupFile)) {
97
- const content = fs.readFileSync(mockStartupFile, "utf-8");
98
- assert.strictEqual(content.trim(), "");
99
- }
100
- });
101
80
  });
102
81
 
103
82
  describe("teardown", () => {
@@ -174,14 +153,14 @@ describe("Bash shell integration", () => {
174
153
  // Setup
175
154
  bash.setup(tools);
176
155
  let content = fs.readFileSync(mockStartupFile, "utf-8");
177
- assert.ok(content.includes('alias npm="aikido-npm"'));
178
- assert.ok(content.includes('alias yarn="aikido-yarn"'));
156
+ assert.ok(content.includes("source ~/.safe-chain/scripts/init-posix.sh"));
179
157
 
180
158
  // Teardown
181
159
  bash.teardown(tools);
182
160
  content = fs.readFileSync(mockStartupFile, "utf-8");
183
- assert.ok(!content.includes("alias npm="));
184
- assert.ok(!content.includes("alias yarn="));
161
+ assert.ok(
162
+ !content.includes("source ~/.safe-chain/scripts/init-posix.sh")
163
+ );
185
164
  });
186
165
 
187
166
  it("should handle multiple setup calls", () => {
@@ -192,8 +171,29 @@ describe("Bash shell integration", () => {
192
171
  bash.setup(tools);
193
172
 
194
173
  const content = fs.readFileSync(mockStartupFile, "utf-8");
195
- const npmMatches = (content.match(/alias npm="/g) || []).length;
196
- assert.strictEqual(npmMatches, 1, "Should not duplicate aliases");
174
+ const sourceMatches = (content.match(/source.*init-posix\.sh/g) || [])
175
+ .length;
176
+ assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines");
177
+ });
178
+
179
+ it("should handle mixed content with aliases and source lines", () => {
180
+ const initialContent = [
181
+ "#!/bin/bash",
182
+ "alias npm='old-npm'",
183
+ "source ~/.safe-chain/scripts/init-posix.sh",
184
+ "alias ls='ls --color=auto'",
185
+ ].join("\n");
186
+
187
+ fs.writeFileSync(mockStartupFile, initialContent, "utf-8");
188
+
189
+ // Teardown should remove both aliases and source line
190
+ bash.teardown(knownAikidoTools);
191
+ const content = fs.readFileSync(mockStartupFile, "utf-8");
192
+ assert.ok(!content.includes("alias npm="));
193
+ assert.ok(
194
+ !content.includes("source ~/.safe-chain/scripts/init-posix.sh")
195
+ );
196
+ assert.ok(content.includes("alias ls="));
197
197
  });
198
198
  });
199
199
  });
@@ -24,18 +24,22 @@ function teardown(tools) {
24
24
  );
25
25
  }
26
26
 
27
+ // Removes the line that sources the safe-chain fish initialization script (~/.safe-chain/scripts/init-fish.fish)
28
+ removeLinesMatchingPattern(
29
+ startupFile,
30
+ /^source\s+~\/\.safe-chain\/scripts\/init-fish\.fish/
31
+ );
32
+
27
33
  return true;
28
34
  }
29
35
 
30
- function setup(tools) {
36
+ function setup() {
31
37
  const startupFile = getStartupFile();
32
38
 
33
- for (const { tool, aikidoCommand } of tools) {
34
- addLineToFile(
35
- startupFile,
36
- `alias ${tool} "${aikidoCommand}" # Safe-chain alias for ${tool}`
37
- );
38
- }
39
+ addLineToFile(
40
+ startupFile,
41
+ `source ~/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script`
42
+ );
39
43
 
40
44
  return true;
41
45
  }
@@ -66,47 +66,34 @@ describe("Fish shell integration", () => {
66
66
  });
67
67
 
68
68
  describe("setup", () => {
69
- it("should add aliases for all provided tools", () => {
70
- const tools = [
71
- { tool: "npm", aikidoCommand: "aikido-npm" },
72
- { tool: "npx", aikidoCommand: "aikido-npx" },
73
- { tool: "yarn", aikidoCommand: "aikido-yarn" },
74
- ];
75
-
76
- const result = fish.setup(tools);
69
+ it("should add source line for safe-chain fish initialization script", () => {
70
+ const result = fish.setup();
77
71
  assert.strictEqual(result, true);
78
72
 
79
73
  const content = fs.readFileSync(mockStartupFile, "utf-8");
80
74
  assert.ok(
81
- content.includes('alias npm "aikido-npm" # Safe-chain alias for npm')
82
- );
83
- assert.ok(
84
- content.includes('alias npx "aikido-npx" # Safe-chain alias for npx')
85
- );
86
- assert.ok(
87
- content.includes('alias yarn "aikido-yarn" # Safe-chain alias for yarn')
75
+ content.includes('source ~/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script')
88
76
  );
89
77
  });
90
78
 
91
- it("should handle empty tools array", () => {
92
- const result = fish.setup([]);
93
- assert.strictEqual(result, true);
79
+ it("should not duplicate source lines on multiple calls", () => {
80
+ fish.setup();
81
+ fish.setup();
94
82
 
95
- // File should be created during teardown call even if no tools are provided
96
- if (fs.existsSync(mockStartupFile)) {
97
- const content = fs.readFileSync(mockStartupFile, "utf-8");
98
- assert.strictEqual(content.trim(), "");
99
- }
83
+ const content = fs.readFileSync(mockStartupFile, "utf-8");
84
+ const sourceMatches = (content.match(/source ~\/\.safe-chain\/scripts\/init-fish\.fish/g) || []).length;
85
+ assert.strictEqual(sourceMatches, 2, "Should allow multiple source lines (helper doesn't dedupe)");
100
86
  });
101
87
  });
102
88
 
103
89
  describe("teardown", () => {
104
- it("should remove npm, npx, and yarn aliases", () => {
90
+ it("should remove npm, npx, yarn aliases and source line", () => {
105
91
  const initialContent = [
106
92
  "#!/usr/bin/env fish",
107
93
  "alias npm 'aikido-npm'",
108
94
  "alias npx 'aikido-npx'",
109
95
  "alias yarn 'aikido-yarn'",
96
+ "source ~/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script",
110
97
  "alias ls 'ls --color=auto'",
111
98
  "alias grep 'grep --color=auto'",
112
99
  ].join("\n");
@@ -120,6 +107,7 @@ describe("Fish shell integration", () => {
120
107
  assert.ok(!content.includes("alias npm "));
121
108
  assert.ok(!content.includes("alias npx "));
122
109
  assert.ok(!content.includes("alias yarn "));
110
+ assert.ok(!content.includes("source ~/.safe-chain/scripts/init-fish.fish"));
123
111
  assert.ok(content.includes("alias ls "));
124
112
  assert.ok(content.includes("alias grep "));
125
113
  });
@@ -133,7 +121,7 @@ describe("Fish shell integration", () => {
133
121
  assert.strictEqual(result, true);
134
122
  });
135
123
 
136
- it("should handle file with no relevant aliases", () => {
124
+ it("should handle file with no relevant aliases or source lines", () => {
137
125
  const initialContent = [
138
126
  "#!/usr/bin/env fish",
139
127
  "alias ls 'ls --color=auto'",
@@ -172,28 +160,24 @@ describe("Fish shell integration", () => {
172
160
  ];
173
161
 
174
162
  // Setup
175
- fish.setup(tools);
163
+ fish.setup();
176
164
  let content = fs.readFileSync(mockStartupFile, "utf-8");
177
- assert.ok(content.includes('alias npm "aikido-npm"'));
178
- assert.ok(content.includes('alias yarn "aikido-yarn"'));
165
+ assert.ok(content.includes('source ~/.safe-chain/scripts/init-fish.fish'));
179
166
 
180
167
  // Teardown
181
168
  fish.teardown(tools);
182
169
  content = fs.readFileSync(mockStartupFile, "utf-8");
183
- assert.ok(!content.includes("alias npm "));
184
- assert.ok(!content.includes("alias yarn "));
170
+ assert.ok(!content.includes("source ~/.safe-chain/scripts/init-fish.fish"));
185
171
  });
186
172
 
187
173
  it("should handle multiple setup calls", () => {
188
- const tools = [{ tool: "npm", aikidoCommand: "aikido-npm" }];
189
-
190
- fish.setup(tools);
191
- fish.teardown(tools);
192
- fish.setup(tools);
174
+ fish.setup();
175
+ fish.teardown(knownAikidoTools);
176
+ fish.setup();
193
177
 
194
178
  const content = fs.readFileSync(mockStartupFile, "utf-8");
195
- const npmMatches = (content.match(/alias npm "/g) || []).length;
196
- assert.strictEqual(npmMatches, 1, "Should not duplicate aliases");
179
+ const sourceMatches = (content.match(/source ~\/\.safe-chain\/scripts\/init-fish\.fish/g) || []).length;
180
+ assert.strictEqual(sourceMatches, 1, "Should have exactly one source line after setup-teardown-setup cycle");
197
181
  });
198
182
  });
199
183
  });
@@ -24,18 +24,22 @@ function teardown(tools) {
24
24
  );
25
25
  }
26
26
 
27
+ // Remove the line that sources the safe-chain PowerShell initialization script
28
+ removeLinesMatchingPattern(
29
+ startupFile,
30
+ /^\.\s+["']?\$HOME[/\\].safe-chain[/\\]scripts[/\\]init-pwsh\.ps1["']?/
31
+ );
32
+
27
33
  return true;
28
34
  }
29
35
 
30
- function setup(tools) {
36
+ function setup() {
31
37
  const startupFile = getStartupFile();
32
38
 
33
- for (const { tool, aikidoCommand } of tools) {
34
- addLineToFile(
35
- startupFile,
36
- `Set-Alias ${tool} ${aikidoCommand} # Safe-chain alias for ${tool}`
37
- );
38
- }
39
+ addLineToFile(
40
+ startupFile,
41
+ `. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script`
42
+ );
39
43
 
40
44
  return true;
41
45
  }
@@ -69,49 +69,47 @@ describe("PowerShell Core shell integration", () => {
69
69
  });
70
70
 
71
71
  describe("setup", () => {
72
- it("should add aliases for all provided tools", () => {
73
- const tools = [
74
- { tool: "npm", aikidoCommand: "aikido-npm" },
75
- { tool: "npx", aikidoCommand: "aikido-npx" },
76
- { tool: "yarn", aikidoCommand: "aikido-yarn" },
77
- ];
78
-
79
- const result = powershell.setup(tools);
72
+ it("should add init-pwsh.ps1 source line", () => {
73
+ const result = powershell.setup();
80
74
  assert.strictEqual(result, true);
81
75
 
82
76
  const content = fs.readFileSync(mockStartupFile, "utf-8");
83
- assert.ok(
84
- content.includes("Set-Alias npm aikido-npm # Safe-chain alias for npm")
85
- );
86
- assert.ok(
87
- content.includes("Set-Alias npx aikido-npx # Safe-chain alias for npx")
88
- );
89
77
  assert.ok(
90
78
  content.includes(
91
- "Set-Alias yarn aikido-yarn # Safe-chain alias for yarn"
79
+ '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script'
92
80
  )
93
81
  );
94
82
  });
83
+ });
84
+
85
+ describe("teardown", () => {
86
+ it("should remove init-pwsh.ps1 source line", () => {
87
+ const initialContent = [
88
+ "# PowerShell profile",
89
+ '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script',
90
+ "Set-Alias ls Get-ChildItem",
91
+ "Set-Alias grep Select-String",
92
+ ].join("\n");
95
93
 
96
- it("should handle empty tools array", () => {
97
- const result = powershell.setup([]);
94
+ fs.writeFileSync(mockStartupFile, initialContent, "utf-8");
95
+
96
+ const result = powershell.teardown(knownAikidoTools);
98
97
  assert.strictEqual(result, true);
99
98
 
100
- // File should be created during teardown call even if no tools are provided
101
- if (fs.existsSync(mockStartupFile)) {
102
- const content = fs.readFileSync(mockStartupFile, "utf-8");
103
- assert.strictEqual(content.trim(), "");
104
- }
99
+ const content = fs.readFileSync(mockStartupFile, "utf-8");
100
+ assert.ok(
101
+ !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"')
102
+ );
103
+ assert.ok(content.includes("Set-Alias ls "));
104
+ assert.ok(content.includes("Set-Alias grep "));
105
105
  });
106
- });
107
106
 
108
- describe("teardown", () => {
109
- it("should remove npm, npx, and yarn aliases", () => {
107
+ it("should remove old-style aliases from earlier versions", () => {
110
108
  const initialContent = [
111
109
  "# PowerShell profile",
112
- "Set-Alias npm aikido-npm",
113
- "Set-Alias npx aikido-npx",
114
- "Set-Alias yarn aikido-yarn",
110
+ "Set-Alias npm aikido-npm # Safe-chain alias for npm",
111
+ "Set-Alias npx aikido-npx # Safe-chain alias for npx",
112
+ "Set-Alias yarn aikido-yarn # Safe-chain alias for yarn",
115
113
  "Set-Alias ls Get-ChildItem",
116
114
  "Set-Alias grep Select-String",
117
115
  ].join("\n");
@@ -138,7 +136,7 @@ describe("PowerShell Core shell integration", () => {
138
136
  assert.strictEqual(result, true);
139
137
  });
140
138
 
141
- it("should handle file with no relevant aliases", () => {
139
+ it("should handle file with no relevant content", () => {
142
140
  const initialContent = [
143
141
  "# PowerShell profile",
144
142
  "Set-Alias ls Get-ChildItem",
@@ -171,34 +169,32 @@ describe("PowerShell Core shell integration", () => {
171
169
 
172
170
  describe("integration tests", () => {
173
171
  it("should handle complete setup and teardown cycle", () => {
174
- const tools = [
175
- { tool: "npm", aikidoCommand: "aikido-npm" },
176
- { tool: "yarn", aikidoCommand: "aikido-yarn" },
177
- ];
178
-
179
172
  // Setup
180
- powershell.setup(tools);
173
+ powershell.setup();
181
174
  let content = fs.readFileSync(mockStartupFile, "utf-8");
182
- assert.ok(content.includes("Set-Alias npm aikido-npm"));
183
- assert.ok(content.includes("Set-Alias yarn aikido-yarn"));
175
+ assert.ok(
176
+ content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"')
177
+ );
184
178
 
185
179
  // Teardown
186
- powershell.teardown(tools);
180
+ powershell.teardown(knownAikidoTools);
187
181
  content = fs.readFileSync(mockStartupFile, "utf-8");
188
- assert.ok(!content.includes("Set-Alias npm "));
189
- assert.ok(!content.includes("Set-Alias yarn "));
182
+ assert.ok(
183
+ !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"')
184
+ );
190
185
  });
191
186
 
192
187
  it("should handle multiple setup calls", () => {
193
- const tools = [{ tool: "npm", aikidoCommand: "aikido-npm" }];
194
-
195
- powershell.setup(tools);
196
- powershell.teardown(tools);
197
- powershell.setup(tools);
188
+ powershell.setup();
189
+ powershell.teardown(knownAikidoTools);
190
+ powershell.setup();
198
191
 
199
192
  const content = fs.readFileSync(mockStartupFile, "utf-8");
200
- const npmMatches = (content.match(/Set-Alias npm /g) || []).length;
201
- assert.strictEqual(npmMatches, 1, "Should not duplicate aliases");
193
+ const sourceMatches = (
194
+ content.match(/\. "\$HOME\\.safe-chain\\scripts\\init-pwsh\.ps1"/g) ||
195
+ []
196
+ ).length;
197
+ assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines");
202
198
  });
203
199
  });
204
200
  });
@@ -24,18 +24,22 @@ function teardown(tools) {
24
24
  );
25
25
  }
26
26
 
27
+ // Remove the line that sources the safe-chain PowerShell initialization script
28
+ removeLinesMatchingPattern(
29
+ startupFile,
30
+ /^\.\s+["']?\$HOME[/\\].safe-chain[/\\]scripts[/\\]init-pwsh\.ps1["']?/
31
+ );
32
+
27
33
  return true;
28
34
  }
29
35
 
30
- function setup(tools) {
36
+ function setup() {
31
37
  const startupFile = getStartupFile();
32
38
 
33
- for (const { tool, aikidoCommand } of tools) {
34
- addLineToFile(
35
- startupFile,
36
- `Set-Alias ${tool} ${aikidoCommand} # Safe-chain alias for ${tool}`
37
- );
38
- }
39
+ addLineToFile(
40
+ startupFile,
41
+ `. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script`
42
+ );
39
43
 
40
44
  return true;
41
45
  }
@@ -69,49 +69,47 @@ describe("Windows PowerShell shell integration", () => {
69
69
  });
70
70
 
71
71
  describe("setup", () => {
72
- it("should add aliases for all provided tools", () => {
73
- const tools = [
74
- { tool: "npm", aikidoCommand: "aikido-npm" },
75
- { tool: "npx", aikidoCommand: "aikido-npx" },
76
- { tool: "yarn", aikidoCommand: "aikido-yarn" },
77
- ];
78
-
79
- const result = windowsPowershell.setup(tools);
72
+ it("should add init-pwsh.ps1 source line", () => {
73
+ const result = windowsPowershell.setup();
80
74
  assert.strictEqual(result, true);
81
75
 
82
76
  const content = fs.readFileSync(mockStartupFile, "utf-8");
83
- assert.ok(
84
- content.includes("Set-Alias npm aikido-npm # Safe-chain alias for npm")
85
- );
86
- assert.ok(
87
- content.includes("Set-Alias npx aikido-npx # Safe-chain alias for npx")
88
- );
89
77
  assert.ok(
90
78
  content.includes(
91
- "Set-Alias yarn aikido-yarn # Safe-chain alias for yarn"
79
+ '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script'
92
80
  )
93
81
  );
94
82
  });
83
+ });
84
+
85
+ describe("teardown", () => {
86
+ it("should remove init-pwsh.ps1 source line", () => {
87
+ const initialContent = [
88
+ "# Windows PowerShell profile",
89
+ '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script',
90
+ "Set-Alias ls Get-ChildItem",
91
+ "Set-Alias grep Select-String",
92
+ ].join("\n");
95
93
 
96
- it("should handle empty tools array", () => {
97
- const result = windowsPowershell.setup([]);
94
+ fs.writeFileSync(mockStartupFile, initialContent, "utf-8");
95
+
96
+ const result = windowsPowershell.teardown(knownAikidoTools);
98
97
  assert.strictEqual(result, true);
99
98
 
100
- // File should be created during teardown call even if no tools are provided
101
- if (fs.existsSync(mockStartupFile)) {
102
- const content = fs.readFileSync(mockStartupFile, "utf-8");
103
- assert.strictEqual(content.trim(), "");
104
- }
99
+ const content = fs.readFileSync(mockStartupFile, "utf-8");
100
+ assert.ok(
101
+ !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"')
102
+ );
103
+ assert.ok(content.includes("Set-Alias ls "));
104
+ assert.ok(content.includes("Set-Alias grep "));
105
105
  });
106
- });
107
106
 
108
- describe("teardown", () => {
109
- it("should remove npm, npx, and yarn aliases", () => {
107
+ it("should remove old-style aliases from earlier versions", () => {
110
108
  const initialContent = [
111
109
  "# Windows PowerShell profile",
112
- "Set-Alias npm aikido-npm",
113
- "Set-Alias npx aikido-npx",
114
- "Set-Alias yarn aikido-yarn",
110
+ "Set-Alias npm aikido-npm # Safe-chain alias for npm",
111
+ "Set-Alias npx aikido-npx # Safe-chain alias for npx",
112
+ "Set-Alias yarn aikido-yarn # Safe-chain alias for yarn",
115
113
  "Set-Alias ls Get-ChildItem",
116
114
  "Set-Alias grep Select-String",
117
115
  ].join("\n");
@@ -138,7 +136,7 @@ describe("Windows PowerShell shell integration", () => {
138
136
  assert.strictEqual(result, true);
139
137
  });
140
138
 
141
- it("should handle file with no relevant aliases", () => {
139
+ it("should handle file with no relevant content", () => {
142
140
  const initialContent = [
143
141
  "# Windows PowerShell profile",
144
142
  "Set-Alias ls Get-ChildItem",
@@ -171,34 +169,32 @@ describe("Windows PowerShell shell integration", () => {
171
169
 
172
170
  describe("integration tests", () => {
173
171
  it("should handle complete setup and teardown cycle", () => {
174
- const tools = [
175
- { tool: "npm", aikidoCommand: "aikido-npm" },
176
- { tool: "yarn", aikidoCommand: "aikido-yarn" },
177
- ];
178
-
179
172
  // Setup
180
- windowsPowershell.setup(tools);
173
+ windowsPowershell.setup();
181
174
  let content = fs.readFileSync(mockStartupFile, "utf-8");
182
- assert.ok(content.includes("Set-Alias npm aikido-npm"));
183
- assert.ok(content.includes("Set-Alias yarn aikido-yarn"));
175
+ assert.ok(
176
+ content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"')
177
+ );
184
178
 
185
179
  // Teardown
186
- windowsPowershell.teardown(tools);
180
+ windowsPowershell.teardown(knownAikidoTools);
187
181
  content = fs.readFileSync(mockStartupFile, "utf-8");
188
- assert.ok(!content.includes("Set-Alias npm "));
189
- assert.ok(!content.includes("Set-Alias yarn "));
182
+ assert.ok(
183
+ !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"')
184
+ );
190
185
  });
191
186
 
192
187
  it("should handle multiple setup calls", () => {
193
- const tools = [{ tool: "npm", aikidoCommand: "aikido-npm" }];
194
-
195
- windowsPowershell.setup(tools);
196
- windowsPowershell.teardown(tools);
197
- windowsPowershell.setup(tools);
188
+ windowsPowershell.setup();
189
+ windowsPowershell.teardown(knownAikidoTools);
190
+ windowsPowershell.setup();
198
191
 
199
192
  const content = fs.readFileSync(mockStartupFile, "utf-8");
200
- const npmMatches = (content.match(/Set-Alias npm /g) || []).length;
201
- assert.strictEqual(npmMatches, 1, "Should not duplicate aliases");
193
+ const sourceMatches = (
194
+ content.match(/\. "\$HOME\\.safe-chain\\scripts\\init-pwsh\.ps1"/g) ||
195
+ []
196
+ ).length;
197
+ assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines");
202
198
  });
203
199
  });
204
200
  });
@@ -21,18 +21,22 @@ function teardown(tools) {
21
21
  removeLinesMatchingPattern(startupFile, new RegExp(`^alias\\s+${tool}=`));
22
22
  }
23
23
 
24
+ // Removes the line that sources the safe-chain zsh initialization script (~/.aikido/scripts/init-posix.sh)
25
+ removeLinesMatchingPattern(
26
+ startupFile,
27
+ /^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh/
28
+ );
29
+
24
30
  return true;
25
31
  }
26
32
 
27
- function setup(tools) {
33
+ function setup() {
28
34
  const startupFile = getStartupFile();
29
35
 
30
- for (const { tool, aikidoCommand } of tools) {
31
- addLineToFile(
32
- startupFile,
33
- `alias ${tool}="${aikidoCommand}" # Safe-chain alias for ${tool}`
34
- );
35
- }
36
+ addLineToFile(
37
+ startupFile,
38
+ `source ~/.safe-chain/scripts/init-posix.sh # Safe-chain Zsh initialization script`
39
+ );
36
40
 
37
41
  return true;
38
42
  }
@@ -66,37 +66,24 @@ describe("Zsh shell integration", () => {
66
66
  });
67
67
 
68
68
  describe("setup", () => {
69
- it("should add aliases for all provided tools", () => {
70
- const tools = [
71
- { tool: "npm", aikidoCommand: "aikido-npm" },
72
- { tool: "npx", aikidoCommand: "aikido-npx" },
73
- { tool: "yarn", aikidoCommand: "aikido-yarn" },
74
- ];
75
-
76
- const result = zsh.setup(tools);
69
+ it("should add source line for zsh initialization script", () => {
70
+ const result = zsh.setup();
77
71
  assert.strictEqual(result, true);
78
72
 
79
73
  const content = fs.readFileSync(mockStartupFile, "utf-8");
80
74
  assert.ok(
81
- content.includes('alias npm="aikido-npm" # Safe-chain alias for npm')
82
- );
83
- assert.ok(
84
- content.includes('alias npx="aikido-npx" # Safe-chain alias for npx')
85
- );
86
- assert.ok(
87
- content.includes('alias yarn="aikido-yarn" # Safe-chain alias for yarn')
75
+ content.includes(
76
+ "source ~/.safe-chain/scripts/init-posix.sh # Safe-chain Zsh initialization script"
77
+ )
88
78
  );
89
79
  });
90
80
 
91
- it("should handle empty tools array", () => {
92
- const result = zsh.setup([]);
81
+ it("should handle empty startup file", () => {
82
+ const result = zsh.setup();
93
83
  assert.strictEqual(result, true);
94
84
 
95
- // File should be created during teardown call even if no tools are provided
96
- if (fs.existsSync(mockStartupFile)) {
97
- const content = fs.readFileSync(mockStartupFile, "utf-8");
98
- assert.strictEqual(content.trim(), "");
99
- }
85
+ const content = fs.readFileSync(mockStartupFile, "utf-8");
86
+ assert.ok(content.includes("source ~/.safe-chain/scripts/init-posix.sh"));
100
87
  });
101
88
  });
102
89
 
@@ -124,6 +111,25 @@ describe("Zsh shell integration", () => {
124
111
  assert.ok(content.includes("alias grep="));
125
112
  });
126
113
 
114
+ it("should remove zsh initialization script source line", () => {
115
+ const initialContent = [
116
+ "#!/bin/zsh",
117
+ "source ~/.safe-chain/scripts/init-posix.sh",
118
+ "alias ls='ls --color=auto'",
119
+ ].join("\n");
120
+
121
+ fs.writeFileSync(mockStartupFile, initialContent, "utf-8");
122
+
123
+ const result = zsh.teardown(knownAikidoTools);
124
+ assert.strictEqual(result, true);
125
+
126
+ const content = fs.readFileSync(mockStartupFile, "utf-8");
127
+ assert.ok(
128
+ !content.includes("source ~/.safe-chain/scripts/init-posix.sh")
129
+ );
130
+ assert.ok(content.includes("alias ls="));
131
+ });
132
+
127
133
  it("should handle file that doesn't exist", () => {
128
134
  if (fs.existsSync(mockStartupFile)) {
129
135
  fs.unlinkSync(mockStartupFile);
@@ -133,7 +139,7 @@ describe("Zsh shell integration", () => {
133
139
  assert.strictEqual(result, true);
134
140
  });
135
141
 
136
- it("should handle file with no relevant aliases", () => {
142
+ it("should handle file with no relevant aliases or source lines", () => {
137
143
  const initialContent = [
138
144
  "#!/bin/zsh",
139
145
  "alias ls='ls --color=auto'",
@@ -172,16 +178,16 @@ describe("Zsh shell integration", () => {
172
178
  ];
173
179
 
174
180
  // Setup
175
- zsh.setup(tools);
181
+ zsh.setup();
176
182
  let content = fs.readFileSync(mockStartupFile, "utf-8");
177
- assert.ok(content.includes('alias npm="aikido-npm"'));
178
- assert.ok(content.includes('alias yarn="aikido-yarn"'));
183
+ assert.ok(content.includes("source ~/.safe-chain/scripts/init-posix.sh"));
179
184
 
180
185
  // Teardown
181
186
  zsh.teardown(tools);
182
187
  content = fs.readFileSync(mockStartupFile, "utf-8");
183
- assert.ok(!content.includes("alias npm="));
184
- assert.ok(!content.includes("alias yarn="));
188
+ assert.ok(
189
+ !content.includes("source ~/.safe-chain/scripts/init-posix.sh")
190
+ );
185
191
  });
186
192
 
187
193
  it("should handle multiple setup calls", () => {
@@ -192,8 +198,29 @@ describe("Zsh shell integration", () => {
192
198
  zsh.setup(tools);
193
199
 
194
200
  const content = fs.readFileSync(mockStartupFile, "utf-8");
195
- const npmMatches = (content.match(/alias npm="/g) || []).length;
196
- assert.strictEqual(npmMatches, 1, "Should not duplicate aliases");
201
+ const sourceMatches = (content.match(/source.*init-posix\.sh/g) || [])
202
+ .length;
203
+ assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines");
204
+ });
205
+
206
+ it("should handle mixed content with aliases and source lines", () => {
207
+ const initialContent = [
208
+ "#!/bin/zsh",
209
+ "alias npm='old-npm'",
210
+ "source ~/.safe-chain/scripts/init-posix.sh",
211
+ "alias ls='ls --color=auto'",
212
+ ].join("\n");
213
+
214
+ fs.writeFileSync(mockStartupFile, initialContent, "utf-8");
215
+
216
+ // Teardown should remove both aliases and source line
217
+ zsh.teardown(knownAikidoTools);
218
+ const content = fs.readFileSync(mockStartupFile, "utf-8");
219
+ assert.ok(!content.includes("alias npm="));
220
+ assert.ok(
221
+ !content.includes("source ~/.safe-chain/scripts/init-posix.sh")
222
+ );
223
+ assert.ok(content.includes("alias ls="));
197
224
  });
198
225
  });
199
226
  });
@@ -1,41 +0,0 @@
1
- name: Create Release
2
-
3
- on:
4
- push:
5
- tags:
6
- - "*"
7
-
8
- jobs:
9
- build:
10
- runs-on: ubuntu-latest
11
-
12
- steps:
13
- - name: Checkout code
14
- uses: actions/checkout@v3
15
-
16
- - name: Set up Node.js
17
- uses: actions/setup-node@v3
18
- with:
19
- node-version: "lts/*"
20
- registry-url: "https://registry.npmjs.org/"
21
- env:
22
- NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
23
-
24
- - name: Set version number
25
- id: get_version
26
- run: |
27
- version="${{ github.ref_name }}"
28
- echo "tag=$version" >> $GITHUB_OUTPUT
29
-
30
- - name: Set the version
31
- run: npm --no-git-tag-version version ${{ steps.get_version.outputs.tag }}
32
-
33
- - name: Install dependencies
34
- run: npm ci
35
-
36
- - name: Publish to npm
37
- run: |
38
- echo "Publishing version ${{ steps.get_version.outputs.tag }} to NPM"
39
- npm publish --access public
40
- env:
41
- NPM_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
@@ -1,28 +0,0 @@
1
- name: Run Unit Tests
2
-
3
- on:
4
- pull_request:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- test:
10
- runs-on: ubuntu-latest
11
-
12
- steps:
13
- - name: Checkout code
14
- uses: actions/checkout@v3
15
-
16
- - name: Set up Node.js
17
- uses: actions/setup-node@v3
18
- with:
19
- node-version: "lts/*"
20
-
21
- - name: Install dependencies
22
- run: npm ci
23
-
24
- - name: Run tests
25
- run: npm test
26
-
27
- - name: Run ESLint
28
- run: npm run lint