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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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