index.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. 'use strict';
  2. const {promisify} = require('util');
  3. const path = require('path');
  4. const childProcess = require('child_process');
  5. const fs = require('fs');
  6. const isWsl = require('is-wsl');
  7. const pAccess = promisify(fs.access);
  8. const pExecFile = promisify(childProcess.execFile);
  9. // Path to included `xdg-open`
  10. const localXdgOpenPath = path.join(__dirname, 'xdg-open');
  11. // Convert a path from WSL format to Windows format:
  12. // `/mnt/c/Program Files/Example/MyApp.exe` → `C:\Program Files\Example\MyApp.exe`
  13. const wslToWindowsPath = async path => {
  14. const {stdout} = await pExecFile('wslpath', ['-w', path]);
  15. return stdout.trim();
  16. };
  17. module.exports = async (target, options) => {
  18. if (typeof target !== 'string') {
  19. throw new TypeError('Expected a `target`');
  20. }
  21. options = {
  22. wait: false,
  23. background: false,
  24. ...options
  25. };
  26. let command;
  27. let appArguments = [];
  28. const cliArguments = [];
  29. const childProcessOptions = {};
  30. if (Array.isArray(options.app)) {
  31. appArguments = options.app.slice(1);
  32. options.app = options.app[0];
  33. }
  34. if (process.platform === 'darwin') {
  35. command = 'open';
  36. if (options.wait) {
  37. cliArguments.push('--wait-apps');
  38. }
  39. if (options.background) {
  40. cliArguments.push('--background');
  41. }
  42. if (options.app) {
  43. cliArguments.push('-a', options.app);
  44. }
  45. } else if (process.platform === 'win32' || isWsl) {
  46. command = 'cmd' + (isWsl ? '.exe' : '');
  47. cliArguments.push('/c', 'start', '""', '/b');
  48. target = target.replace(/&/g, '^&');
  49. if (options.wait) {
  50. cliArguments.push('/wait');
  51. }
  52. if (options.app) {
  53. if (isWsl && options.app.startsWith('/mnt/')) {
  54. const windowsPath = await wslToWindowsPath(options.app);
  55. options.app = windowsPath;
  56. }
  57. cliArguments.push(options.app);
  58. }
  59. if (appArguments.length > 0) {
  60. cliArguments.push(...appArguments);
  61. }
  62. } else {
  63. if (options.app) {
  64. command = options.app;
  65. } else {
  66. // When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
  67. const isBundled = !__dirname || __dirname === '/';
  68. // Check if local `xdg-open` exists and is executable.
  69. let exeLocalXdgOpen = false;
  70. try {
  71. await pAccess(localXdgOpenPath, fs.constants.X_OK);
  72. exeLocalXdgOpen = true;
  73. } catch (error) {}
  74. const useSystemXdgOpen = process.versions.electron ||
  75. process.platform === 'android' || isBundled || !exeLocalXdgOpen;
  76. command = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;
  77. }
  78. if (appArguments.length > 0) {
  79. cliArguments.push(...appArguments);
  80. }
  81. if (!options.wait) {
  82. // `xdg-open` will block the process unless stdio is ignored
  83. // and it's detached from the parent even if it's unref'd.
  84. childProcessOptions.stdio = 'ignore';
  85. childProcessOptions.detached = true;
  86. }
  87. }
  88. cliArguments.push(target);
  89. if (process.platform === 'darwin' && appArguments.length > 0) {
  90. cliArguments.push('--args', ...appArguments);
  91. }
  92. const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
  93. if (options.wait) {
  94. return new Promise((resolve, reject) => {
  95. subprocess.once('error', reject);
  96. subprocess.once('close', exitCode => {
  97. if (exitCode > 0) {
  98. reject(new Error(`Exited with code ${exitCode}`));
  99. return;
  100. }
  101. resolve(subprocess);
  102. });
  103. });
  104. }
  105. subprocess.unref();
  106. return subprocess;
  107. };