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

13 KiB

description
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

# 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:

# 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):

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:

gem 'rack-cors', '~> 2.0'  # For API
gem 'multi_json', '~> 1.15'

For database options:

# 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):

require 'sinatra'
require 'sinatra/reloader' if development?
require_relative 'config/environment'

get '/' do
  erb :index
end

Modular Base Controller (app/controllers/base_controller.rb):

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):

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):

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:

require_relative 'config/environment'

# Modular
map '/' do
  run ApplicationController
end

# API
# map '/api/v1' do
#   run ApiController
# end

config/environment.rb:

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):

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:

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:

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:

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:

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:

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:

# [Project Name]

[Brief description of the project]

## Setup

1. Install dependencies:
   ```bash
   bundle install
  1. Set up environment variables:

    cp .env.example .env
    # Edit .env with your configuration
    
  2. Set up database (if applicable):

    rake db:migrate
    

Development

Run the application:

bundle exec rerun 'rackup -p 3000'

Or with Puma:

bundle exec puma -C config/puma.rb

Testing

Run tests:

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:

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