homura-runtime 0.1.4 → 0.1.5
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 +10 -0
- data/README.md +1 -1
- data/bin/cloudflare-workers-build +10 -3
- data/exe/auto-await +9 -2
- data/exe/compile-erb +38 -5
- data/lib/cloudflare_workers/build_support.rb +25 -0
- data/lib/cloudflare_workers/version.rb +1 -1
- 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: b3d8286719ce164088136af4d1caf0476c026d4ec2e42b98b08db8c84df6766d
|
|
4
|
+
data.tar.gz: 849f3bb4ae14131b950de3a89efff6032a4204e9878a19c84cfe9dcdfa618e8b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c692cab5b2375646806e28b72b2e3c82da48563dcfc6a9074b8a8f3d9a756a92caee4ce100eee22bfaf81d00a459b3b85541176565fc1c3de5545f5c08b5216b
|
|
7
|
+
data.tar.gz: 0eda9cfebebb75b1efd6764d6be2b27710769e30a215e9c9cb5b9d635534afbf685cc8aeac632ad09378ecf106768d9b395093f4055c2293179b81817484f8a6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5 (2026-04-23)
|
|
4
|
+
|
|
5
|
+
- Make `auto-await` emit rewritten files for existing hand-written `.__await__`
|
|
6
|
+
usage when the only missing piece is `# await: true`.
|
|
7
|
+
- Make `cloudflare-workers-build --standalone` restore `cf-runtime/` from the
|
|
8
|
+
packaged gem and derive standalone template/asset namespaces from the project
|
|
9
|
+
name by default, with explicit override flags when needed.
|
|
10
|
+
- Reject unsupported ERB yield forms like `<% yield %>` and `yield(arg)` with
|
|
11
|
+
compile-time guidance toward the supported Sinatra-style layout forms.
|
|
12
|
+
|
|
3
13
|
## 0.1.4 (2026-04-23)
|
|
4
14
|
|
|
5
15
|
- Teach the precompiled ERB runtime to support Sinatra-style layout blocks and
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ 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
|
-
- `bin/cloudflare-workers-build` — single build pipeline (ERB → assets → Opal → patch → `worker.entrypoint.mjs`). Use `--standalone` in generated apps.
|
|
12
|
+
- `bin/cloudflare-workers-build` — single build pipeline (ERB → assets → Opal → patch → `worker.entrypoint.mjs`). Use `--standalone` in generated apps; it now restores `cf-runtime/` automatically and derives standalone template/asset namespaces from the project name by default.
|
|
13
13
|
- `docs/ARCHITECTURE.md` — wrangler `main`, codegen entrypoint, and fixed-import policy.
|
|
14
14
|
|
|
15
15
|
## Quick start (homura monorepo)
|
|
@@ -44,7 +44,9 @@ options = {
|
|
|
44
44
|
bundle_import: nil,
|
|
45
45
|
worker_module_import: nil,
|
|
46
46
|
entrypoint_out: nil,
|
|
47
|
-
with_db: false
|
|
47
|
+
with_db: false,
|
|
48
|
+
templates_namespace: nil,
|
|
49
|
+
assets_namespace: nil
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
OptionParser.new do |o|
|
|
@@ -63,9 +65,13 @@ OptionParser.new do |o|
|
|
|
63
65
|
o.on('--bundle-import PATH', 'Opal bundle import in entrypoint') { |p| options[:bundle_import] = p }
|
|
64
66
|
o.on('--worker-module-import PATH', 'worker_module.mjs import in entrypoint') { |p| options[:worker_module_import] = p }
|
|
65
67
|
o.on('--entrypoint-out PATH', 'Where to write worker.entrypoint.mjs') { |p| options[:entrypoint_out] = p }
|
|
68
|
+
o.on('--templates-namespace NAME', 'Standalone templates module name (default: project-derived)') { |n| options[:templates_namespace] = n }
|
|
69
|
+
o.on('--assets-namespace NAME', 'Standalone assets module name (default: project-derived)') { |n| options[:assets_namespace] = n }
|
|
66
70
|
end.parse!
|
|
67
71
|
|
|
68
72
|
root = Pathname(options[:root]).expand_path
|
|
73
|
+
options[:templates_namespace] ||= CloudflareWorkers::BuildSupport.standalone_namespace(root, 'Templates') if options[:standalone]
|
|
74
|
+
options[:assets_namespace] ||= CloudflareWorkers::BuildSupport.standalone_namespace(root, 'Assets') if options[:standalone]
|
|
69
75
|
|
|
70
76
|
if options[:standalone]
|
|
71
77
|
Dir.chdir(root) { require 'bundler/setup' }
|
|
@@ -199,12 +205,13 @@ unless options[:standalone]
|
|
|
199
205
|
opal_out = root.join(opal_out) unless opal_out.absolute?
|
|
200
206
|
run_opal_homura!(root, opal_in.relative_path_from(root).to_s, opal_out.relative_path_from(root).to_s)
|
|
201
207
|
else
|
|
208
|
+
CloudflareWorkers::BuildSupport.ensure_standalone_runtime(root, current_file: __FILE__)
|
|
202
209
|
run!(
|
|
203
210
|
[
|
|
204
211
|
'ruby', CloudflareWorkersBuild.exe_path('compile-erb').to_s,
|
|
205
212
|
'--input', 'views',
|
|
206
213
|
'--output', 'build/app_templates.rb',
|
|
207
|
-
'--namespace',
|
|
214
|
+
'--namespace', options[:templates_namespace]
|
|
208
215
|
],
|
|
209
216
|
chdir: root
|
|
210
217
|
)
|
|
@@ -213,7 +220,7 @@ else
|
|
|
213
220
|
'ruby', CloudflareWorkersBuild.exe_path('compile-assets').to_s,
|
|
214
221
|
'--input', 'public',
|
|
215
222
|
'--output', 'build/app_assets.rb',
|
|
216
|
-
'--namespace',
|
|
223
|
+
'--namespace', options[:assets_namespace]
|
|
217
224
|
],
|
|
218
225
|
chdir: root
|
|
219
226
|
)
|
data/exe/auto-await
CHANGED
|
@@ -72,18 +72,25 @@ paths.each do |path|
|
|
|
72
72
|
# needed to tell Opal to wrap the file in an async function and to
|
|
73
73
|
# translate #__await__ calls into real JS await keywords.
|
|
74
74
|
has_magic = source.lines.first(5).any? { |l| l.match?(/#\s*await:/) }
|
|
75
|
+
has_existing_await = source.include?('.__await__')
|
|
75
76
|
|
|
76
77
|
begin
|
|
77
78
|
analyzer = CloudflareWorkers::AutoAwait::Analyzer.new(registry, debug: options[:debug])
|
|
78
79
|
buffer, nodes = analyzer.process(source, path)
|
|
80
|
+
needs_magic_only_output = has_existing_await && !has_magic
|
|
79
81
|
|
|
80
|
-
if nodes.empty?
|
|
82
|
+
if nodes.empty? && !needs_magic_only_output
|
|
81
83
|
puts "[auto-await] skip (no changes) #{rel}" if options[:debug]
|
|
82
84
|
skipped += 1
|
|
83
85
|
next
|
|
84
86
|
end
|
|
85
87
|
|
|
86
|
-
transformed =
|
|
88
|
+
transformed =
|
|
89
|
+
if nodes.empty?
|
|
90
|
+
source
|
|
91
|
+
else
|
|
92
|
+
CloudflareWorkers::AutoAwait::Transformer.transform(source, nodes, buffer)
|
|
93
|
+
end
|
|
87
94
|
unless has_magic
|
|
88
95
|
transformed = "# await: true\n" + transformed
|
|
89
96
|
end
|
data/exe/compile-erb
CHANGED
|
@@ -43,13 +43,40 @@ USAGE
|
|
|
43
43
|
module HomuraERB
|
|
44
44
|
module_function
|
|
45
45
|
|
|
46
|
-
def
|
|
46
|
+
def supported_yield_fragment?(fragment)
|
|
47
47
|
stripped = fragment.strip
|
|
48
|
-
|
|
48
|
+
stripped.match?(/\Ayield(?:\(\s*\))?\z/)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def yield_with_args_fragment?(fragment)
|
|
52
|
+
fragment.strip.match?(/\Ayield\s*\(.+\)\z/)
|
|
53
|
+
end
|
|
49
54
|
|
|
55
|
+
def normalize_fragment(fragment)
|
|
56
|
+
return '__homura_template_yield__' if supported_yield_fragment?(fragment)
|
|
50
57
|
fragment
|
|
51
58
|
end
|
|
52
59
|
|
|
60
|
+
def validate_code_fragment!(fragment)
|
|
61
|
+
stripped = fragment.strip
|
|
62
|
+
return unless supported_yield_fragment?(stripped) || yield_with_args_fragment?(stripped)
|
|
63
|
+
|
|
64
|
+
raise <<~MSG.strip
|
|
65
|
+
Unsupported ERB yield form `<% #{stripped} %>`.
|
|
66
|
+
Use `<%= yield %>` or `<%== yield %>` in layout templates.
|
|
67
|
+
MSG
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def validate_expression_fragment!(fragment)
|
|
71
|
+
stripped = fragment.strip
|
|
72
|
+
return unless yield_with_args_fragment?(stripped)
|
|
73
|
+
|
|
74
|
+
raise <<~MSG.strip
|
|
75
|
+
Unsupported ERB yield form `<%= #{stripped} %>`.
|
|
76
|
+
`yield(arg)` is not supported; use `<%= yield %>` or `<%== yield %>`, or pass data via ivars/locals instead.
|
|
77
|
+
MSG
|
|
78
|
+
end
|
|
79
|
+
|
|
53
80
|
# Compile an ERB source string to a Ruby method body that assembles
|
|
54
81
|
# an HTML string in a local variable `_out`. The body references
|
|
55
82
|
# `@ivars` and method calls directly, so it must be run via
|
|
@@ -85,15 +112,21 @@ module HomuraERB
|
|
|
85
112
|
elsif inner.start_with?('==')
|
|
86
113
|
# `<%== expression %>` — identical to `<%= %>` in this minimal
|
|
87
114
|
# dialect (no HTML escaping yet; the author is responsible).
|
|
88
|
-
expr =
|
|
115
|
+
expr = inner[2..].strip
|
|
116
|
+
validate_expression_fragment!(expr)
|
|
117
|
+
expr = normalize_fragment(expr)
|
|
89
118
|
ruby << "_out = _out + ((#{expr})).to_s\n"
|
|
90
119
|
elsif inner.start_with?('=')
|
|
91
120
|
# `<%= expression %>`
|
|
92
|
-
expr =
|
|
121
|
+
expr = inner[1..].strip
|
|
122
|
+
validate_expression_fragment!(expr)
|
|
123
|
+
expr = normalize_fragment(expr)
|
|
93
124
|
ruby << "_out = _out + ((#{expr})).to_s\n"
|
|
94
125
|
else
|
|
95
126
|
# `<% code %>` — Ruby statement(s), emitted verbatim
|
|
96
|
-
|
|
127
|
+
code = inner.strip
|
|
128
|
+
validate_code_fragment!(code)
|
|
129
|
+
ruby << normalize_fragment(code) << "\n"
|
|
97
130
|
end
|
|
98
131
|
|
|
99
132
|
cursor = close_idx + 2
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
3
4
|
require 'pathname'
|
|
4
5
|
|
|
5
6
|
module CloudflareWorkers
|
|
@@ -37,6 +38,30 @@ module CloudflareWorkers
|
|
|
37
38
|
nil
|
|
38
39
|
end
|
|
39
40
|
|
|
41
|
+
def runtime_file(*names, current_file: __FILE__, loaded_specs: Gem.loaded_specs)
|
|
42
|
+
runtime_root(current_file: current_file, loaded_specs: loaded_specs).join('runtime', *names)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ensure_standalone_runtime(project_root, current_file: __FILE__, loaded_specs: Gem.loaded_specs)
|
|
46
|
+
target_dir = Pathname(project_root).join('cf-runtime')
|
|
47
|
+
FileUtils.mkdir_p(target_dir)
|
|
48
|
+
|
|
49
|
+
%w[setup-node-crypto.mjs worker_module.mjs].each do |name|
|
|
50
|
+
FileUtils.cp(runtime_file(name, current_file: current_file, loaded_specs: loaded_specs), target_dir.join(name))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
target_dir
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def standalone_namespace(project_root, suffix)
|
|
57
|
+
base = Pathname(project_root).basename.to_s
|
|
58
|
+
parts = base.split(/[^A-Za-z0-9]+/).reject(&:empty?)
|
|
59
|
+
module_name = parts.map { |part| part[0].upcase + part[1..].to_s }.join
|
|
60
|
+
module_name = 'App' if module_name.empty?
|
|
61
|
+
module_name = "App#{module_name}" if module_name.match?(/\A\d/)
|
|
62
|
+
"#{module_name}#{suffix}"
|
|
63
|
+
end
|
|
64
|
+
|
|
40
65
|
def vendor_from_gemfile(project_root)
|
|
41
66
|
gf = Pathname(project_root).join('Gemfile')
|
|
42
67
|
return unless gf.file?
|