diff --git a/Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj b/Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj index 7422388..f9ffd11 100644 --- a/Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj +++ b/Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj @@ -1,28 +1,29 @@ - - - - net6.0 - enable - false - - - - - - - - - - - - - - - - - - PreserveNewest - - - - + + + + net8.0;net10.0 + enable + false + Exe + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/Tomlyn.Extensions.Configuration.sln b/Tomlyn.Extensions.Configuration.sln deleted file mode 100644 index fbde3fd..0000000 --- a/Tomlyn.Extensions.Configuration.sln +++ /dev/null @@ -1,26 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tomlyn.Extensions.Configuration", "Tomlyn.Extensions.Configuration\Tomlyn.Extensions.Configuration.csproj", "{B9253898-AA34-4086-A8AD-0D6A35C36DF2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tomlyn.Extensions.Configuration.Tests", "Tomlyn.Extensions.Configuration.Tests\Tomlyn.Extensions.Configuration.Tests.csproj", "{1A6BB949-3C9C-4685-8663-7DD5EDFFA5C4}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {79527171-556C-416D-AE11-D19828BE72FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79527171-556C-416D-AE11-D19828BE72FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79527171-556C-416D-AE11-D19828BE72FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79527171-556C-416D-AE11-D19828BE72FF}.Release|Any CPU.Build.0 = Release|Any CPU - {B9253898-AA34-4086-A8AD-0D6A35C36DF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9253898-AA34-4086-A8AD-0D6A35C36DF2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9253898-AA34-4086-A8AD-0D6A35C36DF2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9253898-AA34-4086-A8AD-0D6A35C36DF2}.Release|Any CPU.Build.0 = Release|Any CPU - {1A6BB949-3C9C-4685-8663-7DD5EDFFA5C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A6BB949-3C9C-4685-8663-7DD5EDFFA5C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A6BB949-3C9C-4685-8663-7DD5EDFFA5C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A6BB949-3C9C-4685-8663-7DD5EDFFA5C4}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/Tomlyn.Extensions.Configuration.slnx b/Tomlyn.Extensions.Configuration.slnx new file mode 100644 index 0000000..ef1b851 --- /dev/null +++ b/Tomlyn.Extensions.Configuration.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/Tomlyn.Extensions.Configuration/TomlConfigurationExtensions.cs b/Tomlyn.Extensions.Configuration/TomlConfigurationExtensions.cs index 964fcdf..8733b87 100644 --- a/Tomlyn.Extensions.Configuration/TomlConfigurationExtensions.cs +++ b/Tomlyn.Extensions.Configuration/TomlConfigurationExtensions.cs @@ -52,6 +52,10 @@ public static IConfigurationBuilder AddTomlStream(this IConfigurationBuilder bui { throw new ArgumentNullException(nameof(builder)); } + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } return builder.Add(s => s.Stream = stream); } diff --git a/Tomlyn.Extensions.Configuration/TomlConfigurationFileParser.cs b/Tomlyn.Extensions.Configuration/TomlConfigurationFileParser.cs index 8688d16..23f2659 100644 --- a/Tomlyn.Extensions.Configuration/TomlConfigurationFileParser.cs +++ b/Tomlyn.Extensions.Configuration/TomlConfigurationFileParser.cs @@ -20,8 +20,9 @@ public static IDictionary Parse(Stream input) private IDictionary ParseStream(Stream input) { using var reader = new StreamReader(input); - var doc = Toml.Parse(reader.ReadToEnd()); - VisitObject(doc.ToModel()); + var model = TomlSerializer.Deserialize(reader.ReadToEnd()) + ?? throw new FormatException("TOML deserialization returned null"); + VisitObject(model); return _data; } diff --git a/Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj b/Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj index 7c264e9..82d72b2 100644 --- a/Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj +++ b/Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj @@ -1,21 +1,20 @@ - netstandard2.0;net461 + netstandard2.0;net8.0;net10.0 TomlConfigurationProvider using Tomlyn https://github.com/bugproof/Tomlyn.Extensions.Configuration git - 1.0.6 + 2.0.0 toml;configuration - https://raw.githubusercontent.com/xoofx/Tomlyn/master/img/logo.png MIT true default - - + + diff --git a/docs/superpowers/plans/2026-04-16-modernization.md b/docs/superpowers/plans/2026-04-16-modernization.md new file mode 100644 index 0000000..2708deb --- /dev/null +++ b/docs/superpowers/plans/2026-04-16-modernization.md @@ -0,0 +1,258 @@ +# Tomlyn.Extensions.Configuration Modernization Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Modernize the solution to .slnx, target net8.0/net10.0/netstandard2.0, upgrade Tomlyn to v2.3.0, and migrate tests to xUnit v3. + +**Architecture:** Replace legacy .sln with .slnx XML format. Update target frameworks (drop net461, add net8.0 and net10.0). Migrate Tomlyn API from `Toml.Parse().ToModel()` to `Toml.ToModel()`. Migrate test framework from xUnit v2 to xUnit v3. + +**Tech Stack:** .NET 10 SDK, Tomlyn 2.3.0, Microsoft.Extensions.Configuration 10.0.6, xUnit v3 (3.2.2), FluentAssertions 8.9.0 + +--- + +## File Map + +| File | Action | Responsibility | +|------|--------|----------------| +| `Tomlyn.Extensions.Configuration.sln` | Delete | Legacy solution format | +| `Tomlyn.Extensions.Configuration.slnx` | Create | New XML solution format | +| `Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj` | Modify | TFMs, version, deps | +| `Tomlyn.Extensions.Configuration/TomlConfigurationFileParser.cs` | Modify | Tomlyn v2 API migration | +| `Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj` | Modify | TFMs, xUnit v3, deps | + +--- + +### Task 1: Convert solution file from .sln to .slnx + +**Files:** +- Delete: `Tomlyn.Extensions.Configuration.sln` +- Create: `Tomlyn.Extensions.Configuration.slnx` + +- [ ] **Step 1: Create the new .slnx file** + +Create `Tomlyn.Extensions.Configuration.slnx` with: + +```xml + + + + +``` + +- [ ] **Step 2: Delete the old .sln file** + +```bash +rtk git rm Tomlyn.Extensions.Configuration.sln +``` + +- [ ] **Step 3: Verify the new solution loads** + +```bash +rtk dotnet sln Tomlyn.Extensions.Configuration.slnx list +``` + +Expected: Both projects listed. + +--- + +### Task 2: Update main project (.csproj) + +**Files:** +- Modify: `Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj` + +- [ ] **Step 1: Update the .csproj file** + +Replace the entire contents of `Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj` with: + +```xml + + + + netstandard2.0;net8.0;net10.0 + TomlConfigurationProvider using Tomlyn + https://github.com/bugproof/Tomlyn.Extensions.Configuration + git + 2.0.0 + toml;configuration + MIT + true + default + + + + + + + + +``` + +Changes from original: +- `TargetFrameworks`: `netstandard2.0;net461` → `netstandard2.0;net8.0;net10.0` +- `PackageVersion`: `1.0.6` → `2.0.0` +- `PackageIconUrl` removed (deprecated NuGet property) +- `Tomlyn`: `0.14.3` → `2.3.0` +- `Microsoft.Extensions.Configuration.FileExtensions`: `5.0.0` → `10.0.6` + +- [ ] **Step 2: Verify the project restores** + +```bash +rtk dotnet restore Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj +``` + +Expected: Restore succeeds for all three TFMs. + +--- + +### Task 3: Migrate Tomlyn v2 API in TomlConfigurationFileParser.cs + +**Files:** +- Modify: `Tomlyn.Extensions.Configuration/TomlConfigurationFileParser.cs:20-25` + +- [ ] **Step 1: Update the ParseStream method** + +In `Tomlyn.Extensions.Configuration/TomlConfigurationFileParser.cs`, replace lines 20-26: + +```csharp + private IDictionary ParseStream(Stream input) + { + using var reader = new StreamReader(input); + var doc = Toml.Parse(reader.ReadToEnd()); + VisitObject(doc.ToModel()); + return _data; + } +``` + +With: + +```csharp + private IDictionary ParseStream(Stream input) + { + using var reader = new StreamReader(input); + var model = Toml.ToModel(reader.ReadToEnd()); + VisitObject(model); + return _data; + } +``` + +This changes the Tomlyn API call from the removed `Toml.Parse().ToModel()` (legacy v0.x) to `Toml.ToModel()` (v1.0+/v2.x). The return type is still `TomlTable`. Parse errors now throw `TomlException` instead of setting `doc.HasErrors`, but the caller (`TomlConfigurationProvider.Load`) already catches all exceptions and wraps them as `FormatException`. + +- [ ] **Step 2: Verify the main project builds for all TFMs** + +```bash +rtk dotnet build Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj +``` + +Expected: Build succeeds for netstandard2.0, net8.0, and net10.0. + +--- + +### Task 4: Update test project (.csproj) for xUnit v3 + +**Files:** +- Modify: `Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj` + +- [ ] **Step 1: Update the test .csproj file** + +Replace the entire contents of `Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj` with: + +```xml + + + + net8.0;net10.0 + enable + false + Exe + + + + + + + + + + + + + + + + + + PreserveNewest + + + + +``` + +Changes from original: +- `TargetFramework`: `net6.0` → `TargetFrameworks`: `net8.0;net10.0` (multi-target) +- Added `Exe` (required for xUnit v3) +- `xunit` 2.4.1 → `xunit.v3` 3.2.2 +- `xunit.runner.visualstudio` 2.4.5 → 3.1.5 +- `FluentAssertions` 6.6.0 → 8.9.0 +- `Microsoft.Extensions.Configuration.Binder` 6.0.0 → 10.0.6 +- `Microsoft.NET.Test.Sdk` 17.1.0 → 18.4.0 + +- [ ] **Step 2: Verify the test project restores** + +```bash +rtk dotnet restore Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj +``` + +Expected: Restore succeeds for net8.0 and net10.0. + +--- + +### Task 5: Build the full solution and run tests + +**Files:** None (verification only) + +- [ ] **Step 1: Build the full solution** + +```bash +rtk dotnet build Tomlyn.Extensions.Configuration.slnx +``` + +Expected: Build succeeds with 0 errors. Warnings about nullable reference types are acceptable. + +- [ ] **Step 2: Run all tests** + +```bash +rtk dotnet test Tomlyn.Extensions.Configuration.slnx +``` + +Expected: All tests pass on both net8.0 and net10.0 TFMs: +- `TomlConfigurationExtensionsTest.Bind` - PASS +- `TomlToSnakeCaseModifierTest.BindCorrect` - PASS + +- [ ] **Step 3: Troubleshoot if tests fail** + +If tests fail due to FluentAssertions 8.x API changes, check the error message. Common changes: +- `BeEquivalentTo` options API may have changed +- Some assertion methods may have been renamed + +If tests fail due to Tomlyn v2 type changes, check whether `TomlTableArray` is still used or if arrays of tables are now represented differently. + +--- + +### Task 6: Commit all changes + +- [ ] **Step 1: Stage and commit** + +```bash +rtk git add Tomlyn.Extensions.Configuration.slnx Tomlyn.Extensions.Configuration/Tomlyn.Extensions.Configuration.csproj Tomlyn.Extensions.Configuration/TomlConfigurationFileParser.cs Tomlyn.Extensions.Configuration.Tests/Tomlyn.Extensions.Configuration.Tests.csproj +rtk git rm Tomlyn.Extensions.Configuration.sln +rtk git commit -m "Modernize solution: slnx, net8.0/net10.0, Tomlyn v2.3.0, xUnit v3 + +- Convert .sln to .slnx format +- Target netstandard2.0, net8.0, net10.0 (drop net461) +- Upgrade Tomlyn from 0.14.3 to 2.3.0 +- Migrate Toml.Parse().ToModel() to Toml.ToModel() +- Upgrade test framework to xUnit v3 (3.2.2) +- Update all NuGet dependencies to latest stable +- Bump package version to 2.0.0" +``` diff --git a/docs/superpowers/specs/2026-04-16-modernization-design.md b/docs/superpowers/specs/2026-04-16-modernization-design.md new file mode 100644 index 0000000..cf1ac97 --- /dev/null +++ b/docs/superpowers/specs/2026-04-16-modernization-design.md @@ -0,0 +1,92 @@ +# Tomlyn.Extensions.Configuration Modernization Design + +## Summary + +Modernize the Tomlyn.Extensions.Configuration project to support .NET 8/10, upgrade to Tomlyn v2.3.0, convert to .slnx format, and bump package version to 2.0.0. + +## Scope + +### In Scope + +1. Solution file: `.sln` -> `.slnx` +2. Target frameworks: remove `net461`, keep `netstandard2.0`, add `net8.0` and `net10.0` +3. Tomlyn dependency: `0.14.3` -> `2.3.0` +4. Microsoft.Extensions dependencies: upgrade to latest stable +5. Test project: update to `net8.0;net10.0` and latest test dependencies +6. Package version: `1.0.6` -> `2.0.0` + +### Out of Scope + +- Directory.Build.props / Directory.Packages.props (Central Package Management) +- global.json +- CI/CD configuration +- Public API surface changes (extensions, sources, providers) +- TomlToSnakeCaseModifier logic (unaffected by Tomlyn v2) + +## Design Details + +### 1. Solution File (.slnx) + +Delete `Tomlyn.Extensions.Configuration.sln` and create `Tomlyn.Extensions.Configuration.slnx`: + +```xml + + + + +``` + +### 2. Main Project (.csproj) + +| Property | Old | New | +|----------|-----|-----| +| TargetFrameworks | `netstandard2.0;net461` | `netstandard2.0;net8.0;net10.0` | +| PackageVersion | `1.0.6` | `2.0.0` | +| Tomlyn | `0.14.3` | `2.3.0` | +| M.E.Configuration.FileExtensions | `5.0.0` | latest stable | + +Clean up deprecated properties: +- Remove `PackageIconUrl` (deprecated in modern NuGet) + +### 3. Tomlyn v2 API Migration + +Single code change in `TomlConfigurationFileParser.cs`: + +```csharp +// Old (v0.14.3): +var doc = Toml.Parse(reader.ReadToEnd()); +VisitObject(doc.ToModel()); + +// New (v2.3.0): +var model = TomlSerializer.Deserialize(reader.ReadToEnd()); +VisitObject(model); +``` + +Rationale: +- `Toml` static facade was removed in Tomlyn v1.0, replaced by `TomlSerializer` +- `TomlSerializer.Deserialize(string)` returns `TomlTable`, throwing `TomlException` on errors +- `TomlTable`, `TomlArray`, `TomlTableArray` types still exist in v2 - visitor logic unchanged +- `TomlConfigurationProvider.Load()` already wraps exceptions as `FormatException` + +### 4. Test Project (.csproj) + +| Property | Old | New | +|----------|-----|-----| +| TargetFrameworks | `net6.0` | `net8.0;net10.0` | +| FluentAssertions | `6.6.0` | latest stable | +| M.E.Configuration.Binder | `6.0.0` | latest stable | +| xunit | `2.4.1` | remove, replaced by `xunit.v3` (latest v3) | +| xunit.runner.visualstudio | `2.4.5` | remove, replaced by `xunit.v3.runner.visualstudio` (latest v3) | +| Microsoft.NET.Test.Sdk | `17.1.0` | latest stable | + +xUnit v3 migration notes: +- Package names change: `xunit` -> `xunit.v3`, `xunit.runner.visualstudio` -> `xunit.v3.runner.visualstudio` +- `[Fact]` and `[Theory]` attributes remain compatible +- Test code may need minor namespace adjustments + +### 5. Risk Assessment + +- **Low risk**: Framework target changes - netstandard2.0 remains the baseline +- **Low risk**: Tomlyn API change - only one call site, well-documented migration +- **Low risk**: .slnx format - straightforward XML, supported since VS 17.10/.NET 9 +- **Medium risk**: Tomlyn v2 may change how certain TOML types are represented - tests will validate