homura-runtime 0.2.5 → 0.2.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2b3ce399f63be057b0f83676eb0634e304af26474f2377ffbda23d1e89c2107
4
- data.tar.gz: 5dc9a30ab22c1688c167c052ac92660190193282f73e0aa314433e5de9596b53
3
+ metadata.gz: b1a1cd099709c81d7dfba2a41d071ab47f343c253f5826a031107011903706c9
4
+ data.tar.gz: 54dcad51d26b66db5da83a6245ee3402418bd1af891cee59627b61f0c7760fc7
5
5
  SHA512:
6
- metadata.gz: f406f03d5fc65c9c89499aae50e8fba67b4a277dc702033937e9f54f7a8c9408fce6927a06ac96e6ec864fd6492ae0e700b006ddeafd4018ca55722b0fd2351d
7
- data.tar.gz: b6ab470270263184c1298fd22799b4b2a35185b1695539eea81b135f1715454043f2682f1158b42eb940d2a3dfc3283ea1631ed73cbed63c8fad0f22aa02eaab
6
+ metadata.gz: 17be34147d20fe3c90c5bf50b5e1aa3fc0f0fa659e519659243566b3828bf7ffae88b1d129589ff34916b7406a650d1aed652500de4f5ba6e0400c5e00e5aae9
7
+ data.tar.gz: e5a79b2eff5c805f2b06c83843dd99a1614d99defab71d1be52a8634046b1eb97f51f2e07f80ab8b41543e3b0b559c88d483bc70a00e429fafbc8b4ec9daf41d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.7 (2026-04-24)
4
+
5
+ - Auto-detect standalone app entrypoints from `config.ru`, `app/hello.rb`, then
6
+ `app/app.rb`, and accept `config.ru` inputs by compiling a temporary Ruby copy
7
+ under the hood.
8
+ - Add the project root to standalone Opal load paths so standard
9
+ `require_relative 'app/app'` config.ru setups compile without extra wrappers.
10
+ - Synthesize `HTTP_HOST` from the request URL inside the Rack env so absolute
11
+ redirects preserve the current host and non-default port in local dev.
12
+
13
+ ## 0.2.6 (2026-04-24)
14
+
15
+ - Remove the shipped `cloudflare-workers-build` filename entirely and keep the
16
+ build implementation under `exe/homura-build`, matching the `homura build`
17
+ public surface.
18
+ - Make `homura build --with-db` imply the consumer-safe standalone path so D1 /
19
+ Sequel apps do not need to remember both flags.
20
+ - Let `compile-assets` succeed for an empty `public/` directory and improve the
21
+ auto-await analyzer so async local helper methods are awaited at their call
22
+ sites.
23
+
3
24
  ## 0.2.5 (2026-04-23)
4
25
 
5
26
  - Load Workers-only `zlib`, `tempfile`, and `tilt` shims via `require_relative`
@@ -27,14 +48,14 @@
27
48
 
28
49
  ## 0.2.0 (2026-04-23)
29
50
 
30
- - Remove the old `cloudflare-workers-build` public executable and expose the
51
+ - Remove the old standalone build executable and expose the
31
52
  build pipeline through `homura build`.
32
53
  - Keep the internal build implementation in the runtime gem while letting the
33
54
  single `homura` CLI dispatch to it.
34
55
 
35
56
  ## 0.1.6 (2026-04-23)
36
57
 
37
- - Teach `cloudflare-workers-build --standalone --with-db` to add the packaged
58
+ - Teach `homura build --standalone --with-db` to add the packaged
38
59
  `sequel-d1` gem `vendor/` directory to the Opal load path before the gem's
39
60
  `lib/`, so `require 'sequel'` resolves to the bundled Opal-compatible Sequel
40
61
  subset instead of the CRuby gem.
@@ -43,7 +64,7 @@
43
64
 
44
65
  - Make `auto-await` emit rewritten files for existing hand-written `.__await__`
45
66
  usage when the only missing piece is `# await: true`.
46
- - Make `cloudflare-workers-build --standalone` restore `cf-runtime/` from the
67
+ - Make `homura build --standalone` restore `cf-runtime/` from the
47
68
  packaged gem and derive standalone template/asset namespaces from the project
48
69
  name by default, with explicit override flags when needed.
49
70
  - Reject unsupported ERB yield forms like `<% yield %>` and `yield(arg)` with
@@ -68,12 +89,12 @@
68
89
 
69
90
  - Package the runtime's Opal compile-time vendor shims (`digest`, `zlib`,
70
91
  `tempfile`, `tilt`, `rubygems/version`) inside the gem.
71
- - Teach `cloudflare-workers-build --standalone` to add packaged gem `vendor/`
92
+ - Teach `homura build --standalone` to add packaged gem `vendor/`
72
93
  directories to the Opal load path, so published gems no longer depend on the
73
94
  monorepo root `vendor/`.
74
95
  ## 0.1.1 (2026-04-23)
75
96
 
76
- - Fix `cloudflare-workers-build --standalone` and `exe/auto-await` to resolve only
97
+ - Fix `homura build --standalone` and `exe/auto-await` to resolve only
77
98
  the published gem names `homura-runtime` / `sinatra-homura`.
78
99
  - Add regression coverage for gem name resolution and Gemfile path detection.
79
100
 
data/exe/compile-assets CHANGED
@@ -82,10 +82,6 @@ end
82
82
  ns = options[:namespace]
83
83
 
84
84
  files = Dir.glob(File.join(public_dir, '**', '*')).select { |f| File.file?(f) }.sort
85
- if files.empty?
86
- warn 'compile-assets: directory is empty'
87
- exit 1
88
- end
89
85
 
90
86
  out_path = options[:output]
91
87
  FileUtils.mkdir_p(File.dirname(out_path))
@@ -37,7 +37,7 @@ end
37
37
  options = {
38
38
  root: Dir.pwd,
39
39
  standalone: false,
40
- opal_input: ENV['HOMURA_OPAL_INPUT'] || 'app/hello.rb',
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,
@@ -69,6 +69,8 @@ OptionParser.new do |o|
69
69
  o.on('--assets-namespace NAME', 'Standalone assets module name (default: project-derived)') { |n| options[:assets_namespace] = n }
70
70
  end.parse!
71
71
 
72
+ options[:standalone] = true if options[:with_db]
73
+
72
74
  root = Pathname(options[:root]).expand_path
73
75
  options[:templates_namespace] ||= CloudflareWorkers::BuildSupport.standalone_namespace(root, 'Templates') if options[:standalone]
74
76
  options[:assets_namespace] ||= CloudflareWorkers::BuildSupport.standalone_namespace(root, 'Assets') if options[:standalone]
@@ -98,6 +100,7 @@ def run_opal_homura!(root, opal_input, opal_output)
98
100
  argv = [
99
101
  'bundle', 'exec', 'opal',
100
102
  '-c', '-E', '--esm', '--no-source-map',
103
+ '-I', '.',
101
104
  '-I', 'build/auto_await/app', '-I', 'build/auto_await', '-I', 'app',
102
105
  '-I', 'gems/homura-runtime/lib',
103
106
  '-I', 'gems/sinatra-homura/lib',
@@ -113,7 +116,7 @@ def run_opal_homura!(root, opal_input, opal_output)
113
116
  env = { 'OPAL_PREFORK_DISABLE' => '1' }
114
117
  out_err, status = Open3.capture2e(env, *argv, chdir: root.to_s)
115
118
  File.write(stderr_log, out_err)
116
- abort('homura build: opal failed') unless status.success?
119
+ abort("homura build: opal failed (see #{stderr_log})") unless status.success?
117
120
  end
118
121
 
119
122
  def homura_vendor_from_gemfile(project_root)
@@ -132,7 +135,7 @@ def run_opal_standalone!(root, opal_input, opal_output, with_db:)
132
135
  env = { 'OPAL_PREFORK_DISABLE' => '1' }
133
136
  out_err, status = Open3.capture2e(env, *argv, chdir: root.to_s)
134
137
  File.write(stderr_log, out_err)
135
- abort('homura build: opal failed') unless status.success?
138
+ abort("homura build: opal failed (see #{stderr_log})") unless status.success?
136
139
  end
137
140
 
138
141
  def write_entrypoint!(root, out_path, setup:, bundle:, worker_mod:)
@@ -145,6 +148,26 @@ def write_entrypoint!(root, out_path, setup:, bundle:, worker_mod:)
145
148
  File.write(root.join(out_path), body)
146
149
  end
147
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 = root.join(".homura-build-#{rel.basename}.rb")
167
+ File.write(tmp, root.join(rel).read)
168
+ [tmp.relative_path_from(root).to_s, tmp]
169
+ end
170
+
148
171
  unless options[:standalone]
149
172
  run!(['ruby', 'bin/inline-routes-for-opal'], chdir: root)
150
173
  run!(
@@ -182,11 +205,16 @@ unless options[:standalone]
182
205
  chdir: root
183
206
  )
184
207
 
185
- opal_in = Pathname(options[:opal_input])
208
+ opal_in = Pathname(resolve_opal_input(root, options[:opal_input]))
186
209
  opal_out = Pathname(options[:opal_output])
187
210
  opal_in = root.join(opal_in) unless opal_in.absolute?
188
211
  opal_out = root.join(opal_out) unless opal_out.absolute?
189
- run_opal_homura!(root, opal_in.relative_path_from(root).to_s, opal_out.relative_path_from(root).to_s)
212
+ prepared_in, temp_input = prepare_opal_input(root, opal_in)
213
+ begin
214
+ run_opal_homura!(root, prepared_in, opal_out.relative_path_from(root).to_s)
215
+ ensure
216
+ FileUtils.rm_f(temp_input) if temp_input
217
+ end
190
218
  else
191
219
  CloudflareWorkers::BuildSupport.ensure_standalone_runtime(root, current_file: __FILE__)
192
220
  run!(
@@ -216,12 +244,17 @@ else
216
244
  chdir: root
217
245
  )
218
246
 
219
- opal_in = Pathname(options[:opal_input])
247
+ opal_in = Pathname(resolve_opal_input(root, options[:opal_input]))
220
248
  opal_out = Pathname(options[:opal_output])
221
249
  opal_in = root.join(opal_in) unless opal_in.absolute?
222
250
  opal_out = root.join(opal_out) unless opal_out.absolute?
223
- run_opal_standalone!(root, opal_in.relative_path_from(root).to_s, opal_out.relative_path_from(root).to_s,
224
- with_db: options[:with_db])
251
+ prepared_in, temp_input = prepare_opal_input(root, opal_in)
252
+ begin
253
+ run_opal_standalone!(root, prepared_in, opal_out.relative_path_from(root).to_s,
254
+ with_db: options[:with_db])
255
+ ensure
256
+ FileUtils.rm_f(temp_input) if temp_input
257
+ end
225
258
  end
226
259
 
227
260
  patch_rel = Pathname(patch_target)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'parser/current'
4
4
  require 'parser/source/tree_rewriter'
5
+ require 'set'
5
6
 
6
7
  module CloudflareWorkers
7
8
  module AutoAwait
@@ -12,6 +13,7 @@ module CloudflareWorkers
12
13
  @await_nodes = []
13
14
  @env = {}
14
15
  @method_returns = {}
16
+ @async_local_methods = Set.new
15
17
  end
16
18
 
17
19
  def process(source, filename = '(auto-await)')
@@ -19,14 +21,22 @@ module CloudflareWorkers
19
21
  buffer.source = source
20
22
  parser = Parser::CurrentRuby.new
21
23
  ast = parser.parse(buffer)
22
- @await_nodes = []
23
- @env = {}
24
- process_node(ast)
24
+ @method_returns = {}
25
+ @async_local_methods = Set.new
26
+
27
+ analyze_pass(ast)
28
+ analyze_pass(ast)
25
29
  [buffer, @await_nodes]
26
30
  end
27
31
 
28
32
  private
29
33
 
34
+ def analyze_pass(ast)
35
+ @await_nodes = []
36
+ @env = {}
37
+ process_node(ast)
38
+ end
39
+
30
40
  def process_node(node)
31
41
  return unless node.is_a?(Parser::AST::Node)
32
42
 
@@ -72,6 +82,7 @@ module CloudflareWorkers
72
82
  def process_def(node)
73
83
  method_name = node.children[0]
74
84
  saved_env = @env
85
+ before_awaits = @await_nodes.length
75
86
  @env = @registry.helper_factories.dup
76
87
 
77
88
  node.children[1..-1].each { |child| process_node(child) if child.is_a?(Parser::AST::Node) }
@@ -79,6 +90,10 @@ module CloudflareWorkers
79
90
  body = node.children[2]
80
91
  return_cls = infer_class(body)
81
92
  @method_returns[method_name] = return_cls if return_cls
93
+ body_source = node.loc.expression&.source.to_s
94
+ if @await_nodes.length > before_awaits || body_source.include?('.__await__')
95
+ @async_local_methods << method_name
96
+ end
82
97
 
83
98
  @env = saved_env
84
99
  end
@@ -119,6 +134,7 @@ module CloudflareWorkers
119
134
  # Receiver-less calls (implicit self) — check helpers.
120
135
  helpers = @registry.async_helpers[method_name]
121
136
  return true if helpers && !helpers.empty?
137
+ return true if @async_local_methods.include?(method_name)
122
138
  false
123
139
  end
124
140
 
@@ -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/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),
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloudflareWorkers
4
- VERSION = '0.2.5'
4
+ VERSION = '0.2.7'
5
5
  end
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: homura-runtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuhiro Homma
@@ -48,11 +48,11 @@ extra_rdoc_files: []
48
48
  files:
49
49
  - CHANGELOG.md
50
50
  - README.md
51
- - bin/cloudflare-workers-build
52
51
  - docs/ARCHITECTURE.md
53
52
  - exe/auto-await
54
53
  - exe/compile-assets
55
54
  - exe/compile-erb
55
+ - exe/homura-build
56
56
  - lib/cloudflare_workers.rb
57
57
  - lib/cloudflare_workers/ai.rb
58
58
  - lib/cloudflare_workers/async_registry.rb