Programmatic API
The jup package exports runMain. Use it to run the jup CLI from JavaScript.
#Run the CLI
Call runMain(argv, options) to run the same code as the jup command. It
returns an object with an exit code: { code }.
By default, runMain does not call process.exit() or take control of your
process. You can call it from any part of a script, and you can call it more
than once.
Pass the command-line arguments as an array. Do not include jup itself. This
example runs the pnpm version chosen by the project:
import { runMain } from "jup";
const { code } = await runMain(["pnpm", "install"]);
process.exitCode = code;
// Same as: jup pnpm installYou can include a version in the tool name. This example runs a Node.js version
that matches ^22, without using a shim:
import { runMain } from "jup";
const { code } = await runMain(["node@^22", "script.js", "--verbose"]);
process.exitCode = code;
// Same as: jup node@^22 script.js --verboseYou can also run jup commands:
const { code } = await runMain(["info", "--json"]);
// Same as: jup info --jsonArguments, input, output, errors, exit codes, and signals work like they do in the CLI. See the command reference.
By default, jup starts each tool in a child process. It does not change your
process.argv or process.execArgv. Apart from the project environment file
described below, it does not change process.env. The returned code is the
tool's exit code. If signal N kills the tool, code is 128 + N. Your script
keeps running.
const install = await runMain(["pnpm", "install"]);
if (install.code !== 0) console.error("install failed");
// This is still your script, so you can run another command.
await runMain(["pnpm", "test"]);There is one environment detail to know. For each run, jup reads the nearest
project environment file. It uses .jup.env, or .corepack.env when there is
no .jup.env. It copies allowed values from that file into process.env.
Existing environment variables take priority. See
Settings for the list of allowed values.
These copied values stay in process.env. In a long-running process, values
loaded for the first project may still be present when you run jup in another
project.
jup handles bad user input in the same way as the CLI. For example, an invalid
specification, an unknown tool, or a version blocked by the project returns
{ code: 1 }. jup writes the same message to stdout or stderr as the CLI. It
throws only when an operation fails or jup has a bug.
#Let the tool take over the process
Set handover: true only when the jup run is the last thing your script needs
to do. For a JavaScript tool, jup then runs the tool in the current process
instead of starting a child process. The jup command and its shims use this
mode. It lets a command such as pnpm run as one process instead of two.
const { code } = await runMain(["pnpm", "install"], { handover: true });
if (code !== 0) process.exitCode = code;Do not use this option in the middle of a script. The tool takes control of the
process. runMain returns 0 before the tool runs, and the tool sets the real
exit code. If a signal kills the tool, it also kills your process.
Use this option when you are writing a small CLI wrapper and have no more work to do after the call.
#Other APIs
There are no other public entry points. Version selection, project discovery,
specification parsing, and the store are internal. The package does not export
any types. TypeScript still checks a call, because the option and result types
come with the runMain signature.
You can do everything jup supports through runMain. For project information,
jup info --json usually provides what a script needs.
If the CLI cannot do what your script needs, open an issue and describe what you are trying to build. Keeping the public API small makes it easier to add new features without breaking existing scripts.