Skip to content

Commit ebfd5be

Browse files
Copilotaaronpowell
andauthored
Fix npm package installation to check appropriate files based on command (#711)
* Initial plan for issue * Initial analysis and plan for npm package installation fix Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Fix npm package installation to check appropriate files based on command Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Clean up temporary files and restore original target frameworks Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Fixing compiler errors --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> Co-authored-by: Aaron Powell <me@aaron-powell.com>
1 parent 1b7bbb0 commit ebfd5be

File tree

3 files changed

+87
-4
lines changed

3 files changed

+87
-4
lines changed

src/CommunityToolkit.Aspire.Hosting.NodeJS.Extensions/NodePackageInstaller.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,29 @@ private async Task PerformInstall(NodeAppResource resource, CancellationToken ca
4646
{
4747
var logger = loggerService.GetLogger(resource);
4848

49-
var packageJsonPath = Path.Combine(resource.WorkingDirectory, lockfile);
49+
// For 'ci' command, we need the lockfile. For 'install' command, package.json is sufficient
50+
string requiredFile;
51+
string requiredFilePath;
52+
53+
if (installCommand == "ci")
54+
{
55+
requiredFile = lockfile;
56+
requiredFilePath = Path.Combine(resource.WorkingDirectory, lockfile);
57+
}
58+
else
59+
{
60+
requiredFile = "package.json";
61+
requiredFilePath = Path.Combine(resource.WorkingDirectory, "package.json");
62+
}
5063

51-
if (!File.Exists(packageJsonPath))
64+
if (!File.Exists(requiredFilePath))
5265
{
5366
await notificationService.PublishUpdateAsync(resource, state => state with
5467
{
55-
State = new($"No {lockfile} file found in {resource.WorkingDirectory}", KnownResourceStates.FailedToStart)
68+
State = new($"No {requiredFile} file found in {resource.WorkingDirectory}", KnownResourceStates.FailedToStart)
5669
}).ConfigureAwait(false);
5770

58-
throw new InvalidOperationException($"No {lockfile} file found in {resource.WorkingDirectory}");
71+
throw new InvalidOperationException($"No {requiredFile} file found in {resource.WorkingDirectory}");
5972
}
6073

6174
await notificationService.PublishUpdateAsync(resource, state => state with
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Aspire.Hosting;
2+
3+
namespace CommunityToolkit.Aspire.Hosting.NodeJS.Extensions.Tests;
4+
5+
public class PackageInstallationTests
6+
{
7+
/// <summary>
8+
/// This test validates that the WithNpmPackageInstallation method can be called
9+
/// and properly registers the lifecycle hook for npm install operations.
10+
/// The issue in #618 would cause failures when no package-lock.json exists
11+
/// but package.json does exist when using npm install (not ci).
12+
/// </summary>
13+
[Fact]
14+
public void WithNpmPackageInstallation_CanBeConfiguredWithInstallAndCIOptions()
15+
{
16+
var builder = DistributedApplication.CreateBuilder();
17+
18+
var nodeApp = builder.AddNpmApp("test-app", "./test-app");
19+
20+
// Test that both configurations can be set up without errors
21+
nodeApp.WithNpmPackageInstallation(useCI: false); // Uses npm install
22+
23+
var nodeApp2 = builder.AddNpmApp("test-app-ci", "./test-app-ci");
24+
nodeApp2.WithNpmPackageInstallation(useCI: true); // Uses npm ci
25+
26+
using var app = builder.Build();
27+
28+
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
29+
var resources = appModel.Resources.OfType<NodeAppResource>().ToList();
30+
31+
Assert.Equal(2, resources.Count);
32+
Assert.All(resources, resource => Assert.Equal("npm", resource.Command));
33+
}
34+
}

tests/CommunityToolkit.Aspire.Hosting.NodeJS.Extensions.Tests/ResourceCreationTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,40 @@ public void ViteAppHasExposedExternalHttpEndpoints()
159159

160160
Assert.Contains(endpoints, e => e.IsExternal);
161161
}
162+
163+
[Fact]
164+
public void WithNpmPackageInstallationDefaultsToInstallCommand()
165+
{
166+
var builder = DistributedApplication.CreateBuilder();
167+
168+
var nodeApp = builder.AddNpmApp("test-app", "./test-app");
169+
170+
// Add package installation with default settings (should use npm install, not ci)
171+
nodeApp.WithNpmPackageInstallation(useCI: false);
172+
173+
using var app = builder.Build();
174+
175+
// Verify that the resource was created successfully
176+
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
177+
var resource = Assert.Single(appModel.Resources.OfType<NodeAppResource>());
178+
Assert.Equal("npm", resource.Command);
179+
}
180+
181+
[Fact]
182+
public void WithNpmPackageInstallationCanUseCICommand()
183+
{
184+
var builder = DistributedApplication.CreateBuilder();
185+
186+
var nodeApp = builder.AddNpmApp("test-app", "./test-app");
187+
188+
// Add package installation with CI enabled
189+
nodeApp.WithNpmPackageInstallation(useCI: true);
190+
191+
using var app = builder.Build();
192+
193+
// Verify that the resource was created successfully
194+
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
195+
var resource = Assert.Single(appModel.Resources.OfType<NodeAppResource>());
196+
Assert.Equal("npm", resource.Command);
197+
}
162198
}

0 commit comments

Comments
 (0)