Skip to content

Commit f3a1bf3

Browse files
w3cjehmicky
andauthored
Fix deno node:process execPath compatibility (#1160)
Co-authored-by: ehmicky <[email protected]>
1 parent d3a146e commit f3a1bf3

File tree

5 files changed

+49
-2
lines changed

5 files changed

+49
-2
lines changed

lib/arguments/file-url.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {fileURLToPath} from 'node:url';
22

33
// Allow some arguments/options to be either a file path string or a file URL
44
export const safeNormalizeFileUrl = (file, name) => {
5-
const fileString = normalizeFileUrl(file);
5+
const fileString = normalizeFileUrl(normalizeDenoExecPath(file));
66

77
if (typeof fileString !== 'string') {
88
throw new TypeError(`${name} must be a string or a file URL: ${fileString}.`);
@@ -11,5 +11,15 @@ export const safeNormalizeFileUrl = (file, name) => {
1111
return fileString;
1212
};
1313

14+
// In Deno node:process execPath is a special object, not just a string:
15+
// https://github.com/denoland/deno/blob/f460188e583f00144000aa0d8ade08218d47c3c1/ext/node/polyfills/process.ts#L344
16+
const normalizeDenoExecPath = file => isDenoExecPath(file)
17+
? file.toString()
18+
: file;
19+
20+
export const isDenoExecPath = file => typeof file !== 'string'
21+
&& file
22+
&& Object.getPrototypeOf(file) === String.prototype;
23+
1424
// Same but also allows other values, e.g. `boolean` for the `shell` option
1525
export const normalizeFileUrl = file => file instanceof URL ? fileURLToPath(file) : file;

lib/pipe/pipe-arguments.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {normalizeParameters} from '../methods/parameters.js';
22
import {getStartTime} from '../return/duration.js';
33
import {SUBPROCESS_OPTIONS, getToStream, getFromStream} from '../arguments/fd-options.js';
4+
import {isDenoExecPath} from '../arguments/file-url.js';
45

56
// Normalize and validate arguments passed to `source.pipe(destination)`
67
export const normalizePipeArguments = ({source, sourcePromise, boundOptions, createNested}, ...pipeArguments) => {
@@ -56,7 +57,7 @@ const getDestination = (boundOptions, createNested, firstArgument, ...pipeArgume
5657
return {destination, pipeOptions: boundOptions};
5758
}
5859

59-
if (typeof firstArgument === 'string' || firstArgument instanceof URL) {
60+
if (typeof firstArgument === 'string' || firstArgument instanceof URL || isDenoExecPath(firstArgument)) {
6061
if (Object.keys(boundOptions).length > 0) {
6162
throw new TypeError('Please use .pipe("file", ..., options) or .pipe(execa("file", ..., options)) instead of .pipe(options)("file", ...).');
6263
}

test/helpers/file-path.js

+11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
import path from 'node:path';
2+
import process from 'node:process';
23

34
export const getAbsolutePath = file => ({file});
45
export const getRelativePath = filePath => ({file: path.relative('.', filePath)});
6+
// Defined as getter so call to toString is not cached
7+
export const getDenoNodePath = () => Object.freeze({
8+
__proto__: String.prototype,
9+
toString() {
10+
return process.execPath;
11+
},
12+
get length() {
13+
return this.toString().length;
14+
},
15+
});

test/methods/node.js

+14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {execa, execaSync, execaNode} from '../../index.js';
77
import {FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
88
import {identity, fullStdio} from '../helpers/stdio.js';
99
import {foobarString} from '../helpers/input.js';
10+
import {getDenoNodePath} from '../helpers/file-path.js';
1011

1112
process.chdir(FIXTURES_DIRECTORY);
1213

@@ -73,6 +74,9 @@ test('Cannot use "node" as binary - "node" option sync', testDoubleNode, 'node',
7374
test('Cannot use path to "node" as binary - execaNode()', testDoubleNode, process.execPath, execaNode);
7475
test('Cannot use path to "node" as binary - "node" option', testDoubleNode, process.execPath, runWithNodeOption);
7576
test('Cannot use path to "node" as binary - "node" option sync', testDoubleNode, process.execPath, runWithNodeOptionSync);
77+
test('Cannot use deno style nodePath as binary - execaNode()', testDoubleNode, getDenoNodePath(), execaNode);
78+
test('Cannot use deno style nodePath as binary - "node" option', testDoubleNode, getDenoNodePath(), runWithNodeOption);
79+
test('Cannot use deno style nodePath as binary - "node" option sync', testDoubleNode, getDenoNodePath(), runWithNodeOptionSync);
7680

7781
const getNodePath = async () => {
7882
const {path} = await getNode(TEST_NODE_VERSION);
@@ -174,6 +178,16 @@ test.serial('The "nodePath" option is relative to "cwd" - execaNode()', testCwdN
174178
test.serial('The "nodePath" option is relative to "cwd" - "node" option', testCwdNodePath, runWithNodeOption);
175179
test.serial('The "nodePath" option is relative to "cwd" - "node" option sync', testCwdNodePath, runWithNodeOptionSync);
176180

181+
const testDenoExecPath = async (t, execaMethod) => {
182+
const {exitCode, stdout} = await execaMethod('noop.js', [], {nodePath: getDenoNodePath()});
183+
t.is(exitCode, 0);
184+
t.is(stdout, foobarString);
185+
};
186+
187+
test('The deno style "nodePath" option can be used - execaNode()', testDenoExecPath, execaNode);
188+
test('The deno style "nodePath" option can be used - "node" option', testDenoExecPath, runWithNodeOption);
189+
test('The deno style "nodePath" option can be used - "node" option sync', testDenoExecPath, runWithNodeOptionSync);
190+
177191
const testNodeOptions = async (t, execaMethod) => {
178192
const {stdout} = await execaMethod('empty.js', [], {nodeOptions: ['--version']});
179193
t.is(stdout, process.version);

test/pipe/pipe-arguments.js

+11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import test from 'ava';
44
import {$, execa} from '../../index.js';
55
import {setFixtureDirectory, FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
66
import {foobarString} from '../helpers/input.js';
7+
import {getDenoNodePath} from '../helpers/file-path.js';
78

89
setFixtureDirectory();
910

@@ -73,6 +74,16 @@ test('execa.$.pipe("file", commandArguments, options)`', async t => {
7374
t.is(stdout, foobarString);
7475
});
7576

77+
test('execa.$.pipe("file", commandArguments, options with denoNodePath)`', async t => {
78+
const {stdout} = await execa('noop.js', [foobarString]).pipe('node', ['stdin.js'], {cwd: FIXTURES_DIRECTORY, nodePath: getDenoNodePath()});
79+
t.is(stdout, foobarString);
80+
});
81+
82+
test('execa.$.pipe("file", commandArguments, denoNodePath)`', async t => {
83+
const {stdout} = await execa('noop.js', [foobarString]).pipe(getDenoNodePath(), ['stdin.js'], {cwd: FIXTURES_DIRECTORY});
84+
t.is(stdout, foobarString);
85+
});
86+
7687
test('$.pipe.pipe("file", commandArguments, options)', async t => {
7788
const {stdout} = await $`noop.js ${foobarString}`
7889
.pipe`stdin.js`

0 commit comments

Comments
 (0)