Issue: Spago errors when used with Yarn 2 Plug'n'Play

Some context:

This issue was opened first on the Spago repo, but it turned out that purs is also affected.

Yarn 2 PnP (possibly even using the much more widely used node_modules approach) can’t run purs or spago installed from npm. More generally, Yarn 2 can’t run a binary unless it is written in JS. It seems that this behavior is intentional and is a wontfix.

One workaround stated in the Yarn issue is to use execFileSync. @hdgarrood mentioned that using a JS file is possible and actually was a previous approach. However, it causes weird issues with the REPL, presumably since all input and output is directed through an extra node process.

cc @thomashoneyman this might be a reason to recommend npm over yarn?

It’s certainly disappointing. The recommended tools post ought to at least mention this as a problem with Yarn 2.

The linked GitHub issue states that shell scripts aren’t meant to be supported via the bin field, but I’d imagine that the compiler is not the only tool that will be affected by this change. I’d be surprised if they truly disallow running anything that’s not JS. Ideally either Yarn addresses this or we are able to work around the restriction rather than give up on Yarn altogether.

For our part at work (where we use Nix to manage all dependencies, and Yarn to describe (but not install) JavaScript dependencies) we’ll continue using Yarn because yarn2nix is so nice, but I understand that this isn’t the most common way people manage PureScript projects and I wouldn’t recommend it as the default.

So with all that in mind, I don’t want to give up on Yarn if we don’t have to, but I do think the recommended tools should prefer NPM if Yarn 2 is picking up steam.

Edit: I’ve edited the recommended tools post.


Here’s a hack I did to test out the execFileSync approach. I used spawn instead:

// first rename purs.bin to purs2.bin
const { spawn } = require('child_process');
spawn('./.yarn/unplugged/purescript-npm-0.13.6-7f5d88f564/node_modules/purescript/purs2.bin', process.argv.slice(2), { detatched: true, stdio: 'inherit' });

Obvious problem with this is the fact that I’ve had to hardcode the path. Wish yarn had an option to provide that for me based on the package name.

Also, this was just a proof-of-concept. I don’t know if this would work in production. I suspect there are a few wrinkles still.

1 Like

Okay, so a little more hacking and I got them working. All the hacking is under the .yarn/unplugged folder.

First, to make purescript work I renamed purs.bin to purs2.bin and I created a new purs.bin with:

// first rename purs.bin to purs2.bin
const { spawn } = require('child_process');
spawn(__dirname + '/purs2.bin', process.argv.slice(2), { detatched: true, stdio: 'inherit' });

And to get spago to work I renamed spago to spago2 and created spago.js with:

// first rename spago to spago2
const { spawn } = require('child_process');
const { chmod, constants } = require('fs');
const spago = __dirname + '/spago2';
chmod(spago, constants.S_IXUSR | constants.S_IXG | constants.S_IXOTH, _ => spawn(spago, process.argv.slice(2), { detatched: true, stdio: 'inherit' }));

This change would have to be made every time you install or upgrade purescript or spago. And notice how I had to make spago2 executable. Not sure why it wasn’t. Seems as if purs2.bin was. Not really motivated ATM to figure this part out.

Still a nightmare to patch unless it could be automated somehow. I did notice that the yarn.lock is where the bin is stored and referenced. Must be cached from the package.json. That’s another possible patch point which eliminates the need for renaming. I’m not too familiar with yarn (actually, today was my first encounter with it), so I’m not sure if this file gets overwritten often and I was worried I’d lose my patches, which is why I chose the rename option.

So an automated hack could be done here. Obviously, it would be far better to fix this at the yarn level, but I thought I’d at least document my findings in case someone else finds this useful.