From 51bcbd38d327fc2f9ea103a9370ddaf5b5414cb2 Mon Sep 17 00:00:00 2001 From: Dan Allen Date: Sun, 4 Aug 2024 02:54:03 -0600 Subject: [PATCH] resolves #28 map shell key on run to option on spawn --- CHANGELOG.adoc | 1 + .../ROOT/pages/configuration-keys.adoc | 13 +++++++++-- packages/collector-extension/lib/index.js | 4 ++-- .../lib/util/run-command.js | 9 ++++++-- .../test/collector-extension-test.js | 23 +++++++++++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 625b921..b2bc771 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -15,6 +15,7 @@ For a detailed view of what's changed, refer to the {url-repo}/commits[commit hi * allow context variables to be referenced in command (#26) * replace $NODE token at start of command with Node.js exec path (#29) * add detection for batch script that ends in .cmd on Windows +* add `shell` key on run entry to run command using the default shell (#28) === Changed diff --git a/docs/modules/ROOT/pages/configuration-keys.adoc b/docs/modules/ROOT/pages/configuration-keys.adoc index cf0b5b9..93bc83d 100644 --- a/docs/modules/ROOT/pages/configuration-keys.adoc +++ b/docs/modules/ROOT/pages/configuration-keys.adoc @@ -202,6 +202,15 @@ Each `run` entry is invoked sequentially in the order specified in the array. TIP: If there's only a single entry, the array can be replaced by a map (i.e., drop the leading `-` marker). +The value of the `command` key consists of a space-separated list of arguments. +An argument only has to be enclosed in single or double quotes (i.e., quoted) if the value itself contains spaces or quotes. +To make an empty argument, use a pair of quotes with nothing in between (e.g., `""`). +If a quoted value contains a quote, that quote can be escaped using a backslash. +No other reserved shell characters need to be escaped by default. + +If the `shell` key is set to `true` (default: `false`), then the command will be run in the default shell. +In this case, all arguments that contain reserved shell characters need to be quoted (e.g, `"console.log(1)"`). + The value of the `command` key may include context variable expressions. The `env` context variable includes all environment variables added by the `env` key. @@ -214,7 +223,7 @@ run: If the command begins with `$NODE` (or `${{env.NODE}}`) followed by a space, that token will be replaced with the value of the `NODE` environment variable set on the Antora process, which defaults to the path of the Node.js executable used to run Antora. Similarly, if the command begins with the path of a JavaScript file, the value of the `NODE` environment variable is inserted as the first command argument. -If the `local` key is specified with the value `true`, the command must be located relative to where the command is run. +If the `local` key is specified with the value `true` (default: `false`), the command must be located relative to where the command is run. This key can be set implicitly by prepending the command with `./`. This setting effectively disables looking for a global command on the current user's PATH. @@ -225,7 +234,7 @@ A failed command is one that exits with a non-zero exit value. If the value is `ignore`, the failure is silently ignored. If the value is `log`, the failure is logged at the error level. The level can be tuned by appending it as a property on the log keyword (e.g., `log.warn`). -If the value is `throw` (the default), the error bubbles, which causes the executio of Antora to immediately stop, just like any other fatal error. +If the value is `throw` (default), the error bubbles, which causes the executio of Antora to immediately stop, just like any other fatal error. The `env` key can be used to set (or unset) additional environment variables for the run command. The value of this key must be an array. diff --git a/packages/collector-extension/lib/index.js b/packages/collector-extension/lib/index.js index 97501f7..d4eef85 100644 --- a/packages/collector-extension/lib/index.js +++ b/packages/collector-extension/lib/index.js @@ -123,9 +123,9 @@ module.exports.register = function ({ config: { createWorktrees = 'auto', keepWo } for (const { clean: cleans, run: runs, scan: scans } of collectors) { for (const clean of cleans) await fsp.rm(clean.dir, { recursive: true, force: true }) - for (const { dir: cwd, command, local, env, onFailure = 'throw' } of runs) { + for (const { dir: cwd, command, local, shell, env, onFailure = 'throw' } of runs) { try { - await runCommand(command, [], { cwd, local, env, output: true, quiet }) + await runCommand(command, [], { cwd, local, shell: !!shell, env, output: true, quiet }) } catch (err) { const loc = worktree || url const qualifier = worktree ? ' ' : remote && worktree === false ? ` ` : '' diff --git a/packages/collector-extension/lib/util/run-command.js b/packages/collector-extension/lib/util/run-command.js index 9a825a9..eb7a4e3 100644 --- a/packages/collector-extension/lib/util/run-command.js +++ b/packages/collector-extension/lib/util/run-command.js @@ -8,6 +8,7 @@ const parseCommand = require('./parse-command') const { spawn } = require('node:child_process') const IS_WIN = process.platform === 'win32' +const ENV_NAME_RX = /\$(\w+)/g // adapted from https://github.com/jpommerening/node-lazystream/blob/master/lib/lazystream.js | license: MIT class LazyReadable extends PassThrough { @@ -29,10 +30,9 @@ const fileExists = (p) => ) async function runCommand (cmd = '', argv = [], opts = {}) { - const cmdv = parseCommand(String(cmd)) + let cmdv = parseCommand(String(cmd), { preserveQuotes: opts.shell }) if (!cmdv.length) throw new TypeError('Command not specified') const { input, output, quiet, implicitStdin, local, ...spawnOpts } = opts - if (input) input instanceof Buffer ? implicitStdin || argv.push('-') : argv.push(input) if (IS_WIN) { if (local) { const cmd0 = cmdv[0] @@ -42,10 +42,15 @@ async function runCommand (cmd = '', argv = [], opts = {}) { if ((await fileExists(absCmd0 + (ext = '.bat'))) || (await fileExists(absCmd0 + (ext = '.cmd')))) cmdv[0] += ext } } + if (spawnOpts.shell) { + cmdv = cmdv.map((it) => (~it.indexOf('$') ? it.replace(ENV_NAME_RX, '%$1%') : it)) + argv = argv.map((it) => (~it.indexOf('$') ? it.replace(ENV_NAME_RX, '%$1%') : it)) + } Object.assign(spawnOpts, { windowsHide: true, windowsVerbatimArguments: false }) } else if (local) { cmdv[0] = `./${cmdv[0]}` } + if (input) input instanceof Buffer ? implicitStdin || argv.push('-') : argv.push(input) return new Promise((resolve, reject) => { const stdout = [] const stderr = [] diff --git a/packages/collector-extension/test/collector-extension-test.js b/packages/collector-extension/test/collector-extension-test.js index df3c185..e2b7968 100644 --- a/packages/collector-extension/test/collector-extension-test.js +++ b/packages/collector-extension/test/collector-extension-test.js @@ -1324,6 +1324,29 @@ describe('collector extension', () => { }) }) + it('should allow command to be run in default shell', async () => { + const collectorConfig = { + run: { + command: '"$NODE" .gen-many-files.js $' + 'NUM_FILES', + shell: true, + dir: '.', + env: [{ name: 'NUM_FILES', value: '5' }], + }, + scan: { dir: './build/many-pages', files: '*.adoc', into: 'modules/ROOT/pages' }, + } + await runScenario({ + repoName: 'test-at-root', + collectorConfig, + before: (contentAggregate) => { + expect(contentAggregate).to.have.lengthOf(1) + expect(contentAggregate[0].files).to.be.empty() + }, + after: (contentAggregate) => { + expect(contentAggregate[0].files).to.have.lengthOf(5) + }, + }) + }) + it('should create dedicated cache folder for collector under Antora cache dir', async () => { await fsp.mkdir(CACHE_DIR, { recursive: true }) await fsp.writeFile(getCollectorCacheDir(), Buffer.alloc(0)) -- GitLab