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
95 changes: 81 additions & 14 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,11 @@
name: CI

permissions:
contents: write
contents: read

concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

on:
push:
Expand All@@ -26,6 +30,8 @@ jobs:
build:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false)
runs-on: ubuntu-latest
outputs:
publication_succeeded: ${{ steps.publish.outputs.succeeded }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
Expand All@@ -37,6 +43,8 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v5
with:
persist-credentials: false

- name: setup .net core sdk
uses: actions/setup-dotnet@v5
Expand All@@ -55,28 +63,87 @@ jobs:
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: Publish NuGet
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: verify versioned package
id: package
shell: bash
run: |
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
package="src/nupkg/solrevdev.seedfolder.${version}.nupkg"
tag="v${version}"

if [[ -z "$version" || ! -f "$package" ]]; then
echo "Unable to find the package version or package file." >&2
if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ ! -f "$package" ]]; then
echo "Expected package does not exist: ${package}" >&2
exit 1
fi

dotnet nuget push "$package" \
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "package=${package}" >> "$GITHUB_OUTPUT"

- name: publish NuGet
id: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE: ${{ steps.package.outputs.package }}
run: |
dotnet nuget push "$PACKAGE" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Tag ${tag} already exists; no tag update needed."
else
git tag "$tag"
git push origin "$tag"
echo "succeeded=true" >> "$GITHUB_OUTPUT"

tag-release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.build.result == 'success' && needs.build.outputs.publication_succeeded == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: checkout published commit
uses: actions/checkout@v5
with:
ref: ${{ github.sha }}

- name: verify or create release tag
shell: bash
run: |
expected_commit="$GITHUB_SHA"
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
tag="v${version}"

if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ "$(git rev-parse HEAD)" != "$expected_commit" ]]; then
echo "Checked-out commit does not match the intended release commit." >&2
exit 1
fi

if git ls-remote --exit-code --refs origin "refs/tags/${tag}" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_commit=$(git rev-list -n 1 "refs/tags/${tag}")
tag_version=$(git show "refs/tags/${tag}:src/solrevdev.seedfolder.csproj" | sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p')

if [[ "$tag_commit" != "$expected_commit" ]]; then
echo "Tag ${tag} resolves to ${tag_commit}, expected ${expected_commit}." >&2
exit 1
fi

if [[ "$tag_version" != "$version" ]]; then
echo "Tag ${tag} contains version ${tag_version}, expected ${version}." >&2
exit 1
fi

echo "Tag ${tag} already identifies the published commit and version; safe retry."
exit 0
fi

git tag "$tag" "$expected_commit"
git push origin "refs/tags/${tag}:refs/tags/${tag}"
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
# Repository Agent Guide

This file documents the project-specific guidance for coding agents working in this repository.

## 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`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

Run the tool directly with:

```bash
dotnet run --project src/solrevdev.seedfolder.csproj
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
84 changes: 38 additions & 46 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,54 @@
# CLAUDE.md
# Repository Agent Guide

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file documents the project-specific guidance for coding agents working in this repository.

## Project Overview
## Project overview

This is a .NET Core Global Tool called `seedfolder` that creates directories and populates them with standard dotfiles. The tool is designed to quickly scaffold project folders with common configuration files.
`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`.

## Development Commands
## Required validation

Run the same Release-mode sequence used by CI:

### Build and Test
```bash
# Build the solution
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

# Pack for local installation
dotnet pack

# Install locally for testing
dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

# Uninstall after testing
dotnet tool uninstall -g solrevdev.seedfolder
```
Run the tool directly with:

### Run the Tool During Development
```bash
# Run with interactive prompts
dotnet run --project src/solrevdev.seedfolder.csproj

# Run with folder name argument
dotnet run --project src/solrevdev.seedfolder.csproj myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

### Single File Structure
The entire application is contained in `src/Program.cs` - a single-file console application using:
- **McMaster.Extensions.CommandLineUtils** for command-line parsing and prompts
- **Figgle** / **Figgle.Fonts** for ASCII art header generation
- **Console.ForegroundColor** for colored console output

### Embedded Resources
Template files are stored as embedded resources in `src/Data/` and copied to new folders:
- `dockerignore` → `.dockerignore`
- `editorconfig` → `.editorconfig`
- `gitattributes` → `.gitattributes`
- `gitignore` → `.gitignore`
- `prettierignore` → `.prettierignore`
- `prettierrc` → `.prettierrc`
- `omnisharp.json` → `omnisharp.json`

### Key Methods
- `WriteFileAsync()` - Extracts embedded resources and writes them to the target directory
- `RemoveSpaces()` - Sanitizes folder names by replacing spaces with dashes
- `SafeNameForFileSystem()` - Removes invalid filesystem characters from folder names

## Multi-Target Framework Support
The project targets .NET 8.0 (LTS), 9.0 (STS), and 10.0 (LTS) to support current and modern .NET versions. .NET 8 provides long-term support until November 2026, .NET 9 offers the latest features with 18-month support, and .NET 10 is the latest LTS release providing long-term support until November 2028.

## CI/CD
GitHub Actions workflow builds, packs, and publishes to NuGet on pushes to master branch. Version is controlled by the `<Version>` property in the .csproj file.
- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
2 changes: 1 addition & 1 deletion src/solrevdev.seedfolder.csproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.4.1</Version>
<Version>1.4.2</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>
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
95 changes: 81 additions & 14 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,11 @@
name: CI

permissions:
contents: write
contents: read

concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

on:
push:
Expand All@@ -26,6 +30,8 @@ jobs:
build:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false)
runs-on: ubuntu-latest
outputs:
publication_succeeded: ${{ steps.publish.outputs.succeeded }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
Expand All@@ -37,6 +43,8 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v5
with:
persist-credentials: false

- name: setup .net core sdk
uses: actions/setup-dotnet@v5
Expand All@@ -55,28 +63,87 @@ jobs:
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: Publish NuGet
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: verify versioned package
id: package
shell: bash
run: |
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
package="src/nupkg/solrevdev.seedfolder.${version}.nupkg"
tag="v${version}"

if [[ -z "$version" || ! -f "$package" ]]; then
echo "Unable to find the package version or package file." >&2
if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ ! -f "$package" ]]; then
echo "Expected package does not exist: ${package}" >&2
exit 1
fi

dotnet nuget push "$package" \
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "package=${package}" >> "$GITHUB_OUTPUT"

- name: publish NuGet
id: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE: ${{ steps.package.outputs.package }}
run: |
dotnet nuget push "$PACKAGE" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Tag ${tag} already exists; no tag update needed."
else
git tag "$tag"
git push origin "$tag"
echo "succeeded=true" >> "$GITHUB_OUTPUT"

tag-release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.build.result == 'success' && needs.build.outputs.publication_succeeded == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: checkout published commit
uses: actions/checkout@v5
with:
ref: ${{ github.sha }}

- name: verify or create release tag
shell: bash
run: |
expected_commit="$GITHUB_SHA"
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
tag="v${version}"

if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ "$(git rev-parse HEAD)" != "$expected_commit" ]]; then
echo "Checked-out commit does not match the intended release commit." >&2
exit 1
fi

if git ls-remote --exit-code --refs origin "refs/tags/${tag}" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_commit=$(git rev-list -n 1 "refs/tags/${tag}")
tag_version=$(git show "refs/tags/${tag}:src/solrevdev.seedfolder.csproj" | sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p')

if [[ "$tag_commit" != "$expected_commit" ]]; then
echo "Tag ${tag} resolves to ${tag_commit}, expected ${expected_commit}." >&2
exit 1
fi

if [[ "$tag_version" != "$version" ]]; then
echo "Tag ${tag} contains version ${tag_version}, expected ${version}." >&2
exit 1
fi

echo "Tag ${tag} already identifies the published commit and version; safe retry."
exit 0
fi

git tag "$tag" "$expected_commit"
git push origin "refs/tags/${tag}:refs/tags/${tag}"
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
# Repository Agent Guide

This file documents the project-specific guidance for coding agents working in this repository.

## 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`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

Run the tool directly with:

```bash
dotnet run --project src/solrevdev.seedfolder.csproj
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
84 changes: 38 additions & 46 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,54 @@
# CLAUDE.md
# Repository Agent Guide

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file documents the project-specific guidance for coding agents working in this repository.

## Project Overview
## Project overview

This is a .NET Core Global Tool called `seedfolder` that creates directories and populates them with standard dotfiles. The tool is designed to quickly scaffold project folders with common configuration files.
`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`.

## Development Commands
## Required validation

Run the same Release-mode sequence used by CI:

### Build and Test
```bash
# Build the solution
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

# Pack for local installation
dotnet pack

# Install locally for testing
dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

# Uninstall after testing
dotnet tool uninstall -g solrevdev.seedfolder
```
Run the tool directly with:

### Run the Tool During Development
```bash
# Run with interactive prompts
dotnet run --project src/solrevdev.seedfolder.csproj

# Run with folder name argument
dotnet run --project src/solrevdev.seedfolder.csproj myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

### Single File Structure
The entire application is contained in `src/Program.cs` - a single-file console application using:
- **McMaster.Extensions.CommandLineUtils** for command-line parsing and prompts
- **Figgle** / **Figgle.Fonts** for ASCII art header generation
- **Console.ForegroundColor** for colored console output

### Embedded Resources
Template files are stored as embedded resources in `src/Data/` and copied to new folders:
- `dockerignore` → `.dockerignore`
- `editorconfig` → `.editorconfig`
- `gitattributes` → `.gitattributes`
- `gitignore` → `.gitignore`
- `prettierignore` → `.prettierignore`
- `prettierrc` → `.prettierrc`
- `omnisharp.json` → `omnisharp.json`

### Key Methods
- `WriteFileAsync()` - Extracts embedded resources and writes them to the target directory
- `RemoveSpaces()` - Sanitizes folder names by replacing spaces with dashes
- `SafeNameForFileSystem()` - Removes invalid filesystem characters from folder names

## Multi-Target Framework Support
The project targets .NET 8.0 (LTS), 9.0 (STS), and 10.0 (LTS) to support current and modern .NET versions. .NET 8 provides long-term support until November 2026, .NET 9 offers the latest features with 18-month support, and .NET 10 is the latest LTS release providing long-term support until November 2028.

## CI/CD
GitHub Actions workflow builds, packs, and publishes to NuGet on pushes to master branch. Version is controlled by the `<Version>` property in the .csproj file.
- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
2 changes: 1 addition & 1 deletion src/solrevdev.seedfolder.csproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.4.1</Version>
<Version>1.4.2</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>
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
95 changes: 81 additions & 14 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,11 @@
name: CI

permissions:
contents: write
contents: read

concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

on:
push:
Expand All@@ -26,6 +30,8 @@ jobs:
build:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false)
runs-on: ubuntu-latest
outputs:
publication_succeeded: ${{ steps.publish.outputs.succeeded }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
Expand All@@ -37,6 +43,8 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v5
with:
persist-credentials: false

- name: setup .net core sdk
uses: actions/setup-dotnet@v5
Expand All@@ -55,28 +63,87 @@ jobs:
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: Publish NuGet
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: verify versioned package
id: package
shell: bash
run: |
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
package="src/nupkg/solrevdev.seedfolder.${version}.nupkg"
tag="v${version}"

if [[ -z "$version" || ! -f "$package" ]]; then
echo "Unable to find the package version or package file." >&2
if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ ! -f "$package" ]]; then
echo "Expected package does not exist: ${package}" >&2
exit 1
fi

dotnet nuget push "$package" \
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "package=${package}" >> "$GITHUB_OUTPUT"

- name: publish NuGet
id: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE: ${{ steps.package.outputs.package }}
run: |
dotnet nuget push "$PACKAGE" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Tag ${tag} already exists; no tag update needed."
else
git tag "$tag"
git push origin "$tag"
echo "succeeded=true" >> "$GITHUB_OUTPUT"

tag-release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.build.result == 'success' && needs.build.outputs.publication_succeeded == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: checkout published commit
uses: actions/checkout@v5
with:
ref: ${{ github.sha }}

- name: verify or create release tag
shell: bash
run: |
expected_commit="$GITHUB_SHA"
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
tag="v${version}"

if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ "$(git rev-parse HEAD)" != "$expected_commit" ]]; then
echo "Checked-out commit does not match the intended release commit." >&2
exit 1
fi

if git ls-remote --exit-code --refs origin "refs/tags/${tag}" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_commit=$(git rev-list -n 1 "refs/tags/${tag}")
tag_version=$(git show "refs/tags/${tag}:src/solrevdev.seedfolder.csproj" | sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p')

if [[ "$tag_commit" != "$expected_commit" ]]; then
echo "Tag ${tag} resolves to ${tag_commit}, expected ${expected_commit}." >&2
exit 1
fi

if [[ "$tag_version" != "$version" ]]; then
echo "Tag ${tag} contains version ${tag_version}, expected ${version}." >&2
exit 1
fi

echo "Tag ${tag} already identifies the published commit and version; safe retry."
exit 0
fi

git tag "$tag" "$expected_commit"
git push origin "refs/tags/${tag}:refs/tags/${tag}"
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
# Repository Agent Guide

This file documents the project-specific guidance for coding agents working in this repository.

## 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`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

Run the tool directly with:

```bash
dotnet run --project src/solrevdev.seedfolder.csproj
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
84 changes: 38 additions & 46 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,54 @@
# CLAUDE.md
# Repository Agent Guide

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file documents the project-specific guidance for coding agents working in this repository.

## Project Overview
## Project overview

This is a .NET Core Global Tool called `seedfolder` that creates directories and populates them with standard dotfiles. The tool is designed to quickly scaffold project folders with common configuration files.
`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`.

## Development Commands
## Required validation

Run the same Release-mode sequence used by CI:

### Build and Test
```bash
# Build the solution
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

# Pack for local installation
dotnet pack

# Install locally for testing
dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

# Uninstall after testing
dotnet tool uninstall -g solrevdev.seedfolder
```
Run the tool directly with:

### Run the Tool During Development
```bash
# Run with interactive prompts
dotnet run --project src/solrevdev.seedfolder.csproj

# Run with folder name argument
dotnet run --project src/solrevdev.seedfolder.csproj myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

### Single File Structure
The entire application is contained in `src/Program.cs` - a single-file console application using:
- **McMaster.Extensions.CommandLineUtils** for command-line parsing and prompts
- **Figgle** / **Figgle.Fonts** for ASCII art header generation
- **Console.ForegroundColor** for colored console output

### Embedded Resources
Template files are stored as embedded resources in `src/Data/` and copied to new folders:
- `dockerignore` → `.dockerignore`
- `editorconfig` → `.editorconfig`
- `gitattributes` → `.gitattributes`
- `gitignore` → `.gitignore`
- `prettierignore` → `.prettierignore`
- `prettierrc` → `.prettierrc`
- `omnisharp.json` → `omnisharp.json`

### Key Methods
- `WriteFileAsync()` - Extracts embedded resources and writes them to the target directory
- `RemoveSpaces()` - Sanitizes folder names by replacing spaces with dashes
- `SafeNameForFileSystem()` - Removes invalid filesystem characters from folder names

## Multi-Target Framework Support
The project targets .NET 8.0 (LTS), 9.0 (STS), and 10.0 (LTS) to support current and modern .NET versions. .NET 8 provides long-term support until November 2026, .NET 9 offers the latest features with 18-month support, and .NET 10 is the latest LTS release providing long-term support until November 2028.

## CI/CD
GitHub Actions workflow builds, packs, and publishes to NuGet on pushes to master branch. Version is controlled by the `<Version>` property in the .csproj file.
- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
2 changes: 1 addition & 1 deletion src/solrevdev.seedfolder.csproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.4.1</Version>
<Version>1.4.2</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>
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
95 changes: 81 additions & 14 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,11 @@
name: CI

permissions:
contents: write
contents: read

concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

on:
push:
Expand All@@ -26,6 +30,8 @@ jobs:
build:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false)
runs-on: ubuntu-latest
outputs:
publication_succeeded: ${{ steps.publish.outputs.succeeded }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
Expand All@@ -37,6 +43,8 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v5
with:
persist-credentials: false

- name: setup .net core sdk
uses: actions/setup-dotnet@v5
Expand All@@ -55,28 +63,87 @@ jobs:
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: Publish NuGet
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: verify versioned package
id: package
shell: bash
run: |
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
package="src/nupkg/solrevdev.seedfolder.${version}.nupkg"
tag="v${version}"

if [[ -z "$version" || ! -f "$package" ]]; then
echo "Unable to find the package version or package file." >&2
if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ ! -f "$package" ]]; then
echo "Expected package does not exist: ${package}" >&2
exit 1
fi

dotnet nuget push "$package" \
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "package=${package}" >> "$GITHUB_OUTPUT"

- name: publish NuGet
id: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE: ${{ steps.package.outputs.package }}
run: |
dotnet nuget push "$PACKAGE" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Tag ${tag} already exists; no tag update needed."
else
git tag "$tag"
git push origin "$tag"
echo "succeeded=true" >> "$GITHUB_OUTPUT"

tag-release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.build.result == 'success' && needs.build.outputs.publication_succeeded == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: checkout published commit
uses: actions/checkout@v5
with:
ref: ${{ github.sha }}

- name: verify or create release tag
shell: bash
run: |
expected_commit="$GITHUB_SHA"
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
tag="v${version}"

if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ "$(git rev-parse HEAD)" != "$expected_commit" ]]; then
echo "Checked-out commit does not match the intended release commit." >&2
exit 1
fi

if git ls-remote --exit-code --refs origin "refs/tags/${tag}" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_commit=$(git rev-list -n 1 "refs/tags/${tag}")
tag_version=$(git show "refs/tags/${tag}:src/solrevdev.seedfolder.csproj" | sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p')

if [[ "$tag_commit" != "$expected_commit" ]]; then
echo "Tag ${tag} resolves to ${tag_commit}, expected ${expected_commit}." >&2
exit 1
fi

if [[ "$tag_version" != "$version" ]]; then
echo "Tag ${tag} contains version ${tag_version}, expected ${version}." >&2
exit 1
fi

echo "Tag ${tag} already identifies the published commit and version; safe retry."
exit 0
fi

git tag "$tag" "$expected_commit"
git push origin "refs/tags/${tag}:refs/tags/${tag}"
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
# Repository Agent Guide

This file documents the project-specific guidance for coding agents working in this repository.

## 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`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

Run the tool directly with:

```bash
dotnet run --project src/solrevdev.seedfolder.csproj
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
84 changes: 38 additions & 46 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,54 @@
# CLAUDE.md
# Repository Agent Guide

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file documents the project-specific guidance for coding agents working in this repository.

## Project Overview
## Project overview

This is a .NET Core Global Tool called `seedfolder` that creates directories and populates them with standard dotfiles. The tool is designed to quickly scaffold project folders with common configuration files.
`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`.

## Development Commands
## Required validation

Run the same Release-mode sequence used by CI:

### Build and Test
```bash
# Build the solution
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

# Pack for local installation
dotnet pack

# Install locally for testing
dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

# Uninstall after testing
dotnet tool uninstall -g solrevdev.seedfolder
```
Run the tool directly with:

### Run the Tool During Development
```bash
# Run with interactive prompts
dotnet run --project src/solrevdev.seedfolder.csproj

# Run with folder name argument
dotnet run --project src/solrevdev.seedfolder.csproj myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

### Single File Structure
The entire application is contained in `src/Program.cs` - a single-file console application using:
- **McMaster.Extensions.CommandLineUtils** for command-line parsing and prompts
- **Figgle** / **Figgle.Fonts** for ASCII art header generation
- **Console.ForegroundColor** for colored console output

### Embedded Resources
Template files are stored as embedded resources in `src/Data/` and copied to new folders:
- `dockerignore` → `.dockerignore`
- `editorconfig` → `.editorconfig`
- `gitattributes` → `.gitattributes`
- `gitignore` → `.gitignore`
- `prettierignore` → `.prettierignore`
- `prettierrc` → `.prettierrc`
- `omnisharp.json` → `omnisharp.json`

### Key Methods
- `WriteFileAsync()` - Extracts embedded resources and writes them to the target directory
- `RemoveSpaces()` - Sanitizes folder names by replacing spaces with dashes
- `SafeNameForFileSystem()` - Removes invalid filesystem characters from folder names

## Multi-Target Framework Support
The project targets .NET 8.0 (LTS), 9.0 (STS), and 10.0 (LTS) to support current and modern .NET versions. .NET 8 provides long-term support until November 2026, .NET 9 offers the latest features with 18-month support, and .NET 10 is the latest LTS release providing long-term support until November 2028.

## CI/CD
GitHub Actions workflow builds, packs, and publishes to NuGet on pushes to master branch. Version is controlled by the `<Version>` property in the .csproj file.
- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
2 changes: 1 addition & 1 deletion src/solrevdev.seedfolder.csproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.4.1</Version>
<Version>1.4.2</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>
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
95 changes: 81 additions & 14 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,11 @@
name: CI

permissions:
contents: write
contents: read

concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

on:
push:
Expand All@@ -26,6 +30,8 @@ jobs:
build:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false)
runs-on: ubuntu-latest
outputs:
publication_succeeded: ${{ steps.publish.outputs.succeeded }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
Expand All@@ -37,6 +43,8 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v5
with:
persist-credentials: false

- name: setup .net core sdk
uses: actions/setup-dotnet@v5
Expand All@@ -55,28 +63,87 @@ jobs:
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: Publish NuGet
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: verify versioned package
id: package
shell: bash
run: |
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
package="src/nupkg/solrevdev.seedfolder.${version}.nupkg"
tag="v${version}"

if [[ -z "$version" || ! -f "$package" ]]; then
echo "Unable to find the package version or package file." >&2
if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ ! -f "$package" ]]; then
echo "Expected package does not exist: ${package}" >&2
exit 1
fi

dotnet nuget push "$package" \
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "package=${package}" >> "$GITHUB_OUTPUT"

- name: publish NuGet
id: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE: ${{ steps.package.outputs.package }}
run: |
dotnet nuget push "$PACKAGE" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Tag ${tag} already exists; no tag update needed."
else
git tag "$tag"
git push origin "$tag"
echo "succeeded=true" >> "$GITHUB_OUTPUT"

tag-release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.build.result == 'success' && needs.build.outputs.publication_succeeded == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: checkout published commit
uses: actions/checkout@v5
with:
ref: ${{ github.sha }}

- name: verify or create release tag
shell: bash
run: |
expected_commit="$GITHUB_SHA"
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
tag="v${version}"

if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ "$(git rev-parse HEAD)" != "$expected_commit" ]]; then
echo "Checked-out commit does not match the intended release commit." >&2
exit 1
fi

if git ls-remote --exit-code --refs origin "refs/tags/${tag}" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_commit=$(git rev-list -n 1 "refs/tags/${tag}")
tag_version=$(git show "refs/tags/${tag}:src/solrevdev.seedfolder.csproj" | sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p')

if [[ "$tag_commit" != "$expected_commit" ]]; then
echo "Tag ${tag} resolves to ${tag_commit}, expected ${expected_commit}." >&2
exit 1
fi

if [[ "$tag_version" != "$version" ]]; then
echo "Tag ${tag} contains version ${tag_version}, expected ${version}." >&2
exit 1
fi

echo "Tag ${tag} already identifies the published commit and version; safe retry."
exit 0
fi

git tag "$tag" "$expected_commit"
git push origin "refs/tags/${tag}:refs/tags/${tag}"
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
# Repository Agent Guide

This file documents the project-specific guidance for coding agents working in this repository.

## 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`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

Run the tool directly with:

```bash
dotnet run --project src/solrevdev.seedfolder.csproj
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
84 changes: 38 additions & 46 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,54 @@
# CLAUDE.md
# Repository Agent Guide

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file documents the project-specific guidance for coding agents working in this repository.

## Project Overview
## Project overview

This is a .NET Core Global Tool called `seedfolder` that creates directories and populates them with standard dotfiles. The tool is designed to quickly scaffold project folders with common configuration files.
`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`.

## Development Commands
## Required validation

Run the same Release-mode sequence used by CI:

### Build and Test
```bash
# Build the solution
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

# Pack for local installation
dotnet pack

# Install locally for testing
dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

# Uninstall after testing
dotnet tool uninstall -g solrevdev.seedfolder
```
Run the tool directly with:

### Run the Tool During Development
```bash
# Run with interactive prompts
dotnet run --project src/solrevdev.seedfolder.csproj

# Run with folder name argument
dotnet run --project src/solrevdev.seedfolder.csproj myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

### Single File Structure
The entire application is contained in `src/Program.cs` - a single-file console application using:
- **McMaster.Extensions.CommandLineUtils** for command-line parsing and prompts
- **Figgle** / **Figgle.Fonts** for ASCII art header generation
- **Console.ForegroundColor** for colored console output

### Embedded Resources
Template files are stored as embedded resources in `src/Data/` and copied to new folders:
- `dockerignore` → `.dockerignore`
- `editorconfig` → `.editorconfig`
- `gitattributes` → `.gitattributes`
- `gitignore` → `.gitignore`
- `prettierignore` → `.prettierignore`
- `prettierrc` → `.prettierrc`
- `omnisharp.json` → `omnisharp.json`

### Key Methods
- `WriteFileAsync()` - Extracts embedded resources and writes them to the target directory
- `RemoveSpaces()` - Sanitizes folder names by replacing spaces with dashes
- `SafeNameForFileSystem()` - Removes invalid filesystem characters from folder names

## Multi-Target Framework Support
The project targets .NET 8.0 (LTS), 9.0 (STS), and 10.0 (LTS) to support current and modern .NET versions. .NET 8 provides long-term support until November 2026, .NET 9 offers the latest features with 18-month support, and .NET 10 is the latest LTS release providing long-term support until November 2028.

## CI/CD
GitHub Actions workflow builds, packs, and publishes to NuGet on pushes to master branch. Version is controlled by the `<Version>` property in the .csproj file.
- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
2 changes: 1 addition & 1 deletion src/solrevdev.seedfolder.csproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.4.1</Version>
<Version>1.4.2</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>
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
95 changes: 81 additions & 14 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,11 @@
name: CI

permissions:
contents: write
contents: read

concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

on:
push:
Expand All@@ -26,6 +30,8 @@ jobs:
build:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false)
runs-on: ubuntu-latest
outputs:
publication_succeeded: ${{ steps.publish.outputs.succeeded }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
Expand All@@ -37,6 +43,8 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v5
with:
persist-credentials: false

- name: setup .net core sdk
uses: actions/setup-dotnet@v5
Expand All@@ -55,28 +63,87 @@ jobs:
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: Publish NuGet
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: verify versioned package
id: package
shell: bash
run: |
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
package="src/nupkg/solrevdev.seedfolder.${version}.nupkg"
tag="v${version}"

if [[ -z "$version" || ! -f "$package" ]]; then
echo "Unable to find the package version or package file." >&2
if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ ! -f "$package" ]]; then
echo "Expected package does not exist: ${package}" >&2
exit 1
fi

dotnet nuget push "$package" \
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "package=${package}" >> "$GITHUB_OUTPUT"

- name: publish NuGet
id: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE: ${{ steps.package.outputs.package }}
run: |
dotnet nuget push "$PACKAGE" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Tag ${tag} already exists; no tag update needed."
else
git tag "$tag"
git push origin "$tag"
echo "succeeded=true" >> "$GITHUB_OUTPUT"

tag-release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.build.result == 'success' && needs.build.outputs.publication_succeeded == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: checkout published commit
uses: actions/checkout@v5
with:
ref: ${{ github.sha }}

- name: verify or create release tag
shell: bash
run: |
expected_commit="$GITHUB_SHA"
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
tag="v${version}"

if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ "$(git rev-parse HEAD)" != "$expected_commit" ]]; then
echo "Checked-out commit does not match the intended release commit." >&2
exit 1
fi

if git ls-remote --exit-code --refs origin "refs/tags/${tag}" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_commit=$(git rev-list -n 1 "refs/tags/${tag}")
tag_version=$(git show "refs/tags/${tag}:src/solrevdev.seedfolder.csproj" | sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p')

if [[ "$tag_commit" != "$expected_commit" ]]; then
echo "Tag ${tag} resolves to ${tag_commit}, expected ${expected_commit}." >&2
exit 1
fi

if [[ "$tag_version" != "$version" ]]; then
echo "Tag ${tag} contains version ${tag_version}, expected ${version}." >&2
exit 1
fi

echo "Tag ${tag} already identifies the published commit and version; safe retry."
exit 0
fi

git tag "$tag" "$expected_commit"
git push origin "refs/tags/${tag}:refs/tags/${tag}"
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
# Repository Agent Guide

This file documents the project-specific guidance for coding agents working in this repository.

## 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`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

Run the tool directly with:

```bash
dotnet run --project src/solrevdev.seedfolder.csproj
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
84 changes: 38 additions & 46 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,54 @@
# CLAUDE.md
# Repository Agent Guide

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file documents the project-specific guidance for coding agents working in this repository.

## Project Overview
## Project overview

This is a .NET Core Global Tool called `seedfolder` that creates directories and populates them with standard dotfiles. The tool is designed to quickly scaffold project folders with common configuration files.
`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`.

## Development Commands
## Required validation

Run the same Release-mode sequence used by CI:

### Build and Test
```bash
# Build the solution
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

# Pack for local installation
dotnet pack

# Install locally for testing
dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

# Uninstall after testing
dotnet tool uninstall -g solrevdev.seedfolder
```
Run the tool directly with:

### Run the Tool During Development
```bash
# Run with interactive prompts
dotnet run --project src/solrevdev.seedfolder.csproj

# Run with folder name argument
dotnet run --project src/solrevdev.seedfolder.csproj myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

### Single File Structure
The entire application is contained in `src/Program.cs` - a single-file console application using:
- **McMaster.Extensions.CommandLineUtils** for command-line parsing and prompts
- **Figgle** / **Figgle.Fonts** for ASCII art header generation
- **Console.ForegroundColor** for colored console output

### Embedded Resources
Template files are stored as embedded resources in `src/Data/` and copied to new folders:
- `dockerignore` → `.dockerignore`
- `editorconfig` → `.editorconfig`
- `gitattributes` → `.gitattributes`
- `gitignore` → `.gitignore`
- `prettierignore` → `.prettierignore`
- `prettierrc` → `.prettierrc`
- `omnisharp.json` → `omnisharp.json`

### Key Methods
- `WriteFileAsync()` - Extracts embedded resources and writes them to the target directory
- `RemoveSpaces()` - Sanitizes folder names by replacing spaces with dashes
- `SafeNameForFileSystem()` - Removes invalid filesystem characters from folder names

## Multi-Target Framework Support
The project targets .NET 8.0 (LTS), 9.0 (STS), and 10.0 (LTS) to support current and modern .NET versions. .NET 8 provides long-term support until November 2026, .NET 9 offers the latest features with 18-month support, and .NET 10 is the latest LTS release providing long-term support until November 2028.

## CI/CD
GitHub Actions workflow builds, packs, and publishes to NuGet on pushes to master branch. Version is controlled by the `<Version>` property in the .csproj file.
- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
2 changes: 1 addition & 1 deletion src/solrevdev.seedfolder.csproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.4.1</Version>
<Version>1.4.2</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>
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
95 changes: 81 additions & 14 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,11 @@
name: CI

permissions:
contents: write
contents: read

concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

on:
push:
Expand All@@ -26,6 +30,8 @@ jobs:
build:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false)
runs-on: ubuntu-latest
outputs:
publication_succeeded: ${{ steps.publish.outputs.succeeded }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
Expand All@@ -37,6 +43,8 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v5
with:
persist-credentials: false

- name: setup .net core sdk
uses: actions/setup-dotnet@v5
Expand All@@ -55,28 +63,87 @@ jobs:
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: Publish NuGet
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: verify versioned package
id: package
shell: bash
run: |
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
package="src/nupkg/solrevdev.seedfolder.${version}.nupkg"
tag="v${version}"

if [[ -z "$version" || ! -f "$package" ]]; then
echo "Unable to find the package version or package file." >&2
if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ ! -f "$package" ]]; then
echo "Expected package does not exist: ${package}" >&2
exit 1
fi

dotnet nuget push "$package" \
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "package=${package}" >> "$GITHUB_OUTPUT"

- name: publish NuGet
id: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE: ${{ steps.package.outputs.package }}
run: |
dotnet nuget push "$PACKAGE" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Tag ${tag} already exists; no tag update needed."
else
git tag "$tag"
git push origin "$tag"
echo "succeeded=true" >> "$GITHUB_OUTPUT"

tag-release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.build.result == 'success' && needs.build.outputs.publication_succeeded == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: checkout published commit
uses: actions/checkout@v5
with:
ref: ${{ github.sha }}

- name: verify or create release tag
shell: bash
run: |
expected_commit="$GITHUB_SHA"
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
tag="v${version}"

if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ "$(git rev-parse HEAD)" != "$expected_commit" ]]; then
echo "Checked-out commit does not match the intended release commit." >&2
exit 1
fi

if git ls-remote --exit-code --refs origin "refs/tags/${tag}" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_commit=$(git rev-list -n 1 "refs/tags/${tag}")
tag_version=$(git show "refs/tags/${tag}:src/solrevdev.seedfolder.csproj" | sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p')

if [[ "$tag_commit" != "$expected_commit" ]]; then
echo "Tag ${tag} resolves to ${tag_commit}, expected ${expected_commit}." >&2
exit 1
fi

if [[ "$tag_version" != "$version" ]]; then
echo "Tag ${tag} contains version ${tag_version}, expected ${version}." >&2
exit 1
fi

echo "Tag ${tag} already identifies the published commit and version; safe retry."
exit 0
fi

git tag "$tag" "$expected_commit"
git push origin "refs/tags/${tag}:refs/tags/${tag}"
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
# Repository Agent Guide

This file documents the project-specific guidance for coding agents working in this repository.

## 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`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

Run the tool directly with:

```bash
dotnet run --project src/solrevdev.seedfolder.csproj
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
84 changes: 38 additions & 46 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,54 @@
# CLAUDE.md
# Repository Agent Guide

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file documents the project-specific guidance for coding agents working in this repository.

## Project Overview
## Project overview

This is a .NET Core Global Tool called `seedfolder` that creates directories and populates them with standard dotfiles. The tool is designed to quickly scaffold project folders with common configuration files.
`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`.

## Development Commands
## Required validation

Run the same Release-mode sequence used by CI:

### Build and Test
```bash
# Build the solution
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

# Pack for local installation
dotnet pack

# Install locally for testing
dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

# Uninstall after testing
dotnet tool uninstall -g solrevdev.seedfolder
```
Run the tool directly with:

### Run the Tool During Development
```bash
# Run with interactive prompts
dotnet run --project src/solrevdev.seedfolder.csproj

# Run with folder name argument
dotnet run --project src/solrevdev.seedfolder.csproj myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

### Single File Structure
The entire application is contained in `src/Program.cs` - a single-file console application using:
- **McMaster.Extensions.CommandLineUtils** for command-line parsing and prompts
- **Figgle** / **Figgle.Fonts** for ASCII art header generation
- **Console.ForegroundColor** for colored console output

### Embedded Resources
Template files are stored as embedded resources in `src/Data/` and copied to new folders:
- `dockerignore` → `.dockerignore`
- `editorconfig` → `.editorconfig`
- `gitattributes` → `.gitattributes`
- `gitignore` → `.gitignore`
- `prettierignore` → `.prettierignore`
- `prettierrc` → `.prettierrc`
- `omnisharp.json` → `omnisharp.json`

### Key Methods
- `WriteFileAsync()` - Extracts embedded resources and writes them to the target directory
- `RemoveSpaces()` - Sanitizes folder names by replacing spaces with dashes
- `SafeNameForFileSystem()` - Removes invalid filesystem characters from folder names

## Multi-Target Framework Support
The project targets .NET 8.0 (LTS), 9.0 (STS), and 10.0 (LTS) to support current and modern .NET versions. .NET 8 provides long-term support until November 2026, .NET 9 offers the latest features with 18-month support, and .NET 10 is the latest LTS release providing long-term support until November 2028.

## CI/CD
GitHub Actions workflow builds, packs, and publishes to NuGet on pushes to master branch. Version is controlled by the `<Version>` property in the .csproj file.
- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
2 changes: 1 addition & 1 deletion src/solrevdev.seedfolder.csproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.4.1</Version>
<Version>1.4.2</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>
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
95 changes: 81 additions & 14 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,11 @@
name: CI

permissions:
contents: write
contents: read

concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

on:
push:
Expand All@@ -26,6 +30,8 @@ jobs:
build:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false)
runs-on: ubuntu-latest
outputs:
publication_succeeded: ${{ steps.publish.outputs.succeeded }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
Expand All@@ -37,6 +43,8 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v5
with:
persist-credentials: false

- name: setup .net core sdk
uses: actions/setup-dotnet@v5
Expand All@@ -55,28 +63,87 @@ jobs:
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols

- name: Publish NuGet
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: verify versioned package
id: package
shell: bash
run: |
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
package="src/nupkg/solrevdev.seedfolder.${version}.nupkg"
tag="v${version}"

if [[ -z "$version" || ! -f "$package" ]]; then
echo "Unable to find the package version or package file." >&2
if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ ! -f "$package" ]]; then
echo "Expected package does not exist: ${package}" >&2
exit 1
fi

dotnet nuget push "$package" \
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "package=${package}" >> "$GITHUB_OUTPUT"

- name: publish NuGet
id: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
PACKAGE: ${{ steps.package.outputs.package }}
run: |
dotnet nuget push "$PACKAGE" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Tag ${tag} already exists; no tag update needed."
else
git tag "$tag"
git push origin "$tag"
echo "succeeded=true" >> "$GITHUB_OUTPUT"

tag-release:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.build.result == 'success' && needs.build.outputs.publication_succeeded == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: checkout published commit
uses: actions/checkout@v5
with:
ref: ${{ github.sha }}

- name: verify or create release tag
shell: bash
run: |
expected_commit="$GITHUB_SHA"
version=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/solrevdev.seedfolder.csproj)
tag="v${version}"

if [[ -z "$version" ]]; then
echo "Unable to find the package version." >&2
exit 1
fi

if [[ "$(git rev-parse HEAD)" != "$expected_commit" ]]; then
echo "Checked-out commit does not match the intended release commit." >&2
exit 1
fi

if git ls-remote --exit-code --refs origin "refs/tags/${tag}" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_commit=$(git rev-list -n 1 "refs/tags/${tag}")
tag_version=$(git show "refs/tags/${tag}:src/solrevdev.seedfolder.csproj" | sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p')

if [[ "$tag_commit" != "$expected_commit" ]]; then
echo "Tag ${tag} resolves to ${tag_commit}, expected ${expected_commit}." >&2
exit 1
fi

if [[ "$tag_version" != "$version" ]]; then
echo "Tag ${tag} contains version ${tag_version}, expected ${version}." >&2
exit 1
fi

echo "Tag ${tag} already identifies the published commit and version; safe retry."
exit 0
fi

git tag "$tag" "$expected_commit"
git push origin "refs/tags/${tag}:refs/tags/${tag}"
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
# Repository Agent Guide

This file documents the project-specific guidance for coding agents working in this repository.

## 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`.

## Required validation

Run the same Release-mode sequence used by CI:

```bash
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

Run the tool directly with:

```bash
dotnet run --project src/solrevdev.seedfolder.csproj
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
84 changes: 38 additions & 46 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,54 @@
# CLAUDE.md
# Repository Agent Guide

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file documents the project-specific guidance for coding agents working in this repository.

## Project Overview
## Project overview

This is a .NET Core Global Tool called `seedfolder` that creates directories and populates them with standard dotfiles. The tool is designed to quickly scaffold project folders with common configuration files.
`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`.

## Development Commands
## Required validation

Run the same Release-mode sequence used by CI:

### Build and Test
```bash
# Build the solution
dotnet build solrevdev.seedfolder.sln --configuration Release
./tests/integration-test.sh
dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
```

# Pack for local installation
dotnet pack

# Install locally for testing
dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
Packages are written to `src/nupkg/`. For interactive local install/uninstall testing, see `build/test-local.sh` and review its global-tool side effects before running it.

# Uninstall after testing
dotnet tool uninstall -g solrevdev.seedfolder
```
Run the tool directly with:

### Run the Tool During Development
```bash
# Run with interactive prompts
dotnet run --project src/solrevdev.seedfolder.csproj

# Run with folder name argument
dotnet run --project src/solrevdev.seedfolder.csproj myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- myfolder
dotnet run --project src/solrevdev.seedfolder.csproj -- --dry-run -t node myfolder
```

## Architecture

### Single File Structure
The entire application is contained in `src/Program.cs` - a single-file console application using:
- **McMaster.Extensions.CommandLineUtils** for command-line parsing and prompts
- **Figgle** / **Figgle.Fonts** for ASCII art header generation
- **Console.ForegroundColor** for colored console output

### Embedded Resources
Template files are stored as embedded resources in `src/Data/` and copied to new folders:
- `dockerignore` → `.dockerignore`
- `editorconfig` → `.editorconfig`
- `gitattributes` → `.gitattributes`
- `gitignore` → `.gitignore`
- `prettierignore` → `.prettierignore`
- `prettierrc` → `.prettierrc`
- `omnisharp.json` → `omnisharp.json`

### Key Methods
- `WriteFileAsync()` - Extracts embedded resources and writes them to the target directory
- `RemoveSpaces()` - Sanitizes folder names by replacing spaces with dashes
- `SafeNameForFileSystem()` - Removes invalid filesystem characters from folder names

## Multi-Target Framework Support
The project targets .NET 8.0 (LTS), 9.0 (STS), and 10.0 (LTS) to support current and modern .NET versions. .NET 8 provides long-term support until November 2026, .NET 9 offers the latest features with 18-month support, and .NET 10 is the latest LTS release providing long-term support until November 2028.

## CI/CD
GitHub Actions workflow builds, packs, and publishes to NuGet on pushes to master branch. Version is controlled by the `<Version>` property in the .csproj file.
- `src/Program.cs` contains CLI parsing, interactive prompts, validation, rendering, and template extraction.
- `McMaster.Extensions.CommandLineUtils` supplies prompt helpers; option parsing is implemented in `Main()` and accepts options before or after the single folder-name argument.
- `Figgle` and `Figgle.Fonts` render the ASCII header; colored output uses `Console.ForegroundColor` through the local `WriteLine()` helper.
- `src/Data/` files are embedded by `src/solrevdev.seedfolder.csproj` and extracted with `WriteFileAsync()`.
- `TemplateFile` and `ProjectType` define the six templates: markdown, dotnet, node, python, ruby, and universal.
- Folder names pass through `RemoveSpaces()` and `SafeNameForFileSystem()` before use.

## Release model

- `<Version>` in `src/solrevdev.seedfolder.csproj` is the release source of truth and is bumped manually.
- Pull requests and `release/*` pushes build, test, pack, and verify the exact versioned package; only a push to `master` publishes to NuGet.
- A publish-capable change must use a version that is not already associated with another commit. After NuGet publication succeeds, CI creates the lightweight `v<Version>` tag for that exact master commit.
- Release retries are accepted only when the existing tag resolves to the same commit and its csproj contains the same version.
- Keep `actions/checkout@v5` and `actions/setup-dotnet@v5` unless a deliberate, validated action-major upgrade is required; both use the Node 24 action runtime.
- NuGet publication uses native `dotnet nuget push --skip-duplicate` and requires the `NUGET_API_KEY` Actions secret.
- Push commits containing `***NO_CI***`, `[ci skip]`, or `[skip ci]` skip the build job. Pull-request validation is not bypassed by these markers.

## Commit messages

Use Conventional Commits with an imperative subject, for example `ci: harden release workflow`. Add a scope when useful and mark breaking changes with `!` and/or a `BREAKING CHANGE:` footer.

## Global versus repository guidance

Keep machine-specific operational rules—Python environment management, browser tooling, SVG rasterization, safe deletion utilities, and Homebrew/.NET SDK repair—in global agent settings. They are useful defaults but are not requirements of this .NET project.
2 changes: 1 addition & 1 deletion src/solrevdev.seedfolder.csproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
<ToolCommandName>seedfolder</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<NoDefaultExcludes>true</NoDefaultExcludes>
<Version>1.4.1</Version>
<Version>1.4.2</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>
Expand Down