Skip to content

Commit e27ba64

Browse files
committed
Allow calling nodeswap use to make use of .nodeswap file.
1 parent bbd54be commit e27ba64

File tree

3 files changed

+147
-18
lines changed

3 files changed

+147
-18
lines changed

NodeSwap.Tests/Commands/UseCommandTests.cs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,72 @@ public void Cleanup()
5050
}
5151

5252
[TestMethod]
53-
public void Run_WhenVersionIsNull_ShouldReturnError()
53+
public void Run_WhenVersionIsNull_AndNoNodeSwapFile_ShouldReturnError()
5454
{
55+
_mockFileSystem.FileExists(Arg.Any<string>()).Returns(false);
56+
57+
var useCommand = new UseCommand(_globalContext, _mockNodeJs, _mockProcessElevation, _mockConsoleWriter, _mockFileSystem)
58+
{
59+
Version = null,
60+
};
61+
62+
var result = useCommand.Run();
63+
64+
result.ShouldBe(1);
65+
_mockConsoleWriter.Received(1).WriteErrorLine("Missing version argument. Either provide a version or create a .nodeswap file.");
66+
}
67+
68+
[TestMethod]
69+
public void Run_WhenVersionIsNull_AndNodeSwapFileExists_ShouldUseVersionFromFile()
70+
{
71+
var version = new Version(18, 17, 0);
72+
var installedVersions = new List<NodeJsVersion>
73+
{
74+
new() { Version = version, Path = $"/fake/path/node-v{version}", IsActive = false },
75+
};
76+
77+
_mockFileSystem.FileExists(Arg.Is<string>(path => path.EndsWith(".nodeswap"))).Returns(true);
78+
_mockFileSystem.ReadAllText(Arg.Is<string>(path => path.EndsWith(".nodeswap"))).Returns("18.17.0");
79+
_mockNodeJs.GetInstalledVersions().Returns(installedVersions);
80+
_mockProcessElevation.IsAdministrator().Returns(true);
81+
_mockFileSystem.CreateSymbolicLink(Arg.Any<string>(), Arg.Any<string>(), true).Returns(true);
82+
83+
var useCommand = new UseCommand(_globalContext, _mockNodeJs, _mockProcessElevation, _mockConsoleWriter, _mockFileSystem)
84+
{
85+
Version = null,
86+
};
87+
88+
var result = useCommand.Run();
89+
90+
result.ShouldBe(0);
91+
_mockConsoleWriter.Received(1).WriteLine("Using Node.js version from .nodeswap: 18.17.0");
92+
_mockConsoleWriter.Received(1).WriteLine("Done");
93+
}
94+
95+
[TestMethod]
96+
public void Run_WhenVersionIsNull_AndNodeSwapFileIsEmpty_ShouldReturnError()
97+
{
98+
_mockFileSystem.FileExists(Arg.Is<string>(path => path.EndsWith(".nodeswap"))).Returns(true);
99+
_mockFileSystem.ReadAllText(Arg.Is<string>(path => path.EndsWith(".nodeswap"))).Returns(" ");
100+
101+
var useCommand = new UseCommand(_globalContext, _mockNodeJs, _mockProcessElevation, _mockConsoleWriter, _mockFileSystem)
102+
{
103+
Version = null,
104+
};
105+
106+
var result = useCommand.Run();
107+
108+
result.ShouldBe(1);
109+
_mockConsoleWriter.Received(1).WriteErrorLine("The .nodeswap file is empty");
110+
}
111+
112+
[TestMethod]
113+
public void Run_WhenVersionIsNull_AndNodeSwapFileReadFails_ShouldReturnError()
114+
{
115+
_mockFileSystem.FileExists(Arg.Is<string>(path => path.EndsWith(".nodeswap"))).Returns(true);
116+
_mockFileSystem.When(x => x.ReadAllText(Arg.Is<string>(path => path.EndsWith(".nodeswap"))))
117+
.Do(x => throw new IOException("File access denied"));
118+
55119
var useCommand = new UseCommand(_globalContext, _mockNodeJs, _mockProcessElevation, _mockConsoleWriter, _mockFileSystem)
56120
{
57121
Version = null,
@@ -60,7 +124,7 @@ public void Run_WhenVersionIsNull_ShouldReturnError()
60124
var result = useCommand.Run();
61125

62126
result.ShouldBe(1);
63-
_mockConsoleWriter.Received(1).WriteErrorLine("Missing version argument");
127+
_mockConsoleWriter.Received(1).WriteErrorLine("Error reading .nodeswap: File access denied");
64128
}
65129

66130
[TestMethod]
@@ -141,6 +205,7 @@ public void Run_WhenAdministratorAndVersionInstalled_ShouldSwitchSuccessfully()
141205
new() { Version = version, Path = $"/fake/path/node-v{version}", IsActive = false },
142206
};
143207
_mockNodeJs.GetInstalledVersions().Returns(installedVersions);
208+
_mockNodeJs.GetActiveVersion().Returns((Version)null); // No active version
144209
_mockProcessElevation.IsAdministrator().Returns(true);
145210
_mockFileSystem.DirectoryExists(_globalContext.SymlinkPath).Returns(false);
146211
_mockFileSystem.CreateSymbolicLink(_globalContext.SymlinkPath, $"/fake/path/node-v{version}", true).Returns(true);
@@ -167,6 +232,7 @@ public void Run_WhenSymlinkCreationFails_ShouldReturnError()
167232
new() { Version = version, Path = $"/fake/path/node-v{version}", IsActive = false },
168233
};
169234
_mockNodeJs.GetInstalledVersions().Returns(installedVersions);
235+
_mockNodeJs.GetActiveVersion().Returns((Version)null); // No active version
170236
_mockProcessElevation.IsAdministrator().Returns(true);
171237
_mockFileSystem.DirectoryExists(_globalContext.SymlinkPath).Returns(false);
172238
_mockFileSystem.CreateSymbolicLink(_globalContext.SymlinkPath, $"/fake/path/node-v{version}", true).Returns(false);
@@ -214,6 +280,7 @@ public void Run_WhenExistingSymlinkExists_ShouldDeleteFirst()
214280
new() { Version = version, Path = $"/fake/path/node-v{version}", IsActive = false },
215281
};
216282
_mockNodeJs.GetInstalledVersions().Returns(installedVersions);
283+
_mockNodeJs.GetActiveVersion().Returns((Version)null); // No active version
217284
_mockProcessElevation.IsAdministrator().Returns(true);
218285
_mockFileSystem.DirectoryExists(_globalContext.SymlinkPath).Returns(true);
219286
_mockFileSystem.CreateSymbolicLink(_globalContext.SymlinkPath, $"/fake/path/node-v{version}", true).Returns(true);
@@ -229,4 +296,33 @@ public void Run_WhenExistingSymlinkExists_ShouldDeleteFirst()
229296
_mockFileSystem.Received(1).DeleteDirectory(_globalContext.SymlinkPath, true);
230297
_mockFileSystem.Received(1).CreateSymbolicLink(_globalContext.SymlinkPath, $"/fake/path/node-v{version}", true);
231298
}
299+
300+
[TestMethod]
301+
public void Run_WhenAlreadyUsingRequestedVersion_ShouldReturnEarlyWithoutChanges()
302+
{
303+
var version = new Version(18, 17, 0);
304+
var installedVersions = new List<NodeJsVersion>
305+
{
306+
new() { Version = version, Path = $"/fake/path/node-v{version}", IsActive = false },
307+
};
308+
_mockNodeJs.GetInstalledVersions().Returns(installedVersions);
309+
_mockNodeJs.GetActiveVersion().Returns(version); // Already using this version
310+
_mockProcessElevation.IsAdministrator().Returns(true);
311+
312+
var useCommand = new UseCommand(_globalContext, _mockNodeJs, _mockProcessElevation, _mockConsoleWriter, _mockFileSystem)
313+
{
314+
Version = version.ToString(),
315+
};
316+
317+
var result = useCommand.Run();
318+
319+
result.ShouldBe(0);
320+
_mockConsoleWriter.Received(1).WriteLine($"Already using Node.js version {version}");
321+
322+
// Should not perform any file operations
323+
_mockFileSystem.DidNotReceive().WriteAllText(_globalContext.PreviousVersionTrackerFilePath, Arg.Any<string>());
324+
_mockFileSystem.DidNotReceive().DeleteDirectory(Arg.Any<string>(), Arg.Any<bool>());
325+
_mockFileSystem.DidNotReceive().CreateSymbolicLink(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<bool>());
326+
_mockFileSystem.DidNotReceive().WriteAllText(_globalContext.ActiveVersionTrackerFilePath, Arg.Any<string>());
327+
}
232328
}

NodeSwap/Commands/UseCommand.cs

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.IO;
23
using DotMake.CommandLine;
34
using NodeSwap.Interfaces;
45

@@ -9,21 +10,20 @@ namespace NodeSwap.Commands;
910
Parent = typeof(RootCommand)
1011
)]
1112
public class UseCommand(
12-
GlobalContext globalContext,
13-
INodeJs nodeLocal,
13+
GlobalContext globalContext,
14+
INodeJs nodeLocal,
1415
IProcessElevation processElevation,
1516
IConsoleWriter console,
1617
IFileSystem fileSystem)
1718
{
18-
[CliArgument(Description = "`latest` or specific e.g. `22.6.0`. Run `list` command to see installed versions.")]
19+
[CliArgument(
20+
Description = "`latest` or specific e.g. `22.6.0`. Run `list` command to see installed versions.",
21+
Required = false
22+
)]
1923
public string Version { get; set; }
2024

2125
public int Run()
2226
{
23-
// Validate input
24-
var validationResult = ValidateInput();
25-
if (validationResult != null) return validationResult.Value;
26-
2727
// Find the version to use
2828
var nodeVersion = ResolveNodeVersion();
2929
if (nodeVersion == null) return 1;
@@ -38,18 +38,42 @@ public int Run()
3838
return SwitchToVersion(nodeVersion);
3939
}
4040

41-
private int? ValidateInput()
41+
private NodeJsVersion ResolveNodeVersion()
4242
{
43+
// If no version specified, try to read from .nodeswap file
4344
if (Version == null)
4445
{
45-
console.WriteErrorLine("Missing version argument");
46-
return 1;
46+
var currentDirectory = Directory.GetCurrentDirectory();
47+
var nodeSwapFilePath = Path.Combine(currentDirectory, ".nodeswap");
48+
49+
if (fileSystem.FileExists(nodeSwapFilePath))
50+
{
51+
try
52+
{
53+
var versionText = fileSystem.ReadAllText(nodeSwapFilePath).Trim();
54+
if (string.IsNullOrWhiteSpace(versionText))
55+
{
56+
console.WriteErrorLine("The .nodeswap file is empty");
57+
return null;
58+
}
59+
60+
console.WriteLine($"Using Node.js version from .nodeswap: {versionText}");
61+
Version = versionText;
62+
}
63+
catch (Exception ex)
64+
{
65+
console.WriteErrorLine($"Error reading .nodeswap: {ex.Message}");
66+
return null;
67+
}
68+
}
69+
else
70+
{
71+
console.WriteErrorLine(
72+
"Missing version argument. Either provide a version or create a .nodeswap file.");
73+
return null;
74+
}
4775
}
48-
return null;
49-
}
5076

51-
private NodeJsVersion ResolveNodeVersion()
52-
{
5377
if (Version == "latest")
5478
{
5579
var latestVersion = nodeLocal.GetLatestInstalledVersion();
@@ -58,6 +82,7 @@ private NodeJsVersion ResolveNodeVersion()
5882
console.WriteErrorLine("There are no versions installed");
5983
return null;
6084
}
85+
6186
return latestVersion;
6287
}
6388

@@ -70,6 +95,7 @@ private NodeJsVersion ResolveNodeVersion()
7095
console.WriteErrorLine($"{version} not installed");
7196
return null;
7297
}
98+
7399
return nodeVersion;
74100
}
75101
catch (ArgumentException)
@@ -81,8 +107,15 @@ private NodeJsVersion ResolveNodeVersion()
81107

82108
private int SwitchToVersion(NodeJsVersion nodeVersion)
83109
{
84-
// Track the previous version
110+
// Check if we're already using this version
85111
var activeVersion = nodeLocal.GetActiveVersion();
112+
if (activeVersion != null && activeVersion.Equals(nodeVersion.Version))
113+
{
114+
console.WriteLine($"Already using Node.js version {nodeVersion.Version}");
115+
return 0;
116+
}
117+
118+
// Track the previous version only if switching to a new version
86119
if (activeVersion != null)
87120
{
88121
fileSystem.WriteAllText(globalContext.PreviousVersionTrackerFilePath, activeVersion.ToString());

NodeSwap/NodeSwap.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<OutputType>Exe</OutputType>
66
<TargetFramework>net8.0-windows</TargetFramework>
77
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
8-
<Version>1.6.2</Version>
8+
<Version>1.6.3</Version>
99
<!-- Single file app – https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file -->
1010
<PublishSingleFile>true</PublishSingleFile>
1111
<RuntimeIdentifier>win-x64</RuntimeIdentifier>

0 commit comments

Comments
 (0)