homura-runtime 0.2.6 → 0.2.8
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 +22 -0
- data/exe/compile-erb +3 -0
- data/exe/homura-build +40 -6
- data/lib/cloudflare_workers/auto_await/analyzer.rb +5 -1
- data/lib/cloudflare_workers/build_support.rb +1 -1
- data/lib/cloudflare_workers/version.rb +1 -1
- data/lib/cloudflare_workers.rb +21 -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: f2a642d762f1d0c562e0c54d9fc9b1a5299833ad7f58620af82421022f9c6f46
|
|
4
|
+
data.tar.gz: b2858fc319f7e895221437cfafedcf6f559cc7cdddfdc8659ef5195b7f2e4bb5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c6b623fb0618ff32c105ea8dfd104963e85967c154e4fb9b28beb7ce87d625d7889bda1c658133671305bc2c312b569742c90c4b544db646d5cf701de5ea13c
|
|
7
|
+
data.tar.gz: 3a69ce9560b4ef766a247e26f12fc173c56239842aee7fa78e16b7af2679885bc2ef762445b69d89c5c916911968ea817937adee64ac133f00a4c9e44cf1413b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.8 (2026-04-24)
|
|
4
|
+
|
|
5
|
+
- Compile standalone `config.ru` inputs from `build/auto_await/` so ordinary
|
|
6
|
+
`require_relative 'app/app'` entrypoints pick up the auto-awaited app copy
|
|
7
|
+
instead of silently falling back to the original source.
|
|
8
|
+
- Teach the auto-await analyzer to prefer locally inferred helper return types
|
|
9
|
+
over generic registry defaults, and infer memoized `foo ||= ...` helper bodies.
|
|
10
|
+
- Preserve redirect headers when awaited routes resolve to Rack tuples through
|
|
11
|
+
the Promise response builder.
|
|
12
|
+
- Add `layout.erb` by default in the precompiled ERB runtime so `erb :index`
|
|
13
|
+
behaves like ordinary Sinatra unless the app passes `layout: false`.
|
|
14
|
+
|
|
15
|
+
## 0.2.7 (2026-04-24)
|
|
16
|
+
|
|
17
|
+
- Auto-detect standalone app entrypoints from `config.ru`, `app/hello.rb`, then
|
|
18
|
+
`app/app.rb`, and accept `config.ru` inputs by compiling a temporary Ruby copy
|
|
19
|
+
under the hood.
|
|
20
|
+
- Add the project root to standalone Opal load paths so standard
|
|
21
|
+
`require_relative 'app/app'` config.ru setups compile without extra wrappers.
|
|
22
|
+
- Synthesize `HTTP_HOST` from the request URL inside the Rack env so absolute
|
|
23
|
+
redirects preserve the current host and non-default port in local dev.
|
|
24
|
+
|
|
3
25
|
## 0.2.6 (2026-04-24)
|
|
4
26
|
|
|
5
27
|
- Remove the shipped `cloudflare-workers-build` filename entirely and keep the
|
data/exe/compile-erb
CHANGED
|
@@ -264,6 +264,9 @@ def emit_sinatra_patch(io, namespace)
|
|
|
264
264
|
|
|
265
265
|
layout = options[:layout]
|
|
266
266
|
layout = false if layout.nil? && options.include?(:layout)
|
|
267
|
+
if layout.nil? && template.to_sym != :layout && ::#{namespace}.registered?(:layout)
|
|
268
|
+
layout = :layout
|
|
269
|
+
end
|
|
267
270
|
if layout
|
|
268
271
|
layout = :layout if layout == true
|
|
269
272
|
layout = layout.to_sym
|
data/exe/homura-build
CHANGED
|
@@ -37,7 +37,7 @@ end
|
|
|
37
37
|
options = {
|
|
38
38
|
root: Dir.pwd,
|
|
39
39
|
standalone: false,
|
|
40
|
-
opal_input: ENV['HOMURA_OPAL_INPUT']
|
|
40
|
+
opal_input: ENV['HOMURA_OPAL_INPUT'],
|
|
41
41
|
opal_output: ENV['HOMURA_OPAL_OUTPUT'] || 'build/hello.no-exit.mjs',
|
|
42
42
|
patch_input: ENV['HOMURA_OPAL_PATCH_INPUT'],
|
|
43
43
|
setup_import: nil,
|
|
@@ -100,6 +100,7 @@ def run_opal_homura!(root, opal_input, opal_output)
|
|
|
100
100
|
argv = [
|
|
101
101
|
'bundle', 'exec', 'opal',
|
|
102
102
|
'-c', '-E', '--esm', '--no-source-map',
|
|
103
|
+
'-I', '.',
|
|
103
104
|
'-I', 'build/auto_await/app', '-I', 'build/auto_await', '-I', 'app',
|
|
104
105
|
'-I', 'gems/homura-runtime/lib',
|
|
105
106
|
'-I', 'gems/sinatra-homura/lib',
|
|
@@ -147,6 +148,29 @@ def write_entrypoint!(root, out_path, setup:, bundle:, worker_mod:)
|
|
|
147
148
|
File.write(root.join(out_path), body)
|
|
148
149
|
end
|
|
149
150
|
|
|
151
|
+
def resolve_opal_input(root, explicit_input)
|
|
152
|
+
return explicit_input if explicit_input
|
|
153
|
+
|
|
154
|
+
candidates = ['config.ru', 'app/hello.rb', 'app/app.rb']
|
|
155
|
+
found = candidates.find { |path| root.join(path).file? }
|
|
156
|
+
return found if found
|
|
157
|
+
|
|
158
|
+
abort("homura build: no default entrypoint found (looked for #{candidates.join(', ')})")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def prepare_opal_input(root, input_path)
|
|
162
|
+
rel = Pathname(input_path)
|
|
163
|
+
rel = root.join(rel).relative_path_from(root) if rel.absolute?
|
|
164
|
+
return [rel.to_s, nil] unless rel.extname == '.ru'
|
|
165
|
+
|
|
166
|
+
tmp_dir = root.join('build', 'auto_await')
|
|
167
|
+
tmp_dir = root unless tmp_dir.directory?
|
|
168
|
+
tmp = tmp_dir.join(".homura-build-#{rel.basename}.rb")
|
|
169
|
+
FileUtils.mkdir_p(tmp.dirname)
|
|
170
|
+
File.write(tmp, root.join(rel).read)
|
|
171
|
+
[tmp.relative_path_from(root).to_s, tmp]
|
|
172
|
+
end
|
|
173
|
+
|
|
150
174
|
unless options[:standalone]
|
|
151
175
|
run!(['ruby', 'bin/inline-routes-for-opal'], chdir: root)
|
|
152
176
|
run!(
|
|
@@ -184,11 +208,16 @@ unless options[:standalone]
|
|
|
184
208
|
chdir: root
|
|
185
209
|
)
|
|
186
210
|
|
|
187
|
-
opal_in = Pathname(options[:opal_input])
|
|
211
|
+
opal_in = Pathname(resolve_opal_input(root, options[:opal_input]))
|
|
188
212
|
opal_out = Pathname(options[:opal_output])
|
|
189
213
|
opal_in = root.join(opal_in) unless opal_in.absolute?
|
|
190
214
|
opal_out = root.join(opal_out) unless opal_out.absolute?
|
|
191
|
-
|
|
215
|
+
prepared_in, temp_input = prepare_opal_input(root, opal_in)
|
|
216
|
+
begin
|
|
217
|
+
run_opal_homura!(root, prepared_in, opal_out.relative_path_from(root).to_s)
|
|
218
|
+
ensure
|
|
219
|
+
FileUtils.rm_f(temp_input) if temp_input
|
|
220
|
+
end
|
|
192
221
|
else
|
|
193
222
|
CloudflareWorkers::BuildSupport.ensure_standalone_runtime(root, current_file: __FILE__)
|
|
194
223
|
run!(
|
|
@@ -218,12 +247,17 @@ else
|
|
|
218
247
|
chdir: root
|
|
219
248
|
)
|
|
220
249
|
|
|
221
|
-
opal_in = Pathname(options[:opal_input])
|
|
250
|
+
opal_in = Pathname(resolve_opal_input(root, options[:opal_input]))
|
|
222
251
|
opal_out = Pathname(options[:opal_output])
|
|
223
252
|
opal_in = root.join(opal_in) unless opal_in.absolute?
|
|
224
253
|
opal_out = root.join(opal_out) unless opal_out.absolute?
|
|
225
|
-
|
|
226
|
-
|
|
254
|
+
prepared_in, temp_input = prepare_opal_input(root, opal_in)
|
|
255
|
+
begin
|
|
256
|
+
run_opal_standalone!(root, prepared_in, opal_out.relative_path_from(root).to_s,
|
|
257
|
+
with_db: options[:with_db])
|
|
258
|
+
ensure
|
|
259
|
+
FileUtils.rm_f(temp_input) if temp_input
|
|
260
|
+
end
|
|
227
261
|
end
|
|
228
262
|
|
|
229
263
|
patch_rel = Pathname(patch_target)
|
|
@@ -144,16 +144,20 @@ module CloudflareWorkers
|
|
|
144
144
|
when :send
|
|
145
145
|
receiver, method_name = *node
|
|
146
146
|
if receiver.nil?
|
|
147
|
-
return @env[method_name] if @env.key?(method_name)
|
|
148
147
|
return @method_returns[method_name] if @method_returns.key?(method_name)
|
|
148
|
+
return @env[method_name] if @env.key?(method_name)
|
|
149
149
|
end
|
|
150
150
|
infer_send_class(node)
|
|
151
151
|
when :index
|
|
152
152
|
infer_index_class(node)
|
|
153
|
+
when :begin
|
|
154
|
+
infer_class(node.children.last)
|
|
153
155
|
when :lvar
|
|
154
156
|
@env[node.children[0]]
|
|
155
157
|
when :ivar
|
|
156
158
|
nil
|
|
159
|
+
when :or_asgn
|
|
160
|
+
infer_class(node.children.last)
|
|
157
161
|
when :const
|
|
158
162
|
const_path(node)
|
|
159
163
|
else
|
|
@@ -61,7 +61,7 @@ module CloudflareWorkers
|
|
|
61
61
|
hv = vendor_from_gemfile(root)
|
|
62
62
|
load_paths << hv.to_s if hv
|
|
63
63
|
|
|
64
|
-
load_paths += ['build/auto_await/app', 'app']
|
|
64
|
+
load_paths += ['.', 'build/auto_await', 'build/auto_await/app', 'app']
|
|
65
65
|
[
|
|
66
66
|
gem_lib(RUNTIME_GEM_NAME, loaded_specs: loaded_specs),
|
|
67
67
|
gem_vendor(RUNTIME_GEM_NAME, loaded_specs: loaded_specs),
|
data/lib/cloudflare_workers.rb
CHANGED
|
@@ -204,6 +204,11 @@ module Rack
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
copy_headers_into_env(js_req, env)
|
|
207
|
+
env['HTTP_HOST'] = if port == (scheme == 'https' ? '443' : '80')
|
|
208
|
+
host
|
|
209
|
+
else
|
|
210
|
+
"#{host}:#{port}"
|
|
211
|
+
end
|
|
207
212
|
|
|
208
213
|
# Cloudflare-specific extras under their own namespace, per the
|
|
209
214
|
# Rack convention that env keys other than the standard ones
|
|
@@ -400,7 +405,7 @@ module Rack
|
|
|
400
405
|
# used for 101 WebSocket upgrades where the Workers
|
|
401
406
|
# runtime's own Response carries runtime-only properties
|
|
402
407
|
# (`.webSocket`) that a reconstructed Response would lose.
|
|
403
|
-
`Promise.all(#{js_chunks}).then(function(resolved) { var bodyToText = function(v) { if (v == null) { return ''; } if (Array.isArray(v)) { var joined = ''; for (var j = 0; j < v.length; j++) { joined += bodyToText(v[j]); } return joined; } if (typeof v === 'string') { return v; } if (v != null && v.$$is_string) { return v.toString(); } try { return JSON.stringify(v); } catch (e) { return String(v); } }; for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r != null && typeof r === 'object' && typeof r['$raw_response?'] === 'function' && typeof r['$js_response'] === 'function') { try { if (r['$raw_response?']()) { return r['$js_response'](); } } catch (_) {} } } for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r != null && r.stream != null && r.content_type != null) { var bh = {}; bh['content-type'] = r.content_type; if (r.cache_control) bh['cache-control'] = r.cache_control; return new Response(r.stream, { status: #{status_int}, headers: bh }); } } if (resolved.length === 1 && resolved[0] != null && Array.isArray(resolved[0]) && resolved[0].length >= 1 && typeof resolved[0][0] === 'number') { var ov = resolved[0]; var ovs = ov[0]|0; var ovh = #{js_headers}; var ovb = ''; if (ov.length >= 3 && ov[1] != null
|
|
408
|
+
`Promise.all(#{js_chunks}).then(function(resolved) { var bodyToText = function(v) { if (v == null) { return ''; } if (Array.isArray(v)) { var joined = ''; for (var j = 0; j < v.length; j++) { joined += bodyToText(v[j]); } return joined; } if (typeof v === 'string') { return v; } if (v != null && v.$$is_string) { return v.toString(); } try { return JSON.stringify(v); } catch (e) { return String(v); } }; for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r != null && typeof r === 'object' && typeof r['$raw_response?'] === 'function' && typeof r['$js_response'] === 'function') { try { if (r['$raw_response?']()) { return r['$js_response'](); } } catch (_) {} } } for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r != null && r.stream != null && r.content_type != null) { var bh = {}; bh['content-type'] = r.content_type; if (r.cache_control) bh['cache-control'] = r.cache_control; return new Response(r.stream, { status: #{status_int}, headers: bh }); } } if (resolved.length === 1 && resolved[0] != null && Array.isArray(resolved[0]) && resolved[0].length >= 1 && typeof resolved[0][0] === 'number') { var ov = resolved[0]; var ovs = ov[0]|0; var ovh = #{Cloudflare}.$headers_to_js(nil, #{js_headers}); var ovb = ''; if (ov.length >= 3 && ov[1] != null) { ovh = #{Cloudflare}.$headers_to_js(ov[1], #{js_headers}); ovb = bodyToText(ov[2]); } else if (ov.length >= 2) { ovb = bodyToText(ov[ov.length - 1]); } return new Response(ovb, { status: ovs, headers: ovh }); } var parts = []; for (var i = 0; i < resolved.length; i++) { var r = resolved[i]; if (r == null) { parts.push(''); continue; } if (typeof r === 'string') { parts.push(r); continue; } if (r != null && r.$$is_string) { parts.push(r.toString()); continue; } try { parts.push(JSON.stringify(r)); } catch (e) { parts.push(String(r)); } } return new Response(parts.join(''), { status: #{status_int}, headers: #{js_headers} }); })`
|
|
404
409
|
else
|
|
405
410
|
body_str = ''
|
|
406
411
|
chunks.each { |c| body_str = body_str + c.to_s }
|
|
@@ -507,6 +512,21 @@ module Cloudflare
|
|
|
507
512
|
h
|
|
508
513
|
end
|
|
509
514
|
|
|
515
|
+
def self.headers_to_js(headers, fallback = nil)
|
|
516
|
+
return fallback || `({})` if headers.nil?
|
|
517
|
+
return headers if `#{headers} != null && #{headers}.constructor === Object`
|
|
518
|
+
|
|
519
|
+
js_headers = fallback || `({})`
|
|
520
|
+
if headers.respond_to?(:each)
|
|
521
|
+
headers.each do |key, value|
|
|
522
|
+
ks = key.to_s
|
|
523
|
+
vs = value.to_s
|
|
524
|
+
`#{js_headers}[#{ks}] = #{vs}`
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
js_headers
|
|
528
|
+
end
|
|
529
|
+
|
|
510
530
|
# RawResponse wraps an already-constructed JS `Response` so routes
|
|
511
531
|
# can return it through Sinatra and have `build_js_response` pass
|
|
512
532
|
# it through to the Workers runtime untouched. Needed when the
|