5

There is a CLI that runs on Node and can be executed via command on the command line. In its code, some functionality is deactivated for Windows users by if (process.platform === 'win32') { process.exit(1); }. Now I want to disable this check, and allow Windows users to use the "hidden" functionality (usually the the required software is not installed on Windows, but I have a special setup where it is).

Is there some way, to lie to this CLI and simulate that it is running on e.g. Linux or macOS?

Of course I could fork the CLI, remove the check and work with my own version - but this would require repeating this process for each release. As this functionality should also be available to other users, I would have to publish the forked and modified CLI which could be a frequent source of confusion for its users (What about version numbers? Do I use the same command?) - 99.9% of the code will stay untouched.

Could I maybe write my own CLI, that does something to "fake" the environment, then executes the installed, original CLI, and cleans up afterwards?


I created an example package that works identical to the CLI, but only does exactly that part that I am investigating:

Install via npm i -g nodejs-cli-nowin.

janpio
  • 10,645
  • 16
  • 64
  • 107
  • The case is specific to a package. Please, specify package name. – Estus Flask Jan 26 '19 at 00:08
  • It is not really, but I created https://github.com/janpio/nodejs-cli-nowin https://www.npmjs.com/package/nodejs-cli-nowin that recreated the behavior for testing. Install via `npm i -g nodejs-cli-nowin`. – janpio Jan 26 '19 at 00:28
  • 1
    It doesn't clarify the problem because it depends on package internals. I provided an answer in general. It's impossible to say whether it's applicable without seeing how the package works. – Estus Flask Jan 26 '19 at 00:32
  • I understand after reading your answer. Package I am looking at right now is [`weex-toolkit`](https://www.npmjs.com/package/weex-toolkit): `npm i -g weex-toolkit`, `weex create weex`, `weex platform add ios`, `weex run ios` or `weex build ios` then execute the condition. Code with the `process.platform` is [here](https://github.com/weexteam/weex-pack/blob/d125e25e87c933bd725be584ab96bd059c2371f8/src/utils/index.js#L137-L154), but to be honest I don't understand yet how this is called in `weex-toolkit`. Plugin loading somehow :/ – janpio Jan 26 '19 at 00:44
  • So is it weex-toolkit or weex-pack? They seem to be different packages. I checked it, it has a bunch of dependencies, you can be sure that wrong process.platform will mess up a lot of platform-dependent things. The best thing you could do is to mock `process` in specific module, i.e. modify [module wrapper](https://nodejs.org/api/modules.html#modules_the_module_wrapper) to add *local* process variable, `var process = isModuleWherePlatformIsMocked ? Object.create(global.process, { platform: { value: 'linux' } }) : global.process`. – Estus Flask Jan 26 '19 at 01:10
  • I seriously doubt that monkey-patching the package right that is a good thing. You will have to hard-code it to specific module location where `process` should be mocked, if original package changes somehow, a patch will be broken. At this point forking would be cleaner. – Estus Flask Jan 26 '19 at 01:12
  • The CLI is `weex-toolkit`, but as far as I understood it the `weex run ios` command is actually running logic from `weex-pack` - but I couldn't find out where/why for now. – janpio Jan 26 '19 at 01:14
  • weex toolkit doesn't have weex-pack as a dependency. It seems to be a replacement for weex-pack. Both have `weex` CLI command. FWIW, this module https://github.com/weexteam/weex-pack/blob/d125e25e87c933bd725be584ab96bd059c2371f8/src/utils/index.js#L137-L154 already was modified in latest weex-pack. That's why trying to patch specific module where `process.platform` is used is not a good idea. When package internasl change, your tool breaks. – Estus Flask Jan 26 '19 at 01:19
  • `weex-toolkit` somehow installs `weex-pack`, when I reply `n` to `weex platform add ios` when the iOS stuff is already added, I get `02:40:11 : TypeError: Cannot read property 'version' of undefined at C:\Users\Jan\.xtoolkit\node_modules\weexpack\lib\platform\index.js:214:55 ...` – janpio Jan 26 '19 at 01:42

1 Answers1

6

Basically, wrapper package should be used to provide entry point instead of original package. process.platform should be mocked with new value before original package runs.

wrapper-package/index.js

Object.defineProperty(process, 'platform', { value: 'linux' });

require('original-package/bin/entry-point.js');

This won't affect child processes if original package spawns them.

This doesn't guarantee that the package will work as intended with mocked platform, there's a good chance that it won't. Even if the package itself works correctly, this may result in unpredictable behaviour in package dependencies that rely on process.platform.

In order to affect process.platform only for specific module, platform global should be shadowed with local variable. To do this, module wrapper function should be modified:

const Module = require('module')

const escapedPatchedModulePath = require.resolve('original-package/module-to-patch.js')
  .replace(/\\/g, '\\\\');

Module.wrapper[0] += `
const isPatchedModule = __filename === "${escapedPatchedModulePath}";
let process = isPatchedModule
  ? Object.create(global.process, { platform: { value: 'linux' } })
  : global.process;
`;

require('original-package/bin/entry-point.js');
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Ironically spawning child processes doesn't work as node tries to use `bin/sh` for it: `child_process.js:651 throw err; ^ Error: spawnSync /bin/sh ENOENT` ;) – janpio Jan 26 '19 at 01:19
  • Makes sense. Mocked `process.platform` will break every thing in Node that has dynamic platform-dependent code. – Estus Flask Jan 26 '19 at 01:23
  • But in general your code works! Maybe there is a way to only apply it before specific modules/methods/calls? – janpio Jan 26 '19 at 01:26
  • 1
    I posted an example how module wrapper can be modified to change process.platform in specific module. I cannot say if this can be easily applied to your case. Such patch is fragile, I'd suggest to avoid this if possible. – Estus Flask Jan 26 '19 at 02:09
  • Holy moly, that really works! What a great and and terrible piece of code :D I was playing around with the possibility to define a getter in `Object.defineProperty`, but didn't get anywhere because I didn't know who was calling the method. You are awesome, thanks! – janpio Jan 26 '19 at 02:29