diff --git a/src/Aspire.Cli/Templating/Templates/py-starter/apphost.ts b/src/Aspire.Cli/Templating/Templates/py-starter/apphost.ts index e51954bafab..d3c55007e81 100644 --- a/src/Aspire.Cli/Templating/Templates/py-starter/apphost.ts +++ b/src/Aspire.Cli/Templating/Templates/py-starter/apphost.ts @@ -17,7 +17,7 @@ const app = await builder .withReference(cache) .waitFor(cache) // {{/redis}} - .withHttpHealthCheck("/health"); + .withHttpHealthCheck({ path: "/health" }); // Run the Vite frontend after the API and inject the API URL for local proxying. const frontend = await builder diff --git a/src/Aspire.Cli/Templating/Templates/py-starter/eslint.config.mjs b/src/Aspire.Cli/Templating/Templates/py-starter/eslint.config.mjs index e7e33edb3fd..2c6536d4bd5 100644 --- a/src/Aspire.Cli/Templating/Templates/py-starter/eslint.config.mjs +++ b/src/Aspire.Cli/Templating/Templates/py-starter/eslint.config.mjs @@ -8,7 +8,8 @@ export default defineConfig({ extends: [tseslint.configs.base], languageOptions: { parserOptions: { - projectService: true, + project: './tsconfig.apphost.json', + tsconfigRootDir: import.meta.dirname, }, }, rules: { diff --git a/src/Aspire.Cli/Templating/Templates/py-starter/package.json b/src/Aspire.Cli/Templating/Templates/py-starter/package.json index 7878cd35dbe..c64a43c21c9 100644 --- a/src/Aspire.Cli/Templating/Templates/py-starter/package.json +++ b/src/Aspire.Cli/Templating/Templates/py-starter/package.json @@ -3,12 +3,16 @@ "private": true, "type": "module", "scripts": { - "lint": "eslint apphost.ts", - "predev": "npm run lint", - "dev": "aspire run", - "prebuild": "npm run lint", - "build": "tsc", - "watch": "tsc --watch" + "aspire:lint": "eslint apphost.ts", + "aspire:start": "aspire run", + "aspire:build": "tsc -p tsconfig.apphost.json", + "aspire:dev": "tsc --watch -p tsconfig.apphost.json", + "lint": "npm run aspire:lint", + "predev": "npm run aspire:lint", + "dev": "npm run aspire:start", + "prebuild": "npm run aspire:lint", + "build": "npm run aspire:build", + "watch": "npm run aspire:dev" }, "dependencies": { "vscode-jsonrpc": "^8.2.0" diff --git a/src/Aspire.Cli/Templating/Templates/py-starter/tsconfig.apphost.json b/src/Aspire.Cli/Templating/Templates/py-starter/tsconfig.apphost.json new file mode 100644 index 00000000000..939a631a123 --- /dev/null +++ b/src/Aspire.Cli/Templating/Templates/py-starter/tsconfig.apphost.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "outDir": "./dist/apphost", + "rootDir": ".", + "strict": true + }, + "include": ["apphost.ts", ".modules/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/src/Aspire.Cli/Templating/Templates/py-starter/tsconfig.json b/src/Aspire.Cli/Templating/Templates/py-starter/tsconfig.json deleted file mode 100644 index 1e44664428b..00000000000 --- a/src/Aspire.Cli/Templating/Templates/py-starter/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "module": "esnext", - "moduleResolution": "bundler", - "strict": true - } -} diff --git a/src/Aspire.Cli/Templating/Templates/ts-starter/eslint.config.mjs b/src/Aspire.Cli/Templating/Templates/ts-starter/eslint.config.mjs index e7e33edb3fd..2c6536d4bd5 100644 --- a/src/Aspire.Cli/Templating/Templates/ts-starter/eslint.config.mjs +++ b/src/Aspire.Cli/Templating/Templates/ts-starter/eslint.config.mjs @@ -8,7 +8,8 @@ export default defineConfig({ extends: [tseslint.configs.base], languageOptions: { parserOptions: { - projectService: true, + project: './tsconfig.apphost.json', + tsconfigRootDir: import.meta.dirname, }, }, rules: { diff --git a/src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs b/src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs index 06f5df0cd12..94558f85412 100644 --- a/src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs +++ b/src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs @@ -96,7 +96,8 @@ public Dictionary Scaffold(ScaffoldRequest request) extends: [tseslint.configs.base], languageOptions: { parserOptions: { - projectService: true, + project: './tsconfig.apphost.json', + tsconfigRootDir: import.meta.dirname, }, }, rules: { @@ -145,7 +146,8 @@ private static string CreatePackageJson(ScaffoldRequest request) var packageJson = new JsonObject(); var packageJsonPath = Path.Combine(request.TargetPath, PackageJsonFileName); - if (!File.Exists(packageJsonPath)) + var isGreenfield = !File.Exists(packageJsonPath); + if (isGreenfield) { // Greenfield: include root metadata so the scaffold output is a complete package.json. var packageName = request.ProjectName?.ToLowerInvariant() ?? "aspire-apphost"; @@ -168,6 +170,16 @@ private static string CreatePackageJson(ScaffoldRequest request) scripts["aspire:build"] = $"tsc -p {AppHostTsConfigFileName}"; scripts["aspire:dev"] = $"tsc --watch -p {AppHostTsConfigFileName}"; + if (isGreenfield) + { + scripts["lint"] = "npm run aspire:lint"; + scripts["predev"] = "npm run aspire:lint"; + scripts["dev"] = "npm run aspire:start"; + scripts["prebuild"] = "npm run aspire:lint"; + scripts["build"] = "npm run aspire:build"; + scripts["watch"] = "npm run aspire:dev"; + } + EnsureDependency(packageJson, "dependencies", "vscode-jsonrpc", "^8.2.0"); EnsureDependency(packageJson, "devDependencies", "@types/node", "^22.0.0"); EnsureDependency(packageJson, "devDependencies", "eslint", "^10.0.3"); diff --git a/tests/Aspire.Cli.EndToEnd.Tests/PythonReactTemplateTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/PythonReactTemplateTests.cs index 38f133b34dc..c5f15576660 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/PythonReactTemplateTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/PythonReactTemplateTests.cs @@ -42,7 +42,10 @@ public async Task CreateAndRunPythonReactProject() await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); - // Step 3: Start and stop the project + // Step 3: Verify the generated TypeScript AppHost builds successfully. + await auto.RunCommandFailFastAsync("npm run build", counter, TimeSpan.FromMinutes(2)); + + // Step 4: Start and stop the project await auto.AspireStartAsync(counter); await auto.AspireStopAsync(counter); diff --git a/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptEmptyAppHostTemplateTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptEmptyAppHostTemplateTests.cs index 19cc6aa538a..847cd9126c0 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptEmptyAppHostTemplateTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptEmptyAppHostTemplateTests.cs @@ -40,6 +40,8 @@ public async Task CreateAndRunTypeScriptEmptyAppHostProject() await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); + await auto.RunCommandFailFastAsync("npm run build", counter, TimeSpan.FromMinutes(2)); + await auto.AspireStartAsync(counter); await auto.AspireStopAsync(counter); diff --git a/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptStarterTemplateTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptStarterTemplateTests.cs index 34cab5277e3..864c1ed1db3 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptStarterTemplateTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptStarterTemplateTests.cs @@ -56,6 +56,8 @@ public async Task CreateAndRunTypeScriptStarterProject() await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); + await auto.RunCommandFailFastAsync("npm run build", counter, TimeSpan.FromMinutes(2)); + await auto.AspireStartAsync(counter); await auto.AspireStopAsync(counter); diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/TypeScriptLanguageSupportTests.cs b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/TypeScriptLanguageSupportTests.cs index adaab97bdf7..5c04cbc6c9b 100644 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/TypeScriptLanguageSupportTests.cs +++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/TypeScriptLanguageSupportTests.cs @@ -38,9 +38,12 @@ public void Scaffold_CreatesAppHostSpecificScriptsAndTsConfig_ForNewProject() Assert.Equal("tsc -p tsconfig.apphost.json", scripts["aspire:build"]?.GetValue()); Assert.Equal("tsc --watch -p tsconfig.apphost.json", scripts["aspire:dev"]?.GetValue()); Assert.Equal("eslint apphost.ts", scripts["aspire:lint"]?.GetValue()); - Assert.False(scripts.ContainsKey("start")); - Assert.False(scripts.ContainsKey("build")); - Assert.False(scripts.ContainsKey("dev")); + Assert.Equal("npm run aspire:lint", scripts["lint"]?.GetValue()); + Assert.Equal("npm run aspire:lint", scripts["predev"]?.GetValue()); + Assert.Equal("npm run aspire:start", scripts["dev"]?.GetValue()); + Assert.Equal("npm run aspire:lint", scripts["prebuild"]?.GetValue()); + Assert.Equal("npm run aspire:build", scripts["build"]?.GetValue()); + Assert.Equal("npm run aspire:dev", scripts["watch"]?.GetValue()); Assert.Equal("^4.21.0", devDependencies["tsx"]?.GetValue()); Assert.Equal("^5.9.3", devDependencies["typescript"]?.GetValue()); Assert.Equal("^10.0.3", devDependencies["eslint"]?.GetValue()); @@ -53,6 +56,7 @@ public void Scaffold_CreatesAppHostSpecificScriptsAndTsConfig_ForNewProject() Assert.DoesNotContain("\\u003E", files["package.json"]); Assert.Contains("eslint.config.mjs", files.Keys); + Assert.Contains("project: './tsconfig.apphost.json'", files["eslint.config.mjs"]); var tsConfig = ParseJson(files["tsconfig.apphost.json"]); Assert.Equal("./dist/apphost", tsConfig["compilerOptions"]?["outDir"]?.GetValue());