-
Notifications
You must be signed in to change notification settings - Fork 27
/
repl.js
executable file
·231 lines (199 loc) · 7 KB
/
repl.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/env node
if (!process.stdout.isTTY) {
console.error('Must run in a TTY');
process.exit(1);
}
if (!process.env.SAUCE_ACCESS_KEY || !process.env.SAUCE_USERNAME) {
console.error('Please configure $SAUCE_ACCESS_KEY and $SAUCE_USERNAME in your shell');
console.error('Sign up at saucelabs.com');
process.exit(1);
}
var wd = require('wd');
var env = process.env;
var repl = require('repl');
var args = process.argv.slice(2);
var argv = require('minimist')(args);
var sio = require('socket.io');
var ngrok = require('ngrok').connect;
var join = require('path').join;
var http = require('http').Server;
var express = require('express');
// config
var config = require('./browsers');
var browsers = config.browsers;
var platforms = config.platforms;
// parse args
if (2 == argv._.length) platform = argv._.pop();
var str = argv._.join('');
var parts = str.match(/([a-z]+) *(\d+(\.\d+)?)?/);
if (!parts) return usage();
// locate browser
var browser = browsers[str] || browsers[parts[1]];
if (!browser) return usage();
var version = parts[2] || browser.version;
var platform = platforms[platform || browser.platform];
// app
var app = express();
var srv = http(app);
app.get('/', function(req, res){
res.send([
'<!DOCTYPE html>',
'<script>options = ' + JSON.stringify(argv) + ';</script>',
'<script src="/build.js"></script>'
].join('\n'));
});
app.use(express.static(join(__dirname, 'static')));
var io = sio(srv);
var socket;
setup();
function setup(){
console.log('… setting up tunnel');
srv.listen(function(){
ngrok(srv.address().port, function(err, url){
if (err) {
console.error('… error setting up reverse tunnel');
console.error(err.stack);
return;
}
console.log('… booting up \033[96m'
+ browser.name + '\033[39m (' + (version || 'latest')
+ ') on ' + platform);
spawn(url);
});
// let `error` throw
});
}
function spawn(url){
var user = env.SAUCE_USERNAME;
var key = env.SAUCE_ACCESS_KEY;
var vm = wd.remote('ondemand.saucelabs.com', 80, user, key);
var isAndroid = browser.name == "android";
var isiPhone = /^ip(hone|ad)$/.test(browser.name);
var opts = {
browserName: browser.name,
platform : platform,
version : version ? version : undefined,
deviceName : isAndroid ? "Android Emulator" : isiPhone ? "iPhone Simulator" : undefined,
'device-orientation' : isAndroid || isiPhone ? 'portrait' : undefined,
'record-video' : false,
'record-screenshots' : false,
};
vm.init(opts, function(err, sessionid, client){
if (err) throw err;
if (client) console.log('… connected to', client.browserName, client.version);
vm.get(url, function(err){
if (err) throw err;
// set up a heartbeat to keep session alive
setInterval(function(){
vm.eval('', function(err){
if (err) throw err;
});
}, 30000);
// socket io `connection` should fire now
});
});
io.on('connection', function(s){
socket = s;
socket.on('disconnect', function(){
console.log('socket disconnected');
process.exit(1);
});
start();
});
}
function usage(){
console.error('');
console.error('usage: repl <browser>[version] [platform]');
console.error('');
console.error('options:');
console.error(' -h: this message');
console.error(' -k: no remote `console` override');
console.error('');
console.error('examples:');
console.error(' $ repl ie6 # ie 6');
console.error(' $ repl chrome # chrome latest');
console.error('');
console.error('available browsers: ');
var browsernames = {};
Object.keys(browsers).map(function(k){ return browsers[k] }).forEach(function(k){ browsernames[k.name] = true; });
Object.keys(browsernames).forEach(function(name){
console.error(
' ' + name + ': ',
Object.keys(browsers).filter(function(val){ return browsers[val].name == name }).join(' ')
);
});
console.error('\navailable platforms: \n ' + Object.keys(platforms).join(' '));
console.error('');
process.exit(1);
}
function start(){
console.log('… ready!');
var isAnsiReadlineOK = 'stripVTControlCharacters' in require('readline');
var cmd = repl.start({
prompt: isAnsiReadlineOK ? '\u001b[96m' + str + ' › \u001b[39m' : str + ' › ',
eval: function(cmd, ctx, file, fn){
socket.emit('run', cmd, function(err, data){
if (err) {
// we have to create a synthetic SyntaxError if one occurred in the
// browser because the REPL special-cases that error
// to display the "more" prompt
if (
// most browsers set the `name` to "SyntaxError"
('SyntaxError' == err.name &&
// firefox
('syntax error' == err.message ||
'function statement requires a name' == err.message ||
// iOS
'Parse error' == err.message ||
// opera
/syntax error$/.test(err.message) ||
/expected (.*), got (.*)$/.test(err.message) ||
// safari
/^Unexpected token (.*)$/.test(err.message)
)
) ||
// old IE doens't even have a "name" property :\
('Syntax error' == err.message || /^expected /i.test(err.message))
) {
err = new SyntaxError('Unexpected end of input');
} else {
// any other `err` needs to be converted to an `Error` object
// with the given `err`s properties copied over
var e = new Error();
// force an empty stack trace on the server-side... in the case where
// the client-side didn't send us a `stack` property (old IE, safari),
// it's confusing to see a server-side stack trace.
e.stack = '';
for (var i in err) {
e[i] = err[i];
}
// firefox and opera, in particular, doesn't include the "name"
// or "message" in the stack trace
var prefix = e.name;
if (e.message) prefix += ': ' + e.message;
if (e.stack.substring(0, prefix.length) != prefix) {
e.stack = prefix + '\n' + e.stack;
}
err = e;
}
}
// We're intentionally passing the successful "data" response as the
// `err` argument to the eval function. This is because the `data` is
// actually a properly formatted String output from `util.inspect()` run
// on the client-side, with proper coloring, etc. coincidentally, if we
// pass that as the `err` argument then node's `repl` module will simply
// console.log() the formatted string for us, which is what we want
fn(err || data);
});
}
});
socket.on('global err', function(message, url, linenumber){
console.log('Global error: ', message, url, linenumber);
});
socket.on('console', function(method, args){
console[method].apply(console, args);
});
cmd.on('exit', function(){
process.exit(0);
});
}