Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preferLocal and addExecaPath options #18

Merged
merged 2 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 30 additions & 24 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface RunPathOptions {
type CommonOptions = {
/**
Working directory.

Expand All @@ -7,46 +7,50 @@ export interface RunPathOptions {
readonly cwd?: string | URL;

/**
PATH to be appended. Default: [`PATH`](https://github.com/sindresorhus/path-key).

Set it to an empty string to exclude the default PATH.
*/
readonly path?: string;

/**
Path to the Node.js executable to use in child processes if that is different from the current one. Its directory is pushed to the front of PATH.
The path to the current Node.js executable.

This can be either an absolute path or a path relative to the `cwd` option.

@default process.execPath
@default [process.execPath](https://nodejs.org/api/process.html#processexecpath)
*/
readonly execPath?: string | URL;
}

export type ProcessEnv = Record<string, string | undefined>;

export interface EnvOptions {
/**
The working directory.
Whether to push the current Node.js executable's directory (`execPath` option) to the front of PATH.

@default process.cwd()
@default true
*/
readonly cwd?: string | URL;
readonly addExecPath?: boolean;

/**
Accepts an object of environment variables, like `process.env`, and modifies the PATH using the correct [PATH key](https://github.com/sindresorhus/path-key). Use this if you're modifying the PATH for use in the `child_process` options.
Whether to push the locally installed binaries' directory to the front of PATH.

@default true
*/
readonly env?: ProcessEnv;
readonly preferLocal?: boolean;
};

export type RunPathOptions = CommonOptions & {
/**
The path to the current Node.js executable. Its directory is pushed to the front of PATH.
PATH to be appended.

This can be either an absolute path or a path relative to the `cwd` option.
Set it to an empty string to exclude the default PATH.

@default process.execPath
@default [`PATH`](https://github.com/sindresorhus/path-key)
*/
readonly execPath?: string | URL;
}
readonly path?: string;
};

export type ProcessEnv = Record<string, string | undefined>;

export type EnvOptions = CommonOptions & {
/**
Accepts an object of environment variables, like `process.env`, and modifies the PATH using the correct [PATH key](https://github.com/sindresorhus/path-key). Use this if you're modifying the PATH for use in the `child_process` options.

@default [process.env](https://nodejs.org/api/process.html#processenv)
*/
readonly env?: ProcessEnv;
};

/**
Get your [PATH](https://en.wikipedia.org/wiki/PATH_(variable)) prepended with locally installed binaries.
Expand All @@ -68,6 +72,8 @@ console.log(npmRunPath());
export function npmRunPath(options?: RunPathOptions): string;

/**
Get your [PATH](https://en.wikipedia.org/wiki/PATH_(variable)) prepended with locally installed binaries.

@returns The augmented [`process.env`](https://nodejs.org/api/process.html#process_process_env) object.

@example
Expand Down
55 changes: 34 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,52 @@
import process from 'node:process';
import path from 'node:path';
import url from 'node:url';
import {resolve, join, delimiter} from 'node:path';
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
import {fileURLToPath} from 'node:url';
import pathKey from 'path-key';

export function npmRunPath(options = {}) {
const {
cwd = process.cwd(),
path: path_ = process.env[pathKey()],
execPath = process.execPath,
} = options;
export const npmRunPath = ({
cwd = process.cwd(),
path = process.env[pathKey()],
preferLocal = true,
execPath = process.execPath,
addExecPath = true,
} = {}) => {
const cwdString = cwd instanceof URL ? fileURLToPath(cwd) : cwd;
const cwdPath = resolve(cwdString);
const result = [];

if (preferLocal) {
applyPreferLocal(result, cwdPath);
}

if (addExecPath) {
applyExecPath(result, execPath, cwdPath);
}

return [...result, path].join(delimiter);
};

const applyPreferLocal = (result, cwdPath) => {
let previous;
const execPathString = execPath instanceof URL ? url.fileURLToPath(execPath) : execPath;
const cwdString = cwd instanceof URL ? url.fileURLToPath(cwd) : cwd;
let cwdPath = path.resolve(cwdString);
const result = [];

while (previous !== cwdPath) {
result.push(path.join(cwdPath, 'node_modules/.bin'));
result.push(join(cwdPath, 'node_modules/.bin'));
previous = cwdPath;
cwdPath = path.resolve(cwdPath, '..');
cwdPath = resolve(cwdPath, '..');
}
};

// Ensure the running `node` binary is used.
result.push(path.resolve(cwdString, execPathString, '..'));

return [...result, path_].join(path.delimiter);
}
// Ensure the running `node` binary is used
const applyExecPath = (result, execPath, cwdPath) => {
const execPathString = execPath instanceof URL ? fileURLToPath(execPath) : execPath;
result.push(resolve(cwdPath, execPathString, '..'));
};

export function npmRunPathEnv({env = process.env, ...options} = {}) {
export const npmRunPathEnv = ({env = process.env, ...options} = {}) => {
env = {...env};

const path = pathKey({env});
options.path = env[path];
env[path] = npmRunPath(options);

return env;
}
};
31 changes: 26 additions & 5 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
import process from 'node:process';
import {expectType} from 'tsd';
import {expectType, expectError} from 'tsd';
import {npmRunPath, npmRunPathEnv, ProcessEnv} from './index.js';

const fileUrl = new URL('file:///foo');

expectType<string>(npmRunPath());
expectType<string>(npmRunPath({cwd: '/foo'}));
expectType<string>(npmRunPath({cwd: new URL('file:///foo')}));
expectType<string>(npmRunPath({cwd: fileUrl}));
expectError(npmRunPath({cwd: false}));
expectType<string>(npmRunPath({path: '/usr/local/bin'}));
expectError(npmRunPath({path: fileUrl}));
expectError(npmRunPath({path: false}));
expectType<string>(npmRunPath({execPath: '/usr/local/bin'}));
expectType<string>(npmRunPath({execPath: new URL('file:///usr/local/bin')}));
expectType<string>(npmRunPath({execPath: fileUrl}));
expectError(npmRunPath({execPath: false}));
expectType<string>(npmRunPath({addExecPath: false}));
expectError(npmRunPath({addExecPath: ''}));
expectType<string>(npmRunPath({preferLocal: false}));
expectError(npmRunPath({preferLocal: ''}));

expectType<ProcessEnv>(npmRunPathEnv());
expectType<ProcessEnv>(npmRunPathEnv({cwd: '/foo'}));
expectType<ProcessEnv>(npmRunPathEnv({cwd: new URL('file:///foo')}));
expectType<ProcessEnv>(npmRunPathEnv({cwd: fileUrl}));
expectError(npmRunPathEnv({cwd: false}));
expectType<ProcessEnv>(npmRunPathEnv({env: process.env})); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
expectType<ProcessEnv>(npmRunPathEnv({env: {foo: 'bar'}}));
expectType<ProcessEnv>(npmRunPathEnv({env: {foo: undefined}}));
expectError(npmRunPath({env: false}));
expectError(npmRunPath({env: {[Symbol('key')]: 'bar'}}));
expectError(npmRunPath({env: {foo: false}}));
expectType<ProcessEnv>(npmRunPathEnv({execPath: '/usr/local/bin'}));
expectType<ProcessEnv>(npmRunPathEnv({execPath: new URL('file:///usr/local/bin')}));
expectType<ProcessEnv>(npmRunPathEnv({execPath: fileUrl}));
expectError(npmRunPath({execPath: false}));
expectType<ProcessEnv>(npmRunPathEnv({addExecPath: false}));
expectError(npmRunPathEnv({addExecPath: ''}));
expectType<ProcessEnv>(npmRunPathEnv({preferLocal: false}));
expectError(npmRunPathEnv({preferLocal: ''}));
65 changes: 35 additions & 30 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,66 +32,71 @@ childProcess.execFileSync('foo', {

### npmRunPath(options?)

`options`: [`Options`](#options)\
_Returns_: `string`

Returns the augmented PATH string.

#### options
### npmRunPathEnv(options?)

`options`: [`Options`](#options)\
_Returns_: `object`

Returns the augmented [`process.env`](https://nodejs.org/api/process.html#process_process_env) object.

### options

Type: `object`

##### cwd
#### cwd

Type: `string | URL`\
Default: `process.cwd()`

The working directory.

##### path

Type: `string`\
Default: [`PATH`](https://github.com/sindresorhus/path-key)

The PATH to be appended.

Set it to an empty string to exclude the default PATH.

##### execPath
#### execPath

Type: `string | URL`\
Default: `process.execPath`
Default: [`process.execPath`](https://nodejs.org/api/process.html#processexecpath)

The path to the current Node.js executable. Its directory is pushed to the front of PATH.
The path to the current Node.js executable.

This can be either an absolute path or a path relative to the [`cwd` option](#cwd).

### npmRunPathEnv(options?)
#### addExecPath

Returns the augmented [`process.env`](https://nodejs.org/api/process.html#process_process_env) object.
Type: `boolean`\
Default: `true`

#### options
Whether to push the current Node.js executable's directory ([`execPath`](#execpath) option) to the front of PATH.

Type: `object`
#### preferLocal

##### cwd
Type: `boolean`\
Default: `true`

Type: `string | URL`\
Default: `process.cwd()`
Whether to push the locally installed binaries' directory to the front of PATH.

The working directory.
#### path

##### env
Type: `string`\
Default: [`PATH`](https://github.com/sindresorhus/path-key)

Type: `object`
The PATH to be appended.

Accepts an object of environment variables, like `process.env`, and modifies the PATH using the correct [PATH key](https://github.com/sindresorhus/path-key). Use this if you're modifying the PATH for use in the `child_process` options.
Set it to an empty string to exclude the default PATH.

##### execPath
Only available with [`npmRunPath()`](#npmrunpathoptions), not [`npmRunPathEnv()`](#npmrunpathenvoptions).

Type: `string`\
Default: `process.execPath`
#### env

The path to the Node.js executable to use in child processes if that is different from the current one. Its directory is pushed to the front of PATH.
Type: `object`\
Default: [`process.env`](https://nodejs.org/api/process.html#processenv)

This can be either an absolute path or a path relative to the [`cwd` option](#cwd).
Accepts an object of environment variables, like `process.env`, and modifies the PATH using the correct [PATH key](https://github.com/sindresorhus/path-key). Use this if you're modifying the PATH for use in the `child_process` options.

Only available with [`npmRunPathEnv()`](#npmrunpathenvoptions), not [`npmRunPath()`](#npmrunpathoptions).

## Related

Expand Down
Loading