homura-runtime 0.3.8 → 0.3.9
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 +8 -0
- data/exe/auto-await +23 -23
- data/exe/compile-assets +68 -61
- data/exe/compile-erb +263 -255
- data/exe/homura-build +74 -21
- data/lib/homura/runtime/ai.rb +104 -85
- data/lib/homura/runtime/async_registry.rb +124 -109
- data/lib/homura/runtime/auto_await/analyzer.rb +28 -15
- data/lib/homura/runtime/auto_await/transformer.rb +1 -0
- data/lib/homura/runtime/build_support.rb +90 -11
- data/lib/homura/runtime/cache.rb +21 -18
- data/lib/homura/runtime/durable_object.rb +27 -17
- data/lib/homura/runtime/email.rb +14 -14
- data/lib/homura/runtime/http.rb +4 -3
- data/lib/homura/runtime/multipart.rb +11 -4
- data/lib/homura/runtime/queue.rb +53 -23
- data/lib/homura/runtime/scheduled.rb +12 -14
- data/lib/homura/runtime/stream.rb +18 -14
- data/lib/homura/runtime/version.rb +1 -1
- data/lib/homura/runtime.rb +129 -93
- data/lib/homura_vendor_tempfile.rb +4 -2
- data/lib/homura_vendor_tilt.rb +5 -3
- data/lib/homura_vendor_zlib.rb +3 -0
- data/lib/opal_patches.rb +83 -66
- metadata +1 -1
|
@@ -87,38 +87,43 @@ module HomuraRuntime
|
|
|
87
87
|
.each do |path|
|
|
88
88
|
next unless File.read(path).include?("register_async_source")
|
|
89
89
|
|
|
90
|
-
require_path =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
90
|
+
require_path = path.sub(Regexp.new("^#{Regexp.escape(lib_dir)}/"), "").sub(
|
|
91
|
+
/\.rb\z/,
|
|
92
|
+
""
|
|
93
|
+
)
|
|
95
94
|
begin
|
|
96
95
|
require require_path
|
|
96
|
+
|
|
97
97
|
loaded += 1
|
|
98
98
|
if debug
|
|
99
|
-
puts
|
|
99
|
+
puts("[auto-await] loaded async source from #{spec.name}: #{require_path}")
|
|
100
100
|
end
|
|
101
|
+
|
|
101
102
|
rescue LoadError, StandardError => e
|
|
102
103
|
if debug
|
|
103
|
-
warn
|
|
104
|
+
warn(
|
|
105
|
+
"[auto-await] Warning: failed to load async source from #{spec.name}/#{require_path}: #{e.message}"
|
|
106
|
+
)
|
|
104
107
|
end
|
|
105
108
|
end
|
|
106
109
|
end
|
|
107
110
|
end
|
|
108
111
|
|
|
109
112
|
if debug && loaded.positive?
|
|
110
|
-
puts
|
|
113
|
+
puts("[auto-await] auto-loaded #{loaded} async source file(s)")
|
|
111
114
|
end
|
|
112
115
|
end
|
|
113
116
|
end
|
|
114
117
|
|
|
115
|
-
attr_reader
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
attr_reader(
|
|
119
|
+
:async_classes,
|
|
120
|
+
:async_methods,
|
|
121
|
+
:async_factories,
|
|
122
|
+
:taint_returns,
|
|
123
|
+
:async_accessors,
|
|
124
|
+
:async_helpers,
|
|
125
|
+
:helper_factories
|
|
126
|
+
)
|
|
122
127
|
|
|
123
128
|
def initialize
|
|
124
129
|
@async_classes = {}
|
|
@@ -135,10 +140,12 @@ module HomuraRuntime
|
|
|
135
140
|
methods = @async_methods[class_name]
|
|
136
141
|
return true if methods&.include?(method_name)
|
|
137
142
|
except = @async_classes[class_name]
|
|
138
|
-
if except &&
|
|
139
|
-
|
|
143
|
+
if except &&
|
|
144
|
+
!except.include?(method_name.to_s) &&
|
|
145
|
+
!except.include?(method_name.to_sym)
|
|
140
146
|
return true
|
|
141
147
|
end
|
|
148
|
+
|
|
142
149
|
false
|
|
143
150
|
end
|
|
144
151
|
|
|
@@ -151,8 +158,10 @@ module HomuraRuntime
|
|
|
151
158
|
end
|
|
152
159
|
|
|
153
160
|
def tainted_class?(class_name)
|
|
154
|
-
@async_classes.key?(class_name) ||
|
|
155
|
-
@
|
|
161
|
+
@async_classes.key?(class_name) ||
|
|
162
|
+
@async_methods.key?(class_name) ||
|
|
163
|
+
@async_factories.key?(class_name) ||
|
|
164
|
+
@taint_returns.key?(class_name)
|
|
156
165
|
end
|
|
157
166
|
end
|
|
158
167
|
end
|
|
@@ -161,100 +170,106 @@ end
|
|
|
161
170
|
# Each binding declares which methods return Promises so the
|
|
162
171
|
# build-time analyzer can insert .__await__ automatically.
|
|
163
172
|
HomuraRuntime::AsyncRegistry.register_async_source do
|
|
164
|
-
async_method
|
|
165
|
-
async_method
|
|
166
|
-
async_method
|
|
167
|
-
async_method
|
|
168
|
-
taint_return
|
|
169
|
-
taint_return
|
|
170
|
-
|
|
171
|
-
async_method
|
|
172
|
-
async_method
|
|
173
|
-
async_method
|
|
173
|
+
async_method("Cloudflare::D1Database", :execute)
|
|
174
|
+
async_method("Cloudflare::D1Database", :get_first_row)
|
|
175
|
+
async_method("Cloudflare::D1Database", :execute_insert)
|
|
176
|
+
async_method("Cloudflare::D1Database", :execute_batch)
|
|
177
|
+
taint_return("Cloudflare::D1Database", :prepare, "Cloudflare::D1Statement")
|
|
178
|
+
taint_return("Cloudflare::D1Database", :[], "Cloudflare::D1Statement")
|
|
179
|
+
|
|
180
|
+
async_method("Cloudflare::D1Statement", :all)
|
|
181
|
+
async_method("Cloudflare::D1Statement", :first)
|
|
182
|
+
async_method("Cloudflare::D1Statement", :run)
|
|
174
183
|
# `bind` returns a new D1Statement for further chaining. Tainting the
|
|
175
184
|
# return preserves the type so the auto-await pass keeps chaining
|
|
176
185
|
# `.run` / `.all` / `.first` on the bound statement (otherwise
|
|
177
186
|
# `db.prepare(sql).bind(...).run` drops await on `.run` and
|
|
178
187
|
# `flatten_meta` ends up receiving a JS Promise instead of the
|
|
179
188
|
# metadata Hash).
|
|
180
|
-
taint_return
|
|
181
|
-
|
|
182
|
-
async_method
|
|
183
|
-
async_method
|
|
184
|
-
async_method
|
|
185
|
-
async_method
|
|
186
|
-
async_method
|
|
187
|
-
|
|
188
|
-
async_method
|
|
189
|
-
async_method
|
|
190
|
-
async_method
|
|
191
|
-
async_method
|
|
192
|
-
async_method
|
|
193
|
-
async_method
|
|
194
|
-
|
|
195
|
-
async_method
|
|
196
|
-
async_method
|
|
197
|
-
async_method
|
|
198
|
-
taint_return
|
|
199
|
-
async_method
|
|
200
|
-
async_method
|
|
201
|
-
async_method
|
|
202
|
-
async_method
|
|
203
|
-
async_method
|
|
204
|
-
async_method
|
|
205
|
-
async_method
|
|
206
|
-
taint_return
|
|
207
|
-
|
|
208
|
-
async_method
|
|
209
|
-
async_method
|
|
210
|
-
async_method
|
|
211
|
-
|
|
212
|
-
async_factory
|
|
213
|
-
async_method
|
|
214
|
-
|
|
215
|
-
async_method
|
|
216
|
-
async_method
|
|
217
|
-
|
|
218
|
-
async_factory
|
|
219
|
-
taint_return
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
async_method
|
|
236
|
-
async_method
|
|
237
|
-
async_method
|
|
238
|
-
async_method
|
|
239
|
-
async_method
|
|
240
|
-
|
|
241
|
-
async_method
|
|
242
|
-
|
|
243
|
-
async_method
|
|
244
|
-
async_method
|
|
245
|
-
async_method
|
|
246
|
-
|
|
247
|
-
async_method
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
helper_factory
|
|
257
|
-
helper_factory
|
|
258
|
-
helper_factory
|
|
259
|
-
helper_factory
|
|
189
|
+
taint_return("Cloudflare::D1Statement", :bind, "Cloudflare::D1Statement")
|
|
190
|
+
|
|
191
|
+
async_method("Cloudflare::KVNamespace", :get)
|
|
192
|
+
async_method("Cloudflare::KVNamespace", :get_with_metadata)
|
|
193
|
+
async_method("Cloudflare::KVNamespace", :put)
|
|
194
|
+
async_method("Cloudflare::KVNamespace", :delete)
|
|
195
|
+
async_method("Cloudflare::KVNamespace", :list)
|
|
196
|
+
|
|
197
|
+
async_method("Cloudflare::R2Bucket", :get)
|
|
198
|
+
async_method("Cloudflare::R2Bucket", :get_binary)
|
|
199
|
+
async_method("Cloudflare::R2Bucket", :put)
|
|
200
|
+
async_method("Cloudflare::R2Bucket", :delete)
|
|
201
|
+
async_method("Cloudflare::R2Bucket", :list)
|
|
202
|
+
async_method("Cloudflare::R2Bucket", :head)
|
|
203
|
+
|
|
204
|
+
async_method("Cloudflare::AI", :run)
|
|
205
|
+
async_method("Cloudflare::AI", :speak)
|
|
206
|
+
async_method("Cloudflare::AI", :speak_data_url)
|
|
207
|
+
taint_return("Cloudflare::AI", :run_stream, "Cloudflare::AI::Stream")
|
|
208
|
+
async_method("Cloudflare::AI::Binding", :run)
|
|
209
|
+
async_method("Cloudflare::AI::Binding", :chat)
|
|
210
|
+
async_method("Cloudflare::AI::Binding", :chat_text)
|
|
211
|
+
async_method("Cloudflare::AI::Binding", :transcribe)
|
|
212
|
+
async_method("Cloudflare::AI::Binding", :transcribe_text)
|
|
213
|
+
async_method("Cloudflare::AI::Binding", :speak)
|
|
214
|
+
async_method("Cloudflare::AI::Binding", :speak_data_url)
|
|
215
|
+
taint_return("Cloudflare::AI::Binding", :run_stream, "Cloudflare::AI::Stream")
|
|
216
|
+
|
|
217
|
+
async_method("Cloudflare::Cache", :match)
|
|
218
|
+
async_method("Cloudflare::Cache", :put)
|
|
219
|
+
async_method("Cloudflare::Cache", :delete)
|
|
220
|
+
|
|
221
|
+
async_factory("Cloudflare::Email", :new)
|
|
222
|
+
async_method("Cloudflare::Email", :send)
|
|
223
|
+
|
|
224
|
+
async_method("Cloudflare::Queue", :send)
|
|
225
|
+
async_method("Cloudflare::Queue", :send_batch)
|
|
226
|
+
|
|
227
|
+
async_factory("Cloudflare::DurableObjectNamespace", :new)
|
|
228
|
+
taint_return(
|
|
229
|
+
"Cloudflare::DurableObjectNamespace",
|
|
230
|
+
:get,
|
|
231
|
+
"Cloudflare::DurableObjectStub"
|
|
232
|
+
)
|
|
233
|
+
taint_return(
|
|
234
|
+
"Cloudflare::DurableObjectNamespace",
|
|
235
|
+
:get_by_name,
|
|
236
|
+
"Cloudflare::DurableObjectStub"
|
|
237
|
+
)
|
|
238
|
+
taint_return(
|
|
239
|
+
"Cloudflare::DurableObjectState",
|
|
240
|
+
:storage,
|
|
241
|
+
"Cloudflare::DurableObjectStorage"
|
|
242
|
+
)
|
|
243
|
+
async_method("Cloudflare::DurableObjectStub", :fetch)
|
|
244
|
+
async_method("Cloudflare::DurableObjectStub", :request)
|
|
245
|
+
async_method("Cloudflare::DurableObjectStub", :get)
|
|
246
|
+
async_method("Cloudflare::DurableObjectStub", :post)
|
|
247
|
+
async_method("Cloudflare::DurableObjectStub", :put)
|
|
248
|
+
async_method("Cloudflare::DurableObjectStub", :delete)
|
|
249
|
+
|
|
250
|
+
async_method("Cloudflare::DurableObjectStorage", :get)
|
|
251
|
+
async_method("Cloudflare::DurableObjectStorage", :put)
|
|
252
|
+
async_method("Cloudflare::DurableObjectStorage", :delete)
|
|
253
|
+
async_method("Cloudflare::DurableObjectStorage", :list)
|
|
254
|
+
async_method("Cloudflare::DurableObjectStorage", :transaction)
|
|
255
|
+
|
|
256
|
+
async_method("Cloudflare::HTTP", :fetch)
|
|
257
|
+
|
|
258
|
+
async_method("Faraday::Connection", :get)
|
|
259
|
+
async_method("Faraday::Connection", :post)
|
|
260
|
+
async_method("Faraday::Connection", :put)
|
|
261
|
+
async_method("Faraday::Connection", :delete)
|
|
262
|
+
async_method("Faraday::Connection", :patch)
|
|
263
|
+
async_method("Faraday::Connection", :head)
|
|
264
|
+
|
|
265
|
+
helper_factory(:d1, "Cloudflare::D1Database")
|
|
266
|
+
helper_factory(:db, "Cloudflare::D1Database")
|
|
267
|
+
helper_factory(:kv, "Cloudflare::KVNamespace")
|
|
268
|
+
helper_factory(:bucket, "Cloudflare::R2Bucket")
|
|
269
|
+
helper_factory(:ai, "Cloudflare::AI::Binding")
|
|
270
|
+
helper_factory(:send_email, "Cloudflare::Email")
|
|
271
|
+
helper_factory(:jobs_queue, "Cloudflare::Queue")
|
|
272
|
+
helper_factory(:jobs_dlq, "Cloudflare::Queue")
|
|
273
|
+
helper_factory(:do_counter, "Cloudflare::DurableObjectNamespace")
|
|
274
|
+
helper_factory(:durable_object, "Cloudflare::DurableObjectStub")
|
|
260
275
|
end
|
|
@@ -44,6 +44,7 @@ module HomuraRuntime
|
|
|
44
44
|
process_def(node)
|
|
45
45
|
return
|
|
46
46
|
end
|
|
47
|
+
|
|
47
48
|
if node.type == :block
|
|
48
49
|
process_block(node)
|
|
49
50
|
return
|
|
@@ -55,6 +56,7 @@ module HomuraRuntime
|
|
|
55
56
|
node.children.each do |child|
|
|
56
57
|
process_node(child) if child.is_a?(Parser::AST::Node)
|
|
57
58
|
end
|
|
59
|
+
|
|
58
60
|
case node.type
|
|
59
61
|
when :lvasgn
|
|
60
62
|
process_lvasgn(node)
|
|
@@ -96,7 +98,7 @@ module HomuraRuntime
|
|
|
96
98
|
@method_returns[method_name] = return_cls if return_cls
|
|
97
99
|
body_source = node.loc.expression&.source.to_s
|
|
98
100
|
if @await_nodes.length > before_awaits ||
|
|
99
|
-
|
|
101
|
+
body_source.include?(".__await__")
|
|
100
102
|
@async_local_methods << method_name
|
|
101
103
|
end
|
|
102
104
|
|
|
@@ -121,12 +123,13 @@ module HomuraRuntime
|
|
|
121
123
|
def process_send(node)
|
|
122
124
|
receiver, method_name = *node
|
|
123
125
|
if receiver.nil? &&
|
|
124
|
-
|
|
126
|
+
(factory_cls = @registry.helper_factories[method_name])
|
|
125
127
|
@env[method_name] = factory_cls
|
|
126
128
|
end
|
|
129
|
+
|
|
127
130
|
if should_await?(node)
|
|
128
131
|
@await_nodes << node
|
|
129
|
-
debug
|
|
132
|
+
debug("await target: #{node.loc.expression.source}")
|
|
130
133
|
end
|
|
131
134
|
end
|
|
132
135
|
|
|
@@ -153,8 +156,10 @@ module HomuraRuntime
|
|
|
153
156
|
if @method_returns.key?(method_name)
|
|
154
157
|
return @method_returns[method_name]
|
|
155
158
|
end
|
|
159
|
+
|
|
156
160
|
return @env[method_name] if @env.key?(method_name)
|
|
157
161
|
end
|
|
162
|
+
|
|
158
163
|
infer_send_class(node)
|
|
159
164
|
when :index
|
|
160
165
|
infer_index_class(node)
|
|
@@ -178,16 +183,17 @@ module HomuraRuntime
|
|
|
178
183
|
if method_name == :new && receiver&.type == :const
|
|
179
184
|
return const_path(receiver)
|
|
180
185
|
end
|
|
186
|
+
|
|
181
187
|
if receiver
|
|
182
188
|
if method_name == :[]
|
|
183
189
|
key_node = node.children[2]
|
|
184
190
|
if key_node&.type == :str
|
|
185
191
|
key = key_node.children[0]
|
|
186
|
-
mapped =
|
|
187
|
-
@registry.async_accessors[[env_name(receiver), key.to_sym]]
|
|
192
|
+
mapped = @registry.async_accessors[[env_name(receiver), key.to_sym]]
|
|
188
193
|
return mapped if mapped
|
|
189
194
|
end
|
|
190
195
|
end
|
|
196
|
+
|
|
191
197
|
accessor_cls = infer_env_accessor(receiver, method_name)
|
|
192
198
|
return accessor_cls if accessor_cls
|
|
193
199
|
recv_cls = infer_class(receiver)
|
|
@@ -202,6 +208,7 @@ module HomuraRuntime
|
|
|
202
208
|
return @method_returns[method_name]
|
|
203
209
|
end
|
|
204
210
|
end
|
|
211
|
+
|
|
205
212
|
nil
|
|
206
213
|
end
|
|
207
214
|
|
|
@@ -209,17 +216,18 @@ module HomuraRuntime
|
|
|
209
216
|
return {} unless durable_object_define_call?(call_node)
|
|
210
217
|
return {} unless args_node&.type == :args
|
|
211
218
|
|
|
212
|
-
arg_names =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
219
|
+
arg_names = args_node.children.filter_map do |arg|
|
|
220
|
+
next unless arg&.type == :arg
|
|
221
|
+
arg.children[0]
|
|
222
|
+
end
|
|
223
|
+
|
|
217
224
|
return {} if arg_names.empty?
|
|
218
225
|
|
|
219
|
-
bindings = {
|
|
220
|
-
|
|
221
|
-
arg_names[1]
|
|
222
|
-
|
|
226
|
+
bindings = {arg_names[0] => "Cloudflare::DurableObjectState"}
|
|
227
|
+
if arg_names.length > 1
|
|
228
|
+
bindings[arg_names[1]] = "Cloudflare::DurableObjectRequest"
|
|
229
|
+
end
|
|
230
|
+
|
|
223
231
|
bindings
|
|
224
232
|
end
|
|
225
233
|
|
|
@@ -244,6 +252,7 @@ module HomuraRuntime
|
|
|
244
252
|
mapped = @registry.async_accessors[[lvar, method_name.to_sym]]
|
|
245
253
|
return mapped if mapped
|
|
246
254
|
end
|
|
255
|
+
|
|
247
256
|
if receiver_node.type == :send
|
|
248
257
|
recv, meth = *receiver_node
|
|
249
258
|
if meth == :[] && env_node?(recv)
|
|
@@ -253,6 +262,7 @@ module HomuraRuntime
|
|
|
253
262
|
mapped = @registry.async_accessors[[env_name(recv), key.to_sym]]
|
|
254
263
|
return mapped if mapped
|
|
255
264
|
end
|
|
265
|
+
|
|
256
266
|
lvar = env_name(recv)
|
|
257
267
|
mapped = @registry.async_accessors[[lvar, method_name.to_sym]]
|
|
258
268
|
return mapped if mapped
|
|
@@ -262,6 +272,7 @@ module HomuraRuntime
|
|
|
262
272
|
mapped = @registry.async_accessors[[lvar, method_name.to_sym]]
|
|
263
273
|
return mapped if mapped
|
|
264
274
|
end
|
|
275
|
+
|
|
265
276
|
parent_cls = infer_env_accessor(recv, meth) if recv&.type == :send
|
|
266
277
|
return parent_cls if parent_cls
|
|
267
278
|
elsif receiver_node.type == :lvar
|
|
@@ -269,6 +280,7 @@ module HomuraRuntime
|
|
|
269
280
|
mapped = @registry.async_accessors[[lvar, method_name.to_sym]]
|
|
270
281
|
return mapped if mapped
|
|
271
282
|
end
|
|
283
|
+
|
|
272
284
|
nil
|
|
273
285
|
end
|
|
274
286
|
|
|
@@ -288,11 +300,12 @@ module HomuraRuntime
|
|
|
288
300
|
parts.unshift(n.children[1])
|
|
289
301
|
n = n.children[0]
|
|
290
302
|
end
|
|
303
|
+
|
|
291
304
|
parts.join("::")
|
|
292
305
|
end
|
|
293
306
|
|
|
294
307
|
def debug(msg)
|
|
295
|
-
puts
|
|
308
|
+
puts("[auto-await] #{msg}") if @debug
|
|
296
309
|
end
|
|
297
310
|
end
|
|
298
311
|
end
|
|
@@ -8,6 +8,11 @@ module HomuraRuntime
|
|
|
8
8
|
RUNTIME_GEM_NAME = "homura-runtime"
|
|
9
9
|
SINATRA_GEM_NAME = "sinatra-homura"
|
|
10
10
|
SEQUEL_D1_GEM_NAME = "sequel-d1"
|
|
11
|
+
SUPPORTED_OPAL_ENTRY_GEMS = %w[phlex literal].freeze
|
|
12
|
+
OPAL_ENTRY_COMPAT_REQUIRES = {
|
|
13
|
+
"phlex" => "phlex/opal_compat",
|
|
14
|
+
"literal" => "literal/opal_compat"
|
|
15
|
+
}.freeze
|
|
11
16
|
|
|
12
17
|
class << self
|
|
13
18
|
def loaded_spec(name, loaded_specs: Gem.loaded_specs)
|
|
@@ -66,7 +71,8 @@ module HomuraRuntime
|
|
|
66
71
|
runtime_root(
|
|
67
72
|
current_file: current_file,
|
|
68
73
|
loaded_specs: loaded_specs
|
|
69
|
-
)
|
|
74
|
+
)
|
|
75
|
+
.join("runtime", *names)
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
def ensure_standalone_runtime(
|
|
@@ -137,8 +143,7 @@ module HomuraRuntime
|
|
|
137
143
|
# app code.
|
|
138
144
|
opal_gem_paths(root, loaded_specs: loaded_specs).each do |gem_path|
|
|
139
145
|
basename = gem_path.basename.to_s
|
|
140
|
-
rewritten_lib =
|
|
141
|
-
root.join("build", "auto_await", "gem_#{basename}", "lib")
|
|
146
|
+
rewritten_lib = root.join("build", "auto_await", "gem_#{basename}", "lib")
|
|
142
147
|
load_paths << rewritten_lib.to_s if rewritten_lib.directory?
|
|
143
148
|
%w[lib vendor].each do |sub|
|
|
144
149
|
dir = gem_path.join(sub)
|
|
@@ -165,12 +170,9 @@ module HomuraRuntime
|
|
|
165
170
|
return unless gf.file?
|
|
166
171
|
|
|
167
172
|
txt = gf.read
|
|
168
|
-
unless (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
/#{Regexp.escape(RUNTIME_GEM_NAME)}['"]\s*,\s*path:\s*['"]([^'"]+)['"]/
|
|
172
|
-
)
|
|
173
|
-
)
|
|
173
|
+
unless (m = txt.match(
|
|
174
|
+
/#{Regexp.escape(RUNTIME_GEM_NAME)}['"]\s*,\s*path:\s*['"]([^'"]+)['"]/
|
|
175
|
+
))
|
|
174
176
|
return
|
|
175
177
|
end
|
|
176
178
|
|
|
@@ -190,6 +192,10 @@ module HomuraRuntime
|
|
|
190
192
|
out = []
|
|
191
193
|
out.concat(path_gemfile_entries(project_root))
|
|
192
194
|
|
|
195
|
+
entry_specs = opal_entry_gem_specs(loaded_specs: loaded_specs)
|
|
196
|
+
out.concat(entry_specs.map { |spec| Pathname(spec.full_gem_path) if spec.full_gem_path }.compact)
|
|
197
|
+
out.concat(opal_dependency_paths(entry_specs, loaded_specs: loaded_specs))
|
|
198
|
+
|
|
193
199
|
loaded_specs.each_value do |spec|
|
|
194
200
|
next if wired.include?(spec.name)
|
|
195
201
|
meta = spec.metadata
|
|
@@ -204,6 +210,78 @@ module HomuraRuntime
|
|
|
204
210
|
out.uniq
|
|
205
211
|
end
|
|
206
212
|
|
|
213
|
+
def opal_entry_gem_specs(loaded_specs: Gem.loaded_specs)
|
|
214
|
+
names = SUPPORTED_OPAL_ENTRY_GEMS + env_opal_gem_names
|
|
215
|
+
names.uniq.filter_map { |name| loaded_specs[name] }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def opal_dependency_paths(specs, loaded_specs: Gem.loaded_specs)
|
|
219
|
+
seen = {}
|
|
220
|
+
out = []
|
|
221
|
+
queue = specs.flat_map { |spec| spec.runtime_dependencies.map(&:name) }
|
|
222
|
+
|
|
223
|
+
until queue.empty?
|
|
224
|
+
name = queue.shift
|
|
225
|
+
next if seen[name]
|
|
226
|
+
seen[name] = true
|
|
227
|
+
|
|
228
|
+
spec = loaded_specs[name]
|
|
229
|
+
next unless spec&.full_gem_path
|
|
230
|
+
|
|
231
|
+
path = Pathname(spec.full_gem_path)
|
|
232
|
+
out << path if path.directory?
|
|
233
|
+
queue.concat(spec.runtime_dependencies.map(&:name))
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
out
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def write_opal_gems_prelude(project_root, loaded_specs: Gem.loaded_specs)
|
|
240
|
+
entry_specs = opal_entry_gem_specs(loaded_specs: loaded_specs)
|
|
241
|
+
return nil if entry_specs.empty?
|
|
242
|
+
|
|
243
|
+
root = Pathname(project_root)
|
|
244
|
+
out = root.join("build", "homura_opal_gems.rb")
|
|
245
|
+
lines = ["# frozen_string_literal: true", ""]
|
|
246
|
+
entry_specs.each do |spec|
|
|
247
|
+
next unless spec.full_gem_path
|
|
248
|
+
|
|
249
|
+
lib = Pathname(spec.full_gem_path).join("lib")
|
|
250
|
+
next unless lib.directory?
|
|
251
|
+
|
|
252
|
+
lines << "require_tree #{lib.to_s.inspect}, autoload: true"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
lines << ""
|
|
256
|
+
lines << "require \"zeitwerk/opal_compat\""
|
|
257
|
+
entry_specs.each do |spec|
|
|
258
|
+
compat = OPAL_ENTRY_COMPAT_REQUIRES[spec.name]
|
|
259
|
+
next unless compat && spec.full_gem_path
|
|
260
|
+
|
|
261
|
+
lib = Pathname(spec.full_gem_path).join("lib")
|
|
262
|
+
root_file = lib.join("#{spec.name}.rb")
|
|
263
|
+
next unless root_file.file?
|
|
264
|
+
|
|
265
|
+
root_require = root_file.to_s.delete_suffix(".rb")
|
|
266
|
+
lines << "Zeitwerk.__homura_next_gem_root = #{root_file.to_s.inspect}"
|
|
267
|
+
lines << "require #{root_require.inspect}"
|
|
268
|
+
lines << "`Opal.loaded([#{spec.name.inspect}])`"
|
|
269
|
+
lines << "require #{compat.inspect}"
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
FileUtils.mkdir_p(out.dirname)
|
|
273
|
+
File.write(out, lines.join("\n") << "\n")
|
|
274
|
+
out
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def env_opal_gem_names(env = ENV)
|
|
278
|
+
env
|
|
279
|
+
.fetch("HOMURA_OPAL_GEMS", "")
|
|
280
|
+
.split(/[\s,]+/)
|
|
281
|
+
.map(&:strip)
|
|
282
|
+
.reject(&:empty?)
|
|
283
|
+
end
|
|
284
|
+
|
|
207
285
|
# Returns absolute Pathnames for every `path:`-declared gem in the
|
|
208
286
|
# project's Gemfile that should ship in the Workers bundle.
|
|
209
287
|
#
|
|
@@ -235,11 +313,11 @@ module HomuraRuntime
|
|
|
235
313
|
next if stripped.empty? || stripped.start_with?("#")
|
|
236
314
|
|
|
237
315
|
if (m = stripped.match(/\Agroup\s+(.+?)\s+do\b/))
|
|
238
|
-
groups =
|
|
239
|
-
m[1].scan(/[:'"]([A-Za-z0-9_]+)['"]?/).flatten.map(&:to_sym)
|
|
316
|
+
groups = m[1].scan(/[:'"]([A-Za-z0-9_]+)['"]?/).flatten.map(&:to_sym)
|
|
240
317
|
group_stack.push(groups)
|
|
241
318
|
next
|
|
242
319
|
end
|
|
320
|
+
|
|
243
321
|
if stripped == "end"
|
|
244
322
|
group_stack.pop unless group_stack.empty?
|
|
245
323
|
next
|
|
@@ -256,6 +334,7 @@ module HomuraRuntime
|
|
|
256
334
|
gem_path = Pathname.new(rel).expand_path(project_root)
|
|
257
335
|
out << gem_path if gem_path.directory?
|
|
258
336
|
end
|
|
337
|
+
|
|
259
338
|
out.uniq
|
|
260
339
|
end
|
|
261
340
|
end
|