Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
27 changes: 2 additions & 25 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
type SpawnSyncOptions
} from 'node:child_process';
import {type Readable} from 'node:stream';
import {normalize as normalizePath} from 'node:path';
import {cwd as getCwd} from 'node:process';
import {computeEnv} from './env.js';
import {combineStreams} from './stream.js';
Expand Down Expand Up @@ -90,22 +89,6 @@ const defaultNodeOptions: SpawnOptions = {
windowsHide: true
};

function normaliseCommandAndArgs(
command: string,
args?: readonly string[]
): {
command: string;
args: readonly string[];
} {
const normalisedPath = normalizePath(command);
const normalisedArgs = args ?? [];

return {
command: normalisedPath,
args: normalisedArgs
};
}

function combineSignals(signals: Iterable<AbortSignal>): AbortSignal {
const controller = new AbortController();

Expand Down Expand Up @@ -329,10 +312,7 @@ export class ExecProcess implements Result {

nodeOptions.env = computeEnv(cwd, nodeOptions.env);

const {command: normalisedCommand, args: normalisedArgs} =
normaliseCommandAndArgs(this._command, this._args);

const crossResult = _parse(normalisedCommand, normalisedArgs, nodeOptions);
const crossResult = _parse(this._command, this._args, nodeOptions);

const handle = spawn(
crossResult.command,
Expand Down Expand Up @@ -406,10 +386,7 @@ export function xSync(

nodeOptions.env = computeEnv(cwd, nodeOptions.env);

const {command: normalisedCommand, args: normalisedArgs} =
normaliseCommandAndArgs(command, args);

const crossResult = _parse(normalisedCommand, normalisedArgs, nodeOptions);
const crossResult = _parse(command, args ?? [], nodeOptions);

const spawnResult = spawnSync(
crossResult.command,
Expand Down
72 changes: 72 additions & 0 deletions src/test/main_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {x, xSync, ExecProcess, NonZeroExitError} from '../main.js';
import {describe, test, expect} from 'vitest';
import os from 'node:os';
import fs from 'node:fs';
import path from 'node:path';

const isWindows = os.platform() === 'win32';

Expand Down Expand Up @@ -209,6 +211,23 @@ if (isWindows) {
'operable program or batch file.'
]);
});

test('preserves leading ./ so cwd-local binary is run, not PATH lookup', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tinyexec-relpath-'));
try {
const scriptPath = path.join(dir, 'mytool.cmd');
fs.writeFileSync(scriptPath, '@echo local\r\n');

const result = await x('./mytool.cmd', [], {
nodeOptions: {cwd: dir}
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toBe('local\r\n');
} finally {
fs.rmSync(dir, {recursive: true, force: true});
}
});
});

describe('exec (windows) (sync)', () => {
Expand All @@ -231,6 +250,23 @@ if (isWindows) {
'operable program or batch file.'
]);
});

test('preserves leading ./ so cwd-local binary is run, not PATH lookup', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tinyexec-relpath-'));
try {
const scriptPath = path.join(dir, 'mytool.cmd');
fs.writeFileSync(scriptPath, '@echo local\r\n');

const result = xSync('./mytool.cmd', [], {
nodeOptions: {cwd: dir}
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toBe('local\r\n');
} finally {
fs.rmSync(dir, {recursive: true, force: true});
}
});
});
}

Expand Down Expand Up @@ -295,6 +331,24 @@ if (!isWindows) {
}
}).rejects.toThrow();
});

test('preserves leading ./ so cwd-local binary is run, not PATH lookup', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tinyexec-relpath-'));
try {
const scriptPath = path.join(dir, 'mytool');
fs.writeFileSync(scriptPath, '#!/bin/sh\necho local\n');
fs.chmodSync(scriptPath, 0o755);

const result = await x('./mytool', [], {
nodeOptions: {cwd: dir, env: {PATH: '/usr/bin:/bin'}}
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toBe('local\n');
} finally {
fs.rmSync(dir, {recursive: true, force: true});
}
});
});

describe('exec (unix-like) (sync)', () => {
Expand All @@ -319,5 +373,23 @@ if (!isWindows) {
xSync('nonexistentforsure');
}).toThrow();
});

test('preserves leading ./ so cwd-local binary is run, not PATH lookup', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tinyexec-relpath-'));
try {
const scriptPath = path.join(dir, 'mytool');
fs.writeFileSync(scriptPath, '#!/bin/sh\necho local\n');
fs.chmodSync(scriptPath, 0o755);

const result = xSync('./mytool', [], {
nodeOptions: {cwd: dir, env: {PATH: '/usr/bin:/bin'}}
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toBe('local\n');
} finally {
fs.rmSync(dir, {recursive: true, force: true});
}
});
});
}