capybara-simulated 0.0.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 +7 -0
- data/LICENSE +20 -0
- data/README.md +225 -0
- data/lib/capybara/simulated/browser.rb +1012 -0
- data/lib/capybara/simulated/driver.rb +191 -0
- data/lib/capybara/simulated/errors.rb +9 -0
- data/lib/capybara/simulated/node.rb +235 -0
- data/lib/capybara/simulated/version.rb +5 -0
- data/lib/capybara/simulated.rb +10 -0
- data/lib/capybara-simulated.rb +1 -0
- data/vendor/esbuild-wasm/LICENSE.md +21 -0
- data/vendor/esbuild-wasm/bin/esbuild +91 -0
- data/vendor/esbuild-wasm/esbuild.wasm +0 -0
- data/vendor/esbuild-wasm/lib/main.js +2337 -0
- data/vendor/esbuild-wasm/wasm_exec.js +575 -0
- data/vendor/esbuild-wasm/wasm_exec_node.js +40 -0
- data/vendor/js/bundle-modules.mjs +168 -0
- data/vendor/js/csim.bundle.js +101015 -0
- data/vendor/js/entry.mjs +8 -0
- data/vendor/js/prelude.js +186 -0
- data/vendor/js/runtime.js +2054 -0
- metadata +106 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
require 'capybara/driver/base'
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Simulated
|
|
5
|
+
class Driver < Capybara::Driver::Base
|
|
6
|
+
DEFAULT_WINDOW_HANDLE = 'main'.freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :app
|
|
9
|
+
|
|
10
|
+
def initialize(app)
|
|
11
|
+
@app = app
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def browser
|
|
15
|
+
@browser ||= begin
|
|
16
|
+
b = Browser.new(app)
|
|
17
|
+
b.driver_for_results = self
|
|
18
|
+
b
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def needs_server? = false
|
|
23
|
+
def javascript_enabled? = true
|
|
24
|
+
# Capybara's synchronize loop will retry on ElementNotFound only
|
|
25
|
+
# when the driver opts into waiting. Even though our DOM updates
|
|
26
|
+
# happen in-process, async setTimeout-style behaviours need
|
|
27
|
+
# Capybara to give them time.
|
|
28
|
+
def wait? = true
|
|
29
|
+
|
|
30
|
+
def visit(path) = browser.visit(path)
|
|
31
|
+
def refresh = browser.refresh
|
|
32
|
+
def go_back = browser.go_back
|
|
33
|
+
def go_forward = browser.go_forward
|
|
34
|
+
def current_url = browser.current_url
|
|
35
|
+
def html = browser.html
|
|
36
|
+
def title = browser.title
|
|
37
|
+
def status_code = browser.status_code
|
|
38
|
+
def response_headers = browser.response_headers || {}
|
|
39
|
+
|
|
40
|
+
def find_xpath(query, **_)
|
|
41
|
+
browser.find_xpath(query).map {|id| Node.new(self, id) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_css(query, **_)
|
|
45
|
+
browser.find_css(query).map {|id| Node.new(self, id) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def execute_script(script, *args)
|
|
49
|
+
browser.execute_script(script, args)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def evaluate_script(script, *args)
|
|
53
|
+
browser.evaluate_script(script, args)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def evaluate_async_script(script, *args)
|
|
57
|
+
browser.evaluate_async_script(script, args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def send_keys(*keys)
|
|
61
|
+
# Capybara calls session-level `send_keys` to drive global key
|
|
62
|
+
# navigation (typically `:tab`). Even when nothing has been
|
|
63
|
+
# focused yet, `<body>` is a valid sink so Tab handling can
|
|
64
|
+
# advance to the first focusable descendant.
|
|
65
|
+
handle_id = browser.active_element || browser.find_css('body').first
|
|
66
|
+
return unless handle_id
|
|
67
|
+
browser.send_keys(handle_id, keys)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def active_element
|
|
71
|
+
handle_id = browser.active_element
|
|
72
|
+
Node.new(self, handle_id) if handle_id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def save_screenshot(path, **_options)
|
|
76
|
+
File.write(path, html)
|
|
77
|
+
path
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def reset!
|
|
81
|
+
return unless @browser
|
|
82
|
+
@browser.reset_state!
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def invalid_element_errors
|
|
86
|
+
[Capybara::Simulated::StaleElementReferenceError]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def no_such_window_error
|
|
90
|
+
Capybara::WindowError
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def current_window_handle = DEFAULT_WINDOW_HANDLE
|
|
94
|
+
def window_handles = [DEFAULT_WINDOW_HANDLE]
|
|
95
|
+
def open_new_window
|
|
96
|
+
raise NotImplementedError, 'capybara-simulated supports a single window'
|
|
97
|
+
end
|
|
98
|
+
def close_window(_handle)
|
|
99
|
+
raise NotImplementedError, 'capybara-simulated supports a single window'
|
|
100
|
+
end
|
|
101
|
+
def switch_to_window(handle)
|
|
102
|
+
return if handle == DEFAULT_WINDOW_HANDLE || handle.respond_to?(:handle) && handle.handle == DEFAULT_WINDOW_HANDLE
|
|
103
|
+
raise Capybara::WindowError, "no such window: #{handle.inspect}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def window_size(_handle) = [1024, 768]
|
|
107
|
+
def resize_window_to(_h, _w, _h2); end
|
|
108
|
+
def maximize_window(_handle); end
|
|
109
|
+
def fullscreen_window(_handle); end
|
|
110
|
+
|
|
111
|
+
def switch_to_frame(_frame)
|
|
112
|
+
raise NotImplementedError, 'frames are not supported by capybara-simulated'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def frame_title
|
|
116
|
+
browser.title
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def frame_url
|
|
120
|
+
browser.current_url
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def accept_modal(type, **options, &blk)
|
|
124
|
+
push_modal_handler(type, options, accept: true)
|
|
125
|
+
wait_for_modal(type, options, &blk)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def dismiss_modal(type, **options, &blk)
|
|
129
|
+
push_modal_handler(type, options, accept: false)
|
|
130
|
+
wait_for_modal(type, options, &blk)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# Modals fired from `setTimeout` callbacks don't appear synchronously
|
|
136
|
+
# when the block runs `click_link`. Advance the virtual clock in
|
|
137
|
+
# short slices until either a matching modal arrives or the wait
|
|
138
|
+
# budget runs out, matching Capybara's `accept_modal` semantics for
|
|
139
|
+
# asynchronous alerts.
|
|
140
|
+
MODAL_POLL_STEP_MS = 50
|
|
141
|
+
def wait_for_modal(type, options, &blk)
|
|
142
|
+
blk.call if blk
|
|
143
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) +
|
|
144
|
+
(options[:wait] || Capybara.default_max_wait_time || 2).to_f
|
|
145
|
+
text_matcher = options[:text]
|
|
146
|
+
loop do
|
|
147
|
+
browser.modal_inbox.concat(browser.drain_modal_queue)
|
|
148
|
+
match = browser.modal_inbox.find {|m|
|
|
149
|
+
m['type'].to_s == type.to_s && modal_text_matches?(m['message'], text_matcher)
|
|
150
|
+
}
|
|
151
|
+
if match
|
|
152
|
+
browser.modal_inbox.delete(match)
|
|
153
|
+
pop_modal_handler(type, options)
|
|
154
|
+
return match['message']
|
|
155
|
+
end
|
|
156
|
+
break if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
157
|
+
browser.advance_virtual_clock_step(MODAL_POLL_STEP_MS)
|
|
158
|
+
end
|
|
159
|
+
pop_modal_handler(type, options)
|
|
160
|
+
raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with text matching #{text_matcher.inspect}" if text_matcher}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def modal_text_matches?(message, matcher)
|
|
164
|
+
return true if matcher.nil?
|
|
165
|
+
case matcher
|
|
166
|
+
when Regexp then matcher.match?(message.to_s)
|
|
167
|
+
else message.to_s.include?(matcher.to_s)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Modal handlers stack in registration order; the JS modal stub
|
|
172
|
+
# picks the first whose text predicate matches the firing message.
|
|
173
|
+
# Required for nested `dismiss_confirm { accept_confirm { ... } }`
|
|
174
|
+
# blocks where two confirms fire in one synchronous click handler.
|
|
175
|
+
def push_modal_handler(type, options, accept:)
|
|
176
|
+
response = case type
|
|
177
|
+
when :alert then true
|
|
178
|
+
when :confirm then accept
|
|
179
|
+
when :prompt then accept ? options[:with] : false
|
|
180
|
+
end
|
|
181
|
+
browser.add_modal_handler(type: type.to_s, text: options[:text], response: response)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def pop_modal_handler(type, options)
|
|
185
|
+
browser.remove_modal_handler(type: type.to_s, text: options[:text])
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
public
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Capybara
|
|
2
|
+
module Simulated
|
|
3
|
+
# Raised by the runtime when a Ruby caller hands back a handle id that
|
|
4
|
+
# no longer maps to a live DOM node — usually because the page was
|
|
5
|
+
# reloaded between the find and the action. Capybara's synchronize
|
|
6
|
+
# block catches these via the driver's `invalid_element_errors`.
|
|
7
|
+
class StaleElementReferenceError < StandardError; end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
require 'capybara/driver/node'
|
|
2
|
+
require 'capybara/node/whitespace_normalizer'
|
|
3
|
+
|
|
4
|
+
module Capybara
|
|
5
|
+
module Simulated
|
|
6
|
+
class Node < Capybara::Driver::Node
|
|
7
|
+
include Capybara::Node::WhitespaceNormalizer
|
|
8
|
+
|
|
9
|
+
def handle_id = native
|
|
10
|
+
|
|
11
|
+
def all_text
|
|
12
|
+
normalize_spacing(browser.all_text(handle_id))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def visible_text
|
|
16
|
+
normalize_visible_spacing(browser.visible_text(handle_id))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def [](name)
|
|
20
|
+
case name.to_s
|
|
21
|
+
when 'value' then value
|
|
22
|
+
when 'checked' then checked? ? 'true' : nil
|
|
23
|
+
when 'selected' then selected? ? 'true' : nil
|
|
24
|
+
else browser.attr(handle_id, name)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def value
|
|
29
|
+
browser.value(handle_id)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def set(value, **_opts)
|
|
33
|
+
return if disabled?
|
|
34
|
+
# readonly is meaningless on checkbox/radio; the HTML spec ignores
|
|
35
|
+
# it for those types, and Capybara's specs explicitly assert that.
|
|
36
|
+
type = (self[:type] || tag_name || '').to_s.downcase
|
|
37
|
+
return if readonly? && type != 'checkbox' && type != 'radio'
|
|
38
|
+
browser.set_value(handle_id, value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def select_option
|
|
42
|
+
return if disabled?
|
|
43
|
+
browser.select_option(handle_id)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def unselect_option
|
|
47
|
+
raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node_multiple?
|
|
48
|
+
browser.unselect_option(handle_id)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def tag_name
|
|
52
|
+
browser.tag_name(handle_id)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def visible?
|
|
56
|
+
browser.visible?(handle_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def obscured?
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def checked?
|
|
64
|
+
browser.checked?(handle_id)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def selected?
|
|
68
|
+
browser.selected?(handle_id)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def disabled?
|
|
72
|
+
browser.disabled?(handle_id)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def readonly?
|
|
76
|
+
browser.readonly?(handle_id)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def multiple?
|
|
80
|
+
browser.multiple?(handle_id)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def click(keys = [], **opts)
|
|
84
|
+
browser.click(handle_id, click_options(keys, opts))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def right_click(keys = [], **opts)
|
|
88
|
+
browser.right_click(handle_id, click_options(keys, opts))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def double_click(keys = [], **opts)
|
|
92
|
+
browser.double_click(handle_id, click_options(keys, opts))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# x/y from Capybara are *offsets* — from top-left when
|
|
98
|
+
# Capybara.w3c_click_offset is false, from center when true. The
|
|
99
|
+
# runtime reads `simRect` to translate these into absolute
|
|
100
|
+
# clientX/clientY before dispatching the mouse events. Defaults
|
|
101
|
+
# (no x/y) target the element's center.
|
|
102
|
+
def click_options(keys, opts)
|
|
103
|
+
out = modifier_options(keys)
|
|
104
|
+
if opts.key?(:x) || opts.key?(:y)
|
|
105
|
+
out['offsetX'] = opts[:x].to_f if opts.key?(:x)
|
|
106
|
+
out['offsetY'] = opts[:y].to_f if opts.key?(:y)
|
|
107
|
+
out['w3cOffset'] = !!Capybara.w3c_click_offset
|
|
108
|
+
end
|
|
109
|
+
out['delay'] = opts[:delay].to_f if opts.key?(:delay) && opts[:delay].to_f.positive?
|
|
110
|
+
out
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def modifier_options(keys)
|
|
114
|
+
opts = {}
|
|
115
|
+
Array(keys).each do |k|
|
|
116
|
+
case k
|
|
117
|
+
when :shift then opts['shiftKey'] = true
|
|
118
|
+
when :ctrl, :control then opts['ctrlKey'] = true
|
|
119
|
+
when :alt then opts['altKey'] = true
|
|
120
|
+
when :meta, :command then opts['metaKey'] = true
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
opts
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
public
|
|
127
|
+
|
|
128
|
+
def hover
|
|
129
|
+
browser.hover(handle_id)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def trigger(event)
|
|
133
|
+
browser.trigger(handle_id, event)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def send_keys(*keys)
|
|
137
|
+
browser.send_keys(handle_id, keys)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def drag_to(other, **_opts)
|
|
141
|
+
browser.trigger(handle_id, 'dragstart')
|
|
142
|
+
browser.trigger(other.handle_id, 'dragenter')
|
|
143
|
+
browser.trigger(other.handle_id, 'dragover')
|
|
144
|
+
browser.trigger(other.handle_id, 'drop')
|
|
145
|
+
browser.trigger(handle_id, 'dragend')
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def drop(*args)
|
|
149
|
+
items = args.flat_map {|arg| drop_items_for(arg) }
|
|
150
|
+
browser.drop(handle_id, items)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def scroll_by(_x, _y); end
|
|
154
|
+
def scroll_to(_element, _alignment, _position = nil); end
|
|
155
|
+
|
|
156
|
+
def submit
|
|
157
|
+
browser.submit(handle_id)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def find_xpath(xpath)
|
|
161
|
+
browser.find_xpath(xpath, handle_id).map {|id| self.class.new(driver, id) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def find_css(css)
|
|
165
|
+
browser.find_css(css, handle_id).map {|id| self.class.new(driver, id) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def rect
|
|
169
|
+
browser.rect(handle_id)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def path
|
|
173
|
+
browser.path(handle_id)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def shadow_root
|
|
177
|
+
id = browser.shadow_root(handle_id)
|
|
178
|
+
id ? Node.new(driver, id) : nil
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def style(_styles)
|
|
182
|
+
raise NotImplementedError, 'The simulated driver does not process CSS'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def ==(other)
|
|
186
|
+
other.is_a?(Node) && other.handle_id == handle_id
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def inspect
|
|
190
|
+
%(#<Capybara::Simulated::Node tag="#{tag_name}" id=#{handle_id}>)
|
|
191
|
+
rescue StandardError
|
|
192
|
+
super
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def drop_items_for(arg)
|
|
198
|
+
case arg
|
|
199
|
+
when Hash
|
|
200
|
+
arg.map {|type, data|
|
|
201
|
+
{'kind' => 'string', 'type' => type.to_s, 'data' => data.to_s}
|
|
202
|
+
}
|
|
203
|
+
when String
|
|
204
|
+
[{'kind' => 'file', 'name' => File.basename(arg), 'type' => '', 'contents' => safely_read(arg)}]
|
|
205
|
+
else
|
|
206
|
+
[]
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def safely_read(path)
|
|
211
|
+
File.binread(path)
|
|
212
|
+
rescue StandardError
|
|
213
|
+
''
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def browser = driver.browser
|
|
217
|
+
|
|
218
|
+
def select_node_multiple?
|
|
219
|
+
browser.evaluate_script(<<~JS)
|
|
220
|
+
(() => {
|
|
221
|
+
const el = (#{select_lookup_js})
|
|
222
|
+
const sel = el.closest('select');
|
|
223
|
+
return !!(sel && sel.multiple);
|
|
224
|
+
})()
|
|
225
|
+
JS
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def select_lookup_js
|
|
229
|
+
# The JS-side handles map is private; use evaluate via a tiny accessor.
|
|
230
|
+
# Cheaper: ask browser directly through path/tag inspection.
|
|
231
|
+
"document.evaluate(#{path.inspect}, document, null, 9, null).singleNodeValue"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'capybara'
|
|
2
|
+
require 'capybara/simulated/version'
|
|
3
|
+
require 'capybara/simulated/errors'
|
|
4
|
+
require 'capybara/simulated/browser'
|
|
5
|
+
require 'capybara/simulated/driver'
|
|
6
|
+
require 'capybara/simulated/node'
|
|
7
|
+
|
|
8
|
+
Capybara.register_driver :simulated do |app|
|
|
9
|
+
Capybara::Simulated::Driver.new(app)
|
|
10
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'capybara/simulated'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Evan Wallace
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Forward to the automatically-generated WebAssembly loader from the Go compiler
|
|
4
|
+
|
|
5
|
+
const module_ = require('module');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
|
|
9
|
+
const wasm_exec_node = path.join(__dirname, '..', 'wasm_exec_node.js');
|
|
10
|
+
const esbuild_wasm = path.join(__dirname, '..', 'esbuild.wasm');
|
|
11
|
+
|
|
12
|
+
const code = fs.readFileSync(wasm_exec_node, 'utf8');
|
|
13
|
+
const wrapper = new Function('require', 'WebAssembly', code);
|
|
14
|
+
|
|
15
|
+
function instantiate(bytes, importObject) {
|
|
16
|
+
// Using this API causes "./esbuild --version" to run around 1 second faster
|
|
17
|
+
// than using the "WebAssembly.instantiate()" API when run in node (v12.16.2)
|
|
18
|
+
const module = new WebAssembly.Module(bytes);
|
|
19
|
+
const instance = new WebAssembly.Instance(module, importObject);
|
|
20
|
+
return Promise.resolve({ instance, module });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Node has another bug where using "fs.read" to read from stdin reads
|
|
24
|
+
// everything successfully and then throws an error, but only on Windows. Go's
|
|
25
|
+
// WebAssembly support uses "fs.read" so it hits this problem. This is a patch
|
|
26
|
+
// to try to work around the bug in node. This bug has been reported to node
|
|
27
|
+
// at least twice in https://github.com/nodejs/node/issues/35997 and in
|
|
28
|
+
// https://github.com/nodejs/node/issues/19831. This issue has also been
|
|
29
|
+
// reported to the Go project: https://github.com/golang/go/issues/43913.
|
|
30
|
+
const read = fs.read;
|
|
31
|
+
fs.read = function () {
|
|
32
|
+
const callback = arguments[5];
|
|
33
|
+
arguments[5] = function (err, count) {
|
|
34
|
+
if (count === 0 && err && err.code === 'EOF') {
|
|
35
|
+
arguments[0] = null;
|
|
36
|
+
}
|
|
37
|
+
return callback.apply(this, arguments);
|
|
38
|
+
};
|
|
39
|
+
return read.apply(this, arguments);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Hack around a Unicode bug in node: https://github.com/nodejs/node/issues/24550.
|
|
43
|
+
// See this for the matching Go issue: https://github.com/golang/go/issues/43917.
|
|
44
|
+
const write = fs.write;
|
|
45
|
+
fs.write = function (fd, buf, offset, length, position, callback) {
|
|
46
|
+
if (offset === 0 && length === buf.length && position === null) {
|
|
47
|
+
if (fd === process.stdout.fd) {
|
|
48
|
+
try {
|
|
49
|
+
process.stdout.write(buf, err => err ? callback(err, 0, null) : callback(null, length, buf));
|
|
50
|
+
} catch (err) {
|
|
51
|
+
callback(err, 0, null);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (fd === process.stderr.fd) {
|
|
56
|
+
try {
|
|
57
|
+
process.stderr.write(buf, err => err ? callback(err, 0, null) : callback(null, length, buf));
|
|
58
|
+
} catch (err) {
|
|
59
|
+
callback(err, 0, null);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return write.apply(this, arguments);
|
|
65
|
+
};
|
|
66
|
+
const writeSync = fs.writeSync;
|
|
67
|
+
fs.writeSync = function (fd, buf) {
|
|
68
|
+
if (fd === process.stdout.fd) return process.stdout.write(buf), buf.length;
|
|
69
|
+
if (fd === process.stderr.fd) return process.stderr.write(buf), buf.length;
|
|
70
|
+
return writeSync.apply(this, arguments);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// WASM code generated with Go 1.17.2+ will crash when run in a situation with
|
|
74
|
+
// many environment variables: https://github.com/golang/go/issues/49011. An
|
|
75
|
+
// example of this situation is running a Go-compiled WASM executable in GitHub
|
|
76
|
+
// Actions. Work around this by filtering node's copy of environment variables
|
|
77
|
+
// down to only include the environment variables that esbuild currently uses.
|
|
78
|
+
const esbuildUsedEnvVars = [
|
|
79
|
+
'NO_COLOR',
|
|
80
|
+
'NODE_PATH',
|
|
81
|
+
'npm_config_user_agent',
|
|
82
|
+
'WT_SESSION',
|
|
83
|
+
]
|
|
84
|
+
for (let key in process.env) {
|
|
85
|
+
if (esbuildUsedEnvVars.indexOf(key) < 0) {
|
|
86
|
+
delete process.env[key]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
process.argv.splice(2, 0, esbuild_wasm);
|
|
91
|
+
wrapper(module_.createRequire(wasm_exec_node), Object.assign(Object.create(WebAssembly), { instantiate }));
|
|
Binary file
|