From 8909aa99ed7db0dd9415210bb725094b1fc87d13 Mon Sep 17 00:00:00 2001 From: ehmicky Date: Sun, 26 Feb 2023 21:51:46 +0000 Subject: [PATCH] Add "verbose" option --- index.js | 4 ++++ lib/verbose.js | 19 ++++++++++++++++++ test/fixtures/nested.js | 7 +++++++ test/fixtures/verbose-script.js | 6 ++++++ test/verbose.js | 35 +++++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+) create mode 100644 lib/verbose.js create mode 100755 test/fixtures/nested.js create mode 100755 test/fixtures/verbose-script.js create mode 100644 test/verbose.js diff --git a/index.js b/index.js index bd49574d1e..5c6ac50c71 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ import {spawnedKill, spawnedCancel, setupTimeout, validateTimeout, setExitHandle import {handleInput, getSpawnedResult, makeAllStream, validateInputSync} from './lib/stream.js'; import {mergePromise, getSpawnedPromise} from './lib/promise.js'; import {joinCommand, parseCommand, parseTemplates, getEscapedCommand} from './lib/command.js'; +import {logCommand, verboseDefault} from './lib/verbose.js'; const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; @@ -44,6 +45,7 @@ const handleArguments = (file, args, options = {}) => { cleanup: true, all: false, windowsHide: true, + verbose: verboseDefault, ...options, }; @@ -76,6 +78,7 @@ export function execa(file, args, options) { const parsed = handleArguments(file, args, options); const command = joinCommand(file, args); const escapedCommand = getEscapedCommand(file, args); + logCommand(escapedCommand, parsed.options); validateTimeout(parsed.options); @@ -165,6 +168,7 @@ export function execaSync(file, args, options) { const parsed = handleArguments(file, args, options); const command = joinCommand(file, args); const escapedCommand = getEscapedCommand(file, args); + logCommand(escapedCommand, parsed.options); validateInputSync(parsed.options); diff --git a/lib/verbose.js b/lib/verbose.js new file mode 100644 index 0000000000..5f5490ed02 --- /dev/null +++ b/lib/verbose.js @@ -0,0 +1,19 @@ +import {debuglog} from 'node:util'; +import process from 'node:process'; + +export const verboseDefault = debuglog('execa').enabled; + +const padField = (field, padding) => String(field).padStart(padding, '0'); + +const getTimestamp = () => { + const date = new Date(); + return `${padField(date.getHours(), 2)}:${padField(date.getMinutes(), 2)}:${padField(date.getSeconds(), 2)}.${padField(date.getMilliseconds(), 3)}`; +}; + +export const logCommand = (escapedCommand, {verbose}) => { + if (!verbose) { + return; + } + + process.stderr.write(`[${getTimestamp()}] ${escapedCommand}\n`); +}; diff --git a/test/fixtures/nested.js b/test/fixtures/nested.js new file mode 100755 index 0000000000..5e329af770 --- /dev/null +++ b/test/fixtures/nested.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import process from 'node:process'; +import {execa} from '../../index.js'; + +const [options, file, ...args] = process.argv.slice(2); +const nestedOptions = {stdio: 'inherit', ...JSON.parse(options)}; +await execa(file, args, nestedOptions); diff --git a/test/fixtures/verbose-script.js b/test/fixtures/verbose-script.js new file mode 100755 index 0000000000..c242074b77 --- /dev/null +++ b/test/fixtures/verbose-script.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node +import {$} from '../../index.js'; + +const $$ = $({stdio: 'inherit'}); +await $$`node -p "one"`; +await $$`node -p "two"`; diff --git a/test/verbose.js b/test/verbose.js new file mode 100644 index 0000000000..fa0a605463 --- /dev/null +++ b/test/verbose.js @@ -0,0 +1,35 @@ +import test from 'ava'; +import {execa} from '../index.js'; +import {setFixtureDir} from './helpers/fixtures-dir.js'; + +setFixtureDir(); + +const normalizeTimestamp = output => output.replace(/\d/g, '0'); +const testTimestamp = '[00:00:00.000]'; + +test('Prints command when "verbose" is true', async t => { + const {stdout, stderr, all} = await execa('nested.js', [JSON.stringify({verbose: true}), 'noop.js', 'test'], {all: true}); + t.is(stdout, 'test'); + t.is(normalizeTimestamp(stderr), `${testTimestamp} noop.js test`); + t.is(normalizeTimestamp(all), `${testTimestamp} noop.js test\ntest`); +}); + +test('Prints command with NODE_DEBUG=execa', async t => { + const {stdout, stderr, all} = await execa('nested.js', [JSON.stringify({}), 'noop.js', 'test'], {all: true, env: {NODE_DEBUG: 'execa'}}); + t.is(stdout, 'test'); + t.is(normalizeTimestamp(stderr), `${testTimestamp} noop.js test`); + t.is(normalizeTimestamp(all), `${testTimestamp} noop.js test\ntest`); +}); + +test('Escape verbose command', async t => { + const {stderr} = await execa('nested.js', [JSON.stringify({verbose: true}), 'noop.js', 'one two', '"'], {all: true}); + t.true(stderr.endsWith('"one two" "\\""')); +}); + +test('Verbose option works with inherit', async t => { + const {all} = await execa('verbose-script.js', {all: true, env: {NODE_DEBUG: 'execa'}}); + t.is(normalizeTimestamp(all), `${testTimestamp} node -p "\\"one\\"" +one +${testTimestamp} node -p "\\"two\\"" +two`); +});