Files
gh-geoffjay-claude-plugins-…/commands/sinatra-scaffold.md
2025-11-29 18:28:07 +08:00

655 lines
13 KiB
Markdown

---
description: Scaffold new Sinatra applications with modern structure, best practices, testing setup, and deployment configuration
---
# Sinatra Scaffold Command
Scaffolds a new Sinatra application with modern project structure, testing framework, and deployment configuration.
## Arguments
- **$1: project-name** (required) - Name of the project/application
- **$2: type** (optional) - Application type: `classic`, `modular`, or `api` (default: `modular`)
- **$3: options** (optional) - JSON string with configuration options:
- `testing`: `rspec` or `minitest` (default: `rspec`)
- `database`: `sequel`, `activerecord`, or `none` (default: `sequel`)
- `frontend`: `none`, `erb`, or `haml` (default: `erb`)
## Usage Examples
```bash
# Basic modular app with defaults
/sinatra-scaffold my-app
# Classic app with RSpec and no database
/sinatra-scaffold simple-app classic '{"testing":"rspec","database":"none","frontend":"erb"}'
# API-only app with Minitest and ActiveRecord
/sinatra-scaffold api-service api '{"testing":"minitest","database":"activerecord","frontend":"none"}'
# Full-featured modular app
/sinatra-scaffold webapp modular '{"testing":"rspec","database":"sequel","frontend":"haml"}'
```
## Workflow
### Step 1: Validate and Initialize
**Actions:**
1. Validate project name format (alphanumeric, hyphens, underscores)
2. Check if directory already exists
3. Parse and validate options JSON
4. Create project directory structure
**Validation:**
```bash
# Check project name
if [[ ! "$PROJECT_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Error: Invalid project name. Use alphanumeric characters, hyphens, or underscores."
exit 1
fi
# Check if directory exists
if [ -d "$PROJECT_NAME" ]; then
echo "Error: Directory '$PROJECT_NAME' already exists."
exit 1
fi
```
### Step 2: Create Directory Structure
**Classic Structure:**
```
project-name/
├── app.rb
├── config.ru
├── Gemfile
├── Rakefile
├── config/
│ └── environment.rb
├── public/
│ ├── css/
│ ├── js/
│ └── images/
├── views/
│ ├── layout.erb
│ └── index.erb
├── spec/ or test/
└── README.md
```
**Modular Structure:**
```
project-name/
├── app/
│ ├── controllers/
│ │ ├── application_controller.rb
│ │ └── base_controller.rb
│ ├── models/
│ ├── services/
│ └── helpers/
├── config/
│ ├── environment.rb
│ ├── database.yml (if database selected)
│ └── puma.rb
├── config.ru
├── db/
│ └── migrations/
├── lib/
│ └── tasks/
├── public/
│ ├── css/
│ ├── js/
│ └── images/
├── views/
│ ├── layout.erb
│ └── index.erb
├── spec/ or test/
│ ├── spec_helper.rb
│ └── controllers/
├── Gemfile
├── Rakefile
├── .env.example
├── .gitignore
└── README.md
```
**API Structure:**
```
project-name/
├── app/
│ ├── controllers/
│ │ ├── api_controller.rb
│ │ └── base_controller.rb
│ ├── models/
│ ├── services/
│ └── serializers/
├── config/
│ ├── environment.rb
│ ├── database.yml
│ └── puma.rb
├── config.ru
├── db/
│ └── migrations/
├── lib/
├── spec/ or test/
│ ├── spec_helper.rb
│ ├── requests/
│ └── support/
├── Gemfile
├── Rakefile
├── .env.example
├── .gitignore
└── README.md
```
### Step 3: Generate Gemfile
**Base Dependencies (All Types):**
```ruby
source 'https://rubygems.org'
ruby '~> 3.2'
gem 'sinatra', '~> 3.0'
gem 'sinatra-contrib', '~> 3.0'
gem 'puma', '~> 6.0'
gem 'rake', '~> 13.0'
gem 'dotenv', '~> 2.8'
# Add database gems if selected
# gem 'sequel', '~> 5.0' or gem 'activerecord', '~> 7.0'
# gem 'pg', '~> 1.5' # PostgreSQL
# Add frontend gems if not API
# gem 'haml', '~> 6.0' if haml selected
group :development, :test do
gem 'rspec', '~> 3.12' # or minitest
gem 'rack-test', '~> 2.0'
gem 'rerun', '~> 0.14'
end
group :development do
gem 'pry', '~> 0.14'
end
group :test do
gem 'simplecov', '~> 0.22', require: false
gem 'database_cleaner-sequel', '~> 2.0' # if using Sequel
end
```
**Additional Dependencies by Type:**
For modular/API:
```ruby
gem 'rack-cors', '~> 2.0' # For API
gem 'multi_json', '~> 1.15'
```
For database options:
```ruby
# Sequel
gem 'sequel', '~> 5.0'
gem 'pg', '~> 1.5'
# ActiveRecord
gem 'activerecord', '~> 7.0'
gem 'pg', '~> 1.5'
gem 'sinatra-activerecord', '~> 2.0'
```
### Step 4: Generate Application Files
**Classic App (app.rb):**
```ruby
require 'sinatra'
require 'sinatra/reloader' if development?
require_relative 'config/environment'
get '/' do
erb :index
end
```
**Modular Base Controller (app/controllers/base_controller.rb):**
```ruby
require 'sinatra/base'
require 'sinatra/json'
class BaseController < Sinatra::Base
configure do
set :root, File.expand_path('../..', __dir__)
set :views, Proc.new { File.join(root, 'views') }
set :public_folder, Proc.new { File.join(root, 'public') }
set :show_exceptions, false
set :raise_errors, false
end
configure :development do
require 'sinatra/reloader'
register Sinatra::Reloader
end
helpers do
def json_response(data, status = 200)
halt status, { 'Content-Type' => 'application/json' }, data.to_json
end
end
error do
error = env['sinatra.error']
status 500
json_response({ error: error.message })
end
not_found do
json_response({ error: 'Not found' }, 404)
end
end
```
**Application Controller (app/controllers/application_controller.rb):**
```ruby
require_relative 'base_controller'
class ApplicationController < BaseController
get '/' do
erb :index
end
get '/health' do
json_response({ status: 'ok', timestamp: Time.now.to_i })
end
end
```
**API Controller (for API type):**
```ruby
require_relative 'base_controller'
class ApiController < BaseController
before do
content_type :json
end
# CORS for development
configure :development do
before do
headers 'Access-Control-Allow-Origin' => '*'
end
options '*' do
headers 'Access-Control-Allow-Methods' => 'GET, POST, PUT, PATCH, DELETE, OPTIONS'
headers 'Access-Control-Allow-Headers' => 'Content-Type, Authorization'
200
end
end
get '/' do
json_response({
name: 'API',
version: '1.0',
endpoints: [
{ path: '/health', method: 'GET' }
]
})
end
get '/health' do
json_response({ status: 'healthy', timestamp: Time.now.to_i })
end
end
```
### Step 5: Create Configuration Files
**config.ru:**
```ruby
require_relative 'config/environment'
# Modular
map '/' do
run ApplicationController
end
# API
# map '/api/v1' do
# run ApiController
# end
```
**config/environment.rb:**
```ruby
ENV['RACK_ENV'] ||= 'development'
require 'bundler'
Bundler.require(:default, ENV['RACK_ENV'])
# Load environment variables
require 'dotenv'
Dotenv.load(".env.#{ENV['RACK_ENV']}", '.env')
# Database setup (if selected)
# require_relative 'database'
# Load application files
Dir[File.join(__dir__, '../app/**/*.rb')].sort.each { |file| require file }
```
**config/database.yml (if database selected):**
```yaml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("DB_POOL", 5) %>
host: <%= ENV.fetch("DB_HOST", "localhost") %>
development:
<<: *default
database: <%= ENV.fetch("PROJECT_NAME") %>_development
test:
<<: *default
database: <%= ENV.fetch("PROJECT_NAME") %>_test
production:
<<: *default
database: <%= ENV.fetch("DB_NAME") %>
username: <%= ENV.fetch("DB_USER") %>
password: <%= ENV.fetch("DB_PASSWORD") %>
```
**config/puma.rb:**
```ruby
workers ENV.fetch('WEB_CONCURRENCY', 2)
threads_count = ENV.fetch('MAX_THREADS', 5)
threads threads_count, threads_count
preload_app!
port ENV.fetch('PORT', 3000)
environment ENV.fetch('RACK_ENV', 'development')
on_worker_boot do
# Database reconnection if using ActiveRecord
# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
```
### Step 6: Set Up Testing Framework
**RSpec spec/spec_helper.rb:**
```ruby
ENV['RACK_ENV'] = 'test'
require 'simplecov'
SimpleCov.start
require_relative '../config/environment'
require 'rack/test'
require 'rspec'
# Database cleaner setup (if database)
# require 'database_cleaner/sequel'
RSpec.configure do |config|
config.include Rack::Test::Methods
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
# Database cleaner (if database)
# config.before(:suite) do
# DatabaseCleaner.strategy = :transaction
# DatabaseCleaner.clean_with(:truncation)
# end
#
# config.around(:each) do |example|
# DatabaseCleaner.cleaning do
# example.run
# end
# end
end
```
**Example spec/controllers/application_controller_spec.rb:**
```ruby
require_relative '../spec_helper'
RSpec.describe ApplicationController do
def app
ApplicationController
end
describe 'GET /' do
it 'returns success' do
get '/'
expect(last_response).to be_ok
end
end
describe 'GET /health' do
it 'returns health status' do
get '/health'
expect(last_response).to be_ok
json = JSON.parse(last_response.body)
expect(json['status']).to eq('ok')
end
end
end
```
### Step 7: Create Supporting Files
**.env.example:**
```bash
RACK_ENV=development
PORT=3000
# Database (if selected)
DB_HOST=localhost
DB_NAME=project_name_development
DB_USER=postgres
DB_PASSWORD=
# Session
SESSION_SECRET=your-secret-key-here
# External services
# API_KEY=
```
**.gitignore:**
```
*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/
# Environment files
.env
.env.local
# Database
*.sqlite3
*.db
# Logs
*.log
# Editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
```
**Rakefile:**
```ruby
require_relative 'config/environment'
# Database tasks (if using Sequel)
if defined?(Sequel)
require 'sequel/core'
namespace :db do
desc 'Run migrations'
task :migrate, [:version] do |t, args|
Sequel.extension :migration
db = Sequel.connect(ENV['DATABASE_URL'])
if args[:version]
puts "Migrating to version #{args[:version]}"
Sequel::Migrator.run(db, 'db/migrations', target: args[:version].to_i)
else
puts 'Migrating to latest'
Sequel::Migrator.run(db, 'db/migrations')
end
puts 'Migration complete'
end
end
end
# Testing tasks
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
```
**README.md:**
```markdown
# [Project Name]
[Brief description of the project]
## Setup
1. Install dependencies:
```bash
bundle install
```
2. Set up environment variables:
```bash
cp .env.example .env
# Edit .env with your configuration
```
3. Set up database (if applicable):
```bash
rake db:migrate
```
## Development
Run the application:
```bash
bundle exec rerun 'rackup -p 3000'
```
Or with Puma:
```bash
bundle exec puma -C config/puma.rb
```
## Testing
Run tests:
```bash
bundle exec rspec
```
## Deployment
[Add deployment instructions]
## API Documentation
[Add API documentation if applicable]
```
### Step 8: Initialize Git Repository
**Actions:**
```bash
cd project-name
git init
git add .
git commit -m "Initial commit: Sinatra application scaffold"
```
### Step 9: Install Dependencies
**Actions:**
```bash
bundle install
```
**Verification:**
- Confirm all gems installed successfully
- Check for any dependency conflicts
- Display next steps to user
## Expected Output
```
Creating Sinatra application: my-app
Type: modular
Options: {"testing":"rspec","database":"sequel","frontend":"erb"}
✓ Created directory structure
✓ Generated Gemfile
✓ Created application files
✓ Set up configuration files
✓ Configured RSpec testing
✓ Created supporting files
✓ Initialized git repository
✓ Installed dependencies
Application created successfully!
Next steps:
cd my-app
bundle exec rerun 'rackup -p 3000'
Visit: http://localhost:3000
Tests: bundle exec rspec
```
## Error Handling
- Invalid project name format
- Directory already exists
- Invalid JSON options
- Bundle install failures
- File creation permission errors
## Notes
- All generated code follows Ruby and Sinatra best practices
- Testing framework is fully configured and ready to use
- Development tools (rerun, pry) included for better DX
- Production-ready configuration provided
- Database migrations directory created if database selected
- CORS configured for API applications