From bd449969fa60b9d685fef74eaa05d197f713a59b Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 3 Nov 2025 13:26:00 -0600 Subject: [PATCH 1/3] Detect which version of yarn is being used Change the install args based on yarn 1.x or 2+ --- src/Aspire.Hosting.NodeJs/NodeExtensions.cs | 28 ++++++++++++--- .../PackageInstallationTests.cs | 34 ++++++++++++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/Aspire.Hosting.NodeJs/NodeExtensions.cs b/src/Aspire.Hosting.NodeJs/NodeExtensions.cs index e4d5f9b9731..a500c65a87d 100644 --- a/src/Aspire.Hosting.NodeJs/NodeExtensions.cs +++ b/src/Aspire.Hosting.NodeJs/NodeExtensions.cs @@ -475,11 +475,29 @@ public static IResourceBuilder WithYarn(this IResourceBuil return resource; } - private static string[] GetDefaultYarnInstallArgs(IResourceBuilder resource) => - resource.ApplicationBuilder.ExecutionContext.IsPublishMode && - File.Exists(Path.Combine(resource.Resource.WorkingDirectory, "yarn.lock")) - ? ["--immutable"] - : []; + private static string[] GetDefaultYarnInstallArgs(IResourceBuilder resource) + { + var workingDirectory = resource.Resource.WorkingDirectory; + if (!resource.ApplicationBuilder.ExecutionContext.IsPublishMode || + !File.Exists(Path.Combine(workingDirectory, "yarn.lock"))) + { + // Not publish mode or no yarn.lock, use default install args + return []; + } + + var yarnRcYml = Path.Combine(workingDirectory, ".yarnrc.yml"); + var yarnBerryReleaseDir = Path.Combine(workingDirectory, ".yarn", "releases"); + var hasYarnBerry = File.Exists(yarnRcYml) || Directory.Exists(yarnBerryReleaseDir); + + if (hasYarnBerry) + { + // Yarn 2+ detected + return ["--immutable"]; + } + + // Fallback: default to Yarn v1.x behavior + return ["--frozen-lockfile"]; + } /// /// Configures the Node.js resource to use pnmp as the package manager and optionally installs packages before the application starts. diff --git a/tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs b/tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs index 59a0af6ac4a..f5e5fa267ee 100644 --- a/tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs +++ b/tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs @@ -476,7 +476,7 @@ public void WithYarn_DefaultsArgsInPublishMode() .WithYarn(); Assert.True(app.Resource.TryGetLastAnnotation(out var installCommand)); - Assert.Equal(["install", "--immutable"], installCommand.Args); + Assert.Equal(["install", "--frozen-lockfile"], installCommand.Args); var app2 = builder.AddViteApp("test-app2", tempDir.Path) .WithYarn(installArgs: ["--immutable-cache"]); @@ -485,6 +485,38 @@ public void WithYarn_DefaultsArgsInPublishMode() Assert.Equal(["install", "--immutable-cache"], installCommand.Args); } + [Fact] + public void WithYarn_ReturnsImmutable_WhenYarnRcYmlExists() + { + using var tempDir = new TempDirectory(); + File.WriteAllText(Path.Combine(tempDir.Path, "yarn.lock"), "empty"); + File.WriteAllText(Path.Combine(tempDir.Path, ".yarnrc.yml"), "empty"); // .yarnrc.yml present + + using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); + + var app = builder.AddViteApp("test-app", tempDir.Path) + .WithYarn(); + + Assert.True(app.Resource.TryGetLastAnnotation(out var installCommand)); + Assert.Equal(["install", "--immutable"], installCommand.Args); + } + + [Fact] + public void WithYarn_ReturnsImmutable_WhenYarnReleasesDirExists() + { + using var tempDir = new TempDirectory(); + File.WriteAllText(Path.Combine(tempDir.Path, "yarn.lock"), "empty"); + Directory.CreateDirectory(Path.Combine(tempDir.Path, ".yarn", "releases")); + + using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); + + var app = builder.AddViteApp("test-app", tempDir.Path) + .WithYarn(); + + Assert.True(app.Resource.TryGetLastAnnotation(out var installCommand)); + Assert.Equal(["install", "--immutable"], installCommand.Args); + } + [Fact] public void WithPnpm_DefaultsArgsInPublishMode() { From 5ba939d94096e6a1f3444a2b94276187e3ede7d0 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 3 Nov 2025 13:53:59 -0600 Subject: [PATCH 2/3] Update tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs b/tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs index f5e5fa267ee..cffba9853db 100644 --- a/tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs +++ b/tests/Aspire.Hosting.NodeJs.Tests/PackageInstallationTests.cs @@ -490,7 +490,7 @@ public void WithYarn_ReturnsImmutable_WhenYarnRcYmlExists() { using var tempDir = new TempDirectory(); File.WriteAllText(Path.Combine(tempDir.Path, "yarn.lock"), "empty"); - File.WriteAllText(Path.Combine(tempDir.Path, ".yarnrc.yml"), "empty"); // .yarnrc.yml present + File.WriteAllText(Path.Combine(tempDir.Path, ".yarnrc.yml"), "empty"); using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); From d339096ee62ec14d8046c319c541c1db5c7748ce Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 3 Nov 2025 14:46:52 -0600 Subject: [PATCH 3/3] Update comment --- src/Aspire.Hosting.NodeJs/NodeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Hosting.NodeJs/NodeExtensions.cs b/src/Aspire.Hosting.NodeJs/NodeExtensions.cs index a500c65a87d..4a5921681f5 100644 --- a/src/Aspire.Hosting.NodeJs/NodeExtensions.cs +++ b/src/Aspire.Hosting.NodeJs/NodeExtensions.cs @@ -491,7 +491,7 @@ private static string[] GetDefaultYarnInstallArgs(IResourceBuilder