homura-runtime 0.2.23 → 0.2.24
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 +21 -1
- data/README.md +3 -3
- data/docs/ARCHITECTURE.md +3 -3
- data/exe/homura-build +23 -0
- data/lib/cloudflare_workers/build_support.rb +68 -0
- data/lib/cloudflare_workers/version.rb +1 -1
- data/lib/cloudflare_workers.rb +43 -2
- 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: 0e4fc42870cef61572354025f9091524f2b65d91f93910cb3470acd4038472e7
|
|
4
|
+
data.tar.gz: b8fafbfe977f2bfa1cbaf42f16f0b2849a3f9f3b0f12525eb2f9809497fa38e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3aabb8d2b5678419c57247f834bba6ae02d4de928b154529acf026011cb23c8b08cab01fd95a59b98c74e749b64e86a3f6f4749b6305ab143af54bf5cbc16852
|
|
7
|
+
data.tar.gz: 8b71e4b7a2a297aa463b5d68734ca8dc6d88adab156d4bbc9b33e20f7caa58b1fd188c9a4a208fcce83c3df9a57aa2eaf5d18673f4d36cd4d962932dd59a0b97
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.24 (2026-04-29)
|
|
4
|
+
|
|
5
|
+
- `BuildSupport.standalone_load_paths`: auto-discover `path:`-resolved
|
|
6
|
+
gems in the consumer Gemfile (skipping `require: false` and
|
|
7
|
+
`group :development/:test/:ci/...` blocks) and add their `lib/` and
|
|
8
|
+
`vendor/` to the Opal load path. Lets pure-Ruby gems like
|
|
9
|
+
`sinatra-inertia` drop into a project without runtime-side wiring.
|
|
10
|
+
- `homura-build` now runs the `auto-await` analyzer over each `path:`
|
|
11
|
+
gem's `lib/` and writes the rewritten copy under
|
|
12
|
+
`build/auto_await/gem_<basename>/lib`. The transformed directory
|
|
13
|
+
is preferred over the gem's untransformed `lib/` on the load path,
|
|
14
|
+
so async chains inside gem code (e.g. `db[:foo].all` returning a
|
|
15
|
+
Promise) get `__await__` injected exactly like consumer app code.
|
|
16
|
+
- `cloudflare_workers.rb#build_js_response`: emit `Set-Cookie`
|
|
17
|
+
Arrays via `Headers#append` (multiple lines) instead of stringifying
|
|
18
|
+
the Array via `to_s`. Previously, two cookies set by sequential Rack
|
|
19
|
+
middleware (e.g. session + auth) were serialised as
|
|
20
|
+
`'["a=…", "b=…"]'`, which broke cookie parsing on every Inertia /
|
|
21
|
+
CSRF / auth-cookie pattern.
|
|
22
|
+
|
|
3
23
|
## 0.2.17 (2026-04-27)
|
|
4
24
|
|
|
5
25
|
- Rewrite class-variable references (`@@foo`) inside precompiled ERB
|
|
@@ -22,7 +42,7 @@
|
|
|
22
42
|
|
|
23
43
|
## 0.2.10 (2026-04-24)
|
|
24
44
|
|
|
25
|
-
- Derive `worker.entrypoint.mjs` import paths relative to the actual
|
|
45
|
+
- Derive `build/worker.entrypoint.mjs` import paths relative to the actual
|
|
26
46
|
`--entrypoint-out` location, so standalone builds keep working when apps move
|
|
27
47
|
the bundle and entrypoint under custom output directories.
|
|
28
48
|
|
data/README.md
CHANGED
|
@@ -9,14 +9,14 @@ Core Ruby + Module Worker glue for [Opal](https://opalrb.com/) on [Cloudflare Wo
|
|
|
9
9
|
- `runtime/worker_module.mjs` — fetch / scheduled / queue / DO adapters (**no Opal bundle import**).
|
|
10
10
|
- `runtime/worker.mjs` — thin bootstrap (crypto shim → bundle → `worker_module`) for legacy layouts.
|
|
11
11
|
- `runtime/setup-node-crypto.mjs` — `node:crypto` on `globalThis` before the Opal bundle loads.
|
|
12
|
-
- `homura build` — single build pipeline (ERB → assets → Opal → patch → `worker.entrypoint.mjs`). Use `--standalone` in generated apps; it restores `cf-runtime/` automatically, derives standalone template/asset namespaces from the project name, and computes entrypoint import paths relative to the actual `--entrypoint-out` location.
|
|
12
|
+
- `homura build` — single build pipeline (ERB → assets → Opal → patch → `build/worker.entrypoint.mjs`). Use `--standalone` in generated apps; it restores `cf-runtime/` automatically, derives standalone template/asset namespaces from the project name, and computes entrypoint import paths relative to the actual `--entrypoint-out` location.
|
|
13
13
|
- `docs/ARCHITECTURE.md` — wrangler `main`, codegen entrypoint, and fixed-import policy.
|
|
14
14
|
|
|
15
15
|
## Quick start (homura monorepo)
|
|
16
16
|
|
|
17
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
|
-
2. `bundle exec homura build` — in generated standalone apps, writes `build/hello.no-exit.mjs` plus root-level `worker.entrypoint.mjs`; in the monorepo it writes `build/worker.entrypoint.mjs`.
|
|
19
|
-
3. `wrangler.toml`: generated apps use `main = "worker.entrypoint.mjs"` and `compatibility_flags = ["nodejs_compat"]`.
|
|
18
|
+
2. `bundle exec homura build` — in generated standalone apps, writes `build/hello.no-exit.mjs` plus root-level `build/worker.entrypoint.mjs`; in the monorepo it writes `build/worker.entrypoint.mjs`.
|
|
19
|
+
3. `wrangler.toml`: generated apps use `main = "build/worker.entrypoint.mjs"` and `compatibility_flags = ["nodejs_compat"]`.
|
|
20
20
|
|
|
21
21
|
## Support matrix (indicative)
|
|
22
22
|
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| レイヤ | 役割 |
|
|
6
6
|
|--------|------|
|
|
7
7
|
| `wrangler.toml` の `main` | **Workers Module のエントリ**(`fetch` / `scheduled` / `queue` / DO クラスを export) |
|
|
8
|
-
| `worker.entrypoint.mjs`(生成物) | `setup-node-crypto` → **Opal bundle(副作用)** → `worker_module.mjs` の順で import。import path は entrypoint 出力位置からの **相対パス** に自動調整される |
|
|
8
|
+
| `build/worker.entrypoint.mjs`(生成物) | `setup-node-crypto` → **Opal bundle(副作用)** → `worker_module.mjs` の順で import。import path は entrypoint 出力位置からの **相対パス** に自動調整される |
|
|
9
9
|
| Opal bundle(例: `build/hello.no-exit.mjs`) | **ビルド成果物**。Rack ディスパッチャ等を `globalThis` に登録 |
|
|
10
10
|
| `worker_module.mjs`(gem 同梱) | Rack / Cron / Queue / DO への **純粋な JS アダプタ**(Opal bundle を import しない) |
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ flowchart LR
|
|
|
21
21
|
W1[worker.mjs] -->|hard-coded| B1[../../../build/hello.mjs]
|
|
22
22
|
end
|
|
23
23
|
subgraph after["After (Phase 15-E)"]
|
|
24
|
-
E[worker.entrypoint.mjs] --> S[setup-node-crypto.mjs]
|
|
24
|
+
E[build/worker.entrypoint.mjs] --> S[setup-node-crypto.mjs]
|
|
25
25
|
E --> O[build/*.mjs Opal bundle]
|
|
26
26
|
E --> M[worker_module.mjs]
|
|
27
27
|
end
|
|
@@ -34,7 +34,7 @@ flowchart LR
|
|
|
34
34
|
|
|
35
35
|
## スキャフォールド済みアプリ
|
|
36
36
|
|
|
37
|
-
- プロジェクト直下に `worker.entrypoint.mjs`(`main` と一致)。
|
|
37
|
+
- プロジェクト直下に `build/worker.entrypoint.mjs`(`main` と一致)。
|
|
38
38
|
- `cf-runtime/` に `setup-node-crypto.mjs` と `worker_module.mjs` をコピー(gem から)。
|
|
39
39
|
- `bundle exec homura build --standalone` が consumer 向けパイプラインを実行し、`Gemfile` の `path:` から homura の `vendor/` を追加ロードパスへ取り込み(digest / zlib 等の Workers 向け補助ファイル)。
|
|
40
40
|
- low-level `--output` / `--entrypoint-out` を変えても、entrypoint 内 import は出力先からの相対パスに自動調整される。
|
data/exe/homura-build
CHANGED
|
@@ -275,6 +275,29 @@ else
|
|
|
275
275
|
warn 'homura build: no app/ directory or top-level app.rb — skipping auto-await'
|
|
276
276
|
end
|
|
277
277
|
|
|
278
|
+
# Also run auto-await over any `path:`-resolved gems declared in the
|
|
279
|
+
# consumer's Gemfile (e.g. `sinatra-inertia`). The transformed copies
|
|
280
|
+
# are written to `build/auto_await/gem_<basename>/<original-relative>`,
|
|
281
|
+
# and `standalone_load_paths` puts those directories ahead of the
|
|
282
|
+
# untransformed gem `lib/` so `require 'sinatra/inertia'` resolves to
|
|
283
|
+
# the rewritten file.
|
|
284
|
+
CloudflareWorkers::BuildSupport.path_gemfile_entries(root).each do |gem_path|
|
|
285
|
+
%w[lib].each do |sub|
|
|
286
|
+
src = gem_path.join(sub)
|
|
287
|
+
next unless src.directory?
|
|
288
|
+
out = root.join('build', 'auto_await', "gem_#{gem_path.basename}", sub)
|
|
289
|
+
FileUtils.mkdir_p(out)
|
|
290
|
+
run!(
|
|
291
|
+
[
|
|
292
|
+
'ruby', CloudflareWorkersBuild.exe_path('auto-await').to_s,
|
|
293
|
+
'--input', src.to_s,
|
|
294
|
+
'--output', out.to_s
|
|
295
|
+
],
|
|
296
|
+
chdir: root
|
|
297
|
+
)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
278
301
|
opal_in = Pathname(resolve_opal_input(root, options[:opal_input]))
|
|
279
302
|
opal_out = Pathname(options[:opal_output])
|
|
280
303
|
opal_in = root.join(opal_in) unless opal_in.absolute?
|
|
@@ -86,6 +86,27 @@ module CloudflareWorkers
|
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
# Pick up any other `path:`-resolved gems declared in the consumer's
|
|
90
|
+
# Gemfile (e.g. `sinatra-inertia`). This keeps the build pipeline
|
|
91
|
+
# extensible: users can drop a pure-Ruby gem under `gems/foo`, list
|
|
92
|
+
# it as `gem 'foo', path: '../../gems/foo'`, and Opal will find its
|
|
93
|
+
# `lib/` automatically — no homura-runtime change required.
|
|
94
|
+
#
|
|
95
|
+
# We prefer the auto-await-transformed copy under
|
|
96
|
+
# `build/auto_await/gem_<basename>/lib` if present (homura-build
|
|
97
|
+
# writes one for every path: gem before invoking Opal), so that
|
|
98
|
+
# async chains inside the gem get `__await__` inserted just like
|
|
99
|
+
# consumer app code.
|
|
100
|
+
path_gemfile_entries(root).each do |gem_path|
|
|
101
|
+
basename = gem_path.basename.to_s
|
|
102
|
+
rewritten_lib = root.join('build', 'auto_await', "gem_#{basename}", 'lib')
|
|
103
|
+
load_paths << rewritten_lib.to_s if rewritten_lib.directory?
|
|
104
|
+
%w[lib vendor].each do |sub|
|
|
105
|
+
dir = gem_path.join(sub)
|
|
106
|
+
load_paths << dir.to_s if dir.directory?
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
89
110
|
load_paths << 'vendor' if root.join('vendor').directory?
|
|
90
111
|
load_paths << 'build'
|
|
91
112
|
load_paths.uniq
|
|
@@ -111,6 +132,53 @@ module CloudflareWorkers
|
|
|
111
132
|
vend = runtime_path.join('..', '..', 'vendor').expand_path
|
|
112
133
|
vend if vend.directory?
|
|
113
134
|
end
|
|
135
|
+
|
|
136
|
+
# Returns absolute Pathnames for every `path:`-declared gem in the
|
|
137
|
+
# project's Gemfile that should ship in the Workers bundle.
|
|
138
|
+
#
|
|
139
|
+
# Excludes:
|
|
140
|
+
# * gems we already wire in explicitly
|
|
141
|
+
# (homura-runtime / sinatra-homura / sequel-d1)
|
|
142
|
+
# * `require: false` gems (dev tooling like `gem 'rspec', path: ..., require: false`)
|
|
143
|
+
# * gems declared inside `group :development do … end` /
|
|
144
|
+
# `group :test do … end` blocks (they don't ship to production)
|
|
145
|
+
EXCLUDED_GROUPS = %i[development test dev_test development_test ci tools].freeze
|
|
146
|
+
|
|
147
|
+
def path_gemfile_entries(project_root)
|
|
148
|
+
gf = Pathname(project_root).join('Gemfile')
|
|
149
|
+
return [] unless gf.file?
|
|
150
|
+
|
|
151
|
+
wired = [RUNTIME_GEM_NAME, SINATRA_GEM_NAME, SEQUEL_D1_GEM_NAME]
|
|
152
|
+
out = []
|
|
153
|
+
group_stack = []
|
|
154
|
+
|
|
155
|
+
gf.read.each_line do |line|
|
|
156
|
+
stripped = line.strip
|
|
157
|
+
next if stripped.empty? || stripped.start_with?('#')
|
|
158
|
+
|
|
159
|
+
if (m = stripped.match(/\Agroup\s+(.+?)\s+do\b/))
|
|
160
|
+
groups = m[1].scan(/[:'"]([A-Za-z0-9_]+)['"]?/).flatten.map(&:to_sym)
|
|
161
|
+
group_stack.push(groups)
|
|
162
|
+
next
|
|
163
|
+
end
|
|
164
|
+
if stripped == 'end'
|
|
165
|
+
group_stack.pop unless group_stack.empty?
|
|
166
|
+
next
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
next if group_stack.flatten.any? { |g| EXCLUDED_GROUPS.include?(g) }
|
|
170
|
+
|
|
171
|
+
m = line.match(/gem\s+['"]([^'"]+)['"][^#]*?path:\s*['"]([^'"]+)['"]/)
|
|
172
|
+
next unless m
|
|
173
|
+
name, rel = m[1], m[2]
|
|
174
|
+
next if wired.include?(name)
|
|
175
|
+
next if line.match?(/require:\s*false/)
|
|
176
|
+
|
|
177
|
+
gem_path = Pathname.new(rel).expand_path(project_root)
|
|
178
|
+
out << gem_path if gem_path.directory?
|
|
179
|
+
end
|
|
180
|
+
out.uniq
|
|
181
|
+
end
|
|
114
182
|
end
|
|
115
183
|
end
|
|
116
184
|
end
|
data/lib/cloudflare_workers.rb
CHANGED
|
@@ -376,13 +376,54 @@ module Rack
|
|
|
376
376
|
chunks << body
|
|
377
377
|
end
|
|
378
378
|
|
|
379
|
+
# Build JS-side headers. Set-Cookie is the one HTTP response
|
|
380
|
+
# header that legitimately repeats — Rack 3 surfaces it as an
|
|
381
|
+
# Array of cookie strings (e.g. session middleware + auth
|
|
382
|
+
# middleware both setting cookies). Plain `vs = v.to_s` would
|
|
383
|
+
# collapse that Array into its `inspect` form and break cookie
|
|
384
|
+
# parsing on the client. We use a `__multi__` sentinel here
|
|
385
|
+
# and let `Cloudflare.headers_to_js` (the SSE/streaming code
|
|
386
|
+
# path also uses it) emit each entry as a separate header line
|
|
387
|
+
# via Headers#append.
|
|
379
388
|
js_headers = `({})`
|
|
380
389
|
headers.each do |k, v|
|
|
381
390
|
ks = k.to_s
|
|
382
|
-
|
|
383
|
-
|
|
391
|
+
if v.is_a?(Array)
|
|
392
|
+
arr = `[]`
|
|
393
|
+
v.each { |vi| `#{arr}.push(#{vi.to_s})` }
|
|
394
|
+
`#{js_headers}[#{ks}] = { __multi__: true, values: #{arr} }`
|
|
395
|
+
else
|
|
396
|
+
vs = v.to_s
|
|
397
|
+
`#{js_headers}[#{ks}] = #{vs}`
|
|
398
|
+
end
|
|
384
399
|
end
|
|
385
400
|
|
|
401
|
+
# Convert any `{ __multi__: true, values: [...] }` markers into
|
|
402
|
+
# a real `Headers` object that Workers' `new Response(headers:)`
|
|
403
|
+
# accepts. Single-valued headers stay as plain string values.
|
|
404
|
+
js_headers = `(function(h) {
|
|
405
|
+
var hasMulti = false;
|
|
406
|
+
for (var key in h) {
|
|
407
|
+
if (h[key] && typeof h[key] === 'object' && h[key].__multi__ === true) {
|
|
408
|
+
hasMulti = true;
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (!hasMulti) return h;
|
|
413
|
+
var realHeaders = new Headers();
|
|
414
|
+
for (var key in h) {
|
|
415
|
+
var val = h[key];
|
|
416
|
+
if (val && typeof val === 'object' && val.__multi__ === true) {
|
|
417
|
+
for (var j = 0; j < val.values.length; j++) {
|
|
418
|
+
realHeaders.append(key, val.values[j]);
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
realHeaders.set(key, val);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return realHeaders;
|
|
425
|
+
})(#{js_headers})`
|
|
426
|
+
|
|
386
427
|
status_int = status.to_i
|
|
387
428
|
|
|
388
429
|
js_chunks = `[]`
|