Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,6 @@
}
}
},
"overrides": [
{
"includes": ["modules/*/src/*/types.ts"],
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "off"
},
"complexity": {
"noBannedTypes": "off",
"noEmptyTypeParameters": "off"
}
}
}
}
],
"files": {
"includes": [
"modules/**",
Expand All @@ -62,7 +46,8 @@
"tests/**",
"docs/**",
"website/**",
"!**/wwwroot"
"!**/wwwroot",
"!modules/*/src/*/types.ts"
]
}
}
277 changes: 154 additions & 123 deletions cli/SimpleModule.Cli/Commands/New/NewProjectCommand.cs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions cli/SimpleModule.Cli/Commands/New/NewProjectSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public sealed class NewProjectSettings : CommandSettings
[Description("Show what would be created without writing any files")]
public bool DryRun { get; set; }

[CommandOption("--framework-version")]
[Description("SimpleModule framework version (auto-detected from nuget.org if omitted)")]
public string? FrameworkVersion { get; set; }

public string ResolveName()
{
if (string.IsNullOrWhiteSpace(Name))
Expand Down
39 changes: 39 additions & 0 deletions cli/SimpleModule.Cli/Infrastructure/EmbeddedResourceReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Reflection;

namespace SimpleModule.Cli.Infrastructure;

/// <summary>
/// Reads template files embedded in the CLI assembly.
/// Resource names follow the pattern "Templates.Host.{path}" where path uses dots as separators.
/// </summary>
public static class EmbeddedResourceReader
{
private static readonly Assembly CliAssembly = typeof(EmbeddedResourceReader).Assembly;

/// <summary>
/// Reads an embedded host template resource as a string.
/// </summary>
/// <param name="resourceName">Logical resource name (e.g., "Templates.Host.Program.cs")</param>
public static string ReadTemplate(string resourceName)
{
using var stream = CliAssembly.GetManifestResourceStream(resourceName);
if (stream is null)
{
throw new InvalidOperationException(
$"Embedded resource '{resourceName}' not found in assembly."
);
}

using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}

/// <summary>
/// Reads an embedded host template resource as lines.
/// </summary>
public static List<string> ReadTemplateLines(string resourceName)
{
var content = ReadTemplate(resourceName);
return content.Split(["\r\n", "\n"], StringSplitOptions.None).ToList();
}
}
105 changes: 105 additions & 0 deletions cli/SimpleModule.Cli/Infrastructure/NuGetVersionResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Text.Json;

namespace SimpleModule.Cli.Infrastructure;

public static class NuGetVersionResolver
{
private const string FallbackVersion = "0.0.15";
private static readonly Uri NuGetIndexUri =
new("https://api.nuget.org/v3-flatcontainer/simplemodule.core/index.json");

// Shared HttpClient avoids socket exhaustion from repeated instantiation.
private static readonly HttpClient SharedHttpClient = new() { Timeout = TimeSpan.FromSeconds(5) };

/// <summary>
/// Resolves the SimpleModule framework version to use for a new project.
/// Priority: explicit version > NuGet API > version.json > hardcoded fallback.
/// </summary>
public static string ResolveVersion(
string? explicitVersion = null,
SolutionContext? solution = null
)
{
if (!string.IsNullOrWhiteSpace(explicitVersion))
{
return explicitVersion;
}

var nugetVersion = QueryNuGetLatestVersion();
if (nugetVersion is not null)
{
return nugetVersion;
}

if (solution is not null)
{
var versionFromFile = ReadVersionJson(solution.RootPath);
if (versionFromFile is not null)
{
return versionFromFile;
}
}

return FallbackVersion;
}

private static string? QueryNuGetLatestVersion()
{
try
{
// Intentional sync-over-async: CLI runs single-threaded, no deadlock risk.
var json = SharedHttpClient.GetStringAsync(NuGetIndexUri).GetAwaiter().GetResult();
using var doc = JsonDocument.Parse(json);

if (
doc.RootElement.TryGetProperty("versions", out var versions)
&& versions.ValueKind == JsonValueKind.Array
)
{
var count = versions.GetArrayLength();
if (count > 0)
{
return versions[count - 1].GetString();
}
}
}
catch (HttpRequestException)
{
// Network errors — fall through
}
catch (TaskCanceledException)
{
// Timeout — fall through
}
catch (JsonException)
{
// Parse errors — fall through
}

return null;
}

private static string? ReadVersionJson(string rootPath)
{
try
{
var path = Path.Combine(rootPath, "version.json");
var json = File.ReadAllText(path);
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("version", out var version))
{
return version.GetString();
}
}
catch (JsonException)
{
// Parse errors — fall through
}
catch (IOException)
{
// File read errors — fall through
}

return null;
}
}
16 changes: 16 additions & 0 deletions cli/SimpleModule.Cli/SimpleModule.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,20 @@
<PackageReference Include="NetEscapades.EnumGenerators" />
<PackageReference Include="Spectre.Console.Cli" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\..\template\SimpleModule.Host\SimpleModule.Host.csproj" LogicalName="Templates.Host.SimpleModule.Host.csproj" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\Program.cs" LogicalName="Templates.Host.Program.cs" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\appsettings.json" LogicalName="Templates.Host.appsettings.json" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\appsettings.Development.json" LogicalName="Templates.Host.appsettings.Development.json" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\Components\App.razor" LogicalName="Templates.Host.Components.App.razor" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\Components\InertiaShell.razor" LogicalName="Templates.Host.Components.InertiaShell.razor" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\Components\Routes.razor" LogicalName="Templates.Host.Components.Routes.razor" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\Components\_Imports.razor" LogicalName="Templates.Host.Components._Imports.razor" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\ClientApp\app.tsx" LogicalName="Templates.Host.ClientApp.app.tsx" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\ClientApp\vite.config.ts" LogicalName="Templates.Host.ClientApp.vite.config.ts" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\ClientApp\validate-pages.mjs" LogicalName="Templates.Host.ClientApp.validate-pages.mjs" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\ClientApp\package.json" LogicalName="Templates.Host.ClientApp.package.json" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\Styles\app.css" LogicalName="Templates.Host.Styles.app.css" />
<EmbeddedResource Include="..\..\template\SimpleModule.Host\Properties\launchSettings.json" LogicalName="Templates.Host.Properties.launchSettings.json" />
</ItemGroup>
</Project>
Loading