index.js 2.9 KB

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