homura-runtime 0.2.4 → 0.2.6

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: a7e0278919623ca0bd4057dd685cc0a6121efa0d36e0f0a7eeb8bc9e404938a8
4
- data.tar.gz: 3896ecbe9174de362fd6e9dc4d24ec015adf495bce80b045bd61d2c4a305791d
3
+ metadata.gz: 37cf4aa4a816991923d004144655459aef2da15445ee0c23aaf94105e36e892d
4
+ data.tar.gz: ceae9042b8c7613d03f12be573977846838888d74429e829c01b6328f81f2286
5
5
  SHA512:
6
- metadata.gz: 2607d862746d48cad7eb8c28a8e1cb39477c95fab45c6f90ea0cc8e773dadfca54d1e2d3cb0842774cec3bddbe7216ce0efd433f147f48c86a97a2118f29edd4
7
- data.tar.gz: 14a8a0faca16aa7f1f8ed9a49c2bbc194afd4417a2d4b808519d1d144a94b9ac2a75a01fbdbceb385e732228f3b0bba91667f3d17d5004cec8f3d17ccf72f380
6
+ metadata.gz: 96c093338500d78dde842fc48fe28807749e9ccb68e4fb3157e34fa81e95ae54e75b65b7d6708b38d60e782670553ff4b007aa40f73c8ad2fda4a2d6b9d38d91
7
+ data.tar.gz: 44967c5dfb3edf5b3bfbb52d4f9e8c6e996436de41121b8324f74aad07c694ef133b725906b156d35df10abf023242003236d1cd955846a8a86d3030bc0d5561
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.6 (2026-04-24)
4
+
5
+ - Remove the shipped `cloudflare-workers-build` filename entirely and keep the
6
+ build implementation under `exe/homura-build`, matching the `homura build`
7
+ public surface.
8
+ - Make `homura build --with-db` imply the consumer-safe standalone path so D1 /
9
+ Sequel apps do not need to remember both flags.
10
+ - Let `compile-assets` succeed for an empty `public/` directory and improve the
11
+ auto-await analyzer so async local helper methods are awaited at their call
12
+ sites.
13
+
14
+ ## 0.2.5 (2026-04-23)
15
+
16
+ - Load Workers-only `zlib`, `tempfile`, and `tilt` shims via `require_relative`
17
+ so plain `opal` CLI builds can require `opal_patches` without extra vendor
18
+ load path wiring.
19
+ - Recursively convert nested plain JS objects in `Cloudflare.js_object_to_hash`
20
+ so D1 metadata behaves like Ruby `Hash` values all the way down.
21
+ - Pin `opal-homura` to `1.8.3.rc1.3` for the restored `digest` stdlib shim.
22
+
3
23
  ## 0.2.4 (2026-04-23)
4
24
 
5
25
  - Guard the Rack body close hook against raw JS `undefined` so Workers request teardown
@@ -18,14 +38,14 @@
18
38
 
19
39
  ## 0.2.0 (2026-04-23)
20
40
 
21
- - Remove the old `cloudflare-workers-build` public executable and expose the
41
+ - Remove the old standalone build executable and expose the
22
42
  build pipeline through `homura build`.
23
43
  - Keep the internal build implementation in the runtime gem while letting the
24
44
  single `homura` CLI dispatch to it.
25
45
 
26
46
  ## 0.1.6 (2026-04-23)
27
47
 
28
- - Teach `cloudflare-workers-build --standalone --with-db` to add the packaged
48
+ - Teach `homura build --standalone --with-db` to add the packaged
29
49
  `sequel-d1` gem `vendor/` directory to the Opal load path before the gem's
30
50
  `lib/`, so `require 'sequel'` resolves to the bundled Opal-compatible Sequel
31
51
  subset instead of the CRuby gem.
@@ -34,7 +54,7 @@
34
54
 
35
55
  - Make `auto-await` emit rewritten files for existing hand-written `.__await__`
36
56
  usage when the only missing piece is `# await: true`.
37
- - Make `cloudflare-workers-build --standalone` restore `cf-runtime/` from the
57
+ - Make `homura build --standalone` restore `cf-runtime/` from the
38
58
  packaged gem and derive standalone template/asset namespaces from the project
39
59
  name by default, with explicit override flags when needed.
40
60
  - Reject unsupported ERB yield forms like `<% yield %>` and `yield(arg)` with
@@ -59,12 +79,12 @@
59
79
 
60
80
  - Package the runtime's Opal compile-time vendor shims (`digest`, `zlib`,
61
81
  `tempfile`, `tilt`, `rubygems/version`) inside the gem.
62
- - Teach `cloudflare-workers-build --standalone` to add packaged gem `vendor/`
82
+ - Teach `homura build --standalone` to add packaged gem `vendor/`
63
83
  directories to the Opal load path, so published gems no longer depend on the
64
84
  monorepo root `vendor/`.
65
85
  ## 0.1.1 (2026-04-23)
66
86
 
67
- - Fix `cloudflare-workers-build --standalone` and `exe/auto-await` to resolve only
87
+ - Fix `homura build --standalone` and `exe/auto-await` to resolve only
68
88
  the published gem names `homura-runtime` / `sinatra-homura`.
69
89
  - Add regression coverage for gem name resolution and Gemfile path detection.
70
90
 
data/README.md CHANGED
@@ -14,7 +14,7 @@ Core Ruby + Module Worker glue for [Opal](https://opalrb.com/) on [Cloudflare Wo
14
14
 
15
15
  ## Quick start (homura monorepo)
16
16
 
17
- 1. `Gemfile`: `gem 'homura-runtime', path: 'gems/homura-runtime'` and `gem 'opal-homura', '= 1.8.3.rc1.2', require: 'opal'` (path or exact pin).
17
+ 1. `Gemfile`: `gem 'homura-runtime', path: 'gems/homura-runtime'` and `gem 'opal-homura', '= 1.8.3.rc1.3', require: 'opal'` (path or exact pin).
18
18
  2. `bundle exec homura build` — writes `build/hello.no-exit.mjs` and `build/worker.entrypoint.mjs`.
19
19
  3. `wrangler.toml`: `main = "build/worker.entrypoint.mjs"`, `compatibility_flags = ["nodejs_compat"]`.
20
20
 
@@ -25,7 +25,7 @@ Core Ruby + Module Worker glue for [Opal](https://opalrb.com/) on [Cloudflare Wo
25
25
  | Ruby | 3.4.x |
26
26
  | Node | ≥ 20 |
27
27
  | Wrangler | ^3.99 |
28
- | Opal | = 1.8.3.rc1.2 |
28
+ | Opal | = 1.8.3.rc1.3 |
29
29
 
30
30
  ## Wrangler config
31
31
 
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))
@@ -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]
@@ -113,7 +115,7 @@ def run_opal_homura!(root, opal_input, opal_output)
113
115
  env = { 'OPAL_PREFORK_DISABLE' => '1' }
114
116
  out_err, status = Open3.capture2e(env, *argv, chdir: root.to_s)
115
117
  File.write(stderr_log, out_err)
116
- abort('homura build: opal failed') unless status.success?
118
+ abort("homura build: opal failed (see #{stderr_log})") unless status.success?
117
119
  end
118
120
 
119
121
  def homura_vendor_from_gemfile(project_root)
@@ -132,7 +134,7 @@ def run_opal_standalone!(root, opal_input, opal_output, with_db:)
132
134
  env = { 'OPAL_PREFORK_DISABLE' => '1' }
133
135
  out_err, status = Open3.capture2e(env, *argv, chdir: root.to_s)
134
136
  File.write(stderr_log, out_err)
135
- abort('homura build: opal failed') unless status.success?
137
+ abort("homura build: opal failed (see #{stderr_log})") unless status.success?
136
138
  end
137
139
 
138
140
  def write_entrypoint!(root, out_path, setup:, bundle:, worker_mod:)
@@ -79,7 +79,7 @@ module CloudflareWorkers
79
79
  next unless Dir.exist?(lib_dir)
80
80
 
81
81
  Dir.glob(File.join(lib_dir, '**', '*.rb')).each do |path|
82
- next unless File.read(path, 8192).include?('register_async_source')
82
+ next unless File.read(path).include?('register_async_source')
83
83
 
84
84
  require_path = path.sub(Regexp.new("^#{Regexp.escape(lib_dir)}/"), '').sub(/\.rb\z/, '')
85
85
  begin
@@ -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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloudflareWorkers
4
- VERSION = '0.2.4'
4
+ VERSION = '0.2.6'
5
5
  end
@@ -144,7 +144,10 @@ module Rack
144
144
  raise '`run app` was never called from user code' if @app.nil?
145
145
 
146
146
  env = build_rack_env(js_req, js_env, js_ctx, body_text)
147
- status, headers, body = @app.call(env)
147
+ result = @app.call(env)
148
+ result = result.__await__ if defined?(::Cloudflare) && ::Cloudflare.js_promise?(result)
149
+
150
+ status, headers, body = result
148
151
  build_js_response(status, headers, body)
149
152
  ensure
150
153
  body.close if body && body.respond_to?(:close)
@@ -484,7 +487,9 @@ module Cloudflare
484
487
  out
485
488
  end
486
489
 
487
- # Shallow copy of a JS object's own enumerable string keys into a Hash.
490
+ # Deep copy of a JS object's own enumerable string keys into a Hash.
491
+ # Recursively converts nested plain objects so Opal code can call
492
+ # Ruby methods (e.g. #is_a?) on every value.
488
493
  def self.js_object_to_hash(js_obj)
489
494
  h = {}
490
495
  return h if `#{js_obj} == null`
@@ -494,6 +499,8 @@ module Cloudflare
494
499
  while i < len
495
500
  k = `#{keys}[#{i}]`
496
501
  v = `#{js_obj}[#{k}]`
502
+ # Recurse for nested plain objects (but not Arrays, Dates, etc.)
503
+ v = js_object_to_hash(v) if `typeof #{v} === 'object' && #{v} != null && !Array.isArray(#{v}) && !(#{v} instanceof Date)`
497
504
  h[k] = v
498
505
  i += 1
499
506
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ class Tempfile < StringIO
6
+ def initialize(*)
7
+ super('')
8
+ end
9
+
10
+ def self.open(*)
11
+ raise NotImplementedError, 'Tempfile is stubbed in homura (Workers have no writable FS)'
12
+ end
13
+
14
+ def path
15
+ raise NotImplementedError, 'Tempfile#path stubbed (no FS)'
16
+ end
17
+
18
+ def unlink
19
+ self
20
+ end
21
+
22
+ def delete
23
+ self
24
+ end
25
+
26
+ def close!
27
+ close
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tilt
4
+ class TemplateNotFound < StandardError; end
5
+
6
+ class Cache
7
+ def initialize
8
+ @cache = {}
9
+ end
10
+
11
+ def fetch(*key)
12
+ @cache[key] ||= yield
13
+ end
14
+
15
+ def clear
16
+ @cache.clear
17
+ end
18
+ end
19
+
20
+ class Mapping
21
+ def initialize
22
+ @extensions = {}
23
+ end
24
+
25
+ def register(template_class, *extensions)
26
+ extensions.each { |ext| @extensions[ext.to_s] = template_class }
27
+ end
28
+
29
+ def [](name)
30
+ @extensions[name.to_s]
31
+ end
32
+
33
+ def extensions_for(engine_or_class)
34
+ @extensions.each_with_object([]) do |(ext, klass), out|
35
+ out << ext if klass == engine_or_class
36
+ end
37
+ end
38
+
39
+ def template_for(name)
40
+ @extensions[name.to_s]
41
+ end
42
+ end
43
+
44
+ class << self
45
+ def default_mapping
46
+ @default_mapping ||= Mapping.new
47
+ end
48
+
49
+ def [](name)
50
+ default_mapping[name]
51
+ end
52
+
53
+ def register(template_class, *extensions)
54
+ default_mapping.register(template_class, *extensions)
55
+ end
56
+
57
+ def new(file = nil, line = nil, options = {}, &block)
58
+ raise NotImplementedError,
59
+ 'Tilt template rendering is not available in homura Phase 2 ' \
60
+ '(stubbed). Return Strings or arrays from your Sinatra handlers.'
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zlib
4
+ class Error < StandardError; end
5
+
6
+ class GzipFile
7
+ class Error < Zlib::Error; end
8
+ class CRCError < Error; end
9
+ class LengthError < Error; end
10
+ class NoFooter < Error; end
11
+ end
12
+
13
+ class GzipReader < GzipFile
14
+ def self.wrap(*)
15
+ raise NotImplementedError, 'Zlib stubbed'
16
+ end
17
+ end
18
+
19
+ class GzipWriter < GzipFile
20
+ def self.wrap(*)
21
+ raise NotImplementedError, 'Zlib stubbed'
22
+ end
23
+ end
24
+
25
+ class Deflate
26
+ def self.deflate(*)
27
+ raise NotImplementedError, 'Zlib stubbed'
28
+ end
29
+
30
+ def initialize(*); end
31
+
32
+ def deflate(*)
33
+ raise NotImplementedError, 'Zlib stubbed'
34
+ end
35
+
36
+ def finish
37
+ raise NotImplementedError, 'Zlib stubbed'
38
+ end
39
+
40
+ def close; end
41
+ end
42
+
43
+ class Inflate
44
+ def self.inflate(*)
45
+ raise NotImplementedError, 'Zlib stubbed'
46
+ end
47
+ end
48
+
49
+ DEFAULT_COMPRESSION = -1
50
+ BEST_SPEED = 1
51
+ BEST_COMPRESSION = 9
52
+ end
data/lib/opal_patches.rb CHANGED
@@ -396,14 +396,15 @@ end
396
396
  # same strength CRuby Sinatra gives you when SecureRandom is unavailable
397
397
  # (it falls back to `Kernel.rand`), so we are not reducing security
398
398
  # beyond upstream's own fallback path.
399
- # Ensure our Digest/Zlib/Tempfile/Tilt stubs from vendor/ are available
400
- # everywhere, even when a gem references `Digest::SHA1` at class body
401
- # time without explicitly `require 'digest'`-ing first.
399
+ # Ensure our Digest/Zlib/Tempfile/Tilt shims are available when
400
+ # `opal_patches` is loaded directly through the plain Opal CLI as well as
401
+ # through `homura build`. Digest itself now lives in opal-homura stdlib;
402
+ # the remaining Workers-specific shims stay local to this gem.
402
403
  require 'digest'
403
404
  require 'digest/sha2'
404
- require 'zlib'
405
- require 'tempfile'
406
- require 'tilt'
405
+ require 'homura_vendor_zlib'
406
+ require 'homura_vendor_tempfile'
407
+ require 'homura_vendor_tilt'
407
408
 
408
409
  module ::SecureRandom
409
410
  # Raised when neither node:crypto.randomBytes nor Web Crypto
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.4
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuhiro Homma
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 1.8.3.rc1.2
18
+ version: 1.8.3.rc1.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 1.8.3.rc1.2
25
+ version: 1.8.3.rc1.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: parser
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -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
@@ -68,6 +68,9 @@ files:
68
68
  - lib/cloudflare_workers/scheduled.rb
69
69
  - lib/cloudflare_workers/stream.rb
70
70
  - lib/cloudflare_workers/version.rb
71
+ - lib/homura_vendor_tempfile.rb
72
+ - lib/homura_vendor_tilt.rb
73
+ - lib/homura_vendor_zlib.rb
71
74
  - lib/opal_patches.rb
72
75
  - runtime/patch-opal-evals.mjs
73
76
  - runtime/setup-node-crypto.mjs