diff --git a/playground/AspireWithNode/AspireWithNode.AppHost/AppHost.cs b/playground/AspireWithNode/AspireWithNode.AppHost/AppHost.cs index c27d38d22a0..6f05a9951a0 100644 --- a/playground/AspireWithNode/AspireWithNode.AppHost/AppHost.cs +++ b/playground/AspireWithNode/AspireWithNode.AppHost/AppHost.cs @@ -4,6 +4,8 @@ var builder = DistributedApplication.CreateBuilder(args); +builder.AddDockerComposeEnvironment("compose"); + var pass = builder.AddParameter("pass", "p@ssw0rd1"); var cache = builder @@ -12,14 +14,17 @@ var weatherapi = builder.AddProject("weatherapi"); +#pragma warning disable ASPIREJAVASCRIPT001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var frontend = builder.AddJavaScriptApp("frontend", "../NodeFrontend", "watch") + .WithPnpm() .WithReference(weatherapi) .WaitFor(weatherapi) .WithReference(cache) .WaitFor(cache) .WithHttpEndpoint(env: "PORT") .WithExternalHttpEndpoints() - .PublishAsDockerFile(); + .PublishAsNpmScript("start"); +#pragma warning restore ASPIREJAVASCRIPT001 var launchProfile = builder.Configuration["DOTNET_LAUNCH_PROFILE"]; diff --git a/playground/AspireWithNode/AspireWithNode.AppHost/AspireWithNode.AppHost.csproj b/playground/AspireWithNode/AspireWithNode.AppHost/AspireWithNode.AppHost.csproj index 51f28664c66..3efcbfd8d3b 100644 --- a/playground/AspireWithNode/AspireWithNode.AppHost/AspireWithNode.AppHost.csproj +++ b/playground/AspireWithNode/AspireWithNode.AppHost/AspireWithNode.AppHost.csproj @@ -11,6 +11,7 @@ + diff --git a/playground/AspireWithNode/AspireWithNode.AppHost/aspire-manifest.json b/playground/AspireWithNode/AspireWithNode.AppHost/aspire-manifest.json index c9a52d02b44..cfd7ec1d0fa 100644 --- a/playground/AspireWithNode/AspireWithNode.AppHost/aspire-manifest.json +++ b/playground/AspireWithNode/AspireWithNode.AppHost/aspire-manifest.json @@ -55,7 +55,7 @@ "type": "container.v1", "build": { "context": "../NodeFrontend", - "dockerfile": "../NodeFrontend/Dockerfile" + "dockerfile": "frontend.Dockerfile" }, "env": { "NODE_ENV": "production", @@ -68,7 +68,9 @@ "CACHE_PORT": "{cache.bindings.tcp.port}", "CACHE_PASSWORD": "{pass.value}", "CACHE_URI": "{cache.bindings.tcp.scheme}://:{pass-uri-encoded.value}@{cache.bindings.tcp.host}:{cache.bindings.tcp.port}", - "PORT": "{frontend.bindings.http.targetPort}" + "PORT": "{frontend.bindings.http.targetPort}", + "HOST": "0.0.0.0", + "HOSTNAME": "0.0.0.0" }, "bindings": { "http": { @@ -90,4 +92,4 @@ "connectionString": "" } } -} \ No newline at end of file +} diff --git a/playground/AspireWithNode/AspireWithNode.AppHost/frontend.Dockerfile b/playground/AspireWithNode/AspireWithNode.AppHost/frontend.Dockerfile new file mode 100644 index 00000000000..f0c316d9d63 --- /dev/null +++ b/playground/AspireWithNode/AspireWithNode.AppHost/frontend.Dockerfile @@ -0,0 +1,21 @@ +FROM node:22-slim AS build +WORKDIR /app +RUN corepack enable pnpm +COPY package.json pnpm-lock.yaml ./ +RUN --mount=type=cache,target=/pnpm/store pnpm install --frozen-lockfile +COPY . . +RUN pnpm run build + +FROM node:22-slim AS prod-deps +WORKDIR /app +RUN corepack enable pnpm +COPY package.json pnpm-lock.yaml ./ +RUN --mount=type=cache,target=/pnpm/store pnpm install --frozen-lockfile --prod + +FROM node:22-alpine AS runtime +WORKDIR /app +COPY --from=build /app /app +COPY --from=prod-deps /app/node_modules ./node_modules +RUN corepack enable pnpm && pnpm --version +ENV NODE_ENV=production +ENTRYPOINT ["sh","-c","exec pnpm run start"] diff --git a/playground/AspireWithNode/NodeFrontend/Dockerfile b/playground/AspireWithNode/NodeFrontend/Dockerfile deleted file mode 100644 index 24d282879eb..00000000000 --- a/playground/AspireWithNode/NodeFrontend/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -# DisableDockerDetector "Playground/demo application used for testing Aspire features" -FROM node:20 - -WORKDIR /app - -COPY package.json package.json -COPY package-lock.json package-lock.json - -RUN npm install - -COPY . . - -CMD [ "node", "app.js" ] \ No newline at end of file diff --git a/playground/AspireWithNode/NodeFrontend/package.json b/playground/AspireWithNode/NodeFrontend/package.json index 5eed0a6a4ba..b3c373511da 100644 --- a/playground/AspireWithNode/NodeFrontend/package.json +++ b/playground/AspireWithNode/NodeFrontend/package.json @@ -2,22 +2,27 @@ "name": "nodefrontend", "version": "1.0.0", "type": "module", + "packageManager": "pnpm@10.30.1", "main": "app.js", "engines": { "node": ">=20.12" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node --watch --import ./instrumentation.js app.js", - "watch": "npm install && nodemon --import ./instrumentation.js app.js" + "build": "echo \"no build needed\"", + "start": "node --import ./instrumentation.js app.js", + "watch": "nodemon --import ./instrumentation.js app.js" }, "dependencies": { "@godaddy/terminus": "^4.12.1", + "@grpc/grpc-js": "^1.14.3", "@opentelemetry/api": "^1.9.1", "@opentelemetry/auto-instrumentations-node": "^0.76.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.218.0", "@opentelemetry/exporter-metrics-otlp-grpc": "^0.218.0", "@opentelemetry/exporter-trace-otlp-grpc": "^0.218.0", + "@opentelemetry/instrumentation-express": "^0.66.0", + "@opentelemetry/instrumentation-http": "^0.218.0", "@opentelemetry/instrumentation-redis": "^0.66.0", "@opentelemetry/sdk-logs": "^0.218.0", "@opentelemetry/sdk-metrics": "^2.6.1", diff --git a/playground/AspireWithNode/NodeFrontend/pnpm-lock.yaml b/playground/AspireWithNode/NodeFrontend/pnpm-lock.yaml index ed80940b8d6..a63c5a8650a 100644 --- a/playground/AspireWithNode/NodeFrontend/pnpm-lock.yaml +++ b/playground/AspireWithNode/NodeFrontend/pnpm-lock.yaml @@ -15,6 +15,9 @@ importers: '@godaddy/terminus': specifier: ^4.12.1 version: 4.12.1 + '@grpc/grpc-js': + specifier: ^1.14.3 + version: 1.14.3 '@opentelemetry/api': specifier: ^1.9.1 version: 1.9.1 @@ -30,6 +33,12 @@ importers: '@opentelemetry/exporter-trace-otlp-grpc': specifier: ^0.218.0 version: 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-express': + specifier: ^0.66.0 + version: 0.66.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-http': + specifier: ^0.218.0 + version: 0.218.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-redis': specifier: ^0.66.0 version: 0.66.0(@opentelemetry/api@1.9.1) diff --git a/playground/AspireWithNode/README.md b/playground/AspireWithNode/README.md index 274191330c8..c9541030b46 100644 --- a/playground/AspireWithNode/README.md +++ b/playground/AspireWithNode/README.md @@ -7,6 +7,14 @@ The app consists of two services: - **NodeFrontend**: This is a simple Express-based Node.js app that renders a table of weather forecasts retrieved from a backend API and utilizes a Redis cache. - **AspireWithNode.AspNetCoreApi**: This is an HTTP API that returns randomly generated weather forecast data. +The frontend uses pnpm and Aspire's generated JavaScript Dockerfile support. The AppHost configures it with `WithPnpm()` and `PublishAsNpmScript("start")`, so publish mode builds a production container from the package metadata and runs the app through the package manager script. + +The AppHost also includes Docker Compose publishing support. To deploy this sample locally with Docker Compose, run: + +```shell +aspire deploy +``` + ## Pre-requisites - [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) diff --git a/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs b/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs index 4c0c0f11c12..d682c2d3c6a 100644 --- a/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs +++ b/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs @@ -657,6 +657,22 @@ private static void AddInstallCommand(this DockerfileStage builderStage, JavaScr } } + private static string GetNpmScriptRuntimeImage( + string appDirectory, + IServiceProvider services, + DockerfileBaseImageAnnotation? baseImageAnnotation, + JavaScriptPackageManagerAnnotation packageManager, + string buildImage) + { + if (!string.IsNullOrEmpty(baseImageAnnotation?.RuntimeImage)) + { + return baseImageAnnotation.RuntimeImage; + } + + return packageManager.ResolveNpmScriptRuntimeImage?.Invoke(buildImage) + ?? GetDefaultBaseImage(appDirectory, "alpine", services); + } + private static IResourceBuilder CreateDefaultJavaScriptAppBuilder( this IDistributedApplicationBuilder builder, TResource resource, @@ -783,7 +799,7 @@ private static IResourceBuilder CreateDefaultJavaScriptAppBuilder CreateDefaultJavaScriptAppBuilder WithNpm(this IResourceBuild /// Bun forwards script arguments without requiring the -- command separator, so this method configures the resource to omit it. /// When publishing and a bun lockfile (bun.lock or bun.lockb) is present, --frozen-lockfile is used by default. /// Publishing to a container requires Bun to be present in the build image. This method configures a Bun build image when one is not already specified. + /// also uses the Bun image for the runtime stage unless a custom runtime image is configured. /// To use a specific Bun version, configure a custom build image (for example, oven/bun:<tag>) using . /// /// @@ -1360,6 +1381,7 @@ public static IResourceBuilder WithBun(this IResourceBuild PackageFilesPatterns = { new CopyFilePattern(packageFilesSourcePattern, "./") }, // bun supports passing script flags without the `--` separator. CommandSeparator = null, + ResolveNpmScriptRuntimeImage = buildImage => buildImage, }) .WithAnnotation(new JavaScriptInstallCommandAnnotation(["install", .. installArgs]) { @@ -1508,6 +1530,12 @@ public static IResourceBuilder WithPnpm(this IResourceBuil CommandSeparator = null, // pnpm is not included in the Node.js Docker image by default, so we need to enable it via corepack InitializeDockerBuildStage = stage => stage.Run("corepack enable pnpm"), + InitializeDockerRuntimeStage = stage => + { + // Corepack's shim is not enough by itself: without invoking pnpm during the image build, + // the first container start can try to download pnpm before running the app. + stage.Run("corepack enable pnpm && pnpm --version"); + }, }) .WithAnnotation(new JavaScriptInstallCommandAnnotation(["install", .. installArgs]) { diff --git a/src/Aspire.Hosting.JavaScript/JavaScriptPackageManagerAnnotation.cs b/src/Aspire.Hosting.JavaScript/JavaScriptPackageManagerAnnotation.cs index 420447e4c76..25470dfdad0 100644 --- a/src/Aspire.Hosting.JavaScript/JavaScriptPackageManagerAnnotation.cs +++ b/src/Aspire.Hosting.JavaScript/JavaScriptPackageManagerAnnotation.cs @@ -46,4 +46,15 @@ public sealed class JavaScriptPackageManagerAnnotation(string executableName, st /// [Experimental("ASPIREDOCKERFILEBUILDER001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public Action? InitializeDockerBuildStage { get; init; } + + /// + /// Gets or sets a callback to initialize the Docker runtime stage before configuring the entrypoint. + /// + [Experimental("ASPIREDOCKERFILEBUILDER001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] + internal Action? InitializeDockerRuntimeStage { get; init; } + + /// + /// Gets or sets a callback to resolve the default PublishAsNpmScript runtime image from the build image. + /// + internal Func? ResolveNpmScriptRuntimeImage { get; init; } } diff --git a/tests/Aspire.Hosting.JavaScript.Tests/AddJavaScriptAppTests.cs b/tests/Aspire.Hosting.JavaScript.Tests/AddJavaScriptAppTests.cs index 5a9333f4b0d..8f89546c262 100644 --- a/tests/Aspire.Hosting.JavaScript.Tests/AddJavaScriptAppTests.cs +++ b/tests/Aspire.Hosting.JavaScript.Tests/AddJavaScriptAppTests.cs @@ -127,6 +127,35 @@ public async Task VerifyPnpmDockerfile(bool hasLockFile) await Verify(dockerfileContents); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task VerifyPnpmDockerfileWhenPublishedAsNpmScript(bool hasLockFile) + { + using var tempDir = new TestTempDirectory(); + using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true); + + var appDir = Path.Combine(tempDir.Path, "js"); + Directory.CreateDirectory(appDir); + + if (hasLockFile) + { + File.WriteAllText(Path.Combine(appDir, "pnpm-lock.yaml"), string.Empty); + } + + var pnpmApp = builder.AddJavaScriptApp("js", appDir) + .WithPnpm(installArgs: ["--prefer-frozen-lockfile"]) + .WithBuildScript("mybuild") + .PublishAsNpmScript("start"); + + await ManifestUtils.GetManifest(pnpmApp.Resource, tempDir.Path); + + var dockerfilePath = Path.Combine(tempDir.Path, "js.Dockerfile"); + var dockerfileContents = File.ReadAllText(dockerfilePath); + + await Verify(dockerfileContents); + } + [Fact] public async Task PublishWithExistingDockerfileThrowsWhenRunScriptNameIsExplicit() { @@ -401,6 +430,86 @@ public async Task VerifyPnpmDockerfileBuildSucceeds() Assert.True(process.ExitCode == 0, $"Docker build failed with exit code {process.ExitCode}.\nStdout: {stdout}\nStderr: {stderr}"); } + [Fact] + [RequiresFeature(TestFeature.Docker | TestFeature.DockerPluginBuildx)] + [OuterloopTest("Builds and runs a Docker image to verify the generated pnpm PublishAsNpmScript Dockerfile works")] + public async Task VerifyPnpmDockerfileWhenPublishedAsNpmScriptRunsWithoutNetwork() + { + using var tempDir = new TestTempDirectory(); + using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true); + + var appDir = Path.Combine(tempDir.Path, "pnpm-app"); + Directory.CreateDirectory(appDir); + + var packageJson = """ + { + "name": "pnpm-runtime-test-app", + "version": "1.0.0", + "scripts": { + "build": "echo 'build completed'", + "start": "node -e \"console.log('runtime ok')\"" + } + } + """; + await File.WriteAllTextAsync(Path.Combine(appDir, "package.json"), packageJson); + + var pnpmApp = builder.AddJavaScriptApp("pnpm-app", appDir) + .WithPnpm() + .WithBuildScript("build") + .PublishAsNpmScript("start"); + + await ManifestUtils.GetManifest(pnpmApp.Resource, tempDir.Path); + + var dockerfilePath = Path.Combine(tempDir.Path, "pnpm-app.Dockerfile"); + Assert.True(File.Exists(dockerfilePath), $"Dockerfile should exist at {dockerfilePath}"); + + var dockerfileContent = await File.ReadAllTextAsync(dockerfilePath); + Assert.Contains("RUN corepack enable pnpm && pnpm --version", dockerfileContent); + + var dockerfileInContext = Path.Combine(appDir, "Dockerfile"); + await File.WriteAllTextAsync(dockerfileInContext, dockerfileContent); + + var imageName = $"aspire-pnpm-runtime-test-{Guid.NewGuid():N}"; + + try + { + var buildResult = await RunDockerCommandAsync($"build --network=host -t {imageName} -f Dockerfile .", appDir); + Assert.True(buildResult.ExitCode == 0, $"Docker build failed with exit code {buildResult.ExitCode}.\nStdout: {buildResult.Stdout}\nStderr: {buildResult.Stderr}"); + + var runResult = await RunDockerCommandAsync($"run --rm --network=none {imageName}", appDir); + Assert.True(runResult.ExitCode == 0, $"Docker run failed with exit code {runResult.ExitCode}.\nStdout: {runResult.Stdout}\nStderr: {runResult.Stderr}"); + Assert.Contains("runtime ok", runResult.Stdout); + } + finally + { + await RunDockerCommandAsync($"rmi {imageName}", appDir); + } + } + + private static async Task<(int ExitCode, string Stdout, string Stderr)> RunDockerCommandAsync(string arguments, string workingDirectory) + { + var processStartInfo = new ProcessStartInfo + { + FileName = "docker", + Arguments = arguments, + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(processStartInfo); + Assert.NotNull(process); + + var stdoutTask = process.StandardOutput.ReadToEndAsync(); + var stderrTask = process.StandardError.ReadToEndAsync(); + + await process.WaitForExitAsync(TestContext.Current.CancellationToken); + + return (process.ExitCode, await stdoutTask, await stderrTask); + } + private static string CreateJavaScriptAppWithDockerfile(string rootDirectory) { var appDir = Path.Combine(rootDirectory, "js"); diff --git a/tests/Aspire.Hosting.JavaScript.Tests/AddViteAppTests.cs b/tests/Aspire.Hosting.JavaScript.Tests/AddViteAppTests.cs index f7245991afb..29e24d8c4d8 100644 --- a/tests/Aspire.Hosting.JavaScript.Tests/AddViteAppTests.cs +++ b/tests/Aspire.Hosting.JavaScript.Tests/AddViteAppTests.cs @@ -292,6 +292,26 @@ public async Task VerifyDockerfileWhenNpmScriptUsesPnpm() await Verify(File.ReadAllText(dockerfilePath)); } + [Fact] + public async Task VerifyDockerfileWhenNpmScriptUsesBun() + { + using var tempDir = new TestTempDirectory(); + using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true); + + var appDir = Path.Combine(tempDir.Path, "nuxt"); + Directory.CreateDirectory(appDir); + File.WriteAllText(Path.Combine(appDir, "bun.lock"), ""); + + var nodeApp = builder.AddViteApp("nuxt", appDir) + .WithBun(install: true) + .PublishAsNpmScript("start"); + + await ManifestUtils.GetManifest(nodeApp.Resource, tempDir.Path); + + var dockerfilePath = Path.Combine(tempDir.Path, "nuxt.Dockerfile"); + await Verify(File.ReadAllText(dockerfilePath)); + } + [Fact] public async Task VerifyDockerfileWithNodeVersionFromNvmrc() { diff --git a/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddJavaScriptAppTests.VerifyPnpmDockerfileWhenPublishedAsNpmScript_hasLockFile=False.verified.txt b/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddJavaScriptAppTests.VerifyPnpmDockerfileWhenPublishedAsNpmScript_hasLockFile=False.verified.txt new file mode 100644 index 00000000000..9eaab814941 --- /dev/null +++ b/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddJavaScriptAppTests.VerifyPnpmDockerfileWhenPublishedAsNpmScript_hasLockFile=False.verified.txt @@ -0,0 +1,21 @@ +FROM node:22-slim AS build +WORKDIR /app +RUN corepack enable pnpm +COPY package.json ./ +RUN --mount=type=cache,target=/pnpm/store pnpm install --prefer-frozen-lockfile +COPY . . +RUN pnpm run mybuild + +FROM node:22-slim AS prod-deps +WORKDIR /app +RUN corepack enable pnpm +COPY package.json ./ +RUN --mount=type=cache,target=/pnpm/store pnpm install --prefer-frozen-lockfile --prod + +FROM node:22-alpine AS runtime +WORKDIR /app +COPY --from=build /app /app +COPY --from=prod-deps /app/node_modules ./node_modules +RUN corepack enable pnpm && pnpm --version +ENV NODE_ENV=production +ENTRYPOINT ["sh","-c","exec pnpm run start"] diff --git a/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddJavaScriptAppTests.VerifyPnpmDockerfileWhenPublishedAsNpmScript_hasLockFile=True.verified.txt b/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddJavaScriptAppTests.VerifyPnpmDockerfileWhenPublishedAsNpmScript_hasLockFile=True.verified.txt new file mode 100644 index 00000000000..d6dbd82f3e8 --- /dev/null +++ b/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddJavaScriptAppTests.VerifyPnpmDockerfileWhenPublishedAsNpmScript_hasLockFile=True.verified.txt @@ -0,0 +1,21 @@ +FROM node:22-slim AS build +WORKDIR /app +RUN corepack enable pnpm +COPY package.json pnpm-lock.yaml ./ +RUN --mount=type=cache,target=/pnpm/store pnpm install --prefer-frozen-lockfile +COPY . . +RUN pnpm run mybuild + +FROM node:22-slim AS prod-deps +WORKDIR /app +RUN corepack enable pnpm +COPY package.json pnpm-lock.yaml ./ +RUN --mount=type=cache,target=/pnpm/store pnpm install --prefer-frozen-lockfile --prod + +FROM node:22-alpine AS runtime +WORKDIR /app +COPY --from=build /app /app +COPY --from=prod-deps /app/node_modules ./node_modules +RUN corepack enable pnpm && pnpm --version +ENV NODE_ENV=production +ENTRYPOINT ["sh","-c","exec pnpm run start"] diff --git a/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddViteAppTests.VerifyDockerfileWhenNpmScriptUsesBun.verified.txt b/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddViteAppTests.VerifyDockerfileWhenNpmScriptUsesBun.verified.txt new file mode 100644 index 00000000000..0f36944b0c7 --- /dev/null +++ b/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddViteAppTests.VerifyDockerfileWhenNpmScriptUsesBun.verified.txt @@ -0,0 +1,18 @@ +FROM oven/bun:1 AS build +WORKDIR /app +COPY package.json bun.lock ./ +RUN --mount=type=cache,target=/root/.bun/install/cache bun install --frozen-lockfile +COPY . . +RUN bun run build + +FROM oven/bun:1 AS prod-deps +WORKDIR /app +COPY package.json bun.lock ./ +RUN --mount=type=cache,target=/root/.bun/install/cache bun install --frozen-lockfile --production + +FROM oven/bun:1 AS runtime +WORKDIR /app +COPY --from=build /app /app +COPY --from=prod-deps /app/node_modules ./node_modules +ENV NODE_ENV=production +ENTRYPOINT ["sh","-c","exec bun run start"] diff --git a/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddViteAppTests.VerifyDockerfileWhenNpmScriptUsesPnpm.verified.txt b/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddViteAppTests.VerifyDockerfileWhenNpmScriptUsesPnpm.verified.txt index 1be2b6530bf..d374d162e89 100644 --- a/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddViteAppTests.VerifyDockerfileWhenNpmScriptUsesPnpm.verified.txt +++ b/tests/Aspire.Hosting.JavaScript.Tests/Snapshots/AddViteAppTests.VerifyDockerfileWhenNpmScriptUsesPnpm.verified.txt @@ -16,5 +16,6 @@ FROM node:22-alpine AS runtime WORKDIR /app COPY --from=build /app /app COPY --from=prod-deps /app/node_modules ./node_modules +RUN corepack enable pnpm && pnpm --version ENV NODE_ENV=production ENTRYPOINT ["sh","-c","exec pnpm run start"]