Repository: yargs/yargs (js)

Issue:
Wrong $0 in bundled electron app
We have an election app that is bundled to an .exe file.

A command line like
`bundled.exe -f filename`
will result in:
`
{ _: ['filename'],
  '$0': 'bundled.exe  -f' }
`

The following workaround:
`
const argv = yargs.parse(process.argv.slice(1));
`
will result in 
`
{ _: [],
 f: 'filename',
  '$0': 'bundled.exe  -f' }
`

Please notice that the $0 ist still wrong because it contains the "-f".

If this an expected behavior? Is there a way to work around it?


Pull request:
fix(yargs): correct support of bundled electron apps
Fix the first attempt at detecting bundled electron apps, whose arguments start at `process.argv[1]`, not `process.argv[2]`, by handling `process.versions.electron` and `process.defaultApp`.

Fixes: #685 

Patch:
diff --git a/lib/process-argv.js b/lib/process-argv.js
index 691a693..bfe9153 100644
--- a/lib/process-argv.js
+++ b/lib/process-argv.js
@@ -1,12 +1,25 @@
 function getProcessArgvBinIndex () {
-  // Built Electron app: app argv1 argv2 ... argvn
-  // (process.defaultApp is set to false by electron for built app for this purpose,
-  // see https://github.com/electron/electron/issues/4690#issuecomment-217435222)
-  if (process.defaultApp === false) return 0
-  // Default: node app.js argv1 argv2 ... argvn
+  // The binary name is the first command line argument for:
+  // - bundled Electron apps: bin argv1 argv2 ... argvn
+  if (isBundledElectronApp()) return 0
+  // or the second one (default) for:
+  // - standard node apps: node bin.js argv1 argv2 ... argvn
+  // - unbundled Electron apps: electron bin.js argv1 arg2 ... argvn
   return 1
 }
 
+function isBundledElectronApp () {
+  // process.defaultApp is either set by electron in an electron unbundled app, or undefined
+  // see https://github.com/electron/electron/blob/master/docs/api/process.md#processdefaultapp-readonly
+  return isElectronApp() && !process.defaultApp
+}
+
+function isElectronApp () {
+  // process.versions.electron is either set by electron, or undefined
+  // see https://github.com/electron/electron/blob/master/docs/api/process.md#processversionselectron-readonly
+  return !!process.versions.electron
+}
+
 function getProcessArgvWithoutBin () {
   return process.argv.slice(getProcessArgvBinIndex() + 1)
 }


Test patch:
diff --git a/test/yargs.js b/test/yargs.js
index 1c13791..a16946f 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -15,17 +15,19 @@ const noop = () => {}
 const implicationsFailedPattern = new RegExp(english['Implications failed:'])
 
 describe('yargs dsl tests', () => {
-  const oldProcess = {}
+  const oldProcess = { versions: {} }
 
   beforeEach(() => {
     oldProcess.argv = process.argv
     oldProcess.defaultApp = process.defaultApp
+    oldProcess.versions.electron = process.versions.electron
     yargs = require('../')
   })
 
   afterEach(() => {
     process.argv = oldProcess.argv
     process.defaultApp = oldProcess.defaultApp
+    process.versions.electron = oldProcess.versions.electron
     delete require.cache[require.resolve('../')]
   })
 
@@ -38,13 +40,32 @@ describe('yargs dsl tests', () => {
     yargs.$0.should.equal('ndm')
   })
 
-  it('should not remove the 1st argument of built electron apps', () => {
+  it('should not remove the 1st argument of bundled electron apps', () => {
     delete require.cache[require.resolve('../')]
-    process.argv = ['/usr/local/bin/app', '-f', 'toto']
-    process.defaultApp = false
+    process.argv = ['/usr/local/bin/app', '-f', 'toto', 'tutu']
+    process.versions.electron = '10.0.0-nightly.20200211'
     yargs = require('../')
     const argv = yargs.parse()
+    argv.should.have.property('f')
     argv.f.should.equal('toto')
+    argv._.should.deep.equal(['tutu'])
+  })
+
+  it('should remove the 1st argument of unbundled electron apps', () => {
+    delete require.cache[require.resolve('../')]
+    process.argv = ['/usr/local/bin/electron', 'app.js', '-f', 'toto', 'tutu']
+    process.versions.electron = '10.0.0-nightly.20200211'
+    // Same syntax as in electron
+    Object.defineProperty(process, 'defaultApp', {
+      configurable: false,
+      enumerable: true,
+      value: true
+    })
+    yargs = require('../')
+    const argv = yargs.parse()
+    argv.should.have.property('f')
+    argv.f.should.equal('toto')
+    argv._.should.deep.equal(['tutu'])
   })
 
   it('accepts an object for aliases', () => {