diff --git a/cli/SimpleModule.Cli/Commands/New/NewProjectCommand.cs b/cli/SimpleModule.Cli/Commands/New/NewProjectCommand.cs index 79bd77c8..f2a6ba85 100644 --- a/cli/SimpleModule.Cli/Commands/New/NewProjectCommand.cs +++ b/cli/SimpleModule.Cli/Commands/New/NewProjectCommand.cs @@ -491,7 +491,7 @@ private static string StarterViteConfig() => """ import { defineModuleConfig } from '@simplemodule/client/module'; - export default defineModuleConfig(__dirname); + export default defineModuleConfig(import.meta.dirname); """; private static string StarterPackageJson(string projectName) => @@ -501,9 +501,9 @@ private static string StarterPackageJson(string projectName) => "name": "@{{projectName.ToLowerInvariant()}}/items", "version": "0.0.0", "scripts": { - "build": "vite build", - "build:dev": "cross-env VITE_MODE=dev vite build", - "watch": "cross-env VITE_MODE=dev vite build --watch" + "build": "cross-env VITE_MODE=prod vite build --configLoader runner", + "build:dev": "cross-env VITE_MODE=dev vite build --configLoader runner", + "watch": "cross-env VITE_MODE=dev vite build --configLoader runner --watch" }, "peerDependencies": { "react": "^19.0.0", diff --git a/cli/SimpleModule.Cli/Templates/ProjectTemplates.cs b/cli/SimpleModule.Cli/Templates/ProjectTemplates.cs index 84c26231..d3eca953 100644 --- a/cli/SimpleModule.Cli/Templates/ProjectTemplates.cs +++ b/cli/SimpleModule.Cli/Templates/ProjectTemplates.cs @@ -707,11 +707,13 @@ private static string GenerateRootPackageJson( string frameworkVersion ) { - var name = projectName.ToLowerInvariant(); + // npm 'name' field must be lowercase; workspace glob uses actual project casing. + var npmName = projectName.ToLowerInvariant(); string clientDep; string uiDep; string themeDep; + string tsconfigDep; if (frameworkPackagesPath is not null) { @@ -719,22 +721,24 @@ string frameworkVersion clientDep = $"\"file:{pkgPath}/SimpleModule.Client\""; uiDep = $"\"file:{pkgPath}/SimpleModule.UI\""; themeDep = $"\"file:{pkgPath}/SimpleModule.Theme.Default\""; + tsconfigDep = $"\"file:{pkgPath}/SimpleModule.TsConfig\""; } else { clientDep = $"\"^{frameworkVersion}\""; uiDep = $"\"^{frameworkVersion}\""; themeDep = $"\"^{frameworkVersion}\""; + tsconfigDep = $"\"^{frameworkVersion}\""; } return $$""" { "private": true, - "name": "{{name}}", + "name": "{{npmName}}", "version": "0.0.0", "workspaces": [ "src/modules/*/src/*", - "src/{{name}}.Host/ClientApp" + "src/{{projectName}}.Host/ClientApp" ], "scripts": { "lint": "biome lint .", @@ -745,23 +749,25 @@ string frameworkVersion "build:dev": "cross-env VITE_MODE=dev npm run build --workspaces --if-present" }, "devDependencies": { - "@biomejs/biome": "^2.4.6", - "@tailwindcss/vite": "^4.2.1", + "@biomejs/biome": "^2.4.10", + "@tailwindcss/vite": "^4.2.2", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", - "@vitejs/plugin-react": "^4.4.0", - "cross-env": "^7.0.3", - "typescript": "^5.8.0", - "vite": "^6.2.0" + "@vitejs/plugin-react": "^6.0.1", + "cross-env": "^10.1.0", + "typescript": "^6.0.2", + "vite": "^8.0.3" }, "dependencies": { - "@inertiajs/react": "^2.0.0", + "@inertiajs/react": "^3.0.0", "@simplemodule/client": {{clientDep}}, "@simplemodule/ui": {{uiDep}}, "@simplemodule/theme-default": {{themeDep}}, + "@simplemodule/tsconfig": {{tsconfigDep}}, + "esbuild": "^0.27.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "tailwindcss": "^4.2.1" + "tailwindcss": "^4.2.2" } } """; diff --git a/framework/SimpleModule.Generator/Discovery/CoreSymbols.cs b/framework/SimpleModule.Generator/Discovery/CoreSymbols.cs index cf493e0c..be98b6b7 100644 --- a/framework/SimpleModule.Generator/Discovery/CoreSymbols.cs +++ b/framework/SimpleModule.Generator/Discovery/CoreSymbols.cs @@ -27,7 +27,8 @@ internal readonly record struct CoreSymbols( INamedTypeSymbol? ModuleFeatures, INamedTypeSymbol? SaveChangesInterceptor, INamedTypeSymbol? ModuleOptions, - bool HasAgentsAssembly + bool HasAgentsAssembly, + bool HasRagAssembly ) { /// @@ -81,6 +82,10 @@ bool HasAgentsAssembly ModuleOptions: compilation.GetTypeByMetadataName("SimpleModule.Core.IModuleOptions"), HasAgentsAssembly: compilation.GetTypeByMetadataName( "SimpleModule.Agents.SimpleModuleAgentExtensions" + ) + is not null, + HasRagAssembly: compilation.GetTypeByMetadataName( + "SimpleModule.Rag.RagSettingsDefinitions" ) is not null ); diff --git a/framework/SimpleModule.Generator/Discovery/DiscoveryData.cs b/framework/SimpleModule.Generator/Discovery/DiscoveryData.cs index e55d5741..c2323c80 100644 --- a/framework/SimpleModule.Generator/Discovery/DiscoveryData.cs +++ b/framework/SimpleModule.Generator/Discovery/DiscoveryData.cs @@ -49,6 +49,7 @@ internal readonly record struct DiscoveryData( ImmutableArray KnowledgeSources, ImmutableArray ContractsAssemblyNames, bool HasAgentsAssembly, + bool HasRagAssembly, string HostAssemblyName ) { @@ -80,6 +81,7 @@ string HostAssemblyName ImmutableArray.Empty, ImmutableArray.Empty, false, + false, "" ); @@ -103,6 +105,7 @@ public bool Equals(DiscoveryData other) && KnowledgeSources.SequenceEqual(other.KnowledgeSources) && ContractsAssemblyNames.SequenceEqual(other.ContractsAssemblyNames) && HasAgentsAssembly == other.HasAgentsAssembly + && HasRagAssembly == other.HasRagAssembly && HostAssemblyName == other.HostAssemblyName; } @@ -127,6 +130,7 @@ public override int GetHashCode() hash = HashHelper.HashArray(hash, KnowledgeSources); hash = HashHelper.HashArray(hash, ContractsAssemblyNames); hash = HashHelper.Combine(hash, HasAgentsAssembly.GetHashCode()); + hash = HashHelper.Combine(hash, HasRagAssembly.GetHashCode()); hash = HashHelper.Combine(hash, (HostAssemblyName ?? "").GetHashCode()); return hash; } diff --git a/framework/SimpleModule.Generator/Discovery/DiscoveryDataBuilder.cs b/framework/SimpleModule.Generator/Discovery/DiscoveryDataBuilder.cs index bf5e36d0..ad2e08f5 100644 --- a/framework/SimpleModule.Generator/Discovery/DiscoveryDataBuilder.cs +++ b/framework/SimpleModule.Generator/Discovery/DiscoveryDataBuilder.cs @@ -31,6 +31,7 @@ internal static DiscoveryData Build( List knowledgeSources, Dictionary contractsAssemblyMap, bool hasAgentsAssembly, + bool hasRagAssembly, string hostAssemblyName ) { @@ -175,6 +176,7 @@ string hostAssemblyName .ToImmutableArray(), contractsAssemblyMap.Keys.ToImmutableArray(), hasAgentsAssembly, + hasRagAssembly, hostAssemblyName ); } diff --git a/framework/SimpleModule.Generator/Discovery/SymbolDiscovery.cs b/framework/SimpleModule.Generator/Discovery/SymbolDiscovery.cs index 337b79af..d63450c6 100644 --- a/framework/SimpleModule.Generator/Discovery/SymbolDiscovery.cs +++ b/framework/SimpleModule.Generator/Discovery/SymbolDiscovery.cs @@ -255,6 +255,7 @@ CancellationToken cancellationToken knowledgeSources, contractsAssemblyMap, s.HasAgentsAssembly, + s.HasRagAssembly, hostAssemblyName ); } diff --git a/framework/SimpleModule.Generator/Emitters/AgentExtensionsEmitter.cs b/framework/SimpleModule.Generator/Emitters/AgentExtensionsEmitter.cs index b97462a1..1ef34922 100644 --- a/framework/SimpleModule.Generator/Emitters/AgentExtensionsEmitter.cs +++ b/framework/SimpleModule.Generator/Emitters/AgentExtensionsEmitter.cs @@ -30,6 +30,18 @@ public void Emit(SourceProductionContext context, DiscoveryData data) ); sb.AppendLine(" {"); + if (!data.HasAgentsAssembly) + { + sb.AppendLine(" return services;"); + sb.AppendLine(" }"); + sb.AppendLine("}"); + context.AddSource( + "AgentExtensions.g.cs", + SourceText.From(sb.ToString(), Encoding.UTF8) + ); + return; + } + // Register agent definitions foreach (var agent in data.AgentDefinitions) { diff --git a/framework/SimpleModule.Generator/Emitters/SettingsExtensionsEmitter.cs b/framework/SimpleModule.Generator/Emitters/SettingsExtensionsEmitter.cs index e8ccd18d..0650f8c5 100644 --- a/framework/SimpleModule.Generator/Emitters/SettingsExtensionsEmitter.cs +++ b/framework/SimpleModule.Generator/Emitters/SettingsExtensionsEmitter.cs @@ -34,11 +34,14 @@ public void Emit(SourceProductionContext context, DiscoveryData data) ); } - sb.AppendLine(); - sb.AppendLine(" // RAG settings definitions"); - sb.AppendLine( - " global::SimpleModule.Rag.RagSettingsDefinitions.Register(settings);" - ); + if (data.HasRagAssembly) + { + sb.AppendLine(); + sb.AppendLine(" // RAG settings definitions"); + sb.AppendLine( + " global::SimpleModule.Rag.RagSettingsDefinitions.Register(settings);" + ); + } sb.AppendLine(); sb.AppendLine( diff --git a/framework/SimpleModule.Hosting/SimpleModuleHostExtensions.cs b/framework/SimpleModule.Hosting/SimpleModuleHostExtensions.cs index b88f63f4..7150accd 100644 --- a/framework/SimpleModule.Hosting/SimpleModuleHostExtensions.cs +++ b/framework/SimpleModule.Hosting/SimpleModuleHostExtensions.cs @@ -145,12 +145,23 @@ public static async Task UseSimpleModuleInfrastructure(this WebApplication app) foreach (var info in infos) { - if (info.ModuleName != DatabaseConstants.HostModuleName) + if (scope.ServiceProvider.GetService(info.DbContextType) is not DbContext db) continue; - if (scope.ServiceProvider.GetService(info.DbContextType) is DbContext db) + // DbContexts with EF migrations use MigrateAsync; those without (e.g. scaffolded + // module contexts that ship no migrations) fall back to EnsureCreatedAsync so + // their tables exist on first run. Only the Host context typically ships + // migrations; module contexts rely on the unified HostDbContext for schema. + if (info.ModuleName == DatabaseConstants.HostModuleName) { - await db.Database.MigrateAsync(); + if (db.Database.GetMigrations().Any()) + { + await db.Database.MigrateAsync(); + } + else + { + await db.Database.EnsureCreatedAsync(); + } } } } diff --git a/tests/SimpleModule.Generator.Tests/TopologicalSortTests.cs b/tests/SimpleModule.Generator.Tests/TopologicalSortTests.cs index 390470c0..6f490b84 100644 --- a/tests/SimpleModule.Generator.Tests/TopologicalSortTests.cs +++ b/tests/SimpleModule.Generator.Tests/TopologicalSortTests.cs @@ -249,6 +249,7 @@ public void SortModules_WithDependencies_ReordersByDependency() ImmutableArray.Empty, ImmutableArray.Empty, false, + false, "SimpleModule.Host" ); @@ -324,6 +325,7 @@ public void SortModules_WithCycle_ReturnsOriginalOrder() ImmutableArray.Empty, ImmutableArray.Empty, false, + false, "SimpleModule.Host" ); @@ -415,6 +417,7 @@ public void SortModules_NoDependencies_PreservesOriginalOrder() ImmutableArray.Empty, ImmutableArray.Empty, false, + false, "SimpleModule.Host" );