Skip to content

Commit be178ab

Browse files
committed
feat: enhance development experience with live reload and new Ruby server script
1 parent ebf76fd commit be178ab

File tree

11 files changed

+208
-33
lines changed

11 files changed

+208
-33
lines changed

Makefile

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@ setup: ## Full development setup
2121
@cd frontend && npm install
2222
@echo "Setup complete!"
2323

24-
dev: ## Start development server with hot reload
25-
@bin/dev-with-frontend
24+
dev: ## Start development server with live reload
25+
@echo "Starting html2rss-web development environment..."
26+
@echo "Ruby server: http://localhost:3000"
27+
@echo "Astro dev server: http://localhost:3001 (with live reload)"
28+
@echo "Main development URL: http://localhost:3001"
29+
@echo ""
30+
@bin/dev
2631

2732
dev-ruby: ## Start Ruby server only
28-
@bin/dev
33+
@bin/dev-ruby
2934

3035
dev-frontend: ## Start Astro dev server only
3136
@cd frontend && npm run dev

app.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def self.development? = ENV['RACK_ENV'] == 'development'
6060

6161
plugin :public
6262
plugin :hash_branches
63+
plugin :render, engine: 'erb', views: 'views'
6364

6465
@show_backtrace = !ENV['CI'].to_s.empty? || (ENV['RACK_ENV'] == 'development')
6566

@@ -84,7 +85,7 @@ def self.development? = ENV['RACK_ENV'] == 'development'
8485
handle_auto_source_feed(r, encoded_url)
8586
end
8687

87-
r.get { auto_source_disabled_response }
88+
r.get { auto_source_instructions_response }
8889
end
8990

9091
# Health check route
@@ -125,6 +126,12 @@ def auto_source_disabled_response
125126
'The auto source feature is disabled.'
126127
end
127128

129+
def auto_source_instructions_response
130+
response.status = 200
131+
response['Content-Type'] = 'text/html'
132+
render(:auto_source_instructions)
133+
end
134+
128135
def handle_auto_source_feed(router, encoded_url)
129136
return unauthorized_response unless AutoSource.authenticate(router)
130137
return forbidden_origin_response unless AutoSource.allowed_origin?(router)

app/auto_source.rb

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ module AutoSource
88
module_function
99

1010
def enabled?
11-
ENV['AUTO_SOURCE_ENABLED'] == 'true'
11+
# Enable by default in development, require explicit setting in production
12+
rack_env = ENV['RACK_ENV']
13+
auto_source_enabled = ENV['AUTO_SOURCE_ENABLED']
14+
15+
if rack_env == 'development'
16+
auto_source_enabled != 'false'
17+
else
18+
auto_source_enabled == 'true'
19+
end
1220
end
1321

1422
def authenticate(request)
@@ -18,13 +26,24 @@ def authenticate(request)
1826
credentials = Base64.decode64(auth[6..]).split(':')
1927
username, password = credentials
2028

21-
username == ENV['AUTO_SOURCE_USERNAME'] &&
22-
password == ENV['AUTO_SOURCE_PASSWORD']
29+
# Use default credentials in development if not set
30+
expected_username = ENV['AUTO_SOURCE_USERNAME'] || (ENV['RACK_ENV'] == 'development' ? 'admin' : nil)
31+
expected_password = ENV['AUTO_SOURCE_PASSWORD'] || (ENV['RACK_ENV'] == 'development' ? 'password' : nil)
32+
33+
return false unless expected_username && expected_password
34+
35+
username == expected_username && password == expected_password
2336
end
2437

2538
def allowed_origin?(request)
2639
origin = request.env['HTTP_HOST'] || request.env['HTTP_X_FORWARDED_HOST']
27-
allowed_origins = (ENV['AUTO_SOURCE_ALLOWED_ORIGINS'] || '').split(',').map(&:strip)
40+
41+
# In development, allow localhost origins by default
42+
if ENV['RACK_ENV'] == 'development'
43+
allowed_origins = (ENV['AUTO_SOURCE_ALLOWED_ORIGINS'] || 'localhost:3000,localhost:3001,127.0.0.1:3000,127.0.0.1:3001').split(',').map(&:strip)
44+
else
45+
allowed_origins = (ENV['AUTO_SOURCE_ALLOWED_ORIGINS'] || '').split(',').map(&:strip)
46+
end
2847

2948
allowed_origins.empty? || allowed_origins.include?(origin)
3049
end

bin/dev

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,55 @@
11
#!/usr/bin/env bash
22
# frozen_string_literal: true
33

4-
# Development server startup script
54
set -e
65

7-
# Load environment variables if .env file exists
6+
# Load .env if exists
87
if [ -f .env ]; then
98
export $(cat .env | grep -v '^#' | xargs)
109
fi
1110

12-
# Set default environment
1311
export RACK_ENV=${RACK_ENV:-development}
1412

15-
echo "Starting html2rss-web in development mode..."
16-
echo "Environment: $RACK_ENV"
17-
echo "Port: ${PORT:-3000}"
13+
# Cleanup function for graceful shutdown
14+
cleanup() {
15+
kill $RUBY_PID 2>/dev/null || true
16+
kill $ASTRO_PID 2>/dev/null || true
17+
wait $RUBY_PID 2>/dev/null || true
18+
wait $ASTRO_PID 2>/dev/null || true
19+
exit 0
20+
}
1821

19-
# Start the development server
20-
bundle exec puma -p ${PORT:-3000} -C config/puma.rb
22+
trap cleanup SIGINT SIGTERM
23+
24+
# Prevent silent failures from port conflicts
25+
if lsof -Pi :3000 -sTCP:LISTEN -t >/dev/null 2>&1; then
26+
echo "❌ Port 3000 is already in use. Run: pkill -f 'puma.*html2rss-web'"
27+
exit 1
28+
fi
29+
30+
# Start Ruby server
31+
bundle exec puma -p 3000 -C config/puma.rb &
32+
RUBY_PID=$!
33+
34+
# Verify Ruby server started successfully
35+
sleep 3
36+
if ! kill -0 $RUBY_PID 2>/dev/null || ! lsof -Pi :3000 -sTCP:LISTEN -t >/dev/null 2>&1; then
37+
echo "❌ Ruby server failed to start"
38+
exit 1
39+
fi
40+
41+
# Start Astro dev server
42+
cd frontend
43+
npm run dev &
44+
ASTRO_PID=$!
45+
46+
# Verify Astro server started
47+
sleep 3
48+
if ! kill -0 $ASTRO_PID 2>/dev/null; then
49+
echo "❌ Astro dev server failed to start"
50+
kill $RUBY_PID 2>/dev/null || true
51+
exit 1
52+
fi
53+
54+
echo "✅ Development environment ready at http://localhost:3001"
55+
wait $RUBY_PID $ASTRO_PID

bin/dev-ruby

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env bash
2+
# frozen_string_literal: true
3+
4+
set -e
5+
6+
# Load .env if exists
7+
if [ -f .env ]; then
8+
export $(cat .env | grep -v '^#' | xargs)
9+
fi
10+
11+
export RACK_ENV=${RACK_ENV:-development}
12+
13+
# Prevent silent failures from port conflicts
14+
if lsof -Pi :${PORT:-3000} -sTCP:LISTEN -t >/dev/null 2>&1; then
15+
echo "❌ Port ${PORT:-3000} is already in use. Run: pkill -f 'puma.*html2rss-web'"
16+
exit 1
17+
fi
18+
19+
echo "Starting Ruby server (code reloading enabled)"
20+
bundle exec puma -p ${PORT:-3000} -C config/puma.rb

bin/setup

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ echo "Running tests to verify setup..."
3131
bundle exec rspec
3232

3333
echo "Setup complete! You can now run:"
34-
echo " bin/dev # Start development server"
34+
echo " bin/dev # Start development server (Ruby + Astro)"
35+
echo " bin/dev-ruby # Start Ruby server only"
3536
echo " bundle exec rspec # Run tests"
3637
echo " bundle exec rubocop # Run linter"

config/puma.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
# frozen_string_literal: true
22

3-
workers Integer(ENV.fetch('WEB_CONCURRENCY', 2))
4-
threads_count = Integer(ENV.fetch('WEB_MAX_THREADS', 5))
5-
threads threads_count, threads_count
6-
7-
preload_app!
3+
# Single worker in dev enables code reloading (cluster mode prevents reloading)
4+
if ENV['RACK_ENV'] == 'development'
5+
workers 0
6+
threads_count = Integer(ENV.fetch('WEB_MAX_THREADS', 5))
7+
threads threads_count, threads_count
8+
plugin :tmp_restart
9+
log_requests true
10+
else
11+
workers Integer(ENV.fetch('WEB_CONCURRENCY', 2))
12+
threads_count = Integer(ENV.fetch('WEB_MAX_THREADS', 5))
13+
threads threads_count, threads_count
14+
preload_app!
15+
log_requests false
16+
end
817

918
port ENV.fetch('PORT', 3000)
1019
environment ENV.fetch('RACK_ENV', 'development')

frontend/astro.config.mjs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,12 @@ import { defineConfig } from "astro/config"
33
export default defineConfig({
44
output: "static",
55
server: {
6-
port: 4321,
6+
port: 3001,
77
host: true,
88
},
99
vite: {
1010
server: {
11-
watch: {
12-
usePolling: true,
13-
},
1411
proxy: {
15-
'/api': {
16-
target: 'http://localhost:3000',
17-
changeOrigin: true,
18-
},
1912
'/auto_source': {
2013
target: 'http://localhost:3000',
2114
changeOrigin: true,

frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
"name": "html2rss-frontend",
33
"type": "module",
44
"scripts": {
5+
"dev": "astro dev --port 3001 --host",
56
"build": "astro build",
6-
"dev": "astro dev",
7+
"preview": "astro preview",
78
"format": "prettier --write .",
89
"format:check": "prettier --check .",
910
"test": "vitest",

frontend/src/pages/auto-source.astro

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,22 @@ import Layout from "../layouts/Layout.astro"
9797
if (!url) return
9898

9999
try {
100+
// Show loading state
101+
const submitBtn = e.target.querySelector('button[type="submit"]')
102+
const originalText = submitBtn.textContent
103+
submitBtn.textContent = "Generating..."
104+
submitBtn.disabled = true
105+
100106
// Encode URL for API
101107
const encodedUrl = btoa(url)
102-
const apiUrl = `/auto_source/${encodedUrl}?strategy=${strategy}`
108+
const apiUrl = `http://localhost:3000/auto_source/${encodedUrl}?strategy=${strategy}`
109+
110+
// Test the API call
111+
const response = await fetch(apiUrl)
112+
113+
if (!response.ok) {
114+
throw new Error(`API call failed: ${response.status} ${response.statusText}`)
115+
}
103116

104117
// Show result area
105118
const resultArea = document.getElementById("result")
@@ -114,7 +127,12 @@ import Layout from "../layouts/Layout.astro"
114127
resultArea.scrollIntoView({ behavior: "smooth" })
115128
} catch (error) {
116129
console.error("Error generating feed:", error)
117-
showError("Error generating feed. Please try again.")
130+
showError(`Error generating feed: ${error.message}`)
131+
} finally {
132+
// Reset button state
133+
const submitBtn = e.target.querySelector('button[type="submit"]')
134+
submitBtn.textContent = originalText
135+
submitBtn.disabled = false
118136
}
119137
})
120138

@@ -239,4 +257,33 @@ import Layout from "../layouts/Layout.astro"
239257
margin: 0.25rem 0;
240258
color: #856404;
241259
}
260+
261+
.btn {
262+
display: inline-block;
263+
padding: 0.5rem 1rem;
264+
background: #007bff;
265+
color: white;
266+
text-decoration: none;
267+
border-radius: 4px;
268+
border: none;
269+
cursor: pointer;
270+
font-size: 1rem;
271+
}
272+
273+
.btn:hover {
274+
background: #0056b3;
275+
}
276+
277+
.btn:disabled {
278+
background: #6c757d;
279+
cursor: not-allowed;
280+
}
281+
282+
.btn-secondary {
283+
background: #6c757d;
284+
}
285+
286+
.btn-secondary:hover {
287+
background: #545b62;
288+
}
242289
</style>

0 commit comments

Comments
 (0)