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
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Template files in `src/Data/` are embedded as resources, not file system copies:
Access via `Assembly.GetManifestResourceStream("solrevdev.seedfolder.Data.{filename}")` in `WriteFileAsync()`

### Multi-Framework Targeting
Targets `net8.0;net9.0` for modern .NET compatibility. .NET 8 is LTS (supported until November 2026) and .NET 9 is STS (18-month support). Version bumps in `.csproj` trigger NuGet deployment.
Targets `net10.0;net11.0`, the supported .NET versions. `global.json` pins SDK `10.0.100` with `rollForward: latestMajor`, so .NET 10 (LTS) is the baseline and the newest installed SDK is used. With SDK 11 installed the project builds `net10.0` and `net11.0`. With only SDK 10 it builds `net10.0` alone, because `TargetFrameworks` adds `net11.0` only when `NETCoreSdkVersion` is 11.0 or later. CI installs both SDKs, so the published package always holds both targets. .NET 8 and .NET 9 left support on 10 November 2026 and are no longer targeted. `RollForward` is `Major`, so the tool also runs on newer runtimes. Version bumps in `.csproj` trigger NuGet deployment on push to `master`.

## Key Development Workflows

Expand Down
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,36 @@ jobs:
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
11.0.x

- name: dotnet build
run: dotnet build solrevdev.seedfolder.sln --configuration Release

- name: run integration tests
run: ./tests/integration-test.sh
- name: run integration tests (net10.0)
run: ./tests/integration-test.sh net10.0

- name: run integration tests (net11.0)
run: ./tests/integration-test.sh net11.0

- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: check package targets
shell: bash
run: |
set -euo pipefail
# The targets depend on the SDK that built them; fail rather than
# ship a package missing one.
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
# Name the exact file: a glob would also match the symbols package,
# and unzip reads any extra argument as a member filter.
LISTING=$(unzip -l "src/nupkg/solrevdev.seedfolder.${version}.nupkg")
for tfm in net10.0 net11.0; do
grep -q "tools/$tfm/any/solrevdev.seedfolder.dll" <<<"$LISTING" \
|| { echo "Package is missing tools/$tfm" >&2; exit 1; }
done

- name: verify versioned package
id: package
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/bin/Debug/net8.0/solrevdev.seedfolder.dll",
"program": "${workspaceFolder}/src/bin/Debug/net10.0/solrevdev.seedfolder.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
Expand Down
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ This file documents the project-specific guidance for coding agents working in t

## Project overview

`seedfolder` is a .NET Global Tool that creates project directories from embedded templates. The application is a single-file console program in `src/Program.cs` and targets `net8.0`, `net9.0`, and `net10.0`.
`seedfolder` is a .NET Global Tool that creates project directories from embedded templates. The application is a single-file console program in `src/Program.cs` and targets `net10.0` and `net11.0`. Only supported .NET versions are targeted; .NET 8 and .NET 9 left support on 10 November 2026.

## SDK policy

`global.json` pins SDK `10.0.100` with `rollForward: latestMajor`, so .NET 10 (LTS) is the baseline and the newest installed SDK is used. With SDK 11 installed the project builds `net10.0` and `net11.0`. With only SDK 10 it builds `net10.0` alone, because `TargetFrameworks` adds `net11.0` only when `NETCoreSdkVersion` is 11.0 or later. CI installs both SDKs, so the published package always holds both targets. Do not drop the `TargetFrameworks` condition or the explicit `10.0.x` and `11.0.x` entries in `ci.yml`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
./tests/integration-test.sh net10.0
./tests/integration-test.sh net11.0 # needs SDK 11
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,10 +436,11 @@ git commit -m "Initial commit"

## Requirements

This tool requires **.NET 8.0, .NET 9.0, or .NET 10.0 SDK** to be installed on your system.
This tool requires the **.NET 10.0 SDK or later** to be installed on your system. .NET 11 is used when present.

- **Supported Platforms**: Windows, macOS, Linux
- **Runtime**: .NET 8.0 or later
- **Runtime**: .NET 10.0 or later
- **Older runtimes**: From 1.7.0 the tool no longer targets .NET 8 or .NET 9, which left support on 10 November 2026. A machine with only .NET 8 or .NET 9 cannot install new releases. Install .NET 10 or later, or pin version 1.6.2.
- **Installation**: Via .NET Global Tools

## Installation
Expand Down Expand Up @@ -489,13 +490,18 @@ dotnet run --project src/solrevdev.seedfolder.csproj --framework net10.0 -- --te
# If you omit `--` and run `dotnet run --project ... --help`, the .NET CLI help will be shown.
```

### SDK policy

`global.json` pins SDK `10.0.100` with `rollForward: latestMajor`, so .NET 10 (LTS) is the baseline and the newest installed SDK is used. With SDK 11 installed the project builds `net10.0` and `net11.0`. With only SDK 10 it builds `net10.0` alone, because `TargetFrameworks` adds `net11.0` only when `NETCoreSdkVersion` is 11.0 or later. CI installs both SDKs, so the published package always holds both targets.

### Building and Testing
```bash
# Build the project
dotnet build

# Run tests
dotnet test
# Run the integration tests for each target framework
./tests/integration-test.sh net10.0
./tests/integration-test.sh net11.0 # needs SDK 11

# Create package
dotnet pack -c release
Expand Down
1 change: 1 addition & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "sdk": { "version": "10.0.100", "rollForward": "latestMajor", "allowPrerelease": false } }
12 changes: 7 additions & 5 deletions src/solrevdev.seedfolder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<!-- The published package needs both targets; CI installs both SDKs so it ships both, and SDK 10 alone builds net10.0 only. -->
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '11.0'))">net10.0;net11.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<!-- Roll-forward does not cross major version boundaries by default, so a
tool built for one major will not start when only a newer major is
Expand All @@ -14,16 +16,16 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.6.2</Version>
<Version>1.7.0</Version>
<Title>solrevdev.seedfolder</Title>
<Description>.NET Core Global Tool that creates a folder and copies dotfiles into it therefore seeding a folder.</Description>
<PackageDescription>.NET Core Global Tool that creates a folder and copies dotfiles into it therefore seeding a folder.</PackageDescription>
<Description>.NET Global Tool for .NET 10 and .NET 11 that creates a folder and copies dotfiles into it therefore seeding a folder.</Description>
<PackageDescription>.NET Global Tool for .NET 10 and .NET 11 that creates a folder and copies dotfiles into it therefore seeding a folder.</PackageDescription>
<Authors>solrevdev</Authors>
<Company>solrevdev</Company>
<RepositoryUrl>https://github.com/solrevdev/seedfolder</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/solrevdev/seedfolder</PackageProjectUrl>
<PackageReleaseNotes>https://github.com/solrevdev/seedfolder/releases</PackageReleaseNotes>
<PackageReleaseNotes>1.7.0: Targets net10.0 and net11.0 only. Support for net8.0 and net9.0 is dropped because .NET 8 and .NET 9 left support on 10 November 2026. A machine with only the .NET 8 or .NET 9 runtime can no longer install new releases; install .NET 10 or later, or stay on 1.6.2. Full history: https://github.com/solrevdev/seedfolder/releases</PackageReleaseNotes>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<PackageTags>dotnetcore;dotnet;csharp;dotnet-global-tool;dotnet-global-tools;</PackageTags>
Expand Down
43 changes: 23 additions & 20 deletions tests/integration-test.ps1
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Integration test for seedfolder - validates all template types work correctly (PowerShell)

# Target framework to test; run once per entry in TargetFrameworks.
param([string]$Framework = "net10.0")

$ErrorActionPreference = "Stop"

Write-Host "🚀 Starting seedfolder integration tests..." -ForegroundColor Green
Write-Host "🚀 Starting seedfolder integration tests ($Framework)..." -ForegroundColor Green

# Navigate to project root
Set-Location "$(Split-Path $PSScriptRoot -Parent)"

# Build the project
Write-Host "📦 Building project..." -ForegroundColor Yellow
dotnet build src/solrevdev.seedfolder.csproj --configuration Release --framework net8.0
dotnet build src/solrevdev.seedfolder.csproj --configuration Release --framework $Framework

# Create test directory
$TestDir = "test-output"
Expand Down Expand Up @@ -53,102 +56,102 @@ function Assert-NoDotnetPatterns {
try {
# Test dotnet template
Write-Host "🔹 Testing dotnet template..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --template dotnet test-dotnet
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet --template dotnet test-dotnet
Assert-CommonGitignore "test-dotnet/.gitignore"
Assert-HasDotnetPatterns "test-dotnet/.gitignore"
if (!(Test-Path "test-dotnet/omnisharp.json")) { throw "dotnet omnisharp.json missing" }

# Test node template
Write-Host "🔹 Testing node template..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node test-node
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet -t node test-node
if (!(Test-Path "test-node/package.json")) { throw "node package.json missing" }
if (!(Test-Path "test-node/index.js")) { throw "node index.js missing" }
Assert-CommonGitignore "test-node/.gitignore"
Assert-NoDotnetPatterns "test-node/.gitignore"

# Test python template
Write-Host "🔹 Testing python template..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --template python test-python
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet --template python test-python
if (!(Test-Path "test-python/main.py")) { throw "python main.py missing" }
if (!(Test-Path "test-python/requirements.txt")) { throw "python requirements.txt missing" }
Assert-CommonGitignore "test-python/.gitignore"
Assert-NoDotnetPatterns "test-python/.gitignore"

# Test ruby template
Write-Host "🔹 Testing ruby template..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --type ruby test-ruby
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet --type ruby test-ruby
if (!(Test-Path "test-ruby/Gemfile")) { throw "ruby Gemfile missing" }
if (!(Test-Path "test-ruby/main.rb")) { throw "ruby main.rb missing" }
Assert-CommonGitignore "test-ruby/.gitignore"
Assert-NoDotnetPatterns "test-ruby/.gitignore"

# Test markdown template
Write-Host "🔹 Testing markdown template..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t markdown test-markdown
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet -t markdown test-markdown
if (!(Test-Path "test-markdown/README.md")) { throw "markdown README.md missing" }
Assert-CommonGitignore "test-markdown/.gitignore"
Assert-NoDotnetPatterns "test-markdown/.gitignore"

# Test universal template
Write-Host "🔹 Testing universal template..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t universal test-universal
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet -t universal test-universal
if (!(Test-Path "test-universal/README.md")) { throw "universal README.md missing" }
Assert-CommonGitignore "test-universal/.gitignore"
Assert-NoDotnetPatterns "test-universal/.gitignore"

# Test dry-run mode
Write-Host "🔹 Testing dry-run mode..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --dry-run -t node test-dry-run
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --dry-run -t node test-dry-run
if (Test-Path "test-dry-run") { throw "dry-run should not create directory" }

# Test force mode
Write-Host "🔹 Testing force mode..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node test-force-existing
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --force -t python test-force-existing
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet -t node test-force-existing
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet --force -t python test-force-existing
if (!(Test-Path "test-force-existing/main.py")) { throw "force mode should overwrite with python template" }

# Test space handling
Write-Host "🔹 Testing space handling..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node "test space project"
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet -t node "test space project"
if (!(Test-Path "test-space-project")) { throw "space handling failed" }

# Test flags after the folder name
Write-Host "🔹 Testing flags after folder name (dry-run)..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- test-flags-after --dry-run --quiet -t node
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- test-flags-after --dry-run --quiet -t node
if (Test-Path "test-flags-after") { throw "flags-after-folder-name dry-run should not create directory" }

Write-Host "🔹 Testing flags interleaved around folder name..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --force -t python test-flags-interleaved --dry-run
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --quiet --force -t python test-flags-interleaved --dry-run
if (Test-Path "test-flags-interleaved") { throw "interleaved dry-run should not create directory" }

# Test error handling - unknown flag
Write-Host "🔹 Testing error handling - unknown flag..." -ForegroundColor Cyan
$result = dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --bogus test-error 2>$null
$result = dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --bogus test-error 2>$null
if ($LASTEXITCODE -eq 0) { throw "Should have failed with unknown flag" }

# Test error handling - invalid template type
Write-Host "🔹 Testing error handling - invalid template..." -ForegroundColor Cyan
$result = dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- -t invalidtype test-error 2>$null
$result = dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- -t invalidtype test-error 2>$null
if ($LASTEXITCODE -eq 0) { throw "Should have failed with invalid template type" }

# Test error handling - missing template argument
Write-Host "🔹 Testing error handling - missing template argument..." -ForegroundColor Cyan
$result = dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --template 2>$null
$result = dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --template 2>$null
if ($LASTEXITCODE -eq 0) { throw "Should have failed with missing template argument" }

# Test help command
Write-Host "🔹 Testing help command..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --help >$null
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --help >$null
if ($LASTEXITCODE -ne 0) { throw "help command failed" }

# Test version command
Write-Host "🔹 Testing version command..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --version >$null
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --version >$null
if ($LASTEXITCODE -ne 0) { throw "version command failed" }

# Test list templates command
Write-Host "🔹 Testing list templates command..." -ForegroundColor Cyan
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --list-templates >$null
dotnet run --project ../src/solrevdev.seedfolder.csproj --framework $Framework -- --list-templates >$null
if ($LASTEXITCODE -ne 0) { throw "list templates command failed" }

Write-Host "🎉 All integration tests passed!" -ForegroundColor Green
Expand Down
Loading
Loading