Swap.CLI
0.3.2
dotnet tool install --global Swap.CLI --version 0.3.2
dotnet new tool-manifest
dotnet tool install --local Swap.CLI --version 0.3.2
#tool dotnet:?package=Swap.CLI&version=0.3.2
nuke :add-package Swap.CLI --version 0.3.2
Swap CLI
Generate production-ready ASP.NET Core + HTMX applications with beautiful DaisyUI components.
Swap CLI is a code generator that creates complete, modern web applications using ASP.NET Core MVC, HTMX for interactivity, DaisyUI for UI components, and Entity Framework Core for data access. Generate full CRUD operations with pagination, search, sorting, filtering, and modal-based editing in seconds.
π Why Swap?
- β‘ Production-Ready Code - Generate complete CRUD with modals, pagination, sorting, filtering, and search
- π― HTMX Simplicity - Modern, interactive web apps without JavaScript frameworks
- οΏ½ DaisyUI + Tailwind - Beautiful, accessible components out of the box
- ποΈ Entity Framework Core - Full database integration with migrations support
- π» Developer Experience - CLI-driven workflow, no manual boilerplate
- π¦ Proven Patterns - Every pattern extracted from real production applications
π Quick Start
Prerequisites
Before installing Swap CLI, ensure you have:
- .NET 9.0 SDK or later - Download
- Node.js (LTS) - Includes npm for Tailwind CSS compilation
- Windows:
winget install OpenJS.NodeJS.LTSor download from nodejs.org - macOS:
brew install node - Linux: Use your package manager
- Windows:
- libman CLI - Manages client libraries (HTMX, DaisyUI)
dotnet tool install -g Microsoft.Web.LibraryManager.Cli
Verify installations:
dotnet --version # Should be 9.0 or higher
npm --version # Any recent version
libman --version # Any version
Installation
# Install the Swap CLI tool
dotnet tool install --global Swap.CLI
# Verify installation
swap --version
Create Your First Project
# Create a new ASP.NET Core + HTMX application
swap new MyApp
cd MyApp
# Apply migrations and run
dotnet ef database update
dotnet run
Visit http://localhost:5000 - Your HTMX-powered application is running! π
Note: The CLI automatically runs npm install, libman restore, and npm run build:css during project creation.
Generate Your First CRUD
# Generate a complete CRUD controller with all features
swap generate controller Product --fields "Name:string Price:decimal InStock:bool:f"
# Short alias
swap g c Product --fields "Name:string Price:decimal InStock:bool:f"
# Update database
dotnet ef migrations add AddProduct
dotnet ef database update
Visit http://localhost:5000/Product - Full CRUD with pagination, search, sorting, and filtering! π
No manual file creation. No boilerplate. Just CLI commands and business logic.
π― What You Get
Complete Feature Set
Every generated controller includes:
- β CRUD Operations - Create, Read, Update, Delete via HTMX modals
- β Pagination - Configurable page sizes (10, 25, 50, 100)
- β Real-Time Search - 500ms debounced search across fields
- β Column Sorting - Ascending/descending toggle per field
- β Boolean Filtering - Dropdown filters (All/Yes/No) for bool fields
- β Bulk Operations - Select multiple items and bulk delete
- β Toast Notifications - Success/error messages with DaisyUI alerts
- β Modal Editing - No page reloads, smooth UX
- β Validation - Client and server-side with clear error messages
- β Responsive Design - Works perfectly on mobile and desktop
Generated Stack
- Backend: ASP.NET Core 9.0 MVC
- Frontend: HTMX + DaisyUI + Tailwind CSS
- Database: Entity Framework Core (SQLite, SQL Server, PostgreSQL)
- UI Library: DaisyUI 4.x components
- Styling: Tailwind CSS 3.x utilities
π CLI Commands
swap new <name>
Create a new ASP.NET Core + HTMX application with DaisyUI components.
swap new MyApp
Generates:
- Complete ASP.NET Core MVC project structure
- Entity Framework Core with SQLite (configurable)
- DaisyUI + Tailwind CSS configuration
- Sample TodoItem model and CRUD
- Database migrations
- Ready to run immediately
swap generate controller <name> --fields <fields>
Generate a complete CRUD controller with all features.
# Generate Product controller with fields
swap g c Product --fields "Name:string Price:decimal InStock:bool:f"
# With nullable fields
swap g c Customer --fields "Name:string Email:string Notes:string?"
# Control sorting and filtering per field (space or comma separated)
swap g c Order --fields "OrderNumber:string:ns Total:decimal Date:DateTime Status:bool:f"
swap g c Order --fields OrderNumber:string:ns,Total:decimal,Date:DateTime,Status:bool:f
# Preview without writing files (dry-run)
swap g c Product --fields "Name:string Price:decimal" --dry-run
# Overwrite existing files without prompting
swap g c Product --fields "Name:string Price:decimal" --force
# Generate in a different project directory
swap g c Product --fields "Name:string" --project path/to/project
Options:
--fieldsor-f- Field definitions (space or comma separated)--dry-run- Preview what would be generated without writing files--force- Overwrite existing files without prompting--projector-p- Path to project directory (default: current directory)
Field Flags:
:sortableor:s- Enable sorting (default for all fields):nosortor:ns- Disable sorting:filterableor:f- Enable filtering (bool fields only)
Generates:
- Controller with full CRUD operations
- Model class with validation
- View model for list operations
- Views (Index, _List, _CreateModal, _EditModal, _DetailsModal)
- Automatic DbContext updates
swap generate model <name> --fields <fields>
Generate just a model class (no controller or views).
swap g m Category --fields "Name:string Description:string?"
swap g m Category --fields Name:string,Description:string?
# Preview the generated model
swap g m Product --fields "Name:string Price:decimal" --dry-run
# Overwrite without prompting
swap g m Category --fields "Name:string" --force
# Generate in a different project
swap g m Category --fields "Name:string" --project path/to/project
Options:
--fieldsor-f- Field definitions (space or comma separated)--dry-run- Preview what would be generated without writing files--force- Overwrite existing files without prompting--projector-p- Path to project directory (default: current directory)
swap generate auth
Generate a complete authentication system with ASP.NET Core Identity, including login, registration, and password reset functionality.
# Generate authentication system
swap generate auth
# Short alias
swap g auth
# Preview what would be generated
swap g auth --dry-run
# Force overwrite existing files
swap g auth --force
# Generate in a different project
swap g auth --project path/to/project
Options:
--dry-run- Preview what would be generated without writing files--force- Overwrite existing files without prompting--projector-p- Path to project directory (default: current directory)
What it generates:
Models/ApplicationUser.cs- Extended Identity user with DisplayName, CreatedAt, LastLoginAtViewModels/- LoginViewModel, RegisterViewModel, ForgotPasswordViewModel, ResetPasswordViewModelControllers/AuthController.cs- Complete auth controller with all operationsViews/Auth/- Login, Register, ForgotPassword, ResetPassword, AccessDenied, and confirmation pagesViews/Shared/_LoginPartial.cshtml- Reusable login/logout navigation component- Adds
Microsoft.AspNetCore.Identity.EntityFrameworkCorepackage reference
Features included:
- β User registration with email validation
- β Login with "Remember Me" functionality
- β Password reset with token-based flow
- β Account lockout after failed attempts
- β Last login tracking
- β Access denied page
- β Tailwind CSS styled views
- β HTMX-compatible markup
After generation - Manual configuration required:
- Update your DbContext to inherit from
IdentityDbContext<ApplicationUser>:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using YourApp.Models;
namespace YourApp.Data;
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
// Your existing DbSets here
}
- Configure Identity in Program.cs (add after service configuration):
using Microsoft.AspNetCore.Identity;
using YourApp.Models;
// Add Identity
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
// User settings
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Configure application cookie
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.LoginPath = "/auth/login";
options.AccessDeniedPath = "/auth/access-denied";
options.SlidingExpiration = true;
});
// ... existing code ...
// Add authentication/authorization middleware (BEFORE app.MapControllerRoute())
app.UseAuthentication();
app.UseAuthorization();
- Add the login partial to your layout (
Views/Shared/_Layout.cshtml):
<partial name="_LoginPartial" />
- Create and apply Identity migration:
dotnet ef migrations add AddIdentity
dotnet ef database update
- (Optional) Configure email service for password reset:
- Currently, password reset tokens are logged to the console
- For production, implement an email service and update
AuthController.ForgotPassword
Usage:
- Visit
/auth/registerto create a new account - Visit
/auth/loginto sign in - Use
[Authorize]attribute to protect controllers/actions - Access user info via
User.Identity.Namein controllers and views
Protecting routes:
[Authorize] // Requires authentication
public class AdminController : Controller
{
// ...
}
[Authorize(Roles = "Admin")] // Requires specific role
public IActionResult ManageUsers()
{
// ...
}
swap generate resource <name> --fields <fields>
Generate model + controller together (alias for backward compatibility).
swap generate test <controller>
Generate an integration test class scaffold for a controller using Swap.Testing.
# Generate tests for TodoItemController
swap g test TodoItem
# Short alias
swap g t TodoItem
# Force overwrite
swap g test TodoItem --force
# Specify project/output
swap g test TodoItem --project path/to/project --output Tests
Options:
--force, -fOverwrite existing file--project, -pPath to project (default: current dir)--output, -oOutput folder (default:Tests/)
What it generates:
<Output>/<ControllerName>Tests.cswith HTMX partial assertions- Common test scenarios: index partial, create/edit forms, snapshot example
- References
Swap.Testingpackage
Example test:
[Fact]
public async Task Index_AsHtmx_ReturnsPartial()
{
var resp = await _client.HtmxGetAsync("/todos");
resp.AssertSuccess();
await resp.AssertPartialViewAsync();
await resp.AssertElementCountAsync(".todo-item", 3);
}
swap generate factory <entity>
Generate a Bogus-powered test data factory for an entity model.
# Generate a factory from Models/Post.cs
swap g factory Post
# Short alias
swap g f Post
# Force overwrite
swap g factory Post --force
# Specify project/output
swap g factory Post --project path/to/project --output Tests/Factories
Options:
--forceOverwrite existing file--project, -pPath to project (default: current dir)--output, -oOutput folder (default:Tests/Factories/)
What it generates:
<Output>/<Entity>Factory.cswith intelligent property mappings- Bogus rules based on property names (Email β f.Internet.Email(), etc.)
- Navigation properties skipped
- Nullable type support
Example factory:
public static class PostFactory
{
public static Post Generate()
{
var faker = new Faker<Post>()
.RuleFor(p => p.Title, f => f.Lorem.Sentence())
.RuleFor(p => p.Body, f => f.Lorem.Paragraphs(2))
.RuleFor(p => p.PublishedAt, f => f.Date.Past());
return faker.Generate();
}
}
If Bogus/Swap.Testing packages are missing, the CLI prints the commands to install them.
π§ͺ Swap.Testing (HTMX Testing Framework)
A fluent testing library purpose-built for HTMX applications, included with Swap.
Key Features:
- π― HTMX-Aware Client -
HtmxGetAsync,HtmxPostAsyncwith automatic HX-Request headers - π Rich Assertions -
AssertPartialViewAsync,AssertHxGetAsync,AssertHxTriggered - πΈ Snapshot Testing -
AssertMatchesSnapshotAsyncwithUPDATE_SNAPSHOTS=true - β
Validation Helpers -
AssertHasValidationErrorsAsync,AssertFieldValidationErrorAsync - π Form Helpers -
SubmitFormAsync,FollowHxRedirectAsync - π§Ή Snapshot Scrubbers - Auto-replace GUIDs/timestamps/tokens for stable snapshots
Quick Example:
public class PostControllerTests : IClassFixture<HtmxTestFixture<Program>>
{
private readonly HtmxTestClient<Program> _client;
public PostControllerTests(HtmxTestFixture<Program> fixture) => _client = fixture.Client;
[Fact]
public async Task Create_Form_IsPartial_WithHtmxAttributes()
{
var resp = await _client.HtmxGetAsync("/posts/create");
resp.AssertSuccess();
await resp.AssertPartialViewAsync();
await resp.AssertHxPostAsync("form", "/posts");
await resp.AssertHxTargetAsync("form", "#post-list");
}
}
See also:
- Swap.Testing Framework Guide
- Testing Framework Wiki
- Demo app:
testApps/HtmxTestingDemo/
π§© Swap.Patterns (Common Entity Patterns)
Add battle-tested patterns to your entities with single commands.
swap generate pattern softdelete <entity>
Add soft delete functionality to any entity. Deleted records are hidden from queries but remain in the database.
# Add soft delete to Post entity
swap g pattern softdelete Post
# Short aliases
swap g p soft Post
What it does:
- Adds
ISoftDeletableinterface to your entity - Adds three properties:
IsDeleted,DeletedAt,DeletedBy - Adds using statement for
Swap.Patterns.SoftDelete - Ensures
Swap.Patternspackage reference
After generation:
public class Post : ISoftDeletable
{
public int Id { get; set; }
public string Title { get; set; }
// ISoftDeletable properties
public bool IsDeleted { get; set; }
public DateTime? DeletedAt { get; set; }
public string? DeletedBy { get; set; }
}
Next steps:
- Configure query filter in your
DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ConfigureSoftDeleteFilter();
}
- Create and apply migration:
dotnet ef migrations add AddSoftDeleteToPost
dotnet ef database update
Usage in code:
// Soft delete
post.SoftDelete("user@example.com");
await db.SaveChangesAsync();
// Restore
post.Restore();
await db.SaveChangesAsync();
// Query only deleted
var deleted = await db.Posts.OnlyDeleted().ToListAsync();
// Include deleted in results
var all = await db.Posts.IncludeDeleted().ToListAsync();
// Normal queries automatically exclude deleted
var active = await db.Posts.ToListAsync();
See also:
swap generate pattern auditable <entity>
Add automatic timestamp and user tracking to any entity. Records who created and modified each record and when.
# Add auditable to Post entity
swap g pattern auditable Post
# Short aliases
swap g p audit Post
What it does:
- Adds
IAuditableinterface to your entity - Adds four properties:
CreatedAt,CreatedBy,UpdatedAt,UpdatedBy - Adds using statement for
Swap.Patterns.Auditable - Ensures
Swap.Patternspackage reference
After generation:
public class Post : IAuditable
{
public int Id { get; set; }
public string Title { get; set; }
// IAuditable properties
public DateTime CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTime? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
}
Next steps:
- Add HTTP context accessor in
Program.cs:
builder.Services.AddHttpContextAccessor();
- Configure audit interceptor in your
DbContext:
private readonly IHttpContextAccessor _httpContextAccessor;
public AppDbContext(
DbContextOptions<AppDbContext> options,
IHttpContextAccessor httpContextAccessor) : base(options)
{
_httpContextAccessor = httpContextAccessor;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(_httpContextAccessor.CreateAuditInterceptor());
}
- Create and apply migration:
dotnet ef migrations add AddAuditableToPost
dotnet ef database update
How it works:
- Timestamps and user IDs are set automatically on
SaveChanges() CreatedAtandCreatedByare set once when entity is addedUpdatedAtandUpdatedByare updated on every modification- User ID comes from Claims (
NameIdentifier,Name, orEmail)
swap generate pattern sluggable <entity>
Add SEO-friendly URL slugs to any entity. Perfect for blog posts, products, and content pages.
# Add sluggable to BlogPost entity
swap g pattern sluggable BlogPost
# Short aliases
swap g p slug BlogPost
What it does:
- Adds
ISluggableinterface to your entity - Adds
Slugproperty - Adds using statement for
Swap.Patterns.Sluggable - Ensures
Swap.Patternspackage reference
After generation:
public class BlogPost : ISluggable
{
public int Id { get; set; }
public string Title { get; set; }
// ISluggable property
public string Slug { get; set; } = "";
}
Next steps:
- Add unique index in your
DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ConfigureSlugIndexes();
// OR manually for specific entity:
modelBuilder.Entity<BlogPost>()
.HasIndex(e => e.Slug)
.IsUnique();
}
- Generate slug before saving (in controller or service):
var post = new BlogPost { Title = "Hello World!" };
await post.GenerateSlugAsync(post.Title, _db);
_db.BlogPosts.Add(post);
await _db.SaveChangesAsync();
// post.Slug is now "hello-world"
- Create and apply migration:
dotnet ef migrations add AddSlugToBlogPost
dotnet ef database update
Features:
- Converts text to URL-safe slugs: "Hello World!" β "hello-world"
- Handles international characters: "CafΓ© MΓΌnchen" β "cafe-munchen"
- Automatic collision handling: "post" β "post-2" β "post-3"
- Configurable max length (default: 80 characters)
Usage in routes:
// Find by slug instead of ID
var post = await _db.BlogPosts
.FirstOrDefaultAsync(p => p.Slug == slug);
// Pretty URLs
// Before: /blog/123
// After: /blog/hello-world
swap generate pattern timestampable <entity>
Track creation and update timestamps automatically without user attribution. A lightweight alternative to Auditable when you only need dates.
# Add timestampable to Post entity
swap g pattern timestampable Post
# Short aliases
swap g p ts Post
What it does:
- Adds
ITimestampableinterface to your entity - Adds two properties:
CreatedAt,UpdatedAt - Adds using statement for
Swap.Patterns.Timestampable - Ensures
Swap.Patternspackage reference
After generation:
public class Post : ITimestampable
{
public int Id { get; set; }
public string Title { get; set; }
// ITimestampable properties
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
Next steps:
- Configure timestamp interceptor in your
DbContext:
using Swap.Patterns.Timestampable;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(new TimestampInterceptor());
}
- Create and apply migration:
dotnet ef migrations add AddTimestampsToPost
dotnet ef database update
How it works:
CreatedAtset on insert;UpdatedAtset on every modification- Requires no
IHttpContextAccessor - Safe to combine with most patterns (but not with Auditable)
swap generate pattern orderable <entity>
Add a stable Position property and helpers for ordering lists and drag-and-drop UIs.
# Add orderable to Category entity
swap g pattern orderable Category
# Short aliases
swap g p order Category
What it does:
- Adds
IOrderableinterface to your entity - Adds
Positionproperty - Adds using statement for
Swap.Patterns.Orderable - Ensures
Swap.Patternspackage reference
After generation:
public class Category : IOrderable
{
public int Id { get; set; }
public string Name { get; set; }
// IOrderable property
public int Position { get; set; }
}
Usage:
// Get next position for a new item
var next = await _db.Categories.GetNextPositionAsync();
db.Categories.Add(new Category { Name = "New", Position = next });
// Reorder an item (1-based index)
var item = await _db.Categories.FindAsync(id);
await _db.Categories.ReorderAsync(item!, newPosition: 1);
await _db.SaveChangesAsync();
// Normalize positions after deletes/bulk ops
await _db.Categories.NormalizePositionsAsync();
await _db.SaveChangesAsync();
// Convenient ordering for queries
var ordered = await _db.Categories.OrderByPosition().ToListAsync();
swap generate pattern publishable <entity>
Add a draft/published workflow to your entity with a simple boolean flag and timestamp.
# Add publishable to Article entity
swap g pattern publishable Article
# Short aliases
swap g p publish Article
What it does:
- Adds
IPublishableinterface to your entity - Adds two properties:
IsPublished,PublishedAt - Adds using statement for
Swap.Patterns.Publishable - Ensures
Swap.Patternspackage reference
After generation:
public class Article : IPublishable
{
public int Id { get; set; }
public string Title { get; set; }
// IPublishable properties
public bool IsPublished { get; set; }
public DateTime? PublishedAt { get; set; }
}
Usage:
// Publish now (sets IsPublished and PublishedAt = UtcNow)
article.Publish();
// Unpublish (revert to draft)
article.Unpublish();
// Query helpers
var published = await _db.Articles.Published().ToListAsync();
var drafts = await _db.Articles.Drafts().ToListAsync();
swap generate pattern versionable <entity>
Track and increment a simple integer Version on every update.
# Add versionable to Document entity
swap g pattern versionable Document
# Short aliases
swap g p version Document
What it does:
- Adds
IVersionableinterface to your entity - Adds one property:
Version(int) - Adds using statement for
Swap.Patterns.Versionable - Ensures
Swap.Patternspackage reference
After generation:
public class Document : IVersionable
{
public int Id { get; set; }
public string Title { get; set; }
// IVersionable properties
public int Version { get; set; }
}
Next steps:
- Configure version interceptor in your
DbContext:
using Swap.Patterns.Versionable;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(new VersionInterceptor());
}
- Create and apply migration:
dotnet ef migrations add AddVersionToDocument
dotnet ef database update
How it works:
- Sets
Version = 1on insert if not already set - Increments
Versionon every update (save) - Query helpers:
.WithMinVersion(n),.WithVersion(n),.OrderByVersion()
swap generate pattern visibility <entity>
Add controllable visibility with optional time-based scheduling for features, content releases, or time-bound offers.
# Add visibility to Banner entity
swap g pattern visibility Banner
# Short aliases
swap g p visible Banner
What it does:
- Adds
IVisibilityinterface to your entity - Adds three properties:
IsVisible,VisibleFrom,VisibleUntil - Adds using statement for
Swap.Patterns.Visibility - Ensures
Swap.Patternspackage reference
After generation:
public class Banner : IVisibility
{
public int Id { get; set; }
public string Title { get; set; }
// IVisibility properties
public bool IsVisible { get; set; }
public DateTime? VisibleFrom { get; set; }
public DateTime? VisibleUntil { get; set; }
}
Usage:
// Show/hide manually
banner.Show();
banner.Hide();
// Schedule for future (UTC)
banner.ScheduleVisibility(DateTime.UtcNow.AddDays(7));
// Schedule within a window
banner.ScheduleVisibilityWindow(
DateTime.UtcNow.AddDays(7),
DateTime.UtcNow.AddDays(14)
);
// Check if currently visible (considering time window)
if (banner.IsCurrentlyVisible())
{
// show banner
}
// Query helpers
var visible = await _db.Banners.Visible().ToListAsync();
var scheduled = await _db.Banners.Scheduled().ToListAsync();
var expired = await _db.Banners.Expired().ToListAsync();
Combining Patterns
You can mix and match compatible patterns on the same entity. Do not combine Auditable and Timestampable together (both define CreatedAt/UpdatedAt).
// Example with Auditable
public class Product : ISoftDeletable, IAuditable, ISluggable, IOrderable
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// Soft Delete (3 properties)
public bool IsDeleted { get; set; }
public DateTime? DeletedAt { get; set; }
public string? DeletedBy { get; set; }
// Auditable (4 properties)
public DateTime CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTime? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
// Sluggable (1 property)
public string Slug { get; set; } = "";
// Orderable (1 property)
public int Position { get; set; }
}
Apply all patterns in sequence:
swap g pattern softdelete Product
swap g pattern auditable Product
swap g pattern sluggable Product
swap g pattern orderable Product
Alternatively, replace Auditable with Timestampable when you don't need user attribution:
swap g pattern softdelete Product
swap g pattern timestampable Product
swap g pattern sluggable Product
swap g pattern orderable Product
π¦ Generate Resource
swap g r BlogPost --fields "Title:string Content:string PublishedDate:DateTime"
swap g r BlogPost --fields Title:string,Content:string,PublishedDate:DateTime
# With generator ergonomics options
swap g r Order --fields "Total:decimal Status:string" --dry-run
swap g r Order --fields "Total:decimal Status:string" --force --project path/to/project
Options:
--fieldsor-f- Field definitions (space or comma separated)--dry-run- Preview what would be generated without writing files--force- Overwrite existing files without prompting--projector-p- Path to project directory (default: current directory)
swap generate seed <name>
Generate database seeders with realistic fake data using Bogus.
# Generate a seeder for a single entity
swap g seed Product --count 100 --locale en --if-empty
# Generate seeders for all entities in your DbContext
swap g seed all --count 50 --locale en --if-empty
# Short alias
swap g s all --count 50 --locale en --if-empty
# Overwrite without prompting
swap g s Product --force
# Generate in a different project
swap g s all --project path/to/project
Options:
--count(default: 50) - Number of records to generate--locale(default: "en") - Bogus locale (en, en_GB, de, fr, etc.)--if-empty- Only seed when the table is empty (idempotent)--force- Overwrite existing seeder files without prompting--projector-p- Path to project directory (default: current directory)
What it generates:
Data/Seeders/<Entity>Seeder.cswith smart Bogus rules based on field namesData/Seeders/SeedRunner.csorchestrator (auto-registered)- Adds
Boguspackage reference if missing - Hooks into
Program.csfor Development environment seeding
Field intelligence:
- Strings: emails, URLs, names, titles, descriptions, phone numbers, addresses
- Numbers: realistic ranges based on field names (age, price, quantity)
- Booleans: weighted probabilities (e.g., IsActive ~70% true)
- Dates: distributed over the last 3 years
- Foreign keys: picks from existing related entities with null safety
- Slugs: unique slugs with random suffix for collision avoidance
Pattern integration (v0.0.14+):
- Auto-excludes pattern properties managed by EF Core interceptors:
CreatedAt,CreatedBy,UpdatedAt,UpdatedBy(IAuditable)IsDeleted,DeletedAt,DeletedBy(ISoftDeletable)Version(IVersionable)IsVisible,VisibleFrom,VisibleUntil(IVisibility)Position(IOrderable)
- Smart defaults: Generates appropriate rules for application-managed properties like
PublishedAt,Slug, etc. - Relationship handling: Preloads foreign key IDs and safely handles empty tables
Environment control:
# Control seeding via environment variables
$env:SEED_COUNT = "200"
$env:SEED_LOCALE = "en_GB"
$env:SEED_IFEMPTY = "true"
dotnet run
swap database / swap db
Database workflow commands for easier development.
swap db info
Display database configuration and migration status.
swap db info
swap db migrate [name] [--apply]
Create and/or apply Entity Framework Core migrations.
# Create a new migration
swap db migrate AddProductTable
# Create and apply immediately
swap db migrate AddProductTable --apply
# Apply pending migrations
swap db migrate --apply
swap db reset [--force]
Drop and recreate the database for a fresh start.
swap db reset
swap db reset --force
swap db seed [--count] [--locale] [--if-empty]
Run database seeders via application startup.
swap db seed --count 100 --locale en_GB --if-empty
swap doctor
Check your development environment and dependencies.
swap doctor
Checks .NET SDK, dotnet-ef, Node.js, npm, and libman installations.
swap list [--project]
List all resources (entities) in your project with their completeness status.
swap list
swap list --project path/to/project
Shows which entities have models, controllers, and seeders.
π Documentation
- Getting Started - Complete setup guide
- CLI Reference - All commands and options
- Features Guide - Pagination, search, sorting, filtering
- Pattern Library - 30+ proven HTMX patterns
- The Product Vision - Philosophy and approach
π οΈ Development
Prerequisites
- .NET 9.0 SDK or later
- Your favorite IDE (Visual Studio 2022, VS Code, Rider)
Building the CLI from Source
# Clone the repository
git clone https://github.com/jdtoon/swap.git
cd swap
# Build the CLI tool
cd tools/Swap.CLI
dotnet build
# Run tests
cd ../Swap.CLI.Tests
dotnet test
# Install locally for testing
cd ../Swap.CLI
dotnet pack
dotnet tool install --global --add-source ./nupkg Swap.CLI
Project Structure
swap/
βββ tools/
β βββ Swap.CLI/ # CLI tool source code
β β βββ Commands/ # Command implementations
β β βββ Infrastructure/ # Template engine, helpers
β β βββ Program.cs # CLI entry point
β βββ Swap.CLI.Tests/ # 145 passing tests
β βββ Commands/ # Command tests
β βββ Infrastructure/ # Template engine tests
βββ templates/ # Code generation templates
β βββ monolith/ # New project template
β βββ generate/ # CRUD generation templates
β βββ controller/ # Controller, views, view model
β βββ model/ # Model class
βββ docs/ # Documentation
β βββ THE-PRODUCT.md # Product vision
β βββ PATTERNS-LIBRARY.md # HTMX patterns
βββ wiki/ # Docusaurus documentation site
βββ README.md # This file
π€ Contributing
Contributions are welcome! Whether it's bug reports, feature requests, or code contributions.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes and add tests
- Ensure all tests pass (
dotnet test) - Commit your changes (
git commit -m 'feat: Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines.
π License
Swap CLI is MIT licensed. Use it freely in your projects, commercial or otherwise.
οΏ½ Project Status
Current Version: 0.1.0-dev (Active Development)
β Phase 2C Complete (Current)
- β
New Project Generation -
swap newcommand with full ASP.NET Core setup - β
Controller Generation -
swap g cwith all CRUD operations - β
Model Generation -
swap g mfor entity classes - β Pagination - Configurable page sizes (10, 25, 50, 100)
- β Search - Real-time search with 500ms debounce
- β Sorting - Column sorting with field-level control
- β Filtering - Boolean filters with dropdown UI
- β Modal Editing - Create, Edit, Details modals via HTMX
- β Bulk Delete - Select multiple items and delete
- β Toast Notifications - DaisyUI alerts for success/error
- β DaisyUI Components - Modern, accessible UI library
- β Tailwind CSS - Utility-first styling
- β 145 Passing Tests - Comprehensive test coverage
- β Documentation - Complete wiki with examples
β Phase 2D Complete: Database Seeders
- β
Seeder Generation -
swap g seed <entity>andswap g seed all - β Bogus Integration - Realistic fake data with smart field heuristics
- β Environment Control - SEED_COUNT, SEED_LOCALE, SEED_IFEMPTY
- β Foreign Key Support - Automatic relationship handling
- β Development Startup - Auto-seed on app launch in Development mode
- β
Idempotent Seeding -
--if-emptyflag for safe repeated runs
π― Phase 3: Polish & Release
- β³ NuGet Package - Publish to NuGet.org
- β³ VS Code Extension - Integrated CLI experience
- β³ Video Tutorials - Getting started screencasts
- β³ Production Release (v1.0.0) - Q1 2026
See the complete roadmap for details.
π¬ Community
- Documentation: https://jdtoon.github.io/swap/
- GitHub Issues: https://github.com/jdtoon/swap/issues
- GitHub Discussions: Coming soon
For questions or feedback, open an issue!
π Links
- Documentation: https://jdtoon.github.io/swap/
- GitHub: https://github.com/jdtoon/swap
- Issues: https://github.com/jdtoon/swap/issues
- NuGet (coming soon): https://www.nuget.org/packages/Swap.CLI
Template selection (DX)
Choose the DX-forward template with the Event System fully wired:
swap new MyApp --template swap-monolith --database sqlite
What you get:
AddSwapHtmx(...)with chains scaffold- Middleware:
UseSwapHtmxShell()andUseSwapHtmx() Events/SwapEventChains.csusingEventNames.*constants- Dev-only endpoints at
/_swap/dev/eventsand/_swap/dev/events.json
Or generate a multi-project layered solution (aliases: layered and swap-layered):
swap new MyApp --template layered --database sqlite
What you get:
- Solution with projects: Web, Application, Domain, Infrastructure
- Web registers
AddSwapHtmx(...)andUseSwapHtmxShell()/UseSwapHtmx() - Event chains:
Web/Events/SwapEventChains.csmapping domain β ui.* events - Post-create (automated unless
--skip-setup): npm/libman/CSS inWeb/, EF migrations with-p Infrastructure -s Web
Events inspection and DX helpers
Inspect your event chains from source or from a running app:
# Source scan (resolves EventNames constants)
swap events list -p .
# From running server (Development-only endpoint)
swap events from-server --url http://localhost:5000
# Validate names and cycles
swap events validate -p .
# Graph output (Mermaid or DOT)
swap events graph -p . --format mermaid
swap events graph -p . --format dot --output chains.dot
Notes:
- Run the server in one terminal (or detached) and query from another.
- Dev endpoint returns a live snapshot of Trigger β Chained edges.
- Chains are directional and one-hop by default; advanced modes are available in
SwapEventBusOptionsviaResolutionModeandMaxTransitiveDepth.
Built with β€οΈ for the .NET community
Swap CLI - Generate production-ready ASP.NET + HTMX applications in seconds.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|