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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e37f705dbc9ddf448634efe039bcc390d577c1683ad1759db58fdb1fbceee02
4
- data.tar.gz: 9dfa51f60c34241302354b5069f10ba560a61c51d4b70a1799893d785d2ae434
3
+ metadata.gz: b3d8286719ce164088136af4d1caf0476c026d4ec2e42b98b08db8c84df6766d
4
+ data.tar.gz: 849f3bb4ae14131b950de3a89efff6032a4204e9878a19c84cfe9dcdfa618e8b
5
5
  SHA512:
6
- metadata.gz: 0655d2c8e8f889892df7500cfadc663df079be3c7326815523b2fad12cacdce4eb729f859e145bfdc8c26f8ad0bc12bc2d5ee2dfd3acf14e71c82ffafd4057d3
7
- data.tar.gz: 93c6e9c5607b995d631060f511633bd94b3fa74f9db3c9adee3806b684a1ea7134b136bde12f9c8a3fa7d1da5f3ff76c95929c253251eb7daf3f1f9685bc5342
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', 'AppTemplates'
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', 'AppAssets'
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 = CloudflareWorkers::AutoAwait::Transformer.transform(source, nodes, buffer)
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 normalize_fragment(fragment)
46
+ def supported_yield_fragment?(fragment)
47
47
  stripped = fragment.strip
48
- return '__homura_template_yield__' if stripped == 'yield' || stripped == 'yield()'
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 = normalize_fragment(inner[2..].strip)
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 = normalize_fragment(inner[1..].strip)
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
- ruby << normalize_fragment(inner.strip) << "\n"
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?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloudflareWorkers
4
- VERSION = '0.1.4'
4
+ VERSION = '0.1.5'
5
5
  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.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuhiro NISHIYAMA