2

I have a JS/Node based CLI that was initially developed to run on macOS and Linux. I now want to make it work on Windows, but because of complicated and fixed reasons I have to achieve this without changing the app's source code.

A first step was to lie to the program about its process.platform, which works great by messing with its Module (suggested by @estus) and wrapping the original CLI in another CLI that is then actually used on Windows.

Now I stumbled over some code that runs shelljs.which('ruby') and compares the result to a specific string (/usr/bin/ruby) and outputs an error message or even fails the program when it does not match. I don't know how to overcome that yet.

How can I manipulate what shell.which() returns?


One approach that I could take would be to manipulate require('shelljs') to load my own fork of shelljs that returns whatever I want (via using override-require, which I already used to replace child_process with cross-spawn which works much better on Windows). But I want to avoid maintaining my own fork of shelljs of course - it would be much more practical if I could somehow just manipulate shelljs.which.


I created a super small demo project that is similar to the CLI I am using and can be used to experiment with possible solutions: https://github.com/janpio/nodejs-cli-wrongruby - fake.js would be where I would want to manipulate shelljs.which somehow.

janpio
  • 10,645
  • 16
  • 64
  • 107
  • 1
    In exactly the same way as you modified `process`. You load the module, you overwrite the `which` method with your own version, then load the rest of the app. – Bergi Feb 14 '19 at 16:39
  • So instead of `require('module')` I do `require('shelljs')` and then mess with that `which` directly (How?), and later `require` for `shelljs` won't overwrite that? I fear it seems I didn't understand that code well enough. – janpio Feb 14 '19 at 16:44
  • Oh, I got it @Bergi! https://github.com/janpio/nodejs-cli-wrongruby/blob/57d96b935482e21ab16c56d454ebc16e62480af4/fake.js#L7-L11 I am always surprised what is possible with Javascript. Thanks so much. Do you want to post the solution so I can accept it? The credit belongs to you. – janpio Feb 14 '19 at 16:48
  • 1
    `require('shelljs')` gets cached and always returns the same module object, so you you can just alter that. (If it uses class instances it might get a bit more complicated about what to alter). But in general you can just mess with it, [here](https://stackoverflow.com/a/24196317/1048572) [are a](https://stackoverflow.com/a/26107690/1048572) [few examples](https://stackoverflow.com/a/21243884/1048572) on how to do that. – Bergi Feb 14 '19 at 16:51
  • Better implementation that only messes when asking for `ruby`: https://github.com/janpio/nodejs-cli-wrongruby/blob/cd8a056b920a4c0c30151efc29328c63d70ae807/fake.js#L5-L14 – janpio Feb 14 '19 at 16:55
  • 1
    Yeah, just what I would have suggested (maybe apart from `original_which.call(this, cmd)`) :-) You should [put that in an answer](https://stackoverflow.com/help/self-answer). – Bergi Feb 14 '19 at 17:00
  • Thanks, will do. (Could you quickly explain the difference between what I did and `original_which.call(this, cmd)`?) – janpio Feb 14 '19 at 17:06
  • Uff, I can't get it to work in my actual project. The difference to my "demo project" above is that the actual CLI is also a global package - I am looking into if this could cause this to fail :/ – janpio Feb 14 '19 at 17:22
  • 1
    Using `call` ensures that the original method is called with the `shelljs` object as its `this` value, which might be important - but you'll need to take a look at the actual implementation of the original `which` method for its needs – Bergi Feb 14 '19 at 17:52
  • Now it works in my real project as well: Somehow the cache was sometimes empty and `shelljs` was loaded again from the original files. I worked around this by using `override-require` that I mentioned in my question to manipulate `require('shelljs')` to not only load `shelljs`, but patch it directly after loading with the code that worked in the demo project. Phew. – janpio Feb 14 '19 at 19:32

1 Answers1

0

With the help of @Berdi in the comments I figured out that, similar to how I can mess with process.platform, I can also mess with the shelljs.which method:

// Manipulate shelljs.which('ruby')
const shelljs = require('shelljs')
var original_which = shelljs.which
var new_which = function(cmd) {
    if(cmd == 'ruby') {
        return "/usr/bin/ruby"
    }
    return original_which.call(this, cmd)
}
shelljs.which = new_which

require("./index.js");

(This assumes the original CLI lives in ./index.js)

Here all calls to shelljs.which with the parameter ruby are answered with /usr/bin/ruby, and all the others requests are sent to the actual shelljs.which implementation.

janpio
  • 10,645
  • 16
  • 64
  • 107