From 942cfc80940accba85a427df94ecf3cceac1e078 Mon Sep 17 00:00:00 2001 From: Hiroki Osame Date: Thu, 19 May 2022 18:56:48 -0400 Subject: [PATCH 1/6] feat: binary execution --- README.md | 5 +++++ src/cli.ts | 34 +++++++++++++++++++++-------- src/ignore-node-warnings.ts | 11 +++++----- src/{run.ts => run-binary.ts} | 40 +++++++++++++++++++++++++++-------- src/watch.ts | 6 ++++-- tests/utils/tsx.ts | 1 + 6 files changed, 71 insertions(+), 26 deletions(-) rename src/{run.ts => run-binary.ts} (51%) diff --git a/README.md b/README.md index 1d80f7115..24c53fbf7 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,11 @@ Pass in a file to run: npx tsx ./file.ts ``` +You can also run a local binary: +```sh +npx tsx mocha +``` + ### Watch mode Run file and automatically re-run on changes. diff --git a/src/cli.ts b/src/cli.ts index 1720f9d51..355422c48 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,6 @@ import { cli } from 'cleye'; import { version } from '../package.json'; -import { run } from './run'; +import { isBinaryPath, runBinary } from './run-binary'; import { watchCommand } from './watch'; cli({ @@ -25,7 +25,10 @@ cli({ }, }, help: false, -}, (argv) => { +}, async (argv) => { + let binaryPath = process.execPath; + let args = process.argv.slice(2); + if (argv._.length === 0) { if (argv.flags.version) { console.log(version); @@ -40,16 +43,29 @@ cli({ } // Load REPL - process.argv.push(require.resolve('./repl')); + args.push(require.resolve('./repl')); + } else if (argv._.length === 1) { + const [scriptPath] = argv._; + const foundBinary = await isBinaryPath(scriptPath); + + if (foundBinary) { + binaryPath = foundBinary; + } } - const args = process.argv.slice(2).filter( - argument => (argument !== '--no-cache' && argument !== '--noCache'), - ); + if (argv.flags.noCache) { + args = args.filter( + argument => (argument !== '--no-cache' && argument !== '--noCache'), + ); + } - run(args, { - noCache: Boolean(argv.flags.noCache), - }).on( + runBinary( + binaryPath, + args, + { + noCache: Boolean(argv.flags.noCache), + }, + ).on( 'close', code => process.exit(code!), ); diff --git a/src/ignore-node-warnings.ts b/src/ignore-node-warnings.ts index 881590eb7..11244efd3 100644 --- a/src/ignore-node-warnings.ts +++ b/src/ignore-node-warnings.ts @@ -2,13 +2,13 @@ import { Transform } from 'stream'; const warningTraceTip = '(Use `node --trace-warnings ...` to show where the warning was created)'; const nodeWarningPattern = /^\(node:\d+\) (.+)\n/m; +const warningsToIgnore = [ + 'ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time', + 'ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time', + 'ExperimentalWarning: Importing JSON modules is an experimental feature. This feature could change at any time', +]; export const ignoreNodeWarnings = () => { - const warningsToIgnore = [ - 'ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time', - 'ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time', - 'ExperimentalWarning: Importing JSON modules is an experimental feature. This feature could change at any time', - ]; let filterStderr = true; let counter = 0; @@ -37,7 +37,6 @@ export const ignoreNodeWarnings = () => { return callback(null, chunk); } - warningsToIgnore.splice(ignoreWarning, 1); if (warningsToIgnore.length === 0) { filterStderr = false; } diff --git a/src/run.ts b/src/run-binary.ts similarity index 51% rename from src/run.ts rename to src/run-binary.ts index 35e2501cf..ff0f57698 100644 --- a/src/run.ts +++ b/src/run-binary.ts @@ -1,17 +1,43 @@ +import fs from 'fs'; import type { StdioOptions } from 'child_process'; import { pathToFileURL } from 'url'; import spawn from 'cross-spawn'; import { ignoreNodeWarnings } from './ignore-node-warnings'; -export function run( +const pathExists = (filePath: string) => fs.promises.access(filePath).then(() => true, () => false); + +const isPathPattern = /^\.|\//; + +export const isBinaryPath = async (filePath: string) => { + if (isPathPattern.test(filePath)) { + return false; + } + + const fileExists = await pathExists(filePath); + + if (fileExists) { + return false; + } + + const binaryPath = `./node_modules/.bin/${filePath}`; + if (await pathExists(binaryPath)) { + return binaryPath; + } + + return false; +}; + +export function runBinary( + binaryPath: string, argv: string[], options?: { noCache?: boolean; ipc?: boolean; }, ) { - const environment = { + const environment: Record = { ...process.env, + NODE_OPTIONS: `--loader ${pathToFileURL(require.resolve('./loader.js')).toString()}`, }; if (options?.noCache) { @@ -30,14 +56,10 @@ export function run( } const childProcess = spawn( - process.execPath, - [ - '--loader', - pathToFileURL(require.resolve('./loader.js')).toString(), - - ...argv, - ], + binaryPath, + argv, { + // stdio, stdio, env: environment, }, diff --git a/src/watch.ts b/src/watch.ts index 443667947..0d3014903 100644 --- a/src/watch.ts +++ b/src/watch.ts @@ -2,7 +2,7 @@ import type { ChildProcess } from 'child_process'; import { fileURLToPath } from 'url'; import { command } from 'cleye'; import { watch } from 'chokidar'; -import { run } from './run'; +import { runBinary } from './run-binary'; // From ansi-escapes // https://github.com/sindresorhus/ansi-escapes/blob/2b3b59c56ff77a/index.js#L80 @@ -18,6 +18,8 @@ function isDependencyPath( ); } +const nodeBinary = process.execPath; + export const watchCommand = command({ name: 'watch', parameters: ['