Initial commit
This commit is contained in:
654
commands/sinatra-scaffold.md
Normal file
654
commands/sinatra-scaffold.md
Normal file
@@ -0,0 +1,654 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user