homura-runtime 0.1.1 → 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: e6be075dbd944ef333e509df1dce30614231a25eb96e8384fcce5ed5e6e3e58c
4
- data.tar.gz: 908dd2b9cc441b2b45946446a7e011deffb94bf2f5f16f649fc0a5c0ee352584
3
+ metadata.gz: 99d546ac1db58f957cf5bd81723cfc0e15019f11c27c8b0bcd78fc2ee833b751
4
+ data.tar.gz: 02b08e0580b05b710cfba5d5bc6255ba463a4675a297476583dcacece6b158b6
5
5
  SHA512:
6
- metadata.gz: 32bb2576d6a2b1cde36f0cc4cac86fac4e797e3628714564d9c337b0c18ebb36166f53f6008e708f36ce250423f6da01dbbae1ed3b6d5c2dc1dcc5acd0643386
7
- data.tar.gz: 02166fe7dcb152895f9af50163a7a80bf2c9e3cabdb5d16de7fbd0712bcbf26731264b17b4d7819444ee71034dfce7db5bcb51211d0ae5b7983098b008e7fed8
6
+ metadata.gz: 864e05461b1321d1094bdf27ae5bd971472194df29a440ff24f946449db7b98ec93c14ea53a25956ec1e9254b6e1e9015ac2d25978d8f1aadab02f91c69f287a
7
+ data.tar.gz: 20c08fdaad6cef869cb4256ffed49be996a3bd6a0e3dd9038bb05f4240a27e75791fefe9bdae1c8dc3f73622413ea95ed90c166dd4810c19cfdf709db6a185f2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 0.1.1 (2026-04-23)
4
12
 
5
13
  - Fix `cloudflare-workers-build --standalone` and `exe/auto-await` to resolve only
@@ -27,6 +27,10 @@ module CloudflareWorkersBuild
27
27
  def gem_lib(*names)
28
28
  CloudflareWorkers::BuildSupport.gem_lib(*names)
29
29
  end
30
+
31
+ def gem_vendor(*names)
32
+ CloudflareWorkers::BuildSupport.gem_vendor(*names)
33
+ end
30
34
  end
31
35
  end
32
36
 
@@ -114,11 +118,18 @@ def run_opal_standalone!(root, opal_input, opal_output, with_db:)
114
118
  load_paths = []
115
119
  hv = homura_vendor_from_gemfile(root)
116
120
  load_paths << hv.to_s if hv
117
- load_paths += [
118
- 'build/auto_await/app', 'app',
119
- CloudflareWorkersBuild.gem_lib(CloudflareWorkers::BuildSupport::RUNTIME_GEM_NAME),
120
- CloudflareWorkersBuild.gem_lib(CloudflareWorkers::BuildSupport::SINATRA_GEM_NAME)
121
- ]
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
122
133
  load_paths << CloudflareWorkersBuild.gem_lib('sequel-d1') if with_db
123
134
  load_paths << 'vendor' if root.join('vendor').directory?
124
135
  load_paths << 'build'
@@ -12,6 +12,13 @@ module CloudflareWorkers
12
12
  loaded_specs[name]
13
13
  end
14
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
+
15
22
  def runtime_root(current_file:, loaded_specs: Gem.loaded_specs)
16
23
  spec = loaded_spec(RUNTIME_GEM_NAME, loaded_specs: loaded_specs)
17
24
  return Pathname(spec.full_gem_path) if spec
@@ -20,10 +27,14 @@ module CloudflareWorkers
20
27
  end
21
28
 
22
29
  def gem_lib(name, loaded_specs: Gem.loaded_specs)
23
- spec = loaded_spec(name, loaded_specs: loaded_specs)
24
- return File.join(spec.full_gem_path, 'lib') if spec
30
+ File.join(gem_root(name, loaded_specs: loaded_specs), 'lib')
31
+ end
25
32
 
26
- raise("cloudflare-workers-build: gem #{name} not loaded; use bundle exec from app root")
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
27
38
  end
28
39
 
29
40
  def vendor_from_gemfile(project_root)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloudflareWorkers
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.2'
5
5
  end
@@ -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.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuhiro NISHIYAMA
@@ -76,6 +76,13 @@ files:
76
76
  - runtime/worker_module.mjs
77
77
  - runtime/wrangler.toml.example
78
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
79
86
  homepage: https://github.com/kazuph/homura
80
87
  licenses:
81
88
  - MIT