berry icon indicating copy to clipboard operation
berry copied to clipboard

[Feature] Support non-JavaScript executables in "bin"

Open nex3 opened this issue 1 year ago • 3 comments

  • [ ] I'd be willing to implement this feature (contributing guide)
  • [x] This feature is important to have in this repository; a contrib plugin wouldn't do

Describe the user story

It's useful to be able to use npm to distribute executables that are not strictly written in JavaScript. My specific case is the sass-embedded package, which wraps a native sass executable in a JavaScript API. Providing direct, efficient access to that executable is valuable, but if I need to wrap it in a JavaScript library that produces substantial overhead both in terms of efficiency (I measure about 450ms of pure overhead on my laptop) and in terms of accuracy (Node.js is not very graceful about wrapping executables, so edge cases like signal handling often end up working weirdly).

Describe the solution you'd like

npm already supports this by allowing any executable file in the "bin" section of a package.json file. Yarn currently breaks compatibility by always attempting to run these files as Node.js source. Matching npm's behavior is the minimal solution I'd like to see.

However, npm doesn't gracefully support cross-platform packages in this way (https://github.com/npm/cmd-shim/issues/152). Having additional support for distinguishing between bin/executable and bin/executable.cmd, or explicitly specifying per-platform executables, would be a substantial value-add.

Describe the drawbacks of your solution

This does likely involve maintaining some amount of shim code for Windows, which is always a pain in the ass because of how weird cmd.exe is.

Describe alternatives you've considered

This can't be a plugin because it's intrinsic to the way Yarn installs and exposes executables in a variety of contexts. The current workaround is to write a custom JavaScript shim, but this poses the efficiency and correctness issues I outlined above. Another option could be to add a post-install script to manually set up executables in the user's directories, but this is likely to substantially step on the package manager's toes.

nex3 avatar Jul 30 '24 01:07 nex3

AFAIK we support some native executable formats as bins

clemyan avatar Mar 17 '25 16:03 clemyan

Native binaries can be helpful, but without also supporting shell scripts, the value is severely limited. In my case, we're trying to invoke a pre-existing binary with a specific set of flags.

nex3 avatar Mar 17 '25 21:03 nex3

Hi, I ran into similar issues trying to run node with flags like --experimental-strip-types via Yarn bin.

In my case, I wanted to leverage Node’s native type stripping feature introduced in v22.6.0.

I don't know if this is my setup problem, but.. I found that shebangs don’t work properly when the executable exposed as bin ends with .*js extension. Renaming the executable to use .bin extension did the trick — but then I ran into working directory issue.

I expected this script to run correctly, but it didn’t because my setup uses Yarn workspaces. The linked shell script lives in a nested workspace directory rather than the root (the cli is installed as deps of the workspace root), so the relative paths didn’t resolve as intended:

#!/usr/bin/env -S node --disable-warning=ExperimentalWarning --experimental-strip-types src/index.ts

This resolves to "$(pwd)/src/index.ts", which is not relative to working directory of the project.

For now, I’ve managed to get it working — regardless of the current working directory — by using a .bin shell script that delegates to the node command:

bin/cli.bin

#!/usr/bin/env sh
set -eu

BASEDIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
find_up() {
  target="$1"; dir="${2:-$BASEDIR}"
  while [ "$dir" != "/" ]; do
    [ -e "$dir/$target" ] && { echo "$dir/$target"; return 0; }
    dir=$(dirname "$dir")
  done
  return 1
}

node \
  --disable-warning=ExperimentalWarning \
  --experimental-strip-types \
  --require "$(find_up .pnp.cjs)" \
  --import "data:text/javascript,import{register}from'node:module';import{pathToFileURL}from'node:url';register('./.pnp.loader.mjs',pathToFileURL('$(find_up .pnp.loader.mjs)'));" \
  $BASEDIR/../src/cli.ts \
  "$@"


  • Yarn v4.9.2
  • Node.js v22.11.0

preco21 avatar Nov 07 '25 14:11 preco21