opal 1.6.1 → 1.7.0

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.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +17 -0
  3. data/CHANGELOG.md +35 -1
  4. data/Gemfile +1 -0
  5. data/HACKING.md +47 -26
  6. data/benchmark/benchmarks +415 -103
  7. data/benchmark/bm_call_overhead.yml +28 -0
  8. data/benchmark/run.rb +61 -40
  9. data/docs/cdp_common.json +3364 -0
  10. data/docs/cdp_common.md +18 -0
  11. data/docs/{headless_chrome.md → headless_browsers.md} +31 -12
  12. data/lib/opal/ast/builder.rb +1 -1
  13. data/lib/opal/builder.rb +6 -1
  14. data/lib/opal/builder_processors.rb +5 -3
  15. data/lib/opal/cache.rb +1 -7
  16. data/lib/opal/cli_options.rb +72 -58
  17. data/lib/opal/cli_runners/chrome.rb +47 -9
  18. data/lib/opal/cli_runners/chrome_cdp_interface.rb +238 -112
  19. data/lib/opal/cli_runners/compiler.rb +146 -13
  20. data/lib/opal/cli_runners/deno.rb +32 -0
  21. data/lib/opal/cli_runners/firefox.rb +350 -0
  22. data/lib/opal/cli_runners/firefox_cdp_interface.rb +212 -0
  23. data/lib/opal/cli_runners/node_modules/.bin/chrome-remote-interface.cmd +17 -0
  24. data/lib/opal/cli_runners/node_modules/.bin/chrome-remote-interface.ps1 +28 -0
  25. data/lib/opal/cli_runners/node_modules/.package-lock.json +41 -0
  26. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/LICENSE +1 -1
  27. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/README.md +322 -182
  28. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/bin/client.js +99 -114
  29. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/chrome-remote-interface.js +1 -11
  30. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/index.js +16 -11
  31. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/api.js +41 -33
  32. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/chrome.js +224 -214
  33. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/devtools.js +71 -191
  34. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/external-request.js +26 -6
  35. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/protocol.json +20788 -9049
  36. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/websocket-wrapper.js +10 -3
  37. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/package.json +59 -123
  38. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/webpack.config.js +25 -32
  39. data/lib/opal/cli_runners/node_modules/commander/History.md +298 -0
  40. data/lib/opal/cli_runners/node_modules/commander/LICENSE +22 -0
  41. data/lib/opal/cli_runners/node_modules/commander/Readme.md +217 -61
  42. data/lib/opal/cli_runners/node_modules/commander/index.js +431 -145
  43. data/lib/opal/cli_runners/node_modules/commander/package.json +16 -79
  44. data/lib/opal/cli_runners/node_modules/ws/README.md +334 -98
  45. data/lib/opal/cli_runners/node_modules/ws/browser.js +8 -0
  46. data/lib/opal/cli_runners/node_modules/ws/index.js +5 -10
  47. data/lib/opal/cli_runners/node_modules/ws/lib/buffer-util.js +129 -0
  48. data/lib/opal/cli_runners/node_modules/ws/lib/constants.js +10 -0
  49. data/lib/opal/cli_runners/node_modules/ws/lib/event-target.js +184 -0
  50. data/lib/opal/cli_runners/node_modules/ws/lib/extension.js +223 -0
  51. data/lib/opal/cli_runners/node_modules/ws/lib/limiter.js +55 -0
  52. data/lib/opal/cli_runners/node_modules/ws/lib/permessage-deflate.js +518 -0
  53. data/lib/opal/cli_runners/node_modules/ws/lib/receiver.js +607 -0
  54. data/lib/opal/cli_runners/node_modules/ws/lib/sender.js +409 -0
  55. data/lib/opal/cli_runners/node_modules/ws/lib/stream.js +180 -0
  56. data/lib/opal/cli_runners/node_modules/ws/lib/validation.js +104 -0
  57. data/lib/opal/cli_runners/node_modules/ws/lib/websocket-server.js +447 -0
  58. data/lib/opal/cli_runners/node_modules/ws/lib/websocket.js +1195 -0
  59. data/lib/opal/cli_runners/node_modules/ws/package.json +40 -106
  60. data/lib/opal/cli_runners/package-lock.json +62 -0
  61. data/lib/opal/cli_runners/package.json +1 -1
  62. data/lib/opal/cli_runners.rb +26 -4
  63. data/lib/opal/nodes/args/prepare_post_args.rb +2 -2
  64. data/lib/opal/nodes/def.rb +8 -8
  65. data/lib/opal/nodes/iter.rb +12 -12
  66. data/lib/opal/nodes/logic.rb +1 -1
  67. data/lib/opal/nodes/masgn.rb +2 -2
  68. data/lib/opal/parser/with_ruby_lexer.rb +1 -1
  69. data/lib/opal/paths.rb +14 -0
  70. data/lib/opal/rewriter.rb +2 -0
  71. data/lib/opal/rewriters/forward_args.rb +52 -4
  72. data/lib/opal/rewriters/targeted_patches.rb +94 -0
  73. data/lib/opal/version.rb +1 -1
  74. data/opal/corelib/basic_object.rb +1 -1
  75. data/opal/corelib/boolean.rb +2 -2
  76. data/opal/corelib/class.rb +11 -0
  77. data/opal/corelib/constants.rb +3 -3
  78. data/opal/corelib/enumerable.rb +4 -0
  79. data/opal/corelib/enumerator.rb +1 -1
  80. data/opal/corelib/hash.rb +2 -2
  81. data/opal/corelib/helpers.rb +1 -1
  82. data/opal/corelib/kernel.rb +3 -3
  83. data/opal/corelib/method.rb +1 -1
  84. data/opal/corelib/module.rb +29 -8
  85. data/opal/corelib/proc.rb +7 -5
  86. data/opal/corelib/runtime.js +141 -78
  87. data/opal/corelib/set.rb +252 -0
  88. data/opal/corelib/string.rb +2 -1
  89. data/opal/corelib/time.rb +2 -2
  90. data/opal/opal.rb +1 -0
  91. data/opal.gemspec +1 -0
  92. data/spec/filters/bugs/array.rb +22 -13
  93. data/spec/filters/bugs/base64.rb +5 -5
  94. data/spec/filters/bugs/basicobject.rb +16 -8
  95. data/spec/filters/bugs/bigdecimal.rb +161 -160
  96. data/spec/filters/bugs/binding.rb +10 -10
  97. data/spec/filters/bugs/class.rb +8 -8
  98. data/spec/filters/bugs/complex.rb +2 -1
  99. data/spec/filters/bugs/date.rb +79 -81
  100. data/spec/filters/bugs/datetime.rb +29 -29
  101. data/spec/filters/bugs/delegate.rb +1 -3
  102. data/spec/filters/bugs/encoding.rb +69 -69
  103. data/spec/filters/bugs/enumerable.rb +22 -20
  104. data/spec/filters/bugs/enumerator.rb +88 -85
  105. data/spec/filters/bugs/exception.rb +46 -40
  106. data/spec/filters/bugs/file.rb +32 -32
  107. data/spec/filters/bugs/float.rb +26 -21
  108. data/spec/filters/bugs/freeze.rb +88 -0
  109. data/spec/filters/bugs/hash.rb +39 -38
  110. data/spec/filters/bugs/integer.rb +57 -44
  111. data/spec/filters/bugs/io.rb +1 -1
  112. data/spec/filters/bugs/kernel.rb +349 -269
  113. data/spec/filters/bugs/language.rb +220 -188
  114. data/spec/filters/bugs/main.rb +5 -3
  115. data/spec/filters/bugs/marshal.rb +38 -38
  116. data/spec/filters/bugs/math.rb +2 -1
  117. data/spec/filters/bugs/method.rb +73 -62
  118. data/spec/filters/bugs/module.rb +163 -143
  119. data/spec/filters/bugs/numeric.rb +6 -6
  120. data/spec/filters/bugs/objectspace.rb +16 -16
  121. data/spec/filters/bugs/openstruct.rb +1 -1
  122. data/spec/filters/bugs/pack_unpack.rb +51 -51
  123. data/spec/filters/bugs/pathname.rb +7 -7
  124. data/spec/filters/bugs/proc.rb +63 -63
  125. data/spec/filters/bugs/random.rb +7 -6
  126. data/spec/filters/bugs/range.rb +12 -9
  127. data/spec/filters/bugs/rational.rb +8 -7
  128. data/spec/filters/bugs/regexp.rb +49 -48
  129. data/spec/filters/bugs/ruby-32.rb +56 -0
  130. data/spec/filters/bugs/set.rb +30 -30
  131. data/spec/filters/bugs/singleton.rb +4 -4
  132. data/spec/filters/bugs/string.rb +187 -99
  133. data/spec/filters/bugs/stringio.rb +7 -0
  134. data/spec/filters/bugs/stringscanner.rb +68 -68
  135. data/spec/filters/bugs/struct.rb +11 -9
  136. data/spec/filters/bugs/symbol.rb +1 -1
  137. data/spec/filters/bugs/time.rb +78 -63
  138. data/spec/filters/bugs/trace_point.rb +4 -4
  139. data/spec/filters/bugs/unboundmethod.rb +32 -17
  140. data/spec/filters/bugs/warnings.rb +8 -12
  141. data/spec/filters/unsupported/array.rb +24 -107
  142. data/spec/filters/unsupported/basicobject.rb +12 -12
  143. data/spec/filters/unsupported/bignum.rb +27 -52
  144. data/spec/filters/unsupported/class.rb +1 -2
  145. data/spec/filters/unsupported/delegator.rb +3 -3
  146. data/spec/filters/unsupported/enumerable.rb +2 -9
  147. data/spec/filters/unsupported/enumerator.rb +2 -11
  148. data/spec/filters/unsupported/file.rb +1 -1
  149. data/spec/filters/unsupported/float.rb +28 -47
  150. data/spec/filters/unsupported/hash.rb +8 -14
  151. data/spec/filters/unsupported/integer.rb +75 -91
  152. data/spec/filters/unsupported/kernel.rb +17 -35
  153. data/spec/filters/unsupported/language.rb +11 -19
  154. data/spec/filters/unsupported/marshal.rb +22 -41
  155. data/spec/filters/unsupported/matchdata.rb +28 -52
  156. data/spec/filters/unsupported/math.rb +1 -1
  157. data/spec/filters/unsupported/privacy.rb +229 -285
  158. data/spec/filters/unsupported/range.rb +1 -5
  159. data/spec/filters/unsupported/regexp.rb +40 -66
  160. data/spec/filters/unsupported/set.rb +2 -2
  161. data/spec/filters/unsupported/singleton.rb +4 -4
  162. data/spec/filters/unsupported/string.rb +305 -508
  163. data/spec/filters/unsupported/struct.rb +3 -4
  164. data/spec/filters/unsupported/symbol.rb +15 -18
  165. data/spec/filters/unsupported/thread.rb +1 -7
  166. data/spec/filters/unsupported/time.rb +159 -202
  167. data/spec/filters/unsupported/usage_of_files.rb +170 -259
  168. data/spec/lib/builder_spec.rb +4 -4
  169. data/spec/lib/rewriters/forward_args_spec.rb +32 -12
  170. data/spec/mspec-opal/runner.rb +2 -0
  171. data/spec/ruby_specs +4 -0
  172. data/stdlib/deno/base.rb +28 -0
  173. data/stdlib/deno/file.rb +340 -0
  174. data/stdlib/{headless_chrome.rb → headless_browser/base.rb} +1 -1
  175. data/stdlib/headless_browser/file.rb +15 -0
  176. data/stdlib/headless_browser.rb +4 -0
  177. data/stdlib/native.rb +1 -1
  178. data/stdlib/nodejs/file.rb +5 -0
  179. data/stdlib/opal/platform.rb +8 -6
  180. data/stdlib/opal-platform.rb +14 -8
  181. data/stdlib/set.rb +1 -258
  182. data/tasks/benchmarking.rake +62 -19
  183. data/tasks/performance.rake +1 -1
  184. data/tasks/testing.rake +5 -3
  185. data/test/nodejs/test_file.rb +29 -10
  186. data/test/opal/http_server.rb +28 -11
  187. data/test/opal/unsupported_and_bugs.rb +2 -1
  188. metadata +89 -50
  189. data/lib/opal/cli_runners/node_modules/ultron/LICENSE +0 -22
  190. data/lib/opal/cli_runners/node_modules/ultron/index.js +0 -138
  191. data/lib/opal/cli_runners/node_modules/ultron/package.json +0 -112
  192. data/lib/opal/cli_runners/node_modules/ws/SECURITY.md +0 -33
  193. data/lib/opal/cli_runners/node_modules/ws/lib/BufferUtil.fallback.js +0 -56
  194. data/lib/opal/cli_runners/node_modules/ws/lib/BufferUtil.js +0 -15
  195. data/lib/opal/cli_runners/node_modules/ws/lib/ErrorCodes.js +0 -28
  196. data/lib/opal/cli_runners/node_modules/ws/lib/EventTarget.js +0 -158
  197. data/lib/opal/cli_runners/node_modules/ws/lib/Extensions.js +0 -69
  198. data/lib/opal/cli_runners/node_modules/ws/lib/PerMessageDeflate.js +0 -339
  199. data/lib/opal/cli_runners/node_modules/ws/lib/Receiver.js +0 -520
  200. data/lib/opal/cli_runners/node_modules/ws/lib/Sender.js +0 -438
  201. data/lib/opal/cli_runners/node_modules/ws/lib/Validation.fallback.js +0 -9
  202. data/lib/opal/cli_runners/node_modules/ws/lib/Validation.js +0 -17
  203. data/lib/opal/cli_runners/node_modules/ws/lib/WebSocket.js +0 -705
  204. data/lib/opal/cli_runners/node_modules/ws/lib/WebSocketServer.js +0 -336
  205. data/spec/filters/bugs/boolean.rb +0 -3
  206. data/spec/filters/bugs/matrix.rb +0 -3
  207. data/spec/filters/unsupported/fixnum.rb +0 -15
  208. data/spec/filters/unsupported/freeze.rb +0 -102
  209. data/spec/filters/unsupported/pathname.rb +0 -4
  210. data/spec/filters/unsupported/proc.rb +0 -4
  211. data/spec/filters/unsupported/random.rb +0 -5
  212. data/spec/filters/unsupported/taint.rb +0 -162
@@ -3,143 +3,269 @@
3
3
  # This script I converted into Opal, so that I don't have to write
4
4
  # buffer handling again. We have gets, Node has nothing close to it,
5
5
  # even async.
6
+ # For CDP see docs/cdp_common.(md|json)
6
7
 
7
8
  require 'opal/platform'
9
+ require 'nodejs/env'
8
10
 
9
11
  %x{
10
12
  var CDP = require("chrome-remote-interface");
11
13
  var fs = require("fs");
14
+ var http = require("http");
12
15
 
13
- var dir = #{ARGV.last}
16
+ var dir = #{ARGV.last};
17
+ var ext = #{ENV['OPAL_CDP_EXT']};
18
+ var port_offset; // port offset for http server, depending on number of targets
19
+ var script_id; // of script which is executed after page is initialized, before page scripts are executed
20
+ var cdp_client; // CDP client
21
+ var target_id; // the used Target
14
22
 
15
23
  var options = {
16
24
  host: #{ENV['CHROME_HOST'] || 'localhost'},
17
- port: #{ENV['CHROME_PORT'] || 9222}
25
+ port: parseInt(#{ENV['CHROME_PORT'] || '9222'})
18
26
  };
19
27
 
20
- CDP(options, function(client) {
21
- var Page = client.Page,
22
- Runtime = client.Runtime,
23
- Console = client.Console;
24
-
25
- Promise.all([
26
- Console.enable(),
27
- Page.enable(),
28
- Runtime.enable(),
29
- ]).then(function() {
30
- // This hook catches only the first argument of `console.log`
31
- // More advanced version Runtime.consoleAPICalled returns all arguments
32
- // but all of them are not formatted, i.e. by calling
33
- // console.log('string', [1, 2, 3], {a: 'b'})
34
- // it returns the following data to the callback:
35
- // {
36
- // "type":"log",
37
- // "args":[
38
- // {
39
- // "type":"string",
40
- // "value":"string"
41
- // },
42
- // {
43
- // "type":"object",
44
- // "subtype":"array",
45
- // "className":"Array",
46
- // "description":"Array(3)",
47
- // "objectId":"{\"injectedScriptId\":11,\"id\":1}",
48
- // "preview":{
49
- // "type":"object",
50
- // "subtype":"array",
51
- // "description":"Array(3)",
52
- // "overflow":false,
53
- // "properties":[
54
- // {"name":"0","type":"number","value":"1"},
55
- // {"name":"1","type":"number","value":"2"},
56
- // {"name":"2","type":"number","value":"3"}
57
- // ]
58
- // }
59
- // },
60
- // {
61
- // "type":"object",
62
- // "className":"Object",
63
- // "description":"Object",
64
- // "objectId":"{\"injectedScriptId\":11,\"id\":2}",
65
- // "preview":{
66
- // "type":"object",
67
- // "description":"Object",
68
- // "overflow":false,
69
- // "properties":[
70
- // {"name":"a","type":"string","value":"b"}
71
- // ]
72
- // }
73
- // }
74
- // ],
75
- // // ...
76
- // }
77
- // Supporting this format for complex data structure is challenging, feel free to contribute!
78
- //
79
- Console.messageAdded(function(console_message) {
80
- process.stdout.write(console_message.message.text);
81
- });
28
+ // shared secret
29
+
30
+ function random_string() {
31
+ let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
32
+ let str = '';
33
+ for (let i = 0; i < 256; i++) {
34
+ str += chars.charAt(Math.floor(Math.random() * chars.length));
35
+ }
36
+ return str;
37
+ };
38
+
39
+ var shared_secret = random_string(); // secret must be passed with POST requests
40
+
41
+ // support functions
42
+
43
+ function perror(error) { console.error(error); }
44
+
45
+ var exiting = false;
46
+
47
+ function shutdown(exit_code) {
48
+ if (exiting) { return Promise.resolve(); }
49
+ exiting = true;
50
+ var promises = [];
51
+ cdp_client.Page.removeScriptToEvaluateOnNewDocument(script_id).then(function(){}, perror);
52
+ return Promise.all(promises).then(function () {
53
+ target_id ? cdp_client.Target.closeTarget(target_id) : null;
54
+ }).then(function() {
55
+ server.close();
56
+ process.exit(exit_code);
57
+ }, function(error) {
58
+ perror(error);
59
+ process.exit(1)
60
+ });
61
+ };
82
62
 
83
- Runtime.exceptionThrown(function(exception) {
84
- var properties = exception.exceptionDetails.exception.preview.properties,
85
- stack, i;
63
+ // simple HTTP server to deliver page, scripts to, and trigger commands from browser
86
64
 
87
- for (i = 0; i < properties.length; i++) {
88
- var property = properties[i];
65
+ function not_found(res) {
66
+ res.writeHead(404, { "Content-Type": "text/plain" });
67
+ res.end("NOT FOUND");
68
+ }
89
69
 
90
- if (property.name == "stack") {
91
- stack = property.value;
92
- }
70
+ function response_ok(res) {
71
+ res.writeHead(200, { "Content-Type": "text/plain" });
72
+ res.end("OK");
73
+ }
74
+
75
+ function handle_post(req, res, fun) {
76
+ var data = "";
77
+ req.on('data', function(chunk) {
78
+ data += chunk;
79
+ })
80
+ req.on('end', function() {
81
+ var obj = JSON.parse(data);
82
+ if (obj.secret == shared_secret) {
83
+ fun.call(this, obj);
84
+ } else {
85
+ not_found(res);
86
+ }
87
+ });
88
+ }
89
+
90
+ var server = http.createServer(function(req, res) {
91
+ if (req.method === "GET") {
92
+ var path = dir + '/' + req.url.slice(1);
93
+ if (path.includes('..') || !fs.existsSync(path)) {
94
+ not_found(res);
95
+ } else {
96
+ var content_type;
97
+ if (path.endsWith(".html")) {
98
+ content_type = "text/html"
99
+ } else if (path.endsWith(".map")) {
100
+ content_type = "application/json"
101
+ } else {
102
+ content_type = "application/javascript"
93
103
  }
104
+ res.writeHead(200, { "Content-Type": content_type });
105
+ res.end(fs.readFileSync(path));
106
+ }
107
+ } else if (req.method === "POST") {
108
+ if (req.url === "/File.write") {
109
+ // totally insecure on purpose
110
+ handle_post(req, res, function(obj) {
111
+ fs.writeFileSync(obj.filename, obj.data);
112
+ response_ok(res);
113
+ });
114
+ } else {
115
+ not_found(res);
116
+ }
117
+ } else {
118
+ not_found(res);
119
+ }
120
+ });
94
121
 
95
- console.log(stack);
122
+ // actual CDP code
96
123
 
97
- process.exit(1);
98
- });
124
+ CDP.List(options, function(err, targets) {
125
+ // default CDP port is 9222, Firefox runner is at 9333
126
+ // Lets collect clients for
127
+ // Chrome CDP starting at 9273 ...
128
+ // Firefox CDP starting 9334 ...
129
+ port_offset = targets ? targets.length + 51 : 51; // default CDP port is 9222, Node CDP port 9229, Firefox is at 9333
99
130
 
100
- Page.javascriptDialogOpening((dialog) => {
101
- #{
102
- if `dialog.type` == 'prompt'
103
- message = gets&.chomp
104
- if message
105
- `Page.handleJavaScriptDialog({accept: true, promptText: #{message}})`
106
- else
107
- `Page.handleJavaScriptDialog({accept: false})`
108
- end
109
- elsif `dialog.type` == 'alert' && `dialog.message` == 'opalheadlesschromeexit'
110
- # A special case of an alert with a magic string "opalheadlesschromeexit".
111
- # This denotes that `Kernel#exit` has been called. We would have rather used
112
- # an exception here, but they don't bubble sometimes.
113
- %x{
114
- Page.handleJavaScriptDialog({accept: true});
131
+ return CDP(options, function(browser_client) {
132
+
133
+ server.listen({ port: port_offset + options.port, host: options.host });
134
+
135
+ browser_client.Target.createTarget({url: "about:blank"}).then(function(target) {
136
+ target_id = target;
137
+ options.target = target_id.targetId;
138
+
139
+ CDP(options, function(client) {
140
+ cdp_client = client;
141
+
142
+ var Log = client.Log,
143
+ Page = client.Page,
144
+ Runtime = client.Runtime;
145
+
146
+ // enable used CDP domains
147
+ Promise.all([
148
+ Log.enable(),
149
+ Page.enable(),
150
+ Runtime.enable()
151
+ ]).then(function() {
152
+ // add script to set the shared_secret in the browser
153
+ return Page.addScriptToEvaluateOnNewDocument({source: "window.OPAL_CDP_SHARED_SECRET = '" + shared_secret + "';"}).then(function(scrid) {
154
+ script_id = scrid;
155
+ }, perror);
156
+ }, perror).then(function() {
157
+
158
+ // receive and handle all kinds of log and console messages
159
+ Log.entryAdded(function(entry) {
160
+ process.stdout.write(entry.entry.level + ': ' + entry.entry.text + "\n");
161
+ });
162
+
163
+ Runtime.consoleAPICalled(function(entry) {
164
+ var args = entry.args;
165
+ var stack = null;
166
+ var i, arg, frame, value;
167
+
168
+ // output actual message
169
+ for(i = 0; i < args.length; i++) {
170
+ arg = args[i];
171
+ if (arg.type === "string") { value = arg.value; }
172
+ else { value = JSON.stringify(arg); }
173
+ process.stdout.write(value);
174
+ }
175
+
176
+ if (entry.stackTrace && entry.stackTrace.callFrames) {
177
+ stack = entry.stackTrace.callFrames;
178
+ }
179
+
180
+ if (entry.type === "error" && stack) {
181
+ // print full stack for errors
182
+ process.stdout.write("\n");
183
+ for(i = 0; i < stack.length; i++) {
184
+ frame = stack[i];
185
+ if (frame) {
186
+ value = frame.url + ':' + frame.lineNumer + ':' + frame.columnNumber + '\n';
187
+ process.stdout.write(value);
188
+ }
189
+ }
190
+ }
191
+ });
192
+
193
+ // react to exceptions
194
+ Runtime.exceptionThrown(function(exception) {
195
+ var ex = exception.exceptionDetails.exception.preview.properties;
196
+ var stack = [];
197
+ if (exception.exceptionDetails.stackTrace) {
198
+ stack = exception.exceptionDetails.stackTrace.callFrames;
199
+ } else {
200
+ var d = exception.exceptionDetails;
201
+ stack.push({
202
+ url: d.url,
203
+ lineNumber: d.lineNumber,
204
+ columnNumber: d.columnNumber,
205
+ functionName: "(unknown)"
206
+ });
207
+ }
208
+ var fr;
209
+ for (var i = 0; i < ex.length; i++) {
210
+ fr = ex[i];
211
+ if (fr.name === "message") {
212
+ perror(fr.value);
213
+ }
214
+ }
215
+ for (var i = 0; i < stack.length; i++) {
216
+ fr = stack[i];
217
+ perror(fr.url + ':' + fr.lineNumber + ':' + fr.columnNumber + ': in ' + fr.functionName);
218
+ }
219
+ return shutdown(1);
220
+ });
221
+
222
+ // handle input
223
+ Page.javascriptDialogOpening((dialog) => {
224
+ #{
225
+ if `dialog.type` == 'prompt'
226
+ message = gets&.chomp
227
+ if message
228
+ `Page.handleJavaScriptDialog({accept: true, promptText: #{message}})`
229
+ else
230
+ `Page.handleJavaScriptDialog({accept: false})`
231
+ end
232
+ elsif `dialog.type` == 'alert' && `dialog.message` == 'opalheadlessbrowserexit'
233
+ # A special case of an alert with a magic string "opalheadlessbrowserexit".
234
+ # This denotes that `Kernel#exit` has been called. We would have rather used
235
+ # an exception here, but they don't bubble sometimes.
236
+ %x{
237
+ Page.handleJavaScriptDialog({accept: true});
238
+ Runtime.evaluate({ expression: "window.OPAL_EXIT_CODE" }).then(function(output) {
239
+ var exit_code = 0;
240
+ if (typeof(output.result) !== "undefined" && output.result.type === "number") {
241
+ exit_code = output.result.value;
242
+ }
243
+ return shutdown(exit_code);
244
+ });
245
+ }
246
+ end
247
+ }
248
+ });
249
+
250
+ // page has been loaded, all code has been executed
251
+ Page.loadEventFired(() => {
115
252
  Runtime.evaluate({ expression: "window.OPAL_EXIT_CODE" }).then(function(output) {
116
- client.close();
117
253
  if (typeof(output.result) !== "undefined" && output.result.type === "number") {
118
- process.exit(output.result.value);
254
+ return shutdown(output.result.value);
255
+ } else if (typeof(output.result) !== "undefined" && output.result.type === "string" && output.result.value === "noexit") {
256
+ // do nothing, we have headless chrome support enabled and there are most probably async events awaiting
119
257
  } else {
120
- process.exit(0);
258
+ return shutdown(0);
121
259
  }
122
- });
123
- }
124
- end
125
- }
126
- });
260
+ })
261
+ });
127
262
 
128
- Page.loadEventFired(() => {
129
- Runtime.evaluate({ expression: "window.OPAL_EXIT_CODE" }).then(function(output) {
130
- if (typeof(output.result) !== "undefined" && output.result.type === "number") {
131
- client.close();
132
- process.exit(output.result.value);
133
- } else if (typeof(output.result) !== "undefined" && output.result.type === "string" && output.result.value === "noexit") {
134
- // do nothing, we have headless chrome support enabled and there are most probably async events awaiting
135
- } else {
136
- client.close();
137
- process.exit(0);
138
- }
139
- })
263
+ // init page load
264
+ Page.navigate({ url: "http://localhost:" + (port_offset + options.port).toString() + "/index.html" })
265
+ }, perror);
266
+ });
140
267
  });
141
-
142
- Page.navigate({ url: "file://"+dir+"/index.html" })
143
268
  });
144
269
  });
145
270
  }
271
+ # end of code (marker to help see if brackets match above)
@@ -3,16 +3,149 @@
3
3
  require 'opal/paths'
4
4
 
5
5
  # The compiler runner will just output the compiled JavaScript
6
- Opal::CliRunners::Compiler = ->(data) {
7
- options = data[:options] || {}
8
- builder = data.fetch(:builder).call
9
- map_file = options[:map_file]
10
- output = data.fetch(:output)
11
-
12
- compiled_source = builder.to_s
13
- compiled_source += "\n" + builder.source_map.to_data_uri_comment unless options[:no_source_map]
14
- output.puts compiled_source
15
- File.write(map_file, builder.source_map.to_json) if map_file
16
-
17
- 0
18
- }
6
+ class Opal::CliRunners::Compiler
7
+ def self.call(data)
8
+ new(data).start
9
+ end
10
+
11
+ def initialize(data)
12
+ @options = data[:options] || {}
13
+ @builder_factory = data.fetch(:builder)
14
+ @map_file = @options[:map_file]
15
+ @output = data.fetch(:output)
16
+ @watch = @options[:watch]
17
+ end
18
+
19
+ def compile
20
+ builder = @builder_factory.call
21
+ compiled_source = builder.to_s
22
+ compiled_source += "\n" + builder.source_map.to_data_uri_comment unless @options[:no_source_map]
23
+
24
+ rewind_output if @watch
25
+
26
+ @output.puts compiled_source
27
+ @output.flush
28
+
29
+ File.write(@map_file, builder.source_map.to_json) if @map_file
30
+
31
+ builder
32
+ end
33
+
34
+ def compile_noraise
35
+ compile
36
+ rescue StandardError, Opal::SyntaxError => e
37
+ $stderr.puts "* Compilation failed: #{e.message}"
38
+ nil
39
+ end
40
+
41
+ def rewind_output
42
+ if !@output.is_a?(File) || @output.tty?
43
+ fail_unrewindable!
44
+ else
45
+ begin
46
+ @output.rewind
47
+ @output.truncate(0)
48
+ rescue Errno::ESPIPE
49
+ fail_unrewindable!
50
+ end
51
+ end
52
+ end
53
+
54
+ def fail_unrewindable!
55
+ warn <<~ERROR
56
+ You have specified --watch, but for watch to work, you must specify an
57
+ --output file.
58
+ ERROR
59
+ exit 1
60
+ end
61
+
62
+ def fail_no_listen!
63
+ warn <<~ERROR
64
+ --watch mode requires the `listen` gem present. Please try to run:
65
+
66
+ gem install listen
67
+
68
+ Or if you are using bundler, add listen to your Gemfile.
69
+ ERROR
70
+ exit 1
71
+ end
72
+
73
+ def watch_compile
74
+ begin
75
+ require 'listen'
76
+ rescue LoadError
77
+ fail_no_listen!
78
+ end
79
+
80
+ @opal_deps = Opal.dependent_files
81
+
82
+ builder = compile
83
+ code_deps = builder.dependent_files
84
+ @files = @opal_deps + code_deps
85
+ @code_listener = watch_files
86
+ @code_listener.start
87
+
88
+ $stderr.puts "* Opal v#{Opal::VERSION} successfully compiled your program in --watch mode"
89
+
90
+ sleep
91
+ end
92
+
93
+ def reexec
94
+ system($0, *OriginalARGV) && exit
95
+ end
96
+
97
+ def on_code_change(modified)
98
+ if !(modified & @opal_deps).empty?
99
+ $stderr.puts "* Modified core Opal files: #{modified.join(', ')}; reexecuting"
100
+ reexec
101
+ elsif !modified.all? { |file| @directories.any? { |dir| file.start_with?(dir + '/') } }
102
+ $stderr.puts "* New unwatched files: #{modified.join(', ')}; reexecuting"
103
+ reexec
104
+ end
105
+
106
+ $stderr.puts "* Modified code: #{modified.join(', ')}; rebuilding"
107
+
108
+ builder = compile_noraise
109
+
110
+ # Ignore the bad compilation
111
+ if builder
112
+ code_deps = builder.dependent_files
113
+ @files = @opal_deps + code_deps
114
+ end
115
+ end
116
+
117
+ def files_to_directories
118
+ directories = @files.map { |file| File.dirname(file) }.uniq
119
+
120
+ previous_dir = nil
121
+ # Only get the topmost directories
122
+ directories = directories.sort.map do |dir|
123
+ if previous_dir && dir.start_with?(previous_dir + '/')
124
+ nil
125
+ else
126
+ previous_dir = dir
127
+ end
128
+ end
129
+
130
+ directories.compact
131
+ end
132
+
133
+ def watch_files
134
+ @directories = files_to_directories
135
+
136
+ Listen.to(*@directories) do |modified, added, removed|
137
+ our_modified = @files & (modified + added + removed)
138
+ on_code_change(our_modified) unless our_modified.empty?
139
+ end
140
+ end
141
+
142
+ def start
143
+ if @watch
144
+ watch_compile
145
+ else
146
+ compile
147
+ end
148
+
149
+ 0
150
+ end
151
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require 'opal/paths'
5
+ require 'opal/cli_runners/system_runner'
6
+ require 'opal/os'
7
+
8
+ module Opal
9
+ module CliRunners
10
+ class Deno
11
+ def self.call(data)
12
+ argv = data[:argv].dup.to_a
13
+
14
+ SystemRunner.call(data) do |tempfile|
15
+ [
16
+ 'deno',
17
+ 'run',
18
+ '--allow-read',
19
+ '--allow-write',
20
+ tempfile.path,
21
+ *argv
22
+ ]
23
+ end
24
+ rescue Errno::ENOENT
25
+ raise MissingDeno, 'Please install Deno to be able to run Opal scripts.'
26
+ end
27
+
28
+ class MissingDeno < RunnerError
29
+ end
30
+ end
31
+ end
32
+ end