testem 3.18.0 → 3.19.1

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.
@@ -1,8 +1,7 @@
1
1
 
2
2
 
3
- const log = require('npmlog');
4
- const execa = require('execa');
5
- const Bluebird = require('bluebird');
3
+ const log = require('./log');
4
+ const execa = require('execa').execa;
6
5
  const util = require('util');
7
6
  const EventEmitter = require('events').EventEmitter;
8
7
  const spawnargs = require('spawn-args');
@@ -11,6 +10,8 @@ const _ = require('lodash');
11
10
  const envWithLocalPath = require('./utils/env-with-local-path');
12
11
  const fileutils = require('./utils/fileutils');
13
12
  const Process = require('./utils/process');
13
+ const isWin = require('./utils/is-win')();
14
+ const { reduce } = require('./utils/promises');
14
15
 
15
16
  const fileExists = fileutils.fileExists;
16
17
  const executableExists = fileutils.executableExists;
@@ -37,16 +38,29 @@ module.exports = class ProcessCtl extends EventEmitter {
37
38
 
38
39
  _spawn(exe, args, options) {
39
40
  log.info('spawning: ' + exe + ' - ' + util.inspect(args));
40
- let p = new Process(this.name, { killTimeout: this.killTimeout }, execa(exe, args, options));
41
+
42
+ // promise should always succeed, Process will uses .on('error') to handle errors
43
+ let process = execa(exe, args, Object.assign({ reject: false }, options));
44
+
45
+ process.then(result => {
46
+ if (result.failed && result.exitCode !== 0 &&
47
+ result.shortMessage.includes(isWin ? 'is not recognized' : 'ENOENT'))
48
+ {
49
+ this.emit('processError', new Error(result.shortMessage), result.stdout, result.stderr);
50
+ }
51
+ return Promise.resolve(result);
52
+ });
53
+
54
+ let p = new Process(this.name, { killTimeout: this.killTimeout }, process);
41
55
  this.emit('processStarted', p);
42
- return Bluebird.resolve(p);
56
+ return Promise.resolve(p);
43
57
  }
44
58
 
45
59
  spawn(exe, args, options) {
46
60
  let _options = this.prepareOptions(options);
47
61
 
48
62
  if (Array.isArray(exe)) {
49
- return Bluebird.reduce(exe, (found, exe) => {
63
+ return reduce(exe, (found, exe) => {
50
64
  if (found) {
51
65
  return found;
52
66
  }
@@ -10,7 +10,7 @@ The actual AppView. This encapsulates the entire UI.
10
10
  */
11
11
 
12
12
  const StyledString = require('styled_string');
13
- const log = require('npmlog');
13
+ const log = require('../../log');
14
14
  const Backbone = require('backbone');
15
15
 
16
16
  const View = require('./view');
@@ -60,6 +60,7 @@ module.exports = View.extend({
60
60
  if (atLeastOne && this.get('currentTab') < 0) {
61
61
  this.set('currentTab', 0);
62
62
  }
63
+ this.renderTop();
63
64
  this.renderMiddle();
64
65
  this.renderBottom();
65
66
  });
@@ -213,7 +213,7 @@ module.exports = View.extend({
213
213
  out += 'Top Level:\n' + indent(topLevelError) + '\n\n';
214
214
  }
215
215
 
216
- if (results && (tests = results.get('tests'))) {
216
+ if (results && (tests = results.get('tests')) && !topLevelError) {
217
217
  var total = results.get('total');
218
218
  var pending = results.get('pending');
219
219
  var allDone = results.get('all');
@@ -1,10 +1,9 @@
1
1
 
2
2
 
3
- const log = require('npmlog');
3
+ const log = require('../log');
4
4
  const BrowserTapConsumer = require('../browser_tap_consumer');
5
- const Bluebird = require('bluebird');
6
-
7
5
  const LogEntry = require('../utils/log-entry');
6
+ const { asCallback } = require('../utils/promises');
8
7
  const toResult = require('./to-result');
9
8
 
10
9
  module.exports = class BrowserTestRunner {
@@ -33,7 +32,7 @@ module.exports = class BrowserTestRunner {
33
32
  this.finished = false;
34
33
  this.pending = true;
35
34
 
36
- return new Bluebird.Promise(resolve => {
35
+ return new Promise(resolve => {
37
36
  this.onFinish = resolve;
38
37
 
39
38
  if (this.socket) {
@@ -48,19 +47,19 @@ module.exports = class BrowserTestRunner {
48
47
  this.onProcessError(err);
49
48
  });
50
49
  }
51
- }).asCallback(onFinish);
50
+ }).then(...asCallback(onFinish));
52
51
  }
53
52
 
54
53
  stop(cb) {
55
54
  if (this.socket) {
56
55
  this.socket.emit('stop-run');
57
56
  }
58
- return Bluebird.resolve().asCallback(cb);
57
+ return Promise.resolve().then(...asCallback(cb));
59
58
  }
60
59
 
61
60
  exit() {
62
61
  if (!this.process) {
63
- return Bluebird.resolve();
62
+ return Promise.resolve();
64
63
  }
65
64
 
66
65
  // mark that somebody (generally app.js) has requested that the
@@ -119,6 +118,12 @@ module.exports = class BrowserTestRunner {
119
118
  socket.on('test-metadata', this.onTestMetadata.bind(this));
120
119
  socket.on('top-level-error', this.onGlobalError.bind(this));
121
120
 
121
+ socket.on('tap-debug', function(data) {
122
+ if (process.env.TESTEM_VERBOSE) {
123
+ log.verbose('tap-debug', data && data.msg);
124
+ }
125
+ });
126
+
122
127
  socket.on('browser-console', function(/* ...args */) {
123
128
  let args = Array.prototype.slice.call(arguments);
124
129
  let type = args.shift();
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
- const Bluebird = require('bluebird');
4
- const log = require('npmlog');
3
+ const { fromCallback, disposer } = require('../utils/promises');
4
+ const log = require('../log');
5
5
 
6
6
  const template = require('../utils/strutils').template;
7
7
  const ProcessCtl = require('../process-ctl');
@@ -11,13 +11,13 @@ module.exports = class HookRunner {
11
11
  this.config = config;
12
12
  }
13
13
 
14
- run(hook, data, callback) {
14
+ run(hook, data) {
15
15
  let hookCfg = this.config.get(hook);
16
16
  if (!hookCfg) {
17
- return Bluebird.resolve();
17
+ return Promise.resolve();
18
18
  }
19
19
  if (typeof hookCfg === 'function') {
20
- return Bluebird.fromCallback(callback => {
20
+ return fromCallback(callback => {
21
21
  hookCfg(this.config, data, callback);
22
22
  });
23
23
  }
@@ -57,7 +57,7 @@ module.exports = class HookRunner {
57
57
  hookProcessPromise = this.processCtl.spawn(exe, args, options);
58
58
  command = exe + ' ' + args.join(' ');
59
59
  } else {
60
- return Bluebird.reject(new Error('No command or exe/args specified for hook ' + hook));
60
+ return Promise.reject(new Error('No command or exe/args specified for hook ' + hook));
61
61
  }
62
62
 
63
63
  waitForTextTimeout = waitForTextTimeout || 10000;
@@ -65,7 +65,7 @@ module.exports = class HookRunner {
65
65
 
66
66
  let exited = false;
67
67
  let finished;
68
- let p = new Bluebird.Promise((resolve, reject) => {
68
+ let p = new Promise((resolve, reject) => {
69
69
  finished = (err, stdout, stderr) => {
70
70
  if (exited) {
71
71
  return;
@@ -109,7 +109,7 @@ module.exports = class HookRunner {
109
109
  }
110
110
  });
111
111
 
112
- return p.asCallback(callback);
112
+ return p;
113
113
  }
114
114
 
115
115
  varsubParams() {
@@ -135,17 +135,19 @@ module.exports = class HookRunner {
135
135
  return this.hookProcess.kill();
136
136
  }
137
137
 
138
- return Bluebird.resolve();
138
+ return Promise.resolve();
139
139
  }
140
140
 
141
141
  static with(config, hook, data) {
142
142
  let hookRunner = new HookRunner(config);
143
143
 
144
- return hookRunner.run(hook, data).catch(err => {
144
+ let runPromise = hookRunner.run(hook, data).catch(err => {
145
145
  log.error(hook + ' error', err);
146
146
 
147
147
  throw err;
148
- }).disposer(() => hookRunner.stop());
148
+ });
149
+
150
+ return disposer(runPromise, () => hookRunner.stop());
149
151
  }
150
152
  };
151
153
 
@@ -1,7 +1,6 @@
1
1
 
2
- var Bluebird = require('bluebird');
3
-
4
2
  var toResult = require('./to-result');
3
+ const { asCallback } = require('../utils/promises');
5
4
 
6
5
  class ProcessTestRunner {
7
6
  constructor(launcher, reporter) {
@@ -15,19 +14,20 @@ class ProcessTestRunner {
15
14
  this.onStart();
16
15
  this.finished = false;
17
16
 
18
- return new Bluebird.Promise((resolve, reject) => {
17
+ return new Promise((resolve, reject) => {
19
18
  this.onFinish = resolve;
20
19
  this.launcher.start().then(testProcess => {
21
20
  this.process = testProcess;
22
21
  this.process.once('processExit', this.onProcessExit.bind(this));
23
22
  this.process.once('processError', this.onProcessError.bind(this));
23
+ return Promise.resolve(testProcess);
24
24
  }).catch(reject);
25
- }).asCallback(onFinish);
25
+ }).then(...asCallback(onFinish));
26
26
  }
27
27
 
28
28
  exit() {
29
29
  if (!this.process) {
30
- return Bluebird.resolve();
30
+ return Promise.resolve();
31
31
  }
32
32
 
33
33
  return this.process.kill();
@@ -1,10 +1,9 @@
1
1
 
2
2
 
3
3
  var TapConsumer = require('../tap_consumer');
4
- var log = require('npmlog');
5
- var Bluebird = require('bluebird');
6
-
4
+ var log = require('../log');
7
5
  var toResult = require('./to-result');
6
+ const { asCallback } = require('../utils/promises');
8
7
 
9
8
  class TapProcessTestRunner {
10
9
  constructor(launcher, reporter) {
@@ -23,19 +22,19 @@ class TapProcessTestRunner {
23
22
  this.tapConsumer.on('test-result', this.onTestResult.bind(this));
24
23
  this.tapConsumer.on('all-test-results', this.onAllTestResults.bind(this));
25
24
 
26
- return new Bluebird.Promise((resolve, reject) => {
25
+ return new Promise((resolve, reject) => {
27
26
  this.onFinish = resolve;
28
27
  this.launcher.start().then(tapProcess => {
29
28
  this.process = tapProcess;
30
29
  this.process.once('processError', this.onProcessError.bind(this));
31
30
  this.process.process.stdout.pipe(this.tapConsumer.stream);
32
31
  }).catch(reject);
33
- }).asCallback(onFinish);
32
+ }).then(...asCallback(onFinish));
34
33
  }
35
34
 
36
35
  exit() {
37
36
  if (!this.process) {
38
- return Bluebird.resolve();
37
+ return Promise.resolve();
39
38
  }
40
39
 
41
40
  return this.process.kill();
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- const log = require('npmlog');
3
+ const log = require('../log');
4
4
 
5
5
  module.exports = function toResult(launcherId, err, code, runnerProcess, config, testContext) {
6
6
  let logs = [];
@@ -14,15 +14,13 @@ var socketIO = require('socket.io');
14
14
  var compression = require('compression');
15
15
  var fs = require('fs');
16
16
  var path = require('path');
17
- var log = require('npmlog');
17
+ var log = require('../log');
18
18
  var EventEmitter = require('events').EventEmitter;
19
19
  var Mustache = require('consolidate').mustache;
20
20
  var http = require('http');
21
21
  var https = require('https');
22
22
  var httpProxy = require('http-proxy');
23
- var Bluebird = require('bluebird');
24
-
25
- var readFileAsync = Bluebird.promisify(fs.readFile);
23
+ const { fromCallback, each } = require('../utils/promises');
26
24
 
27
25
  class Server extends EventEmitter {
28
26
  constructor(config) {
@@ -37,7 +35,7 @@ class Server extends EventEmitter {
37
35
  this.nextSocketId = 0;
38
36
  }
39
37
 
40
- start(callback) {
38
+ start() {
41
39
  this.createExpress();
42
40
 
43
41
  // Start the server!
@@ -50,7 +48,7 @@ class Server extends EventEmitter {
50
48
  });
51
49
  });
52
50
 
53
- return new Bluebird.Promise((resolve, reject) => {
51
+ return new Promise((resolve, reject) => {
54
52
  this.server.on('listening', () => {
55
53
  this.config.set('port', this.server.address().port);
56
54
  resolve();
@@ -63,23 +61,23 @@ class Server extends EventEmitter {
63
61
  });
64
62
 
65
63
  this.server.listen(this.config.get('port'));
66
- }).asCallback(callback);
64
+ });
67
65
  }
68
66
 
69
- stop(callback) {
67
+ stop() {
70
68
  if (this.server && !this.stopped) {
71
69
  this.stopped = true;
72
70
 
73
- return Bluebird.fromCallback(closeCallback => {
71
+ return fromCallback(closeCallback => {
74
72
  this.server.close(closeCallback);
75
73
 
76
74
  // Destroy all open sockets
77
75
  for (var socketId in this.sockets) {
78
76
  this.sockets[socketId].destroy();
79
77
  }
80
- }).asCallback(callback);
78
+ });
81
79
  } else {
82
- return Bluebird.resolve().asCallback(callback);
80
+ return Promise.resolve();
83
81
  }
84
82
  }
85
83
 
@@ -281,11 +279,13 @@ class Server extends EventEmitter {
281
279
  var url = base + test_page;
282
280
  res.redirect(url);
283
281
  } else {
284
- config.getServeFiles((err, files) => {
285
- config.getFooterScripts((err, footerScripts) => {
286
- this.renderRunnerPage(err, files, footerScripts, res);
282
+ Promise.all([config.getServeFiles(), config.getFooterScripts()])
283
+ .then(([files, footerScripts]) => {
284
+ this.renderRunnerPage(null, files, footerScripts, res);
285
+ })
286
+ .catch(err => {
287
+ this.renderRunnerPage(err, [], [], res);
287
288
  });
288
- });
289
289
  }
290
290
  }
291
291
 
@@ -314,11 +314,11 @@ class Server extends EventEmitter {
314
314
  'mocha_adapter.js',
315
315
  'testem_client.js'
316
316
  ];
317
- Bluebird.each(files, file => {
317
+ each(files, file => {
318
318
  if (file.indexOf(path.sep) === -1) {
319
319
  file = `${__dirname}/../../public/testem/${file}`;
320
320
  }
321
- return readFileAsync(file).then(data => {
321
+ return fs.promises.readFile(file).then(data => {
322
322
  res.write(`\n//============== ${path.basename(file)} ==================\n\n`);
323
323
  res.write(data);
324
324
  }).catch(err => {
@@ -376,20 +376,21 @@ class Server extends EventEmitter {
376
376
  this.emit('file-requested', filePath);
377
377
  });
378
378
  } else {
379
- fs.stat(filePath, (err, stat) => {
380
- this.emit('file-requested', filePath);
381
- if (err) {
382
- return res.sendFile(filePath);
383
- }
384
- if (stat.isDirectory()) {
385
- fs.readdir(filePath, (err, files) => {
386
- var dirListingPage = `${__dirname}/../../views/directorylisting.mustache`;
387
- res.render(dirListingPage, { files: files });
388
- });
389
- } else {
379
+ fs.promises.stat(filePath)
380
+ .then(stat => {
381
+ this.emit('file-requested', filePath);
382
+ if (stat.isDirectory()) {
383
+ return fs.promises.readdir(filePath).then(files => {
384
+ var dirListingPage = `${__dirname}/../../views/directorylisting.mustache`;
385
+ res.render(dirListingPage, { files: files });
386
+ });
387
+ }
390
388
  res.sendFile(filePath);
391
- }
392
- });
389
+ })
390
+ .catch(() => {
391
+ this.emit('file-requested', filePath);
392
+ res.sendFile(filePath);
393
+ });
393
394
  }
394
395
  }
395
396
 
@@ -1,10 +1,10 @@
1
1
 
2
2
 
3
3
  const yaml = require('js-yaml');
4
- const extend = require('util')._extend;
4
+ const extend = Object.assign;
5
5
  const EventEmitter = require('events').EventEmitter;
6
6
  const TapParser = require('tap-parser');
7
- const log = require('npmlog');
7
+ const log = require('./log');
8
8
 
9
9
  module.exports = class TapConsumer extends EventEmitter {
10
10
  constructor() {
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
3
  const capitalize = require('./capitalize');
4
- const log = require('npmlog');
4
+ const log = require('../log');
5
5
  const _ = require('lodash');
6
6
 
7
7
  function Validation() {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared esbuild config for bundling Node.js-dependent test code to run in a
3
+ * browser via testem.
4
+ *
5
+ * esbuild's `@esbuild-plugins/node-modules-polyfill` stores the `process`
6
+ * polyfill as a scoped variable inside the IIFE it emits, but CJS module
7
+ * factories (wrapped in `__commonJS`) reference `process`, `__dirname`, and
8
+ * `setImmediate` as free globals — causing ReferenceErrors at runtime.
9
+ *
10
+ * These two options fix that:
11
+ * - `banner.js`: sets `globalThis.process`, `globalThis.setImmediate`, and
12
+ * `globalThis.Buffer` before the bundle executes, so CJS factories find
13
+ * them as globals.
14
+ * - `define`: replaces `__dirname` / `__filename` literals, which esbuild
15
+ * does not inject when targeting the browser platform.
16
+ */
17
+
18
+ var banner = '(function(){' +
19
+ 'if(typeof process==="undefined"){' +
20
+ 'var p={env:{},browser:true,platform:"browser",' +
21
+ 'nextTick:function(fn){Promise.resolve().then(fn);},' +
22
+ 'on:function(){},once:function(){},off:function(){},' +
23
+ 'emit:function(){},removeListener:function(){},' +
24
+ 'removeAllListeners:function(){},addListener:function(){},' +
25
+ 'binding:function(){throw new Error("process.binding not supported");},' +
26
+ 'cwd:function(){return "/";},' +
27
+ 'exit:function(){}' +
28
+ '};' +
29
+ 'if(typeof globalThis!=="undefined")globalThis.process=p;' +
30
+ 'else if(typeof window!=="undefined")window.process=p;' +
31
+ '}' +
32
+ 'if(typeof setImmediate==="undefined"){' +
33
+ 'var si=function(fn){Promise.resolve().then(fn);};' +
34
+ 'if(typeof globalThis!=="undefined")globalThis.setImmediate=si;' +
35
+ 'else if(typeof window!=="undefined")window.setImmediate=si;' +
36
+ '}' +
37
+ '})();';
38
+
39
+ module.exports = {
40
+ banner: { js: banner },
41
+ bufferInject: require.resolve('./esbuild-buffer-inject.js'),
42
+ define: {
43
+ __dirname: '""',
44
+ __filename: '""'
45
+ }
46
+ };
@@ -0,0 +1,6 @@
1
+ // esbuild inject shim: exposes Buffer as a global for browser bundles.
2
+ // The @esbuild-plugins/node-modules-polyfill plugin resolves `require('buffer')`
3
+ // to a browser polyfill, but does not assign it to globalThis automatically.
4
+ import { Buffer } from 'buffer';
5
+ if (typeof globalThis !== 'undefined') { globalThis.Buffer = Buffer; }
6
+ else if (typeof window !== 'undefined') { window.Buffer = Buffer; }
@@ -1,23 +1,28 @@
1
1
 
2
2
 
3
3
  const fs = require('fs');
4
- const execa = require('execa');
5
- const Bluebird = require('bluebird');
6
- const log = require('npmlog');
4
+ const execa = require('execa').execa;
5
+ const { promisify } = require('util');
6
+ const log = require('../log');
7
7
 
8
- const fsStatAsync = Bluebird.promisify(fs.stat);
8
+ const fsStatAsync = promisify(fs.stat);
9
9
 
10
10
  const isWin = require('./is-win')();
11
11
 
12
12
  exports.fileExists = function fileExists(path) {
13
- return fsStatAsync(path).then(stat => stat.isFile()).catchReturn(false);
13
+ return fsStatAsync(path).then(stat => stat.isFile()).catch(() => false);
14
14
  };
15
15
 
16
16
  exports.executableExists = function executableExists(exe, options) {
17
17
  let cmd = isWin ? 'where' : 'which';
18
- let test = execa(cmd, [exe], options);
19
-
20
- test.on('error', error => log.error('Error spawning "' + cmd + exe + '"', error));
21
-
22
- return test.then(result => result.code === 0, () => false);
18
+ let test = execa(cmd, [exe], Object.assign({ reject: false }, options));
19
+
20
+ return test.then(result => {
21
+ if (result.exitCode === 0) {
22
+ return true;
23
+ } else if (!result.exitCode) {
24
+ log.error('Error spawning "' + cmd + ' ' + exe + '"', result);
25
+ }
26
+ return false;
27
+ });
23
28
  };
@@ -1,5 +1,3 @@
1
-
2
-
3
1
  function test(platform) {
4
2
  return /^win/.test(platform);
5
3
  }
@@ -84,6 +84,7 @@ function firefoxPaths() {
84
84
  return [
85
85
  'C:\\Program Files\\Mozilla Firefox\\firefox.exe',
86
86
  'C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe',
87
+ 'C:\\Users\\' + process.env.USERNAME + '\\AppData\\Local\\Mozilla Firefox\\firefox.exe',
87
88
  process.env.HOME + '/Applications/Firefox.app/Contents/MacOS/firefox',
88
89
  '/Applications/Firefox.app/Contents/MacOS/firefox'
89
90
  ];
@@ -94,24 +95,25 @@ function firefoxSetup(browserTmpDir, config, done) {
94
95
  let perfJS = path.join(browserTmpDir, 'user.js');
95
96
 
96
97
  if (config.get('firefox_user_js')) {
97
- return fs.readFile(config.get('firefox_user_js'), (err, content) => {
98
- if (err) {
99
- return done(err);
100
- }
101
-
102
- fs.writeFile(perfJS, content, done);
103
- });
98
+ return fs.promises.readFile(config.get('firefox_user_js'))
99
+ .then(content => fs.promises.writeFile(perfJS, content))
100
+ .then(() => done(null), done);
104
101
  }
105
102
 
106
- // using user.js to suppress the check default browser popup
107
- // and the welcome start page
103
+ // using user.js to suppress the check default browser popup,
104
+ // the welcome start page, and first-run helper installation prompts
108
105
  let prefs = [
109
106
  'user_pref("browser.shell.checkDefaultBrowser", false);',
110
107
  'user_pref("browser.cache.disk.smart_size.first_run", false);',
111
108
  'user_pref("datareporting.policy.dataSubmissionEnabled", false);',
112
- 'user_pref("datareporting.policy.dataSubmissionPolicyNotifiedTime", "1481830156314");'
109
+ 'user_pref("datareporting.policy.dataSubmissionPolicyNotifiedTime", "1481830156314");',
110
+ 'user_pref("app.update.auto", false);',
111
+ 'user_pref("app.update.enabled", false);',
112
+ 'user_pref("browser.EULA.override", true);',
113
+ 'user_pref("toolkit.telemetry.reportingpolicy.firstRun", false);'
113
114
  ];
114
- fs.writeFile(perfJS, prefs.join(os.EOL), done);
115
+ fs.promises.writeFile(perfJS, prefs.join(os.EOL))
116
+ .then(() => done(null), done);
115
117
  }
116
118
 
117
119
  // Return the catalogue of the browsers that Testem supports for the platform. Each 'browser object'
@@ -144,7 +146,7 @@ module.exports = function knownBrowsers(platform, config) {
144
146
  possibleExe: 'firefox',
145
147
 
146
148
  args(config, url) {
147
- return ['-profile', '--headless', this.browserTmpDir(), url];
149
+ return ['-profile', '-headless', this.browserTmpDir(), url];
148
150
  },
149
151
  setup(config, done) {
150
152
  firefoxSetup(this.browserTmpDir(), config, done);
@@ -0,0 +1,13 @@
1
+ const isWin = require('./is-win');
2
+
3
+ module.exports = {
4
+ isPosix(path) {
5
+ return path.includes('/') && !path.includes('\\');
6
+ },
7
+ convertToPosix(path) {
8
+ if (isWin()) {
9
+ return path.replace(/\\/g, '/');
10
+ }
11
+ return path;
12
+ }
13
+ };