stipa 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +31 -7
- data/lib/stipa/connection.rb +2 -1
- data/lib/stipa/generators/api.rb +1 -1
- data/lib/stipa/generators/base.rb +5 -5
- data/lib/stipa/generators/vue.rb +1 -1
- data/lib/stipa/middleware.rb +10 -4
- data/lib/stipa/reloader.rb +33 -11
- data/lib/stipa/request.rb +2 -2
- data/lib/stipa/server.rb +1 -1
- data/lib/stipa/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cae685e56a311306ac9de5e6590a97811aeb1037c37c16c8fc1678414853df71
|
|
4
|
+
data.tar.gz: f5fe15523b603ec40905a278e78790b963498ee0aef45b3d431a16723a63493e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4eb79042d8771b8384f37191f0aa99ee5fc63a82b62fc0201be0d074b460c4d066484d6710b5fc659ec6ec0dd6a6be1d21bee6a97035f88155d3ff38a92f94bc
|
|
7
|
+
data.tar.gz: 3db21d82f8e03e20d343fb65092f12bf5f92034dac1eb15289e702c1f23ca62e18e28d4222f091839b33903df077ad18454e9c4674d36374408224b2631ca6e7
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.5] - 2026-03-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Hot reload** (`STIPA_RELOAD=1`) — background thread watches project `.rb` files and
|
|
13
|
+
restarts the process via `exec` when a change is detected
|
|
14
|
+
- **Syntax guard** — if a changed file has a syntax error, the reloader logs the
|
|
15
|
+
problem and keeps watching; it will not restart until the error is fixed, preventing
|
|
16
|
+
the process from dying on a bad save
|
|
17
|
+
- **`.gitignore` generation** — `stipa new` now writes a `.gitignore` covering Ruby,
|
|
18
|
+
Node/npm, OS artefacts, and Vue build output
|
|
19
|
+
- **Automatic git init** — `stipa new` runs `git init && git add . && git commit` after
|
|
20
|
+
scaffolding so the project starts with a clean history
|
|
21
|
+
- **`interface Window { _t0?: number }`** added to the generated `shims-vue.d.ts`
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **CORS header injection** — `Middleware::Cors` no longer reflects an arbitrary
|
|
26
|
+
`Origin` request header. Wildcard config sends the literal `*`; an explicit allowlist
|
|
27
|
+
only echoes origins that are in the list
|
|
28
|
+
- **`instance_variable_set` removed** — the generated `MethodOverride` middleware now
|
|
29
|
+
uses the public `req.method =` setter instead of reaching into private state
|
|
30
|
+
- **Shell-form `system()` calls** — all internal `git` invocations in the generator now
|
|
31
|
+
use array form (`system('git', 'init', '-q')`) so no shell is spawned and there is no
|
|
32
|
+
injection surface
|
|
33
|
+
- **Default bind address changed from `0.0.0.0` to `127.0.0.1`** — the server and
|
|
34
|
+
generated templates now bind localhost-only by default; pass `host: '0.0.0.0'` to
|
|
35
|
+
expose on all interfaces
|
|
36
|
+
- **`BadRequest` details no longer sent to the client** — the error message is logged
|
|
37
|
+
server-side; the response body is now the generic string `'Bad Request'`
|
|
38
|
+
|
|
8
39
|
## [0.1.0] - 2024-03-18
|
|
9
40
|
|
|
10
41
|
### Added
|
data/README.md
CHANGED
|
@@ -52,7 +52,7 @@ app.start(port: 3710)
|
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
ruby server.rb
|
|
55
|
-
# => Stīpa listening on
|
|
55
|
+
# => Stīpa listening on 127.0.0.1:3710
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
---
|
|
@@ -96,6 +96,16 @@ npm run build # compile Vue components
|
|
|
96
96
|
bundle exec ruby server.rb
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
Hot reload during development:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
STIPA_RELOAD=1 bundle exec ruby server.rb
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The reloader watches all `.rb` files under the project root. When a change is saved it
|
|
106
|
+
restarts the process automatically. If the changed file has a syntax error the reloader
|
|
107
|
+
logs the problem and keeps watching — the process will not die on a bad save.
|
|
108
|
+
|
|
99
109
|
---
|
|
100
110
|
|
|
101
111
|
## Routing
|
|
@@ -283,16 +293,17 @@ Build: `npm run build` → outputs `public/components/Counter.js`.
|
|
|
283
293
|
|
|
284
294
|
```ruby
|
|
285
295
|
app.start(
|
|
286
|
-
host: '0.0.0.0'
|
|
296
|
+
host: '127.0.0.1', # default; use '0.0.0.0' to bind all interfaces
|
|
287
297
|
port: 3710,
|
|
288
|
-
pool_size: 32,
|
|
289
|
-
queue_depth: 64,
|
|
290
|
-
drain_timeout: 30,
|
|
298
|
+
pool_size: 32, # worker threads
|
|
299
|
+
queue_depth: 64, # max queued jobs before backpressure
|
|
300
|
+
drain_timeout: 30, # graceful shutdown wait (seconds)
|
|
291
301
|
keepalive_timeout: 5,
|
|
292
|
-
max_requests: 100,
|
|
302
|
+
max_requests: 100, # per connection
|
|
293
303
|
max_body_size: 1_048_576,
|
|
294
|
-
backpressure: :drop,
|
|
304
|
+
backpressure: :drop, # :drop (503) or :block
|
|
295
305
|
log_level: :info,
|
|
306
|
+
reload: false, # or set STIPA_RELOAD=1 in the environment
|
|
296
307
|
)
|
|
297
308
|
```
|
|
298
309
|
|
|
@@ -300,6 +311,19 @@ Handles `SIGTERM` / `SIGINT` with graceful drain.
|
|
|
300
311
|
|
|
301
312
|
---
|
|
302
313
|
|
|
314
|
+
## Security notes
|
|
315
|
+
|
|
316
|
+
- **Bind address** — the default `host: '127.0.0.1'` exposes the server only on
|
|
317
|
+
localhost. Set `host: '0.0.0.0'` (or the specific interface IP) when running behind a
|
|
318
|
+
reverse proxy or in a container.
|
|
319
|
+
- **CORS** — `Middleware::Cors` never reflects an arbitrary `Origin` header back to the
|
|
320
|
+
client. Wildcard config (`origins: ['*']`) sends the literal `*`; an explicit list
|
|
321
|
+
only allows origins that are in the list.
|
|
322
|
+
- **Hot reload** — `STIPA_RELOAD=1` is intended for development only. Do not enable it
|
|
323
|
+
in production.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
303
327
|
## License
|
|
304
328
|
|
|
305
329
|
MIT
|
data/lib/stipa/connection.rb
CHANGED
|
@@ -146,8 +146,9 @@ module Stipa
|
|
|
146
146
|
rescue BadRequest => e
|
|
147
147
|
# Protocol violation — send 400 and close the connection.
|
|
148
148
|
# Closing prevents further requests on a potentially corrupt stream.
|
|
149
|
+
@logger.warn("bad request peer=#{@peer}: #{e.message}")
|
|
149
150
|
res.status = 400
|
|
150
|
-
res.body =
|
|
151
|
+
res.body = 'Bad Request'
|
|
151
152
|
false
|
|
152
153
|
rescue => e
|
|
153
154
|
req_id = req.id || '-'
|
data/lib/stipa/generators/api.rb
CHANGED
|
@@ -46,9 +46,9 @@ module Stipa
|
|
|
46
46
|
return unless git_available?
|
|
47
47
|
|
|
48
48
|
Dir.chdir(target) do
|
|
49
|
-
system('git init -q')
|
|
50
|
-
system('git add .')
|
|
51
|
-
system(
|
|
49
|
+
system('git', 'init', '-q')
|
|
50
|
+
system('git', 'add', '.')
|
|
51
|
+
system('git', 'commit', '-q', '-m', 'Initial commit')
|
|
52
52
|
end
|
|
53
53
|
say ' git initialized repository with initial commit'
|
|
54
54
|
rescue => e
|
|
@@ -62,7 +62,7 @@ module Stipa
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def git_available?
|
|
65
|
-
system('git --version
|
|
65
|
+
system('git', '--version', out: File::NULL, err: File::NULL)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# ── Shared templates ───────────────────────────────────────────────────────
|
|
@@ -103,7 +103,7 @@ module Stipa
|
|
|
103
103
|
h[k] = URI.decode_www_form_component(v.to_s) if k
|
|
104
104
|
end
|
|
105
105
|
override = form['_method']&.upcase
|
|
106
|
-
req.
|
|
106
|
+
req.method = override if %w[PUT PATCH DELETE].include?(override)
|
|
107
107
|
end
|
|
108
108
|
next_app.call(req, res)
|
|
109
109
|
end
|
data/lib/stipa/generators/vue.rb
CHANGED
data/lib/stipa/middleware.rb
CHANGED
|
@@ -102,16 +102,22 @@ module Stipa
|
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def call(req, res)
|
|
105
|
-
origin = req['origin']
|
|
106
|
-
|
|
105
|
+
origin = req['origin']
|
|
106
|
+
wildcard = @origins.include?('*')
|
|
107
|
+
allowed = wildcard || (origin && @origins.include?(origin))
|
|
107
108
|
|
|
108
109
|
if allowed
|
|
109
|
-
|
|
110
|
+
# Never reflect an arbitrary Origin back. When the allowlist is '*',
|
|
111
|
+
# set the header to the literal '*'. When using an explicit list, only
|
|
112
|
+
# echo origins that are actually in the list (already guaranteed by
|
|
113
|
+
# the `allowed` check above).
|
|
114
|
+
res.set_header('Access-Control-Allow-Origin',
|
|
115
|
+
wildcard ? '*' : origin)
|
|
110
116
|
res.set_header('Access-Control-Allow-Methods', @methods)
|
|
111
117
|
res.set_header('Access-Control-Allow-Headers',
|
|
112
118
|
'Content-Type, Authorization, X-Request-Id')
|
|
113
119
|
# Vary tells caches that the response differs by Origin
|
|
114
|
-
res.set_header('Vary', 'Origin')
|
|
120
|
+
res.set_header('Vary', 'Origin') unless wildcard
|
|
115
121
|
end
|
|
116
122
|
|
|
117
123
|
# OPTIONS preflight: respond immediately without hitting the router
|
data/lib/stipa/reloader.rb
CHANGED
|
@@ -44,21 +44,31 @@ module Stipa
|
|
|
44
44
|
def watch_loop
|
|
45
45
|
loop do
|
|
46
46
|
sleep @interval
|
|
47
|
-
|
|
47
|
+
dirty = dirty_files
|
|
48
|
+
next if dirty.empty?
|
|
49
|
+
|
|
50
|
+
bad = dirty.select { |f| f.end_with?('.rb') && !syntax_ok?(f) }
|
|
51
|
+
if bad.any?
|
|
52
|
+
bad.each { |f| @logger.warn("syntax error in #{f} — fix and save to restart") }
|
|
53
|
+
# Advance mtime only for clean files so bad ones stay dirty until fixed
|
|
54
|
+
(dirty - bad).each { |f| @mtimes[f] = mtime(f) }
|
|
55
|
+
else
|
|
56
|
+
dirty.each { |f| @mtimes[f] = mtime(f) }
|
|
48
57
|
@logger.warn('file change detected — restarting')
|
|
49
58
|
$stdout.flush
|
|
50
59
|
$stderr.flush
|
|
51
|
-
|
|
60
|
+
perform_restart
|
|
52
61
|
end
|
|
53
62
|
end
|
|
54
63
|
rescue => e
|
|
55
64
|
@logger.error("reloader crashed: #{e.class}: #{e.message}")
|
|
56
65
|
end
|
|
57
66
|
|
|
58
|
-
#
|
|
59
|
-
#
|
|
67
|
+
# Watch only .rb files under the project root (Dir.pwd), plus any extra
|
|
68
|
+
# paths the user supplied. Avoids polling hundreds of gem files.
|
|
60
69
|
def watched_files
|
|
61
|
-
(
|
|
70
|
+
project_files = Dir.glob(File.join(Dir.pwd, '**', '*.rb'))
|
|
71
|
+
(project_files + @extra).uniq
|
|
62
72
|
end
|
|
63
73
|
|
|
64
74
|
def snapshot!
|
|
@@ -67,13 +77,25 @@ module Stipa
|
|
|
67
77
|
end
|
|
68
78
|
end
|
|
69
79
|
|
|
80
|
+
# Returns files whose mtime has changed since the last snapshot, without
|
|
81
|
+
# updating @mtimes (callers decide when to advance the snapshot).
|
|
82
|
+
def dirty_files
|
|
83
|
+
watched_files.select { |path| mtime(path) != @mtimes[path] }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Kept for backwards compatibility with tests / external callers.
|
|
70
87
|
def changed?
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
dirty = dirty_files
|
|
89
|
+
dirty.each { |f| @mtimes[f] = mtime(f) }
|
|
90
|
+
dirty.any?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def syntax_ok?(path)
|
|
94
|
+
system(RbConfig.ruby, '-c', path, out: File::NULL, err: File::NULL)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def perform_restart
|
|
98
|
+
exec(RbConfig.ruby, $0, *ARGV)
|
|
77
99
|
end
|
|
78
100
|
|
|
79
101
|
def mtime(path)
|
data/lib/stipa/request.rb
CHANGED
|
@@ -23,8 +23,8 @@ module Stipa
|
|
|
23
23
|
MAX_BODY_SIZE = 1 * 1024 * 1024 # 1 MB default; configurable per-server
|
|
24
24
|
VALID_METHODS = %w[GET POST PUT PATCH DELETE HEAD OPTIONS TRACE CONNECT].freeze
|
|
25
25
|
|
|
26
|
-
attr_accessor :id, :params
|
|
27
|
-
attr_reader :
|
|
26
|
+
attr_accessor :id, :params, :method
|
|
27
|
+
attr_reader :path, :query_string, :http_version,
|
|
28
28
|
:headers, :body, :bytes_in
|
|
29
29
|
|
|
30
30
|
# Factory — called by Connection after reading the header block.
|
data/lib/stipa/server.rb
CHANGED
data/lib/stipa/version.rb
CHANGED