homura-runtime 0.1.0 → 0.1.2

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: 772c92137bd489f019d78537753770d0ddc73f76825c48d4cab399532b5dee86
4
- data.tar.gz: b9cfb81748aa74ede6731ff0aa76f5d148df52cc1e3ffc10a3d66c05167a2816
3
+ metadata.gz: 99d546ac1db58f957cf5bd81723cfc0e15019f11c27c8b0bcd78fc2ee833b751
4
+ data.tar.gz: 02b08e0580b05b710cfba5d5bc6255ba463a4675a297476583dcacece6b158b6
5
5
  SHA512:
6
- metadata.gz: 58b61c31af0bf8c46ce28e5a027b598539929a543fb429723bd9212defbfab8d5875b5733b14764d6ab964597354674de998e2ef84e4da933a5b1c0d8aec72e2
7
- data.tar.gz: 36e72393465c2ee4411132447588f356f9e497062aa6a24aa442ebf515d43a96d5f1246593c912a105e509f6206dde397c264b8a5d5e1fc93f76148a76acdefb
6
+ metadata.gz: 864e05461b1321d1094bdf27ae5bd971472194df29a440ff24f946449db7b98ec93c14ea53a25956ec1e9254b6e1e9015ac2d25978d8f1aadab02f91c69f287a
7
+ data.tar.gz: 20c08fdaad6cef869cb4256ffed49be996a3bd6a0e3dd9038bb05f4240a27e75791fefe9bdae1c8dc3f73622413ea95ed90c166dd4810c19cfdf709db6a185f2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 (2026-04-23)
4
+
5
+ - Package the runtime's Opal compile-time vendor shims (`digest`, `zlib`,
6
+ `tempfile`, `tilt`, `rubygems/version`) inside the gem.
7
+ - Teach `cloudflare-workers-build --standalone` to add packaged gem `vendor/`
8
+ directories to the Opal load path, so published gems no longer depend on the
9
+ monorepo root `vendor/`.
10
+
11
+ ## 0.1.1 (2026-04-23)
12
+
13
+ - Fix `cloudflare-workers-build --standalone` and `exe/auto-await` to resolve only
14
+ the published gem names `homura-runtime` / `sinatra-homura`.
15
+ - Add regression coverage for gem name resolution and Gemfile path detection.
16
+
3
17
  ## 0.1.0 (2026-04-20)
4
18
 
5
19
  - Initial extraction from homura as `homura-runtime` (Phase 15-B).
@@ -8,13 +8,12 @@ require 'fileutils'
8
8
  require 'open3'
9
9
  require 'optparse'
10
10
  require 'pathname'
11
+ require_relative '../lib/cloudflare_workers/build_support'
11
12
 
12
13
  module CloudflareWorkersBuild
13
14
  class << self
14
15
  def gem_root
15
- spec = Gem.loaded_specs['cloudflare-workers-runtime']
16
- return Pathname(spec.full_gem_path) if spec
17
- Pathname(__FILE__).expand_path.join('../..')
16
+ CloudflareWorkers::BuildSupport.runtime_root(current_file: __FILE__)
18
17
  end
19
18
 
20
19
  def runtime_dir
@@ -25,9 +24,12 @@ module CloudflareWorkersBuild
25
24
  gem_root.join('exe', name)
26
25
  end
27
26
 
28
- def gem_lib(name)
29
- s = Gem.loaded_specs[name] || raise("cloudflare-workers-build: gem #{name} not loaded; use bundle exec from app root")
30
- File.join(s.full_gem_path, 'lib')
27
+ def gem_lib(*names)
28
+ CloudflareWorkers::BuildSupport.gem_lib(*names)
29
+ end
30
+
31
+ def gem_vendor(*names)
32
+ CloudflareWorkers::BuildSupport.gem_vendor(*names)
31
33
  end
32
34
  end
33
35
  end
@@ -109,26 +111,25 @@ def run_opal_homura!(root, opal_input, opal_output)
109
111
  end
110
112
 
111
113
  def homura_vendor_from_gemfile(project_root)
112
- gf = project_root.join('Gemfile')
113
- return unless gf.file?
114
-
115
- txt = gf.read
116
- return unless (m = txt.match(/cloudflare-workers-runtime['"]\s*,\s*path:\s*['"]([^'"]+)['"]/))
117
-
118
- runtime_path = Pathname.new(m[1]).expand_path(project_root)
119
- vend = runtime_path.join('..', '..', 'vendor').expand_path
120
- vend if vend.directory?
114
+ CloudflareWorkers::BuildSupport.vendor_from_gemfile(project_root)
121
115
  end
122
116
 
123
117
  def run_opal_standalone!(root, opal_input, opal_output, with_db:)
124
118
  load_paths = []
125
119
  hv = homura_vendor_from_gemfile(root)
126
120
  load_paths << hv.to_s if hv
127
- load_paths += [
128
- 'build/auto_await/app', 'app',
129
- CloudflareWorkersBuild.gem_lib('cloudflare-workers-runtime'),
130
- CloudflareWorkersBuild.gem_lib('sinatra-cloudflare-workers')
131
- ]
121
+ runtime_name = CloudflareWorkers::BuildSupport::RUNTIME_GEM_NAME
122
+ sinatra_name = CloudflareWorkers::BuildSupport::SINATRA_GEM_NAME
123
+
124
+ load_paths += ['build/auto_await/app', 'app']
125
+ [
126
+ CloudflareWorkersBuild.gem_lib(runtime_name),
127
+ CloudflareWorkersBuild.gem_vendor(runtime_name),
128
+ CloudflareWorkersBuild.gem_lib(sinatra_name),
129
+ CloudflareWorkersBuild.gem_vendor(sinatra_name)
130
+ ].compact.each do |path|
131
+ load_paths << path
132
+ end
132
133
  load_paths << CloudflareWorkersBuild.gem_lib('sequel-d1') if with_db
133
134
  load_paths << 'vendor' if root.join('vendor').directory?
134
135
  load_paths << 'build'
data/exe/auto-await CHANGED
@@ -15,12 +15,12 @@
15
15
 
16
16
  require 'fileutils'
17
17
  require 'pathname'
18
+ require_relative '../lib/cloudflare_workers/build_support'
18
19
 
19
- # cloudflare-workers-runtime lib path resolution
20
+ # homura-runtime lib path resolution (with legacy alias fallback)
20
21
  runtime_lib = ENV['CFW_RUNTIME_LIB']
21
22
  unless runtime_lib
22
- spec = Gem.loaded_specs['cloudflare-workers-runtime']
23
- runtime_lib = spec ? File.join(spec.full_gem_path, 'lib') : File.expand_path('../lib', __dir__)
23
+ runtime_lib = CloudflareWorkers::BuildSupport.runtime_root(current_file: __FILE__).join('lib').to_s
24
24
  end
25
25
  $LOAD_PATH.unshift(runtime_lib) unless $LOAD_PATH.include?(runtime_lib)
26
26
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module CloudflareWorkers
6
+ module BuildSupport
7
+ RUNTIME_GEM_NAME = 'homura-runtime'
8
+ SINATRA_GEM_NAME = 'sinatra-homura'
9
+
10
+ class << self
11
+ def loaded_spec(name, loaded_specs: Gem.loaded_specs)
12
+ loaded_specs[name]
13
+ end
14
+
15
+ def gem_root(name, loaded_specs: Gem.loaded_specs)
16
+ spec = loaded_spec(name, loaded_specs: loaded_specs)
17
+ return spec.full_gem_path if spec
18
+
19
+ raise("cloudflare-workers-build: gem #{name} not loaded; use bundle exec from app root")
20
+ end
21
+
22
+ def runtime_root(current_file:, loaded_specs: Gem.loaded_specs)
23
+ spec = loaded_spec(RUNTIME_GEM_NAME, loaded_specs: loaded_specs)
24
+ return Pathname(spec.full_gem_path) if spec
25
+
26
+ Pathname(current_file).expand_path.join('../..')
27
+ end
28
+
29
+ def gem_lib(name, loaded_specs: Gem.loaded_specs)
30
+ File.join(gem_root(name, loaded_specs: loaded_specs), 'lib')
31
+ end
32
+
33
+ def gem_vendor(name, loaded_specs: Gem.loaded_specs)
34
+ vendor = File.join(gem_root(name, loaded_specs: loaded_specs), 'vendor')
35
+ return vendor if Dir.exist?(vendor)
36
+
37
+ nil
38
+ end
39
+
40
+ def vendor_from_gemfile(project_root)
41
+ gf = Pathname(project_root).join('Gemfile')
42
+ return unless gf.file?
43
+
44
+ txt = gf.read
45
+ return unless (m = txt.match(/#{Regexp.escape(RUNTIME_GEM_NAME)}['"]\s*,\s*path:\s*['"]([^'"]+)['"]/))
46
+
47
+ runtime_path = Pathname.new(m[1]).expand_path(project_root)
48
+ vend = runtime_path.join('..', '..', 'vendor').expand_path
49
+ vend if vend.directory?
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloudflareWorkers
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.2'
5
5
  end
@@ -1,4 +1,4 @@
1
- # Example wrangler fragment for apps using cloudflare-workers-runtime.
1
+ # Example wrangler fragment for apps using homura-runtime.
2
2
  # Copy patterns into your own wrangler.toml and set:
3
3
  # main = "gems/homura-runtime/runtime/worker.mjs"
4
4
  # (adjust path if you vendor the gem elsewhere.)
@@ -0,0 +1,4 @@
1
+ # Stub for require 'cgi/escape'.
2
+ # Opal stdlib provides `cgi` as a single file containing CGI.escape /
3
+ # CGI.unescape; the upstream `cgi/escape` subfile does not exist.
4
+ require 'cgi'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # Compatibility shim — `digest/sha2` traditionally pulls in
3
+ # Digest::SHA256/SHA384/SHA512. Phase 7 defines them in vendor/digest.rb,
4
+ # so we just re-require digest and rely on the existing constants.
5
+ require 'digest'
data/vendor/digest.rb ADDED
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+ # backtick_javascript: true
3
+
4
+ require 'corelib/array/pack'
5
+ require 'corelib/string/unpack'
6
+ #
7
+ # Phase 7 — Real Digest implementation backed by node:crypto.
8
+ #
9
+ # Replaces the Phase 2 NotImplementedError stub. All hash algos are
10
+ # synchronous (no Promise glue), enabled by importing node:crypto via
11
+ # src/setup-node-crypto.mjs and exposing it on globalThis.
12
+ #
13
+ # Available on:
14
+ # - Cloudflare Workers (with `compatibility_flags = ["nodejs_compat"]`)
15
+ # - Node.js (with `node --import ./src/setup-node-crypto.mjs`)
16
+ #
17
+ # Surface mirrors CRuby's `digest/sha1`, `digest/sha2`, `digest/md5`:
18
+ #
19
+ # Digest::SHA256.hexdigest(str) # one-shot hex
20
+ # Digest::SHA256.digest(str) # one-shot binary
21
+ # Digest::SHA256.new.update(s).hexdigest # streaming
22
+
23
+ module Digest
24
+ # Common base class shared by SHA1 / SHA256 / SHA384 / SHA512 / MD5.
25
+ # Subclasses define ALGO (the node:crypto algorithm name).
26
+ class Base
27
+ def initialize
28
+ reset
29
+ end
30
+
31
+ def reset
32
+ @hasher = `globalThis.__nodeCrypto__.createHash(#{self.class::ALGO})`
33
+ self
34
+ end
35
+
36
+ def update(data)
37
+ str = data.to_s
38
+ `#{@hasher}.update(#{str}, 'utf8')`
39
+ self
40
+ end
41
+ alias_method :<<, :update
42
+
43
+ def hexdigest
44
+ hasher = @hasher
45
+ `#{hasher}.copy().digest('hex')`
46
+ end
47
+
48
+ def digest
49
+ hex = hexdigest
50
+ [hex].pack('H*')
51
+ end
52
+
53
+ def base64digest
54
+ hasher = @hasher
55
+ `#{hasher}.copy().digest('base64')`
56
+ end
57
+
58
+ def to_s
59
+ hexdigest
60
+ end
61
+
62
+ def self.hexdigest(data)
63
+ algo = self::ALGO
64
+ str = data.to_s
65
+ `globalThis.__nodeCrypto__.createHash(#{algo}).update(#{str}, 'utf8').digest('hex')`
66
+ end
67
+
68
+ def self.digest(data)
69
+ hex = hexdigest(data)
70
+ [hex].pack('H*')
71
+ end
72
+
73
+ def self.base64digest(data)
74
+ algo = self::ALGO
75
+ str = data.to_s
76
+ `globalThis.__nodeCrypto__.createHash(#{algo}).update(#{str}, 'utf8').digest('base64')`
77
+ end
78
+
79
+ # Disk-backed digest is impossible on Workers (no FS).
80
+ def self.file(*)
81
+ raise NotImplementedError, 'Digest.file is unavailable on Cloudflare Workers (no filesystem)'
82
+ end
83
+ end
84
+
85
+ # Backwards-compat alias for code that does `class Foo < Digest::Class`.
86
+ Class = Base unless const_defined?(:Class)
87
+
88
+ class SHA1 < Base; ALGO = 'sha1'.freeze; end
89
+ class SHA256 < Base; ALGO = 'sha256'.freeze; end
90
+ class SHA384 < Base; ALGO = 'sha384'.freeze; end
91
+ class SHA512 < Base; ALGO = 'sha512'.freeze; end
92
+ class MD5 < Base; ALGO = 'md5'.freeze; end
93
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # homura Opal stub for Gem::Version.
4
+ #
5
+ # Upstream Sinatra 4.x uses `Gem::Version.new(RUBY_VERSION) >=
6
+ # Gem::Version.new("3.0")` to conditionally activate the `except`
7
+ # override on IndifferentHash. Opal does not bundle RubyGems, so we
8
+ # provide a tiny comparator that parses dotted versions as Integer
9
+ # arrays (sufficient for the Sinatra use-case).
10
+
11
+ module Gem
12
+ class Version
13
+ include Comparable
14
+
15
+ attr_reader :parts
16
+
17
+ def initialize(str)
18
+ @parts = str.to_s.split('.').map { |s| s.to_i rescue 0 }
19
+ end
20
+
21
+ def <=>(other)
22
+ return nil unless other.is_a?(Version)
23
+ i = 0
24
+ max = [parts.size, other.parts.size].max
25
+ while i < max
26
+ a = parts[i] || 0
27
+ b = other.parts[i] || 0
28
+ cmp = a <=> b
29
+ return cmp unless cmp == 0
30
+ i += 1
31
+ end
32
+ 0
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # Minimal Tempfile stub for homura Phase 2.
2
+ # Cloudflare Workers do not have a writable filesystem so a real Tempfile
3
+ # implementation is impossible. Rack only references Tempfile for
4
+ # multipart upload buffering, which is not exercised by the hello-world
5
+ # handler. This stub allows `require 'tempfile'` to succeed; calling the
6
+ # class will raise an explicit error.
7
+
8
+ require 'stringio'
9
+
10
+ class Tempfile < StringIO
11
+ def initialize(*)
12
+ super('')
13
+ end
14
+
15
+ def self.open(*)
16
+ raise NotImplementedError, 'Tempfile is stubbed in homura Phase 2 (Workers have no writable FS)'
17
+ end
18
+
19
+ def path
20
+ raise NotImplementedError, 'Tempfile#path stubbed (no FS)'
21
+ end
22
+
23
+ def unlink
24
+ self
25
+ end
26
+
27
+ def delete
28
+ self
29
+ end
30
+
31
+ def close!
32
+ close
33
+ end
34
+ end
data/vendor/tilt.rb ADDED
@@ -0,0 +1,75 @@
1
+ # Minimal Tilt stub for the homura Phase 2 hello-world handler.
2
+ #
3
+ # Real Sinatra requires the actual `tilt` gem for template rendering
4
+ # (ERB, Haml, etc.). janbiedermann does not maintain a tilt fork because
5
+ # Tilt's internals (binding manipulation, file IO) are difficult to run
6
+ # under Opal as-is.
7
+ #
8
+ # For Phase 2 we do not render any templates — `get '/' do; "hello"; end`
9
+ # returns a String directly and never reaches Tilt. So we provide just
10
+ # enough of the Tilt surface area for `require 'tilt'` to succeed and
11
+ # for Sinatra::Base to load. If a request actually tries to render a
12
+ # template, it will raise an explicit NotImplementedError so the gap is
13
+ # obvious and we can grow this stub deliberately in a later phase.
14
+
15
+ module Tilt
16
+ class TemplateNotFound < StandardError; end
17
+
18
+ class Cache
19
+ def initialize
20
+ @cache = {}
21
+ end
22
+
23
+ def fetch(*key)
24
+ @cache[key] ||= yield
25
+ end
26
+
27
+ def clear
28
+ @cache.clear
29
+ end
30
+ end
31
+
32
+ class Mapping
33
+ def initialize
34
+ @extensions = {}
35
+ end
36
+
37
+ def register(template_class, *extensions)
38
+ extensions.each { |ext| @extensions[ext.to_s] = template_class }
39
+ end
40
+
41
+ def [](name)
42
+ @extensions[name.to_s]
43
+ end
44
+
45
+ def extensions_for(engine_or_class)
46
+ @extensions.each_with_object([]) do |(ext, klass), out|
47
+ out << ext if klass == engine_or_class
48
+ end
49
+ end
50
+
51
+ def template_for(name)
52
+ @extensions[name.to_s]
53
+ end
54
+ end
55
+
56
+ class << self
57
+ def default_mapping
58
+ @default_mapping ||= Mapping.new
59
+ end
60
+
61
+ def [](name)
62
+ default_mapping[name]
63
+ end
64
+
65
+ def register(template_class, *extensions)
66
+ default_mapping.register(template_class, *extensions)
67
+ end
68
+
69
+ def new(file = nil, line = nil, options = {}, &block)
70
+ raise NotImplementedError,
71
+ 'Tilt template rendering is not available in homura Phase 2 ' \
72
+ '(stubbed). Return Strings or arrays from your Sinatra handlers.'
73
+ end
74
+ end
75
+ end
data/vendor/zlib.rb ADDED
@@ -0,0 +1,41 @@
1
+ # Minimal Zlib stub for the homura Phase 2 hello-world handler.
2
+ # Opal stdlib does not ship a zlib module. Real homura apps that need
3
+ # response compression should rely on the Cloudflare edge to gzip
4
+ # responses on the way out. This stub only exists so that
5
+ # `require 'zlib'` (transitively pulled in by rack/deflater) does not
6
+ # fail at compile time. None of the methods below are reachable from
7
+ # the Phase 2 hello-world path.
8
+
9
+ module Zlib
10
+ class Error < StandardError; end
11
+ class GzipFile
12
+ class Error < Zlib::Error; end
13
+ class CRCError < Error; end
14
+ class LengthError < Error; end
15
+ class NoFooter < Error; end
16
+ end
17
+
18
+ class GzipReader < GzipFile
19
+ def self.wrap(*); raise NotImplementedError, 'Zlib stubbed'; end
20
+ end
21
+
22
+ class GzipWriter < GzipFile
23
+ def self.wrap(*); raise NotImplementedError, 'Zlib stubbed'; end
24
+ end
25
+
26
+ class Deflate
27
+ def self.deflate(*); raise NotImplementedError, 'Zlib stubbed'; end
28
+ def initialize(*); end
29
+ def deflate(*); raise NotImplementedError, 'Zlib stubbed'; end
30
+ def finish; raise NotImplementedError, 'Zlib stubbed'; end
31
+ def close; end
32
+ end
33
+
34
+ class Inflate
35
+ def self.inflate(*); raise NotImplementedError, 'Zlib stubbed'; end
36
+ end
37
+
38
+ DEFAULT_COMPRESSION = -1
39
+ BEST_SPEED = 1
40
+ BEST_COMPRESSION = 9
41
+ end
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.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuhiro NISHIYAMA
@@ -59,6 +59,7 @@ files:
59
59
  - lib/cloudflare_workers/async_registry.rb
60
60
  - lib/cloudflare_workers/auto_await/analyzer.rb
61
61
  - lib/cloudflare_workers/auto_await/transformer.rb
62
+ - lib/cloudflare_workers/build_support.rb
62
63
  - lib/cloudflare_workers/cache.rb
63
64
  - lib/cloudflare_workers/durable_object.rb
64
65
  - lib/cloudflare_workers/email.rb
@@ -75,6 +76,13 @@ files:
75
76
  - runtime/worker_module.mjs
76
77
  - runtime/wrangler.toml.example
77
78
  - templates/wrangler.toml.example
79
+ - vendor/cgi/escape.rb
80
+ - vendor/digest.rb
81
+ - vendor/digest/sha2.rb
82
+ - vendor/rubygems/version.rb
83
+ - vendor/tempfile.rb
84
+ - vendor/tilt.rb
85
+ - vendor/zlib.rb
78
86
  homepage: https://github.com/kazuph/homura
79
87
  licenses:
80
88
  - MIT
@@ -84,6 +92,7 @@ metadata:
84
92
  bug_tracker_uri: https://github.com/kazuph/homura/issues
85
93
  changelog_uri: https://github.com/kazuph/homura/blob/main/gems/homura-runtime/CHANGELOG.md
86
94
  readme_uri: https://github.com/kazuph/homura/blob/main/gems/homura-runtime/README.md
95
+ documentation_uri: https://homura.kazu-san.workers.dev/docs/runtime
87
96
  rdoc_options: []
88
97
  require_paths:
89
98
  - lib