homura-runtime 0.1.2 → 0.1.4
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 +15 -1
- data/exe/compile-assets +27 -4
- data/exe/compile-erb +52 -7
- data/lib/cloudflare_workers/version.rb +1 -1
- data/lib/cloudflare_workers.rb +41 -0
- 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: 6e37f705dbc9ddf448634efe039bcc390d577c1683ad1759db58fdb1fbceee02
|
|
4
|
+
data.tar.gz: 9dfa51f60c34241302354b5069f10ba560a61c51d4b70a1799893d785d2ae434
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0655d2c8e8f889892df7500cfadc663df079be3c7326815523b2fad12cacdce4eb729f859e145bfdc8c26f8ad0bc12bc2d5ee2dfd3acf14e71c82ffafd4057d3
|
|
7
|
+
data.tar.gz: 93c6e9c5607b995d631060f511633bd94b3fa74f9db3c9adee3806b684a1ea7134b136bde12f9c8a3fa7d1da5f3ff76c95929c253251eb7daf3f1f9685bc5342
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.4 (2026-04-23)
|
|
4
|
+
|
|
5
|
+
- Teach the precompiled ERB runtime to support Sinatra-style layout blocks and
|
|
6
|
+
`<%= yield %>` in layout templates.
|
|
7
|
+
- Keep legacy `@content` / `@docs_inner` layouts working as compatibility
|
|
8
|
+
fallbacks while apps migrate to the least-surprise Sinatra style.
|
|
9
|
+
|
|
10
|
+
## 0.1.3 (2026-04-23)
|
|
11
|
+
|
|
12
|
+
- Fix binary static asset embedding so image responses preserve exact bytes on
|
|
13
|
+
Workers instead of being mangled through text encoding.
|
|
14
|
+
- Add regression coverage for binary-vs-text compile-assets output.
|
|
15
|
+
- Convert shipped mascot/icon assets to real PNG payloads so their bytes match
|
|
16
|
+
their `.png` filenames and `image/png` content type.
|
|
17
|
+
|
|
3
18
|
## 0.1.2 (2026-04-23)
|
|
4
19
|
|
|
5
20
|
- Package the runtime's Opal compile-time vendor shims (`digest`, `zlib`,
|
|
@@ -7,7 +22,6 @@
|
|
|
7
22
|
- Teach `cloudflare-workers-build --standalone` to add packaged gem `vendor/`
|
|
8
23
|
directories to the Opal load path, so published gems no longer depend on the
|
|
9
24
|
monorepo root `vendor/`.
|
|
10
|
-
|
|
11
25
|
## 0.1.1 (2026-04-23)
|
|
12
26
|
|
|
13
27
|
- Fix `cloudflare-workers-build --standalone` and `exe/auto-await` to resolve only
|
data/exe/compile-assets
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# Usage:
|
|
13
13
|
# ruby bin/compile-assets --input public --output build/homura_assets.rb --namespace HomuraAssets
|
|
14
14
|
|
|
15
|
+
require 'base64'
|
|
15
16
|
require 'fileutils'
|
|
16
17
|
require 'optparse'
|
|
17
18
|
|
|
@@ -39,6 +40,13 @@ def mime_for(path)
|
|
|
39
40
|
MIME_TYPES[ext] || 'application/octet-stream'
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
def binary_content_type?(content_type)
|
|
44
|
+
!(content_type.start_with?('text/') ||
|
|
45
|
+
content_type.include?('javascript') ||
|
|
46
|
+
content_type.include?('json') ||
|
|
47
|
+
content_type.include?('xml'))
|
|
48
|
+
end
|
|
49
|
+
|
|
42
50
|
HELP = <<~USAGE
|
|
43
51
|
Usage:
|
|
44
52
|
ruby bin/compile-assets [--input DIR] [--output FILE] [--namespace NAME]
|
|
@@ -105,10 +113,19 @@ File.open(out_path, 'w') do |io|
|
|
|
105
113
|
if (asset = #{ns}::ASSETS[path])
|
|
106
114
|
headers = {
|
|
107
115
|
'content-type' => asset[:content_type],
|
|
108
|
-
'content-length' => asset[:body].bytesize.to_s,
|
|
109
116
|
'cache-control' => 'public, max-age=3600',
|
|
110
117
|
}
|
|
111
|
-
|
|
118
|
+
if asset[:binary]
|
|
119
|
+
body = ::Cloudflare::EmbeddedBinaryBody.new(
|
|
120
|
+
asset[:body_base64],
|
|
121
|
+
asset[:content_type],
|
|
122
|
+
headers['cache-control']
|
|
123
|
+
)
|
|
124
|
+
[200, headers, [body.raw_response(200, headers)]]
|
|
125
|
+
else
|
|
126
|
+
headers['content-length'] = asset[:body].bytesize.to_s
|
|
127
|
+
[200, headers, [asset[:body]]]
|
|
128
|
+
end
|
|
112
129
|
else
|
|
113
130
|
@app.call(env)
|
|
114
131
|
end
|
|
@@ -119,14 +136,20 @@ File.open(out_path, 'w') do |io|
|
|
|
119
136
|
|
|
120
137
|
files.each do |full_path|
|
|
121
138
|
rel = full_path.sub(public_dir, '') # e.g. "/style.css"
|
|
122
|
-
content = File.
|
|
139
|
+
content = File.binread(full_path)
|
|
123
140
|
ct = mime_for(full_path)
|
|
141
|
+
binary = binary_content_type?(ct)
|
|
124
142
|
|
|
125
143
|
io.puts
|
|
126
144
|
io.puts "# #{rel} (#{content.bytesize} bytes)"
|
|
127
145
|
io.puts "#{ns}::ASSETS[#{rel.inspect}] = {"
|
|
128
146
|
io.puts " content_type: #{ct.inspect},"
|
|
129
|
-
io.puts "
|
|
147
|
+
io.puts " binary: #{binary},"
|
|
148
|
+
if binary
|
|
149
|
+
io.puts " body_base64: #{Base64.strict_encode64(content).inspect}"
|
|
150
|
+
else
|
|
151
|
+
io.puts " body: #{content.inspect}"
|
|
152
|
+
end
|
|
130
153
|
io.puts "}"
|
|
131
154
|
end
|
|
132
155
|
|
data/exe/compile-erb
CHANGED
|
@@ -43,11 +43,21 @@ USAGE
|
|
|
43
43
|
module HomuraERB
|
|
44
44
|
module_function
|
|
45
45
|
|
|
46
|
+
def normalize_fragment(fragment)
|
|
47
|
+
stripped = fragment.strip
|
|
48
|
+
return '__homura_template_yield__' if stripped == 'yield' || stripped == 'yield()'
|
|
49
|
+
|
|
50
|
+
fragment
|
|
51
|
+
end
|
|
52
|
+
|
|
46
53
|
# Compile an ERB source string to a Ruby method body that assembles
|
|
47
54
|
# an HTML string in a local variable `_out`. The body references
|
|
48
55
|
# `@ivars` and method calls directly, so it must be run via
|
|
49
56
|
# `instance_exec` on the Sinatra instance the route was dispatched
|
|
50
|
-
# on. That gives `<%= @name %>` the usual Sinatra semantics.
|
|
57
|
+
# on. That gives `<%= @name %>` the usual Sinatra semantics. Layout
|
|
58
|
+
# templates can also write `<%= yield %>` — we rewrite that specific
|
|
59
|
+
# form to an instance helper because Ruby's `yield` keyword is not
|
|
60
|
+
# valid inside the Proc body we generate here.
|
|
51
61
|
def compile(source)
|
|
52
62
|
ruby = +"_out = ''\n"
|
|
53
63
|
cursor = 0
|
|
@@ -75,15 +85,15 @@ module HomuraERB
|
|
|
75
85
|
elsif inner.start_with?('==')
|
|
76
86
|
# `<%== expression %>` — identical to `<%= %>` in this minimal
|
|
77
87
|
# dialect (no HTML escaping yet; the author is responsible).
|
|
78
|
-
expr = inner[2..].strip
|
|
88
|
+
expr = normalize_fragment(inner[2..].strip)
|
|
79
89
|
ruby << "_out = _out + ((#{expr})).to_s\n"
|
|
80
90
|
elsif inner.start_with?('=')
|
|
81
91
|
# `<%= expression %>`
|
|
82
|
-
expr = inner[1..].strip
|
|
92
|
+
expr = normalize_fragment(inner[1..].strip)
|
|
83
93
|
ruby << "_out = _out + ((#{expr})).to_s\n"
|
|
84
94
|
else
|
|
85
95
|
# `<% code %>` — Ruby statement(s), emitted verbatim
|
|
86
|
-
ruby << inner.strip << "\n"
|
|
96
|
+
ruby << normalize_fragment(inner.strip) << "\n"
|
|
87
97
|
end
|
|
88
98
|
|
|
89
99
|
cursor = close_idx + 2
|
|
@@ -142,10 +152,16 @@ def emit_header(io, namespace)
|
|
|
142
152
|
@templates[name.to_sym] = body
|
|
143
153
|
end
|
|
144
154
|
|
|
145
|
-
def render(name, instance, locals = {})
|
|
155
|
+
def render(name, instance, locals = {}, &block)
|
|
146
156
|
body = @templates[name.to_sym]
|
|
147
157
|
raise "#{namespace}: no template registered for \#{name.inspect}" unless body
|
|
148
|
-
instance.
|
|
158
|
+
previous_block = instance.instance_variable_get(:@__homura_template_block__)
|
|
159
|
+
begin
|
|
160
|
+
instance.instance_variable_set(:@__homura_template_block__, block)
|
|
161
|
+
instance.instance_exec(locals, &body)
|
|
162
|
+
ensure
|
|
163
|
+
instance.instance_variable_set(:@__homura_template_block__, previous_block)
|
|
164
|
+
end
|
|
149
165
|
end
|
|
150
166
|
|
|
151
167
|
def registered?(name)
|
|
@@ -187,6 +203,22 @@ def emit_sinatra_patch(io, namespace)
|
|
|
187
203
|
|
|
188
204
|
module ::Sinatra
|
|
189
205
|
module Templates
|
|
206
|
+
def __homura_template_yield__
|
|
207
|
+
block = @__homura_template_block__
|
|
208
|
+
raise LocalJumpError, 'no block given' unless block
|
|
209
|
+
|
|
210
|
+
block.call
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def __homura_default_template_block_for__(template)
|
|
214
|
+
case template.to_sym
|
|
215
|
+
when :layout
|
|
216
|
+
proc { @content } if defined?(@content)
|
|
217
|
+
when :layout_docs
|
|
218
|
+
proc { @docs_inner } if defined?(@docs_inner)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
190
222
|
# homura patch: dispatch to precompiled templates when we
|
|
191
223
|
# have one. Unknown symbols raise a clear message instead of
|
|
192
224
|
# wandering into upstream Tilt, which would blow up on
|
|
@@ -194,7 +226,20 @@ def emit_sinatra_patch(io, namespace)
|
|
|
194
226
|
def erb(template, options = {}, locals = {}, &block)
|
|
195
227
|
if template.is_a?(::Symbol) && ::#{namespace}.registered?(template)
|
|
196
228
|
locals ||= {}
|
|
197
|
-
|
|
229
|
+
block ||= __homura_default_template_block_for__(template)
|
|
230
|
+
output = ::#{namespace}.render(template, self, locals, &block)
|
|
231
|
+
|
|
232
|
+
layout = options[:layout]
|
|
233
|
+
layout = false if layout.nil? && options.include?(:layout)
|
|
234
|
+
if layout
|
|
235
|
+
layout = :layout if layout == true
|
|
236
|
+
layout = layout.to_sym
|
|
237
|
+
raise "homura: layout \#{layout.inspect} not precompiled; run bin/compile-erb" unless ::#{namespace}.registered?(layout)
|
|
238
|
+
|
|
239
|
+
return ::#{namespace}.render(layout, self, locals) { output }
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
output
|
|
198
243
|
else
|
|
199
244
|
raise "homura: erb \#{template.inspect} not precompiled; run bin/compile-erb"
|
|
200
245
|
end
|
data/lib/cloudflare_workers.rb
CHANGED
|
@@ -313,6 +313,18 @@ module Rack
|
|
|
313
313
|
return `new Response(#{js_stream}, { status: #{status.to_i}, headers: #{js_headers} })`
|
|
314
314
|
end
|
|
315
315
|
|
|
316
|
+
if body.is_a?(::Cloudflare::EmbeddedBinaryBody) || (body.respond_to?(:first) && body.first.is_a?(::Cloudflare::EmbeddedBinaryBody))
|
|
317
|
+
bin = body.is_a?(::Cloudflare::EmbeddedBinaryBody) ? body : body.first
|
|
318
|
+
js_stream = bin.stream
|
|
319
|
+
ct = bin.content_type
|
|
320
|
+
cc = bin.cache_control
|
|
321
|
+
js_headers = `({})`
|
|
322
|
+
headers.each { |k, v| ks = k.to_s; vs = v.to_s; `#{js_headers}[#{ks}] = #{vs}` }
|
|
323
|
+
`#{js_headers}['content-type'] = #{ct}` if ct
|
|
324
|
+
`#{js_headers}['cache-control'] = #{cc}` if cc
|
|
325
|
+
return `new Response(#{js_stream}, { status: #{status.to_i}, headers: #{js_headers} })`
|
|
326
|
+
end
|
|
327
|
+
|
|
316
328
|
# Phase 10 — Workers AI streaming: a Cloudflare::AI::Stream wraps
|
|
317
329
|
# a JS ReadableStream<Uint8Array> emitting SSE-formatted bytes
|
|
318
330
|
# ("data: {json}\n\n"). Pass it straight through so the client
|
|
@@ -530,6 +542,35 @@ module Cloudflare
|
|
|
530
542
|
def close; end
|
|
531
543
|
end
|
|
532
544
|
|
|
545
|
+
# EmbeddedBinaryBody carries a base64-encoded asset payload produced at
|
|
546
|
+
# build time by `compile-assets`, then reconstructs a Uint8Array in the
|
|
547
|
+
# Worker before building the Response stream.
|
|
548
|
+
class EmbeddedBinaryBody
|
|
549
|
+
attr_reader :body_base64, :content_type, :cache_control
|
|
550
|
+
|
|
551
|
+
def initialize(body_base64, content_type = 'application/octet-stream', cache_control = nil)
|
|
552
|
+
@body_base64 = body_base64 || ''
|
|
553
|
+
@content_type = content_type
|
|
554
|
+
@cache_control = cache_control
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def each; end
|
|
558
|
+
|
|
559
|
+
def close; end
|
|
560
|
+
|
|
561
|
+
def raw_response(status, headers = {})
|
|
562
|
+
js_headers = `({})`
|
|
563
|
+
headers.each { |k, v| ks = k.to_s; vs = v.to_s; `#{js_headers}[#{ks}] = #{vs}` }
|
|
564
|
+
`#{js_headers}['content-type'] = #{@content_type}` if @content_type
|
|
565
|
+
`#{js_headers}['cache-control'] = #{@cache_control}` if @cache_control
|
|
566
|
+
RawResponse.new(`new Response(#{stream}, { status: #{status.to_i}, headers: #{js_headers} })`)
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def stream
|
|
570
|
+
`(function(b64) { return new ReadableStream({ start(controller) { var bin = globalThis.atob(b64); var len = bin.length; var out = new Uint8Array(len); for (var i = 0; i < len; i++) { out[i] = bin.charCodeAt(i) & 0xff; } controller.enqueue(out); controller.close(); } }); })(#{@body_base64})`
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
|
|
533
574
|
# NOTE: the single-line backtick `...` form is used below instead of the
|
|
534
575
|
# multi-line `%x{ ... }` or multi-line backtick form. Opal's compiler
|
|
535
576
|
# treats a *multi-line* x-string as a raw statement and refuses to use
|