From 76ea0d504425f575f9b86e74eb9f5d01c9c35ec8 Mon Sep 17 00:00:00 2001 From: Long Hao Date: Tue, 20 Oct 2026 06:31:46 +0000 Subject: [PATCH] feat: support .NET 10 isolated and bump CLI to 2.1.0 Allow explicitly selecting the .NET 10 isolated runtime while preserving existing defaults, runtime support, and dependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/www/docs/cli/swa-deploy.md | 21 ++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- schema/staticwebapp.config.json | 1 + src/cli/commands/deploy/deploy.spec.ts | 17 +++++++++++++ src/core/constants.spec.ts | 25 +++++++++++++++++++ src/core/constants.ts | 2 +- src/core/utils/json.spec.ts | 33 +++++++++++++++++++++++++- 8 files changed, 100 insertions(+), 5 deletions(-) diff --git a/docs/www/docs/cli/swa-deploy.md b/docs/www/docs/cli/swa-deploy.md index d71ca7b8b..06a0404b9 100644 --- a/docs/www/docs/cli/swa-deploy.md +++ b/docs/www/docs/cli/swa-deploy.md @@ -98,6 +98,27 @@ If your project doesn't have any `staticwebapp.config.json` file, add one under swa deploy ./my-dist --api-location ./api ``` +#### Opt in to .NET 10 isolated + +For a .NET 10 isolated Functions API, explicitly select the language and version: + +```bash +swa deploy ./my-dist --api-location ./api --api-language dotnetisolated --api-version 10.0 +``` + +The corresponding runtime setting in `staticwebapp.config.json` is: + +```json +{ + "platform": { + "apiRuntime": "dotnet-isolated:10.0" + } +} +``` + +The default .NET isolated version remains `8.0`. .NET 10 is not supported for the in-process `dotnet` runtime. +CLI runtime selection does not establish runtime availability in Azure Static Web Apps; verify support for your target environment before deploying. + ### Deploy a Blazor app To deploy a Blazor app with (optional) an API to Azure Static Web Apps, use the following steps: diff --git a/package-lock.json b/package-lock.json index 2e47f0c0c..8b937702d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@azure/static-web-apps-cli", - "version": "2.0.10", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@azure/static-web-apps-cli", - "version": "2.0.10", + "version": "2.1.0", "license": "MIT", "dependencies": { "@azure/arm-appservice": "^15.0.0", diff --git a/package.json b/package.json index f2b3c8440..e55fde7f4 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "2.0.10", + "version": "2.1.0", "name": "@azure/static-web-apps-cli", "description": "Azure Static Web Apps CLI", "type": "module", diff --git a/schema/staticwebapp.config.json b/schema/staticwebapp.config.json index 3d36c64fb..6cf29dbe4 100644 --- a/schema/staticwebapp.config.json +++ b/schema/staticwebapp.config.json @@ -588,6 +588,7 @@ "dotnet:8.0", "dotnet-isolated:8.0", "dotnet-isolated:9.0", + "dotnet-isolated:10.0", "node:18", "node:20", "node:22", diff --git a/src/cli/commands/deploy/deploy.spec.ts b/src/cli/commands/deploy/deploy.spec.ts index 82a9025fa..e3f402430 100644 --- a/src/cli/commands/deploy/deploy.spec.ts +++ b/src/cli/commands/deploy/deploy.spec.ts @@ -308,6 +308,23 @@ describe("deploy", () => { expect(SUPPORTED_VERSIONS.DotnetIsolated).toContain(env.FUNCTION_LANGUAGE_VERSION); }); + it.each(["8.0", "9.0", "10.0"])("should forward explicitly selected .NET isolated %s to the deployment process", async (apiVersion) => { + vi.spyOn(optionsModule, "isUserOrConfigOption").mockImplementation((option) => option === "apiLanguage" || option === "apiVersion"); + + await deploy({ outputLocation: "/test-output", dryRun: false, apiLanguage: "dotnetisolated", apiVersion }); + + expect(spawn).toHaveBeenCalledWith( + "mock-binary", + [], + expect.objectContaining({ + env: expect.objectContaining({ + FUNCTION_LANGUAGE: "dotnetisolated", + FUNCTION_LANGUAGE_VERSION: apiVersion, + }), + }), + ); + }); + it("should print an error when --env is an empty string", async () => { await deploy({ outputLocation: "/test-output", diff --git a/src/core/constants.spec.ts b/src/core/constants.spec.ts index 0218be68f..1566defb1 100644 --- a/src/core/constants.spec.ts +++ b/src/core/constants.spec.ts @@ -1,5 +1,6 @@ import { DEFAULT_CONFIG } from "../config.js"; import { DEFAULT_VERSION, SUPPORTED_VERSIONS } from "./constants.js"; +import { getChoicesForApiLanguage, getDefaultVersion } from "./functions-versions.js"; describe("DEFAULT_CONFIG and DEFAULT_VERSION/SUPPORTED_VERSIONS consistency", () => { it("DEFAULT_CONFIG.apiVersion should match DEFAULT_VERSION.Node", () => { @@ -17,3 +18,27 @@ describe("DEFAULT_CONFIG and DEFAULT_VERSION/SUPPORTED_VERSIONS consistency", () expect(SUPPORTED_VERSIONS.Python).toContain(DEFAULT_VERSION.Python); }); }); + +describe(".NET runtime selection", () => { + it("should offer .NET 10 isolated alongside the existing versions", () => { + const choices = getChoicesForApiLanguage("dotnetisolated"); + + expect(choices).toEqual([ + { title: "8.0", value: "8.0" }, + { title: "9.0", value: "9.0" }, + { title: "10.0", value: "10.0" }, + ]); + }); + + it.each(["dotnetisolated", "dotnet"])("should keep the %s default at .NET 8", (apiLanguage) => { + const version = getDefaultVersion(apiLanguage); + + expect(version).toBe("8.0"); + }); + + it("should not offer .NET 10 for the in-process runtime", () => { + const choices = getChoicesForApiLanguage("dotnet"); + + expect(choices).toEqual([{ title: "8.0", value: "8.0" }]); + }); +}); diff --git a/src/core/constants.ts b/src/core/constants.ts index d29c94ebf..3d41b2873 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -307,7 +307,7 @@ export const DEFAULT_VERSION = { export const SUPPORTED_VERSIONS = { Node: ["18", "20", "22"], Dotnet: ["8.0"], - DotnetIsolated: ["8.0", "9.0"], + DotnetIsolated: ["8.0", "9.0", "10.0"], Python: ["3.9", "3.10", "3.11"], }; diff --git a/src/core/utils/json.spec.ts b/src/core/utils/json.spec.ts index e28d4de27..a59d8a90f 100644 --- a/src/core/utils/json.spec.ts +++ b/src/core/utils/json.spec.ts @@ -1,4 +1,5 @@ -import { loadPackageJson } from "./json.js"; +import { loadPackageJson, validateConfigFile } from "./json.js"; +import { logger } from "./logger.js"; describe("json functions", () => { describe("loadPackageJson()", () => { @@ -9,4 +10,34 @@ describe("json functions", () => { expect(pkg.type).toBe("module"); }); }); + + describe("validateConfigFile() API runtimes", () => { + it.each([ + "dotnet-isolated:10.0", + "dotnet:8.0", + "dotnet-isolated:8.0", + "dotnet-isolated:9.0", + "node:18", + "node:20", + "node:22", + "python:3.9", + "python:3.10", + "python:3.11", + ])("should accept %s", async (apiRuntime) => { + const config = { platform: { apiRuntime } }; + + const result = await validateConfigFile(config); + + expect(result).toEqual(config); + }); + + it("should reject .NET 10 for the in-process runtime", async () => { + vi.spyOn(logger, "error").mockImplementation(() => {}); + const config = { platform: { apiRuntime: "dotnet:10.0" } }; + + const result = await validateConfigFile(config); + + expect(result).toBeNull(); + }); + }); });