Created
November 18, 2024 02:53
-
-
Save aileron/8145ff378add31577f3e6b95ad7f6b1a to your computer and use it in GitHub Desktop.
This script sets up a new Rails project with modern frontend stack including Inertia.js, React, Tailwind CSS, and shadcn/ui components.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Exit immediately if a command exits with a non-zero status | |
set -e | |
# Get directory name from argument, default is "my-app" | |
APP_NAME=${1:-"my-app"} | |
# Check if directory already exists | |
if [ -d "$APP_NAME" ]; then | |
echo "Error: Directory $APP_NAME already exists." | |
exit 1 | |
fi | |
echo "=== Starting setup ===" | |
echo "Application name: $APP_NAME" | |
# Create Rails application | |
rails new $APP_NAME --css=tailwind --database=postgresql --skip-javascript | |
# Move into the application directory | |
cd $APP_NAME | |
# Add gems to Gemfile | |
cat << EOF >> Gemfile | |
# Vite | |
gem 'vite_rails' | |
# Inertia adapter for Rails | |
gem 'inertia_rails' | |
# Development tools | |
gem 'foreman' | |
gem 'ridgepole' | |
EOF | |
# Install gems | |
bundle install | |
# Install Vite | |
bundle exec vite install | |
# Setup package.json | |
npm init -y | |
npm pkg set type="module" | |
# Pin Node.js version using Volta | |
volta pin node@20 | |
volta pin npm@10 | |
# Install NPM packages | |
volta run npm install \ | |
react \ | |
react-dom \ | |
@inertiajs/react \ | |
@vitejs/plugin-react \ | |
@types/react \ | |
@types/react-dom \ | |
typescript \ | |
autoprefixer \ | |
postcss \ | |
tailwindcss \ | |
class-variance-authority \ | |
clsx \ | |
@radix-ui/react-icons \ | |
tailwind-merge \ | |
lucide-react \ | |
@radix-ui/react-slot \ | |
tailwindcss-animate \ | |
shadcn \ | |
vite \ | |
vite-plugin-ruby | |
# Create directory structure | |
mkdir -p app/frontend/{components/ui,pages,lib} | |
# Create TypeScript config | |
cat << EOF > tsconfig.json | |
{ | |
"compilerOptions": { | |
"target": "ESNext", | |
"useDefineForClassFields": true, | |
"lib": ["DOM", "DOM.Iterable", "ESNext"], | |
"allowJs": false, | |
"skipLibCheck": true, | |
"esModuleInterop": false, | |
"allowSyntheticDefaultImports": true, | |
"strict": true, | |
"forceConsistentCasingInFileNames": true, | |
"module": "ESNext", | |
"moduleResolution": "Node", | |
"resolveJsonModule": true, | |
"isolatedModules": true, | |
"noEmit": true, | |
"jsx": "react-jsx", | |
"baseUrl": ".", | |
"paths": { | |
"@/*": ["./app/frontend/*"] | |
} | |
}, | |
"include": ["app/frontend/**/*.ts", "app/frontend/**/*.tsx"], | |
"references": [{ "path": "./tsconfig.node.json" }] | |
} | |
EOF | |
cat << EOF > tsconfig.node.json | |
{ | |
"compilerOptions": { | |
"composite": true, | |
"module": "ESNext", | |
"moduleResolution": "Node", | |
"allowSyntheticDefaultImports": true | |
}, | |
"include": ["vite.config.ts"] | |
} | |
EOF | |
# Create Vite config | |
cat << EOF > vite.config.ts | |
import { defineConfig } from 'vite' | |
import RubyPlugin from 'vite-plugin-ruby' | |
import react from '@vitejs/plugin-react' | |
import path from 'path' | |
import tailwindcss from 'tailwindcss' | |
import autoprefixer from 'autoprefixer' | |
export default defineConfig({ | |
plugins: [ | |
RubyPlugin(), | |
react() | |
], | |
resolve: { | |
alias: { | |
'@': path.resolve(__dirname, './app/frontend') | |
} | |
}, | |
css: { | |
postcss: { | |
plugins: [ | |
tailwindcss, | |
autoprefixer | |
] | |
} | |
} | |
}) | |
EOF | |
# Create Vite Ruby config | |
cat << EOF > config/vite.json | |
{ | |
"all": { | |
"sourceCodeDir": "app/frontend", | |
"watchAdditionalPaths": [] | |
} | |
} | |
EOF | |
# Create utility functions | |
cat << EOF > app/frontend/lib/utils.ts | |
import { type ClassValue, clsx } from "clsx" | |
import { twMerge } from "tailwind-merge" | |
export function cn(...inputs: ClassValue[]) { | |
return twMerge(clsx(inputs)) | |
} | |
EOF | |
# Create React entry point | |
cat << EOF > app/frontend/entrypoints/application.ts | |
import './application.css' | |
import './application.tsx' | |
EOF | |
cat << EOF > app/frontend/entrypoints/application.tsx | |
import React from 'react' | |
import { createRoot } from 'react-dom/client' | |
import { createInertiaApp } from '@inertiajs/react' | |
document.addEventListener('DOMContentLoaded', () => { | |
createInertiaApp({ | |
resolve: name => { | |
const pages = import.meta.glob<any>('../pages/**/*.tsx', { eager: true }) | |
return pages[\`../pages/\${name}.tsx\`] | |
}, | |
setup({ el, App, props }) { | |
createRoot(el).render(<App {...props} />) | |
}, | |
}) | |
}) | |
EOF | |
# Create Home page | |
cat << EOF > app/frontend/pages/Home.tsx | |
import React from 'react' | |
import { Button } from "@/components/ui/button"; | |
interface HomeProps { | |
message?: string | |
} | |
export default function Home({ message = "Welcome" }: HomeProps) { | |
return ( | |
<div className="p-8"> | |
<h1 className="text-3xl font-bold text-blue-600"> | |
{message}! | |
</h1> | |
<p className="mt-4"> | |
Built with Rails + Inertia + React + Tailwind CSS + shadcn/ui | |
</p> | |
<Button | |
onClick={() => alert('TEST')} | |
variant="outline" | |
className="w-full" | |
> | |
TEST | |
</Button> | |
</div> | |
) | |
} | |
EOF | |
# Create CSS file | |
cat << EOF > app/frontend/entrypoints/application.css | |
@tailwind base; | |
@tailwind components; | |
@tailwind utilities; | |
@layer base { | |
:root { | |
--background: 0 0% 100%; | |
--foreground: 222.2 84% 4.9%; | |
--card: 0 0% 100%; | |
--card-foreground: 222.2 84% 4.9%; | |
--popover: 0 0% 100%; | |
--popover-foreground: 222.2 84% 4.9%; | |
--primary: 222.2 47.4% 11.2%; | |
--primary-foreground: 210 40% 98%; | |
--secondary: 210 40% 96.1%; | |
--secondary-foreground: 222.2 47.4% 11.2%; | |
--muted: 210 40% 96.1%; | |
--muted-foreground: 215.4 16.3% 46.9%; | |
--accent: 210 40% 96.1%; | |
--accent-foreground: 222.2 47.4% 11.2%; | |
--destructive: 0 84.2% 60.2%; | |
--destructive-foreground: 210 40% 98%; | |
--border: 214.3 31.8% 91.4%; | |
--input: 214.3 31.8% 91.4%; | |
--ring: 222.2 84% 4.9%; | |
--radius: 0.5rem; | |
} | |
.dark { | |
--background: 222.2 84% 4.9%; | |
--foreground: 210 40% 98%; | |
--card: 222.2 84% 4.9%; | |
--card-foreground: 210 40% 98%; | |
--popover: 222.2 84% 4.9%; | |
--popover-foreground: 210 40% 98%; | |
--primary: 210 40% 98%; | |
--primary-foreground: 222.2 47.4% 11.2%; | |
--secondary: 217.2 32.6% 17.5%; | |
--secondary-foreground: 210 40% 98%; | |
--muted: 217.2 32.6% 17.5%; | |
--muted-foreground: 215 20.2% 65.1%; | |
--accent: 217.2 32.6% 17.5%; | |
--accent-foreground: 210 40% 98%; | |
--destructive: 0 62.8% 30.6%; | |
--destructive-foreground: 210 40% 98%; | |
--border: 217.2 32.6% 17.5%; | |
--input: 217.2 32.6% 17.5%; | |
--ring: 212.7 26.8% 83.9%; | |
} | |
} | |
@layer base { | |
* { | |
@apply border-border; | |
} | |
body { | |
@apply bg-background text-foreground; | |
} | |
} | |
EOF | |
# Create layout directory and base layout | |
mkdir -p app/views/layouts | |
cat << EOF > app/views/layouts/application.html.erb | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Taskmaster</title> | |
<meta name="viewport" content="width=device-width,initial-scale=1"> | |
<%= csrf_meta_tags %> | |
<%= csp_meta_tag %> | |
<%= vite_client_tag %> | |
<%= vite_javascript_tag 'application.ts' %> | |
</head> | |
<body> | |
<%= yield %> | |
</body> | |
</html> | |
EOF | |
# Create PostCSS config | |
cat << EOF > postcss.config.js | |
export default { | |
plugins: { | |
tailwindcss: {}, | |
autoprefixer: {}, | |
} | |
} | |
EOF | |
# Create Tailwind config | |
cat << EOF > tailwind.config.js | |
/** @type {import('tailwindcss').Config} */ | |
export default { | |
darkMode: ["class"], | |
content: [ | |
'./app/views/**/*.erb', | |
'./app/frontend/**/*.{js,jsx,ts,tsx}' | |
], | |
theme: { | |
container: { | |
center: true, | |
padding: "2rem", | |
screens: { | |
"2xl": "1400px", | |
}, | |
}, | |
extend: { | |
colors: { | |
border: "hsl(var(--border))", | |
input: "hsl(var(--input))", | |
ring: "hsl(var(--ring))", | |
background: "hsl(var(--background))", | |
foreground: "hsl(var(--foreground))", | |
primary: { | |
DEFAULT: "hsl(var(--primary))", | |
foreground: "hsl(var(--primary-foreground))", | |
}, | |
secondary: { | |
DEFAULT: "hsl(var(--secondary))", | |
foreground: "hsl(var(--secondary-foreground))", | |
}, | |
destructive: { | |
DEFAULT: "hsl(var(--destructive))", | |
foreground: "hsl(var(--destructive-foreground))", | |
}, | |
muted: { | |
DEFAULT: "hsl(var(--muted))", | |
foreground: "hsl(var(--muted-foreground))", | |
}, | |
accent: { | |
DEFAULT: "hsl(var(--accent))", | |
foreground: "hsl(var(--accent-foreground))", | |
}, | |
popover: { | |
DEFAULT: "hsl(var(--popover))", | |
foreground: "hsl(var(--popover-foreground))", | |
}, | |
card: { | |
DEFAULT: "hsl(var(--card))", | |
foreground: "hsl(var(--card-foreground))", | |
}, | |
}, | |
borderRadius: { | |
lg: "var(--radius)", | |
md: "calc(var(--radius) - 2px)", | |
sm: "calc(var(--radius) - 4px)", | |
}, | |
keyframes: { | |
"accordion-down": { | |
from: { height: 0 }, | |
to: { height: "var(--radix-accordion-content-height)" }, | |
}, | |
"accordion-up": { | |
from: { height: "var(--radix-accordion-content-height)" }, | |
to: { height: 0 }, | |
}, | |
}, | |
animation: { | |
"accordion-down": "accordion-down 0.2s ease-out", | |
"accordion-up": "accordion-up 0.2s ease-out", | |
}, | |
}, | |
}, | |
plugins: [require("tailwindcss-animate")], | |
} | |
EOF | |
# Create Procfile.dev | |
cat << EOF > Procfile.dev | |
web: bin/rails server -p 3000 | |
vite: bin/vite dev | |
EOF | |
# Generate controller | |
rails generate controller home index --skip-routes | |
# Update routes | |
echo "Rails.application.routes.draw do | |
root 'home#index' | |
end" > config/routes.rb | |
# Update ApplicationController | |
cat << EOF > app/controllers/application_controller.rb | |
class ApplicationController < ActionController::Base | |
include InertiaRails::Controller | |
end | |
EOF | |
# Update HomeController | |
cat << EOF > app/controllers/home_controller.rb | |
class HomeController < ApplicationController | |
def index | |
render inertia: 'Home', props: { | |
message: 'Welcome' | |
} | |
end | |
end | |
EOF | |
# Create components.json for shadcn/ui CLI | |
cat << EOF > components.json | |
{ | |
"\$schema": "https://ui.shadcn.com/schema.json", | |
"style": "default", | |
"rsc": false, | |
"tsx": true, | |
"tailwind": { | |
"config": "tailwind.config.js", | |
"css": "app/frontend/entrypoints/application.css", | |
"baseColor": "slate", | |
"cssVariables": true | |
}, | |
"aliases": { | |
"components": "@/components", | |
"utils": "@/lib/utils" | |
} | |
} | |
EOF | |
# Initialize git | |
git init | |
git add . | |
git commit -m "Initial commit: Rails + Vite + React + TypeScript + shadcn/ui" | |
echo "=== Setup completed! ===" | |
echo "To start your application:" | |
echo "1. Set up your database:" | |
echo " rails db:create" | |
echo "" | |
echo "2. To add shadcn ui components, use:" | |
echo " Example: npx shadcn add button" | |
echo "" | |
echo "3. Check build and server startup" | |
echo " bin/vite build --clear --mode=development" | |
echo " bin/rails s" | |
echo "" | |
echo "4. Start the development server:" | |
echo " foreman start -f Procfile.dev" | |
echo "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment