openBrowser.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. 'use strict';
  8. var chalk = require('chalk');
  9. var execSync = require('child_process').execSync;
  10. var spawn = require('cross-spawn');
  11. var open = require('open');
  12. // https://github.com/sindresorhus/open#app
  13. var OSX_CHROME = 'google chrome';
  14. const Actions = Object.freeze({
  15. NONE: 0,
  16. BROWSER: 1,
  17. SCRIPT: 2,
  18. });
  19. function getBrowserEnv() {
  20. // Attempt to honor this environment variable.
  21. // It is specific to the operating system.
  22. // See https://github.com/sindresorhus/open#app for documentation.
  23. const value = process.env.BROWSER;
  24. const args = process.env.BROWSER_ARGS
  25. ? process.env.BROWSER_ARGS.split(' ')
  26. : [];
  27. let action;
  28. if (!value) {
  29. // Default.
  30. action = Actions.BROWSER;
  31. } else if (value.toLowerCase().endsWith('.js')) {
  32. action = Actions.SCRIPT;
  33. } else if (value.toLowerCase() === 'none') {
  34. action = Actions.NONE;
  35. } else {
  36. action = Actions.BROWSER;
  37. }
  38. return { action, value, args };
  39. }
  40. function executeNodeScript(scriptPath, url) {
  41. const extraArgs = process.argv.slice(2);
  42. const child = spawn(process.execPath, [scriptPath, ...extraArgs, url], {
  43. stdio: 'inherit',
  44. });
  45. child.on('close', code => {
  46. if (code !== 0) {
  47. console.log();
  48. console.log(
  49. chalk.red(
  50. 'The script specified as BROWSER environment variable failed.'
  51. )
  52. );
  53. console.log(chalk.cyan(scriptPath) + ' exited with code ' + code + '.');
  54. console.log();
  55. return;
  56. }
  57. });
  58. return true;
  59. }
  60. function startBrowserProcess(browser, url, args) {
  61. // If we're on OS X, the user hasn't specifically
  62. // requested a different browser, we can try opening
  63. // Chrome with AppleScript. This lets us reuse an
  64. // existing tab when possible instead of creating a new one.
  65. const shouldTryOpenChromiumWithAppleScript =
  66. process.platform === 'darwin' &&
  67. (typeof browser !== 'string' || browser === OSX_CHROME);
  68. if (shouldTryOpenChromiumWithAppleScript) {
  69. // Will use the first open browser found from list
  70. const supportedChromiumBrowsers = [
  71. 'Google Chrome Canary',
  72. 'Google Chrome',
  73. 'Microsoft Edge',
  74. 'Brave Browser',
  75. 'Vivaldi',
  76. 'Chromium',
  77. ];
  78. for (let chromiumBrowser of supportedChromiumBrowsers) {
  79. try {
  80. // Try our best to reuse existing tab
  81. // on OSX Chromium-based browser with AppleScript
  82. execSync('ps cax | grep "' + chromiumBrowser + '"');
  83. execSync(
  84. 'osascript openChrome.applescript "' +
  85. encodeURI(url) +
  86. '" "' +
  87. chromiumBrowser +
  88. '"',
  89. {
  90. cwd: __dirname,
  91. stdio: 'ignore',
  92. }
  93. );
  94. return true;
  95. } catch (err) {
  96. // Ignore errors.
  97. }
  98. }
  99. }
  100. // Another special case: on OS X, check if BROWSER has been set to "open".
  101. // In this case, instead of passing `open` to `opn` (which won't work),
  102. // just ignore it (thus ensuring the intended behavior, i.e. opening the system browser):
  103. // https://github.com/facebook/create-react-app/pull/1690#issuecomment-283518768
  104. if (process.platform === 'darwin' && browser === 'open') {
  105. browser = undefined;
  106. }
  107. // If there are arguments, they must be passed as array with the browser
  108. if (typeof browser === 'string' && args.length > 0) {
  109. browser = [browser].concat(args);
  110. }
  111. // Fallback to open
  112. // (It will always open new tab)
  113. try {
  114. var options = { app: browser, wait: false, url: true };
  115. open(url, options).catch(() => {}); // Prevent `unhandledRejection` error.
  116. return true;
  117. } catch (err) {
  118. return false;
  119. }
  120. }
  121. /**
  122. * Reads the BROWSER environment variable and decides what to do with it. Returns
  123. * true if it opened a browser or ran a node.js script, otherwise false.
  124. */
  125. function openBrowser(url) {
  126. const { action, value, args } = getBrowserEnv();
  127. switch (action) {
  128. case Actions.NONE:
  129. // Special case: BROWSER="none" will prevent opening completely.
  130. return false;
  131. case Actions.SCRIPT:
  132. return executeNodeScript(value, url);
  133. case Actions.BROWSER:
  134. return startBrowserProcess(value, url, args);
  135. default:
  136. throw new Error('Not implemented.');
  137. }
  138. }
  139. module.exports = openBrowser;