Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuy Adorno <ruyadorno@hotmail.com>2020-11-17 22:56:14 +0300
committerRuy Adorno <ruyadorno@hotmail.com>2020-11-17 22:56:14 +0300
commitb74c05d88dc48fabef031ea66ffaa4e548845655 (patch)
tree859115908364d02c18350e643a9043b489003c68
parent1a519edbdd16a400847e4b6323eaf182b1de7ccf (diff)
@npmcli/run-script@1.8.0
-rw-r--r--node_modules/@npmcli/run-script/lib/make-spawn-args.js20
-rw-r--r--node_modules/@npmcli/run-script/lib/run-script-pkg.js3
-rw-r--r--node_modules/@npmcli/run-script/package.json3
-rw-r--r--node_modules/puka/CHANGELOG.md31
-rw-r--r--node_modules/puka/LICENSE.txt18
-rw-r--r--node_modules/puka/README.md411
-rw-r--r--node_modules/puka/index.js804
-rw-r--r--node_modules/puka/package.json38
-rw-r--r--package-lock.json30
-rw-r--r--package.json2
10 files changed, 1349 insertions, 11 deletions
diff --git a/node_modules/@npmcli/run-script/lib/make-spawn-args.js b/node_modules/@npmcli/run-script/lib/make-spawn-args.js
index 181be8493..aa241d5e6 100644
--- a/node_modules/@npmcli/run-script/lib/make-spawn-args.js
+++ b/node_modules/@npmcli/run-script/lib/make-spawn-args.js
@@ -3,6 +3,24 @@ const isWindows = require('./is-windows.js')
const setPATH = require('./set-path.js')
const {resolve} = require('path')
const npm_config_node_gyp = require.resolve('node-gyp/bin/node-gyp.js')
+const { quoteForShell, ShellString, ShellStringText, ShellStringUnquoted } = require('puka')
+
+const escapeCmd = cmd => {
+ const result = []
+ const parsed = ShellString.sh([cmd])
+ for (const child of parsed.children) {
+ if (child instanceof ShellStringText) {
+ const children = child.contents.filter(segment => segment !== null).map(segment => quoteForShell(segment, false, isWindows && 'win32'))
+ result.push(...children)
+ } else if (child instanceof ShellStringUnquoted) {
+ result.push(child.value)
+ } else {
+ result.push(isWindows ? '&' : ';')
+ }
+ }
+
+ return result.join('')
+}
const makeSpawnArgs = options => {
const {
@@ -16,7 +34,7 @@ const makeSpawnArgs = options => {
} = options
const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(scriptShell)
- const args = isCmd ? ['/d', '/s', '/c', `"${cmd}"`] : ['-c', cmd]
+ const args = isCmd ? ['/d', '/s', '/c', escapeCmd(cmd)] : ['-c', escapeCmd(cmd)]
const spawnOpts = {
env: setPATH(path, {
diff --git a/node_modules/@npmcli/run-script/lib/run-script-pkg.js b/node_modules/@npmcli/run-script/lib/run-script-pkg.js
index 47f386304..ccde173e0 100644
--- a/node_modules/@npmcli/run-script/lib/run-script-pkg.js
+++ b/node_modules/@npmcli/run-script/lib/run-script-pkg.js
@@ -6,7 +6,8 @@ const signalManager = require('./signal-manager.js')
const isServerPackage = require('./is-server-package.js')
// you wouldn't like me when I'm angry...
-const bruce = (id, event, cmd) => `\n> ${id ? id + ' ' : ''}${event}\n> ${cmd}\n`
+const bruce = (id, event, cmd) =>
+ `\n> ${id ? id + ' ' : ''}${event}\n> ${cmd.trim().replace(/\n/g, '\n> ')}\n`
const runScriptPkg = async options => {
const {
diff --git a/node_modules/@npmcli/run-script/package.json b/node_modules/@npmcli/run-script/package.json
index c8a052f03..925e85c06 100644
--- a/node_modules/@npmcli/run-script/package.json
+++ b/node_modules/@npmcli/run-script/package.json
@@ -1,6 +1,6 @@
{
"name": "@npmcli/run-script",
- "version": "1.7.5",
+ "version": "1.8.0",
"description": "Run a lifecycle script for a package (descendant of npm-lifecycle)",
"author": "Isaac Z. Schlueter <i@izs.me> (https://izs.me)",
"license": "ISC",
@@ -32,6 +32,7 @@
"@npmcli/promise-spawn": "^1.3.0",
"infer-owner": "^1.0.4",
"node-gyp": "^7.1.0",
+ "puka": "^1.0.1",
"read-package-json-fast": "^1.1.3"
},
"files": [
diff --git a/node_modules/puka/CHANGELOG.md b/node_modules/puka/CHANGELOG.md
new file mode 100644
index 000000000..781b81295
--- /dev/null
+++ b/node_modules/puka/CHANGELOG.md
@@ -0,0 +1,31 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [1.0.1](https://gitlab.com/rhendric/puka/-/compare/v1.0.0...v1.0.1) - 2020-05-16
+
+### Fixed
+
+- Add more carets to win32 command arguments ([45965ca](https://gitlab.com/rhendric/puka/-/commit/45965ca60fcc518082e0b085d8e81f3f3279ffb4))
+
+ As previously documented and implemented, Puka assumed that all programs
+ are batch files for the purpose of multi-escaping commands that appear
+ in pipelines. However, regardless of whether a command is in a pipeline,
+ one extra layer of escaping is needed if the command invokes a batch
+ file, which Puka was not producing. This only applies to the arguments
+ to the command, not to the batch file path, nor to paths used in
+ redirects. (The property-based spawn test which was supposed to catch
+ such oversights missed this one because it was invoking the Node.js
+ executable directly, not, as recommended in the documentation, a batch
+ file.)
+
+ Going forward, the caveats described in the documentation continue to
+ apply: if you are running programs on Windows with Puka, make sure they
+ are batch files, or you may find arguments are being escaped with too
+ many carets. As the documentation says, if this causes problems for you,
+ please open an issue so we can work out the details of what a good
+ workaround looks like.
+
+## [1.0.0](https://gitlab.com/rhendric/puka/-/tags/v1.0.0) - 2017-09-29
diff --git a/node_modules/puka/LICENSE.txt b/node_modules/puka/LICENSE.txt
new file mode 100644
index 000000000..0141196a5
--- /dev/null
+++ b/node_modules/puka/LICENSE.txt
@@ -0,0 +1,18 @@
+Copyright 2017 Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/puka/README.md b/node_modules/puka/README.md
new file mode 100644
index 000000000..2670f742b
--- /dev/null
+++ b/node_modules/puka/README.md
@@ -0,0 +1,411 @@
+# Puka
+
+[![GitLab CI pipeline status](https://gitlab.com/rhendric/puka/badges/master/pipeline.svg)](https://gitlab.com/rhendric/puka/commits/master) [![AppVeyor build status](https://img.shields.io/appveyor/ci/rhendric/puka.svg?label=windows%20tests)](https://ci.appveyor.com/project/rhendric/puka) [![Codecov status](https://img.shields.io/codecov/c/gl/rhendric/puka.svg)](https://codecov.io/gl/rhendric/puka)
+
+Puka is a cross-platform library for safely passing strings through shells.
+
+#### Contents
+
+- [Introduction](#introduction)
+ - [Why would I use Puka?](#why-would-i-use-puka)
+ - [How do I use Puka?](#how-do-i-use-puka)
+ - [What's the catch?](#whats-the-catch)
+- [API Documentation](#api-documentation)
+ - [Basic API](#basic-api)
+ - [sh](#sh)
+ - [unquoted](#unquoted)
+ - [Advanced API](#advanced-api)
+ - [quoteForShell](#quoteforshell)
+ - [quoteForCmd](#quoteforcmd)
+ - [quoteForSh](#quoteforsh)
+ - [ShellString](#shellstring)
+ - [Secret API](#secret-api)
+- [The sh DSL](#the-sh-dsl)
+ - [Syntax](#syntax)
+ - [Semantics](#semantics)
+ - [Types of placeholders](#types-of-placeholders)
+
+## Introduction
+
+### Why would I use Puka?
+
+When launching a child process from Node, you have a choice between launching
+directly from the operating system (as with [child_process.spawn](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options),
+if you don't use the `{ shell: true }` option), and running the command through
+a shell (as with [child_process.exec](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback)).
+Using a shell gives you more power, such as the ability to chain multiple
+commands together or use redirection, but you have to construct your command as
+a single string instead of using an array of arguments. And doing that can be
+buggy (if not dangerous) if you don't take care to quote any arguments
+correctly for the shell you're targeting, _and_ the quoting has to be done
+differently on Windows and non-Windows shells.
+
+Puka solves that problem by giving you a simple and platform-agnostic way to
+build shell commands with arguments that pass through your shell unaltered and
+with no unsafe side effects, **whether you are running on Windows or a
+Unix-based OS**.
+
+### How do I use Puka?
+
+Puka gives you an `sh` function intended for tagging
+[template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals),
+which quotes (if necessary) any values interpolated into the template. A simple
+example:
+
+```javascript
+const { sh } = require('puka');
+const { execSync } = require('child_process');
+
+const arg = 'file with spaces.txt';
+execSync(sh`some-command ${arg}`);
+```
+
+But Puka supports more than this! See [the `sh` DSL documentation](#the-sh-dsl)
+for a detailed description of all the features currently supported.
+
+### What's the catch?
+
+Here are the ones I know about:
+
+Puka does _not_ ensure that the actual commands you're running are
+cross-platform. If you're running npm programs, you generally won't have a
+problem with that, but if you want to run ``sh`cat file` `` on Windows, you'll
+need to depend on something like
+[cash-cat](https://www.npmjs.com/package/cash-cat).
+
+I searched for days for a way to quote or escape line breaks in arguments to
+`cmd.exe`, but couldn't find one (regular `^`-prepending and quotation marks
+don't seem to cut it). If you know of a way that works, please [open an
+issue](https://gitlab.com/rhendric/puka/issues/new) to tell me about it! Until
+then, any line break characters (`\r` or `\n`) in values being interpolated by
+`sh` will cause an error to be thrown on Windows only.
+
+Also on Windows, you may notice quoting mistakes if you run commands that
+involve invoking a native executable (not a batch file ending in `.cmd` or
+`.bat`). Unfortunately, batch files require some extra escaping on Windows, and
+Puka assumes all programs are batch files because npm creates batch file shims
+for programs it installs (and, if you care about cross-platform, you'll be
+using npm programs in your commands). If this causes problems for you, please
+[open an issue](https://gitlab.com/rhendric/puka/issues/new); if your situation
+is specific enough, there may be workarounds or improvements to Puka to be
+found.
+
+## API Documentation
+
+### Basic API
+
+
+
+
+#### sh
+
+A string template tag for safely constructing cross-platform shell commands.
+
+An `sh` template is not actually treated as a literal string to be
+interpolated; instead, it is a tiny DSL designed to make working with shell
+strings safe, simple, and straightforward. To get started quickly, see the
+examples below. [More detailed documentation][1] is available
+further down.
+
+##### Examples
+
+```javascript
+const title = '"this" & "that"';
+sh`script --title=${title}`; // => "script '--title=\"this\" & \"that\"'"
+// Note: these examples show results for non-Windows platforms.
+// On Windows, the above would instead be
+// 'script ^^^"--title=\\^^^"this\\^^^" ^^^& \\^^^"that\\^^^"^^^"'.
+
+const names = ['file1', 'file 2'];
+sh`rimraf ${names}.txt`; // => "rimraf file1.txt 'file 2.txt'"
+
+const cmd1 = ['cat', 'file 1.txt', 'file 2.txt'];
+const cmd2 = ['use-input', '-abc'];
+sh`${cmd1}|${cmd2}`; // => "cat 'file 1.txt' 'file 2.txt'|use-input -abc"
+```
+
+Returns **[String][2]** a string formatted for the platform Node is currently
+running on.
+
+#### unquoted
+
+This function permits raw strings to be interpolated into a `sh` template.
+
+**IMPORTANT**: If you're using Puka due to security concerns, make sure you
+don't pass any untrusted content to `unquoted`. This may be obvious, but
+stray punctuation in an `unquoted` section can compromise the safety of the
+entire shell command.
+
+##### Parameters
+
+- `value` any value (it will be treated as a string)
+
+##### Examples
+
+```javascript
+const both = true;
+sh`foo ${unquoted(both ? '&&' : '||')} bar`; // => 'foo && bar'
+```
+
+### Advanced API
+
+If these functions make life easier for you, go ahead and use them; they
+are just as well supported as the above. But if you aren't certain you
+need them, you probably don't.
+
+
+#### quoteForShell
+
+Quotes a string for injecting into a shell command.
+
+This function is exposed for some hypothetical case when the `sh` DSL simply
+won't do; `sh` is expected to be the more convenient option almost always.
+Compare:
+
+```javascript
+console.log('cmd' + args.map(a => ' ' + quoteForShell(a)).join(''));
+console.log(sh`cmd ${args}`); // same as above
+
+console.log('cmd' + args.map(a => ' ' + quoteForShell(a, true)).join(''));
+console.log(sh`cmd "${args}"`); // same as above
+```
+
+Additionally, on Windows, `sh` checks the entire command string for pipes,
+which subtly change how arguments need to be quoted. If your commands may
+involve pipes, you are strongly encouraged to use `sh` and not try to roll
+your own with `quoteForShell`.
+
+##### Parameters
+
+- `text` **[String][2]** to be quoted
+- `forceQuote` **[Boolean][3]?** whether to always add quotes even if the string
+ is already safe. Defaults to `false`.
+- `platform` **[String][2]?** a value that `process.platform` might take:
+ `'win32'`, `'linux'`, etc.; determines how the string is to be formatted.
+ When omitted, effectively the same as `process.platform`.
+
+Returns **[String][2]** a string that is safe for the current (or specified)
+platform.
+
+#### quoteForCmd
+
+A Windows-specific version of [quoteForShell][4].
+
+##### Parameters
+
+- `text` **[String][2]** to be quoted
+- `forceQuote` **[Boolean][3]?** whether to always add quotes even if the string
+ is already safe. Defaults to `false`.
+
+#### quoteForSh
+
+A Unix-specific version of [quoteForShell][4].
+
+##### Parameters
+
+- `text` **[String][2]** to be quoted
+- `forceQuote` **[Boolean][3]?** whether to always add quotes even if the string
+ is already safe. Defaults to `false`.
+
+#### ShellString
+
+A ShellString represents a shell command after it has been interpolated, but
+before it has been formatted for a particular platform. ShellStrings are
+useful if you want to prepare a command for a different platform than the
+current one, for instance.
+
+To create a ShellString, use `ShellString.sh` the same way you would use
+top-level `sh`.
+
+##### toString
+
+A method to format a ShellString into a regular String formatted for a
+particular platform.
+
+###### Parameters
+
+- `platform` **[String][2]?** a value that `process.platform` might take:
+ `'win32'`, `'linux'`, etc.; determines how the string is to be formatted.
+ When omitted, effectively the same as `process.platform`.
+
+Returns **[String][2]**
+
+##### sh
+
+`ShellString.sh` is a template tag just like `sh`; the only difference is
+that this function returns a ShellString which has not yet been formatted
+into a String.
+
+Returns **[ShellString][5]**
+
+### Secret API
+
+Some internals of string formatting have been exposed for the ambitious and
+brave souls who want to try to extend Puka to handle more shells or custom
+interpolated values. This ‘secret’ API is partially documented in the code
+but not here, and the semantic versioning guarantees on this API are bumped
+down by one level: in other words, minor version releases of Puka can change
+the secret API in backward-incompatible ways, and patch releases can add or
+deprecate functionality.
+
+If it's not even documented in the code, use at your own risk—no semver
+guarantees apply.
+
+
+[1]: #the-sh-dsl
+
+[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
+
+[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
+
+[4]: #quoteforshell
+
+[5]: #shellstring
+
+## The sh DSL
+
+### Syntax
+
+An `sh` template comprises words, separated by whitespace. Words can contain:
+
+- text, which is composed of any characters that are not whitespace, single or
+ double quotes, or any of the special characters
+ ``# $ & ( ) ; < > \ ` |``;
+- quotations, which are matching single or double quotes surrounding any
+ characters other than the delimiting quote; and
+- placeholders, using the standard JavaScript template syntax (`${}`).
+ (Placeholders may also appear inside quotations.)
+
+The special characters ``# $ & ( ) ; < > \ ` |``, if unquoted, form their own
+words.
+
+Redirect operators (`<`, `>`, `>>`, `2>`, etc.) receive their own special
+handling, as do semicolons. Other than these two exceptions, no attempt is made
+to understand any more sophisticated features of shell syntax.
+
+Standard JavaScript escape sequences, such as `\t`, are honored in the template
+literal, and are treated equivalently to the characters they represent. There
+is no further mechanism for escaping within the `sh` DSL itself; in particular,
+if you want to put quotes inside quotes, you have to use interpolation, like
+this:
+
+```javascript
+sh`echo "${'single = \', double = "'}"` // => "echo 'single = '\\'', double = \"'"
+```
+
+### Semantics
+
+Words that do not contain placeholders are emitted mostly verbatim to the
+output string. Quotations are formatted in the expected style for the target
+platform (single quotes for Unix, double quotes for Windows) regardless of the
+quotes used in the template literal—as with JavaScript, single and double quotes
+are interchangeable, except for the requirement to pair like with like. Unquoted
+semicolons are translated to ampersands on Windows; all other special characters
+(as enumerated above), when unquoted, are passed as-is to the output for the
+shell to interpret.
+
+Puka may still quote words not containing the above special characters, if they
+contain characters that need quoting on the target platform. For example, on
+Windows, the character `%` is used for variable interpolation in `cmd.exe`, and
+Puka quotes it on on that platform even if it appears unquoted in the template
+literal. Consequently, there is no need to be paranoid about quoting anything
+that doesn't look alphanumeric inside a `sh` template literal, for fear of being
+burned on a different operating system; anything that matches the definition of
+‘text’ above will never need manual quoting.
+
+#### Types of placeholders
+
+##### Strings
+
+If a word contains a string placeholder, then the value of the placeholder is
+interpolated into the word and the entire word, if necessary, is quoted. If
+the placeholder occurs within quotes, no further quoting is performed:
+
+```javascript
+sh`script --file="${'herp derp'}.txt"`; // => "script --file='herp derp.txt'"
+```
+
+This behavior can be exploited to force consistent quoting, if desired; but
+both of the examples below are safe on all platforms:
+
+```javascript
+const words = ['oneword', 'two words'];
+sh`minimal ${words[0]}`; // => "minimal oneword"
+sh`minimal ${words[1]}`; // => "minimal 'two words'"
+sh`consistent '${words[0]}'`; // => "consistent 'oneword'"
+sh`consistent '${words[1]}'`; // => "consistent 'two words'"
+```
+
+##### Arrays and iterables
+
+If a word contains a placeholder for an array (or other iterable object), then
+the entire word is repeated once for each value in the array, separated by
+spaces. If the array is empty, then the word is not emitted at all, and neither
+is any leading whitespace.
+
+```javascript
+const files = ['foo', 'bar'];
+sh`script ${files}`; // => "script foo bar"
+sh`script --file=${files}`; // => "script --file=foo --file=bar"
+sh`script --file=${[]}`; // => "script"
+```
+
+Note that, since special characters are their own words, the pipe operator here
+is not repeated:
+
+```javascript
+const cmd = ['script', 'foo', 'bar'];
+sh`${cmd}|another-script`; // => "script foo bar|another-script"
+```
+
+Multiple arrays in the same word generate a Cartesian product:
+
+```javascript
+const names = ['foo', 'bar'], exts = ['log', 'txt'];
+// Same word
+sh`... ${names}.${exts}`; // => "... foo.log foo.txt bar.log bar.txt"
+sh`... "${names} ${exts}"`; // => "... 'foo log' 'foo txt' 'bar log' 'bar txt'"
+
+// Not the same word (extra space just for emphasis):
+sh`... ${names} ${exts}`; // => "... foo bar log txt"
+sh`... ${names};${exts}`; // => "... foo bar;log txt"
+```
+
+Finally, if a placeholder appears in the object of a redirect operator, the
+entire redirect is repeated as necessary:
+
+```javascript
+sh`script > ${['foo', 'bar']}.txt`; // => "script > foo.txt > bar.txt"
+sh`script > ${[]}.txt`; // => "script"
+```
+
+##### unquoted
+
+The `unquoted` function returns a value that will skip being quoted when used
+in a placeholder, alone or in an array.
+
+```javascript
+const cmd = 'script < input.txt';
+const fields = ['foo', 'bar'];
+sh`${unquoted(cmd)} | json ${fields}`; // => "script < input.txt | json foo bar"
+```
+
+##### ShellString
+
+If `ShellString.sh` is used to construct an unformatted ShellString, that value
+can be used in a placeholder to insert the contents of the ShellString into the
+outer template literal. This is safer than using `unquoted` as in the previous
+example, but `unquoted` can be used when all you have is a string from another
+(trusted!) source.
+
+```javascript
+const url = 'http://example.com/data.json?x=1&y=2';
+const curl = ShellString.sh`curl -L ${url}`;
+const fields = ['foo', 'bar'];
+sh`${curl} | json ${fields}`; // => "curl -L 'http://example.com/data.json?x=1&y=2' | json foo bar"
+```
+
+##### Anything else
+
+... is treated like a string—namely, a value `x` is equivalent to `'' + x`, if
+not in one of the above categories.
diff --git a/node_modules/puka/index.js b/node_modules/puka/index.js
new file mode 100644
index 000000000..b69e47d76
--- /dev/null
+++ b/node_modules/puka/index.js
@@ -0,0 +1,804 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+/**
+ * Key a method on your object with this symbol and you can get special
+ * formatting for that value! See ShellStringText, ShellStringUnquoted, or
+ * shellStringSemicolon for examples.
+ * @ignore
+ */
+const formatSymbol = Symbol('format');
+/**
+ * This symbol is for implementing advanced behaviors like the need for extra
+ * carets in Windows shell strings that use pipes. If present, it's called in
+ * an earlier phase than formatSymbol, and is passed a mutable context that can
+ * be read during the format phase to influence formatting.
+ * @ignore
+ */
+const preformatSymbol = Symbol('preformat');
+
+// When minimum Node version becomes 6, replace calls to sticky with /.../y and
+// inline execFrom.
+let stickySupported = true;
+try {
+ new RegExp('', 'y');
+} catch (e) {
+ stickySupported = false;
+}
+const sticky = stickySupported ? source => new RegExp(source, 'y') : source => new RegExp(`^(?:${source})`);
+const execFrom = stickySupported ? (re, haystack, index) => (re.lastIndex = index, re.exec(haystack)) : (re, haystack, index) => re.exec(haystack.substr(index));
+
+function quoteForCmd(text, forceQuote) {
+ let caretDepth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
+ // See the below blog post for an explanation of this function and
+ // quoteForWin32:
+ // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
+ if (!text.length) {
+ return '""';
+ }
+ if (/[\n\r]/.test(text)) {
+ throw new Error("Line breaks can't be quoted on Windows");
+ }
+ const caretEscape = /["%]/.test(text);
+ text = quoteForWin32(text, forceQuote || !caretEscape && /[&()<>^|]/.test(text));
+ if (caretEscape) {
+ // See Win32Context for explanation of what caretDepth is for.
+ do {
+ text = text.replace(/[\t "%&()<>^|]/g, '^$&');
+ } while (caretDepth--);
+ }
+ return text;
+}
+const quoteForWin32 = (text, forceQuote) => forceQuote || /[\t "]/.test(text) ? `"${text.replace(/\\+(?=$|")/g, '$&$&').replace(/"/g, '\\"')}"` : text;
+const cmdMetaChars = /[\t\n\r "%&()<>^|]/;
+class Win32Context {
+ constructor() {
+ this.currentScope = newScope(null);
+ this.scopesByObject = new Map();
+ this.argDetectState = 0;
+ this.argSet = new Set();
+ }
+ read(text) {
+ // When cmd.exe executes a batch file, or pipes to or from one, it spawns a
+ // second copy of itself to run the inner command. This necessitates
+ // doubling up on carets so that escaped characters survive both cmd.exe
+ // invocations. See:
+ // https://stackoverflow.com/questions/8192318/why-does-delayed-expansion-fail-when-inside-a-piped-block-of-code#8194279
+ // https://ss64.com/nt/syntax-redirection.html
+ //
+ // Parentheses can create an additional subshell, requiring additional
+ // escaping... it's a mess.
+ //
+ // So here's what we do about it: we read all unquoted text in a shell
+ // string and put it through this tiny parser that looks for pipes,
+ // sequence operators (&, &&, ||), redirects, and parentheses. This can't
+ // be part of the main Puka parsing, because it can be affected by
+ // `unquoted(...)` values provided at evaluation time.
+ //
+ // Then, after associating each thing that needs to be quoted with a scope
+ // (via `mark()`), and identifying whether or not it's an argument to a
+ // command, we can determine the depth of caret escaping required in each
+ // scope and pass it (via `Formatter::quote()`) to `quoteForCmd()`.
+ //
+ // See also `ShellStringText`, which holds the logic for the previous
+ // paragraph.
+ const length = text.length;
+ for (let pos = 0, match; pos < length;) {
+ while (match = execFrom(reUnimportant, text, pos)) {
+ if (match[2] == null) {
+ // (not whitespace)
+ if (match[1] != null) {
+ // (>&)
+ this.argDetectState = this.argDetectState === 0 ? ADS_FLAG_INITIAL_REDIRECT : 0;
+ } else if (this.argDetectState !== ADS_FLAG_ARGS) {
+ this.argDetectState |= ADS_FLAG_WORD;
+ }
+ } else {
+ // (whitespace)
+ if ((this.argDetectState & ADS_FLAG_WORD) !== 0) {
+ this.argDetectState = ADS_FLAG_ARGS & ~this.argDetectState >> 1;
+ }
+ }
+ pos += match[0].length;
+ }
+ if (pos >= length) break;
+ if (match = execFrom(reSeqOp, text, pos)) {
+ this.seq();
+ pos += match[0].length;
+ } else {
+ const char = text.charCodeAt(pos);
+ if (char === CARET) {
+ pos += 2;
+ } else if (char === QUOTE) {
+ // If you were foolish enough to leave a dangling quotation mark in
+ // an unquoted span... you're likely to have bigger problems than
+ // incorrect escaping. So we just do the simplest thing of looking for
+ // the end quote only in this piece of text.
+ pos += execFrom(reNotQuote, text, pos + 1)[0].length + 2;
+ } else {
+ if (char === OPEN_PAREN) {
+ this.enterScope();
+ } else if (char === CLOSE_PAREN) {
+ this.exitScope();
+ } else if (char === PIPE) {
+ this.pipe();
+ } else {
+ // (char === '<' or '>')
+ this.argDetectState = this.argDetectState === 0 ? ADS_FLAG_INITIAL_REDIRECT : 0;
+ }
+ pos++;
+ }
+ }
+ }
+ }
+ enterScope() {
+ this.currentScope = newScope(this.currentScope);
+ this.argDetectState = 0;
+ }
+ exitScope() {
+ this.currentScope = this.currentScope.parent || (this.currentScope.parent = newScope(null));
+ this.argDetectState = ADS_FLAG_ARGS;
+ }
+ seq() {
+ // | binds tighter than sequence operators, so the latter create new sibling
+ // scopes for future |s to mutate.
+ this.currentScope = newScope(this.currentScope.parent);
+ this.argDetectState = 0;
+ }
+ pipe() {
+ this.currentScope.depthDelta = 1;
+ this.argDetectState = 0;
+ }
+ mark(obj) {
+ this.scopesByObject.set(obj, this.currentScope);
+ if (this.argDetectState === ADS_FLAG_ARGS) {
+ this.argSet.add(obj);
+ } else {
+ this.argDetectState |= ADS_FLAG_WORD;
+ }
+ }
+ at(obj) {
+ const scope = this.scopesByObject.get(obj);
+ return {
+ depth: getDepth(scope),
+ isArgument: this.argSet.has(obj),
+ isNative: scope.isNative
+ };
+ }
+}
+// These flags span the Win32Context's argument detection state machine. WORD
+// is set when the context is inside a word that is not an argument (meaning it
+// is either the first word in the command, or it is the object of a redirect).
+// ARGS is set when the context has reached the arguments of a command.
+// INITIAL_REDIRECT tracks the edge case when a redirect occurs before the
+// first word of the command (if this flag is set, reaching the end of a word
+// should take the state machine back to 0 instead of setting ADS_FLAG_ARGS).
+const ADS_FLAG_WORD = 0x1;
+const ADS_FLAG_ARGS = 0x2;
+const ADS_FLAG_INITIAL_REDIRECT = 0x4;
+const getDepth = scope => scope === null ? 0 : scope.depth !== -1 ? scope.depth : scope.depth = getDepth(scope.parent) + scope.depthDelta;
+const newScope = parent => ({
+ parent,
+ depthDelta: 0,
+ depth: -1,
+ isNative: false
+});
+const CARET = '^'.charCodeAt();
+const QUOTE = '"'.charCodeAt();
+const OPEN_PAREN = '('.charCodeAt();
+const CLOSE_PAREN = ')'.charCodeAt();
+const PIPE = '|'.charCodeAt();
+const reNotQuote = sticky('[^"]*');
+const reSeqOp = sticky('&&?|\\|\\|');
+const reUnimportant = sticky('(\\d*>&)|[^\\s"$&()<>^|]+|(\\s+)');
+
+const quoteForSh = (text, forceQuote) => text.length ? forceQuote || shMetaChars.test(text) ? `'${text.replace(/'/g, "'\\''")}'`.replace(/^(?:'')+(?!$)/, '').replace(/\\'''/g, "\\'") : text : "''";
+const shMetaChars = /[\t\n\r "#$&'()*;<>?\\`|~]/;
+
+/**
+ * To get a Formatter, call `Formatter.for`.
+ *
+ * To create a new Formatter, pass an object to `Formatter.declare`.
+ *
+ * To set the global default Formatter, assign to `Formatter.default`.
+ *
+ * @class
+ * @property {Formatter} default - The Formatter to be used when no platform
+ * is provided—for example, when creating strings with `sh`.
+ * @ignore
+ */
+function Formatter() {}
+Object.assign(Formatter,
+/** @lends Formatter */
+{
+ /**
+ * Gets a Formatter that has been declared for the provided platform, or
+ * the base `'sh'` formatter if there is no Formatter specific to this
+ * platform, or the Formatter for the current platform if no specific platform
+ * is provided.
+ */
+ for(platform) {
+ return platform == null ? Formatter.default || (Formatter.default = Formatter.for(process.platform)) : Formatter._registry.get(platform) || Formatter._registry.get('sh');
+ },
+ /**
+ * Creates a new Formatter or mutates the properties on an existing
+ * Formatter. The `platform` key on the provided properties object determines
+ * when the Formatter is retrieved.
+ */
+ declare(props) {
+ const platform = props && props.platform || 'sh';
+ const existingFormatter = Formatter._registry.get(platform);
+ const formatter = Object.assign(existingFormatter || new Formatter(), props);
+ formatter.emptyString === void 0 && (formatter.emptyString = formatter.quote('', true));
+ existingFormatter || Formatter._registry.set(formatter.platform, formatter);
+ },
+ _registry: new Map(),
+ prototype: {
+ platform: 'sh',
+ quote: quoteForSh,
+ metaChars: shMetaChars,
+ hasExtraMetaChars: false,
+ statementSeparator: ';',
+ createContext() {
+ return defaultContext;
+ }
+ }
+});
+const defaultContext = {
+ at() {}
+};
+Formatter.declare();
+Formatter.declare({
+ platform: 'win32',
+ quote(text, forceQuote, opts) {
+ const caretDepth = opts ? (opts.depth || 0) + (opts.isArgument && !opts.isNative ? 1 : 0) : 0;
+ return quoteForCmd(text, forceQuote, caretDepth);
+ },
+ metaChars: cmdMetaChars,
+ hasExtraMetaChars: true,
+ statementSeparator: '&',
+ createContext(root) {
+ const context = new this.Context();
+ root[preformatSymbol](context);
+ return context;
+ },
+ Context: Win32Context
+});
+
+const isObject = any => any === Object(any);
+function memoize(f) {
+ const cache = new WeakMap();
+ return arg => {
+ let result = cache.get(arg);
+ if (result === void 0) {
+ result = f(arg);
+ cache.set(arg, result);
+ }
+ return result;
+ };
+}
+
+/**
+ * Represents a contiguous span of text that may or must be quoted. The contents
+ * may already contain quoted segments, which will always be quoted. If unquoted
+ * segments also require quoting, the entire span will be quoted together.
+ * @ignore
+ */
+class ShellStringText {
+ constructor(contents, untested) {
+ this.contents = contents;
+ this.untested = untested;
+ }
+ [formatSymbol](formatter, context) {
+ const unformattedContents = this.contents;
+ const length = unformattedContents.length;
+ const contents = new Array(length);
+ for (let i = 0; i < length; i++) {
+ const c = unformattedContents[i];
+ contents[i] = isObject(c) && formatSymbol in c ? c[formatSymbol](formatter) : c;
+ }
+ for (let unquoted = true, i = 0; i < length; i++) {
+ const content = contents[i];
+ if (content === null) {
+ unquoted = !unquoted;
+ } else {
+ if (unquoted && (formatter.hasExtraMetaChars || this.untested && this.untested.has(i)) && formatter.metaChars.test(content)) {
+ return formatter.quote(contents.join(''), false, context.at(this));
+ }
+ }
+ }
+ const parts = [];
+ for (let quoted = null, i = 0; i < length; i++) {
+ const content = contents[i];
+ if (content === null) {
+ quoted = quoted ? (parts.push(formatter.quote(quoted.join(''), true, context.at(this))), null) : [];
+ } else {
+ (quoted || parts).push(content);
+ }
+ }
+ const result = parts.join('');
+ return result.length ? result : formatter.emptyString;
+ }
+ [preformatSymbol](context) {
+ context.mark(this);
+ }
+}
+
+/**
+ * Represents a contiguous span of text that will not be quoted.
+ * @ignore
+ */
+class ShellStringUnquoted {
+ constructor(value) {
+ this.value = value;
+ }
+ [formatSymbol]() {
+ return this.value;
+ }
+ [preformatSymbol](context) {
+ context.read(this.value);
+ }
+}
+
+/**
+ * Represents a semicolon... or an ampersand, on Windows.
+ * @ignore
+ */
+const shellStringSemicolon = {
+ [formatSymbol](formatter) {
+ return formatter.statementSeparator;
+ },
+ [preformatSymbol](context) {
+ context.seq();
+ }
+};
+
+const PLACEHOLDER = {};
+const parse = memoize(templateSpans => {
+ // These are the token types our DSL can recognize. Their values won't escape
+ // this function.
+ const TOKEN_TEXT = 0;
+ const TOKEN_QUOTE = 1;
+ const TOKEN_SEMI = 2;
+ const TOKEN_UNQUOTED = 3;
+ const TOKEN_SPACE = 4;
+ const TOKEN_REDIRECT = 5;
+ const result = [];
+ let placeholderCount = 0;
+ let prefix = null;
+ let onlyPrefixOnce = false;
+ let contents = [];
+ let quote = 0;
+ const lastSpan = templateSpans.length - 1;
+ for (let spanIndex = 0; spanIndex <= lastSpan; spanIndex++) {
+ const templateSpan = templateSpans[spanIndex];
+ const posEnd = templateSpan.length;
+ let tokenStart = 0;
+ if (spanIndex) {
+ placeholderCount++;
+ contents.push(PLACEHOLDER);
+ }
+ // For each span, we first do a recognizing pass in which we use regular
+ // expressions to identify the positions of tokens in the text, and then
+ // a second pass that actually splits the text into the minimum number of
+ // substrings necessary.
+ const recognized = []; // [type1, index1, type2, index2...]
+ let firstWordBreak = -1;
+ let lastWordBreak = -1;
+ {
+ let pos = 0,
+ match;
+ while (pos < posEnd) {
+ if (quote) {
+ if (match = execFrom(quote === CHAR_SQUO ? reQuotation1 : reQuotation2, templateSpan, pos)) {
+ recognized.push(TOKEN_TEXT, pos);
+ pos += match[0].length;
+ }
+ if (pos < posEnd) {
+ recognized.push(TOKEN_QUOTE, pos++);
+ quote = 0;
+ }
+ } else {
+ if (match = execFrom(reRedirectOrSpace, templateSpan, pos)) {
+ firstWordBreak < 0 && (firstWordBreak = pos);
+ lastWordBreak = pos;
+ recognized.push(match[1] ? TOKEN_REDIRECT : TOKEN_SPACE, pos);
+ pos += match[0].length;
+ }
+ if (match = execFrom(reText, templateSpan, pos)) {
+ const setBreaks = match[1] != null;
+ setBreaks && firstWordBreak < 0 && (firstWordBreak = pos);
+ recognized.push(setBreaks ? TOKEN_UNQUOTED : TOKEN_TEXT, pos);
+ pos += match[0].length;
+ setBreaks && (lastWordBreak = pos);
+ }
+ const char = templateSpan.charCodeAt(pos);
+ if (char === CHAR_SEMI) {
+ firstWordBreak < 0 && (firstWordBreak = pos);
+ recognized.push(TOKEN_SEMI, pos++);
+ lastWordBreak = pos;
+ } else if (char === CHAR_SQUO || char === CHAR_DQUO) {
+ recognized.push(TOKEN_QUOTE, pos++);
+ quote = char;
+ }
+ }
+ }
+ }
+ // Word breaks are only important if they separate words with placeholders,
+ // so we can ignore the first/last break if this is the first/last span.
+ spanIndex === 0 && (firstWordBreak = -1);
+ spanIndex === lastSpan && (lastWordBreak = posEnd);
+ // Here begins the second pass mentioned above. This loop runs one more
+ // iteration than there are tokens in recognized, because it handles tokens
+ // on a one-iteration delay; hence the i <= iEnd instead of i < iEnd.
+ const iEnd = recognized.length;
+ for (let i = 0, type = -1; i <= iEnd; i += 2) {
+ let typeNext = -1,
+ pos;
+ if (i === iEnd) {
+ pos = posEnd;
+ } else {
+ typeNext = recognized[i];
+ pos = recognized[i + 1];
+ // If the next token is space or redirect, but there's another word
+ // break in this span, then we can handle that token the same way we
+ // would handle unquoted text because it isn't being attached to a
+ // placeholder.
+ typeNext >= TOKEN_SPACE && pos !== lastWordBreak && (typeNext = TOKEN_UNQUOTED);
+ }
+ const breakHere = pos === firstWordBreak || pos === lastWordBreak;
+ if (pos && (breakHere || typeNext !== type)) {
+ let value = type === TOKEN_QUOTE ? null : type === TOKEN_SEMI ? shellStringSemicolon : templateSpan.substring(tokenStart, pos);
+ if (type >= TOKEN_SEMI) {
+ // This branch handles semicolons, unquoted text, spaces, and
+ // redirects. shellStringSemicolon is already a formatSymbol object;
+ // the rest need to be wrapped.
+ type === TOKEN_SEMI || (value = new ShellStringUnquoted(value));
+ // We don't need to check placeholderCount here like we do below;
+ // that's only relevant during the first word break of the span, and
+ // because this iteration of the loop is processing the token that
+ // was checked for breaks in the previous iteration, it will have
+ // already been handled. For the same reason, prefix is guaranteed to
+ // be null.
+ if (contents.length) {
+ result.push(new ShellStringText(contents, null));
+ contents = [];
+ }
+ // Only spaces and redirects become prefixes, but not if they've been
+ // rewritten to unquoted above.
+ if (type >= TOKEN_SPACE) {
+ prefix = value;
+ onlyPrefixOnce = type === TOKEN_SPACE;
+ } else {
+ result.push(value);
+ }
+ } else {
+ contents.push(value);
+ }
+ tokenStart = pos;
+ }
+ if (breakHere) {
+ if (placeholderCount) {
+ result.push({
+ contents,
+ placeholderCount,
+ prefix,
+ onlyPrefixOnce
+ });
+ } else {
+ // There's no prefix to handle in this branch; a prefix prior to this
+ // span would mean placeholderCount > 0, and a prefix in this span
+ // can't be created because spaces and redirects get rewritten to
+ // unquoted before the last word break.
+ contents.length && result.push(new ShellStringText(contents, null));
+ }
+ placeholderCount = 0;
+ prefix = null;
+ onlyPrefixOnce = false;
+ contents = [];
+ }
+ type = typeNext;
+ }
+ }
+ if (quote) {
+ throw new SyntaxError(`String is missing a ${String.fromCharCode(quote)} character`);
+ }
+ return result;
+});
+const CHAR_SEMI = ';'.charCodeAt();
+const CHAR_SQUO = "'".charCodeAt();
+const CHAR_DQUO = '"'.charCodeAt();
+const reQuotation1 = sticky("[^']+");
+const reQuotation2 = sticky('[^"]+');
+const reText = sticky('[^\\s"#$&\'();<>\\\\`|]+|([#$&()\\\\`|]+)');
+const reRedirectOrSpace = sticky('(\\s*\\d*[<>]+\\s*)|\\s+');
+
+class BitSet {
+ constructor() {
+ this.vector = new Int32Array(1);
+ }
+ has(n) {
+ return (this.vector[n >>> 5] & 1 << n) !== 0;
+ }
+ add(n) {
+ const i = n >>> 5,
+ requiredLength = i + 1;
+ let vector = this.vector,
+ _vector = vector,
+ length = _vector.length;
+ if (requiredLength > length) {
+ while (requiredLength > (length *= 2));
+ const oldValues = vector;
+ vector = new Int32Array(length);
+ vector.set(oldValues);
+ this.vector = vector;
+ }
+ vector[i] |= 1 << n;
+ }
+}
+
+function evaluate(template, values) {
+ values = values.map(toStringishArray);
+ const children = [];
+ let valuesStart = 0;
+ for (let i = 0, iMax = template.length; i < iMax; i++) {
+ const word = template[i];
+ if (formatSymbol in word) {
+ children.push(word);
+ continue;
+ }
+ const contents = word.contents,
+ placeholderCount = word.placeholderCount,
+ prefix = word.prefix,
+ onlyPrefixOnce = word.onlyPrefixOnce;
+ const kMax = contents.length;
+ const valuesEnd = valuesStart + placeholderCount;
+ const tuples = cartesianProduct(values, valuesStart, valuesEnd);
+ valuesStart = valuesEnd;
+ for (let j = 0, jMax = tuples.length; j < jMax; j++) {
+ const needSpace = j > 0;
+ const tuple = tuples[j];
+ (needSpace || prefix) && children.push(needSpace && (onlyPrefixOnce || !prefix) ? unquotedSpace : prefix);
+ let interpolatedContents = [];
+ let untested = null;
+ let quoting = false;
+ let tupleIndex = 0;
+ for (let k = 0; k < kMax; k++) {
+ const content = contents[k];
+ if (content === PLACEHOLDER) {
+ const value = tuple[tupleIndex++];
+ if (quoting) {
+ interpolatedContents.push(value);
+ } else {
+ if (isObject(value) && formatSymbol in value) {
+ if (interpolatedContents.length) {
+ children.push(new ShellStringText(interpolatedContents, untested));
+ interpolatedContents = [];
+ untested = null;
+ }
+ children.push(value);
+ } else {
+ (untested || (untested = new BitSet())).add(interpolatedContents.length);
+ interpolatedContents.push(value);
+ }
+ }
+ } else {
+ interpolatedContents.push(content);
+ content === null && (quoting = !quoting);
+ }
+ }
+ if (interpolatedContents.length) {
+ children.push(new ShellStringText(interpolatedContents, untested));
+ }
+ }
+ }
+ return children;
+}
+const primToStringish = value => value == null ? '' + value : value;
+function toStringishArray(value) {
+ let array;
+ switch (true) {
+ default:
+ if (isObject(value)) {
+ if (Array.isArray(value)) {
+ array = value;
+ break;
+ }
+ if (Symbol.iterator in value) {
+ array = Array.from(value);
+ break;
+ }
+ }
+ array = [value];
+ }
+ return array.map(primToStringish);
+}
+function cartesianProduct(arrs, start, end) {
+ const size = end - start;
+ let resultLength = 1;
+ for (let i = start; i < end; i++) {
+ resultLength *= arrs[i].length;
+ }
+ if (resultLength > 1e6) {
+ throw new RangeError("Far too many elements to interpolate");
+ }
+ const result = new Array(resultLength);
+ const indices = new Array(size).fill(0);
+ for (let i = 0; i < resultLength; i++) {
+ const value = result[i] = new Array(size);
+ for (let j = 0; j < size; j++) {
+ value[j] = arrs[j + start][indices[j]];
+ }
+ for (let j = size - 1; j >= 0; j--) {
+ if (++indices[j] < arrs[j + start].length) break;
+ indices[j] = 0;
+ }
+ }
+ return result;
+}
+const unquotedSpace = new ShellStringUnquoted(' ');
+
+/**
+ * A ShellString represents a shell command after it has been interpolated, but
+ * before it has been formatted for a particular platform. ShellStrings are
+ * useful if you want to prepare a command for a different platform than the
+ * current one, for instance.
+ *
+ * To create a ShellString, use `ShellString.sh` the same way you would use
+ * top-level `sh`.
+ */
+class ShellString {
+ /** @hideconstructor */
+ constructor(children) {
+ this.children = children;
+ }
+ /**
+ * `ShellString.sh` is a template tag just like `sh`; the only difference is
+ * that this function returns a ShellString which has not yet been formatted
+ * into a String.
+ * @returns {ShellString}
+ * @function sh
+ * @static
+ * @memberof ShellString
+ */
+ static sh(templateSpans) {
+ for (var _len = arguments.length, values = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ values[_key - 1] = arguments[_key];
+ }
+ return new ShellString(evaluate(parse(templateSpans), values));
+ }
+ /**
+ * A method to format a ShellString into a regular String formatted for a
+ * particular platform.
+ *
+ * @param {String} [platform] a value that `process.platform` might take:
+ * `'win32'`, `'linux'`, etc.; determines how the string is to be formatted.
+ * When omitted, effectively the same as `process.platform`.
+ * @returns {String}
+ */
+ toString(platform) {
+ return this[formatSymbol](Formatter.for(platform));
+ }
+ [formatSymbol](formatter) {
+ let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : formatter.createContext(this);
+ return this.children.map(child => child[formatSymbol](formatter, context)).join('');
+ }
+ [preformatSymbol](context) {
+ const children = this.children;
+ for (let i = 0, iMax = children.length; i < iMax; i++) {
+ const child = children[i];
+ if (preformatSymbol in child) {
+ child[preformatSymbol](context);
+ }
+ }
+ }
+}
+
+/**
+ * A Windows-specific version of {@link quoteForShell}.
+ * @param {String} text to be quoted
+ * @param {Boolean} [forceQuote] whether to always add quotes even if the string
+ * is already safe. Defaults to `false`.
+ */
+
+/**
+ * A Unix-specific version of {@link quoteForShell}.
+ * @param {String} text to be quoted
+ * @param {Boolean} [forceQuote] whether to always add quotes even if the string
+ * is already safe. Defaults to `false`.
+ */
+
+/**
+ * Quotes a string for injecting into a shell command.
+ *
+ * This function is exposed for some hypothetical case when the `sh` DSL simply
+ * won't do; `sh` is expected to be the more convenient option almost always.
+ * Compare:
+ *
+ * ```javascript
+ * console.log('cmd' + args.map(a => ' ' + quoteForShell(a)).join(''));
+ * console.log(sh`cmd ${args}`); // same as above
+ *
+ * console.log('cmd' + args.map(a => ' ' + quoteForShell(a, true)).join(''));
+ * console.log(sh`cmd "${args}"`); // same as above
+ * ```
+ *
+ * Additionally, on Windows, `sh` checks the entire command string for pipes,
+ * which subtly change how arguments need to be quoted. If your commands may
+ * involve pipes, you are strongly encouraged to use `sh` and not try to roll
+ * your own with `quoteForShell`.
+ *
+ * @param {String} text to be quoted
+ * @param {Boolean} [forceQuote] whether to always add quotes even if the string
+ * is already safe. Defaults to `false`.
+ * @param {String} [platform] a value that `process.platform` might take:
+ * `'win32'`, `'linux'`, etc.; determines how the string is to be formatted.
+ * When omitted, effectively the same as `process.platform`.
+ *
+ * @returns {String} a string that is safe for the current (or specified)
+ * platform.
+ */
+function quoteForShell(text, forceQuote, platform) {
+ return Formatter.for(platform).quote(text, forceQuote);
+}
+
+/**
+ * A string template tag for safely constructing cross-platform shell commands.
+ *
+ * An `sh` template is not actually treated as a literal string to be
+ * interpolated; instead, it is a tiny DSL designed to make working with shell
+ * strings safe, simple, and straightforward. To get started quickly, see the
+ * examples below. {@link #the-sh-dsl More detailed documentation} is available
+ * further down.
+ *
+ * @name sh
+ * @example
+ * const title = '"this" & "that"';
+ * sh`script --title=${title}`; // => "script '--title=\"this\" & \"that\"'"
+ * // Note: these examples show results for non-Windows platforms.
+ * // On Windows, the above would instead be
+ * // 'script ^^^"--title=\\^^^"this\\^^^" ^^^& \\^^^"that\\^^^"^^^"'.
+ *
+ * const names = ['file1', 'file 2'];
+ * sh`rimraf ${names}.txt`; // => "rimraf file1.txt 'file 2.txt'"
+ *
+ * const cmd1 = ['cat', 'file 1.txt', 'file 2.txt'];
+ * const cmd2 = ['use-input', '-abc'];
+ * sh`${cmd1}|${cmd2}`; // => "cat 'file 1.txt' 'file 2.txt'|use-input -abc"
+ *
+ * @returns {String} - a string formatted for the platform Node is currently
+ * running on.
+ */
+const sh = function () {
+ return ShellString.sh.apply(ShellString, arguments).toString();
+};
+
+/**
+ * This function permits raw strings to be interpolated into a `sh` template.
+ *
+ * **IMPORTANT**: If you're using Puka due to security concerns, make sure you
+ * don't pass any untrusted content to `unquoted`. This may be obvious, but
+ * stray punctuation in an `unquoted` section can compromise the safety of the
+ * entire shell command.
+ *
+ * @param value - any value (it will be treated as a string)
+ *
+ * @example
+ * const both = true;
+ * sh`foo ${unquoted(both ? '&&' : '||')} bar`; // => 'foo && bar'
+ */
+const unquoted = value => new ShellStringUnquoted(value);
+
+exports.Formatter = Formatter;
+exports.ShellString = ShellString;
+exports.ShellStringText = ShellStringText;
+exports.ShellStringUnquoted = ShellStringUnquoted;
+exports.quoteForCmd = quoteForCmd;
+exports.quoteForSh = quoteForSh;
+exports.quoteForShell = quoteForShell;
+exports.sh = sh;
+exports.shellStringSemicolon = shellStringSemicolon;
+exports.formatSymbol = formatSymbol;
+exports.preformatSymbol = preformatSymbol;
+exports.unquoted = unquoted;
diff --git a/node_modules/puka/package.json b/node_modules/puka/package.json
new file mode 100644
index 000000000..41798dc24
--- /dev/null
+++ b/node_modules/puka/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "puka",
+ "version": "1.0.1",
+ "description": "A cross-platform library for safely passing strings through shells",
+ "keywords": [
+ "args",
+ "arguments",
+ "cmd",
+ "command",
+ "command-line",
+ "cross-platform",
+ "escape",
+ "escaping",
+ "exec",
+ "linux",
+ "mac",
+ "macos",
+ "osx",
+ "quote",
+ "quoting",
+ "sh",
+ "shell",
+ "spawn",
+ "unix",
+ "win",
+ "win32",
+ "windows"
+ ],
+ "homepage": "https://gitlab.com/rhendric/puka",
+ "bugs": "https://gitlab.com/rhendric/puka/issues",
+ "license": "MIT",
+ "author": "Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>",
+ "repository": "gitlab:rhendric/puka",
+ "dependencies": {},
+ "engines": {
+ "node": ">=4"
+ }
+} \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 57dca6cde..33dd98c2a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -81,7 +81,7 @@
"@npmcli/arborist": "^1.0.11",
"@npmcli/ci-detect": "^1.2.0",
"@npmcli/config": "^1.2.1",
- "@npmcli/run-script": "^1.7.5",
+ "@npmcli/run-script": "^1.8.0",
"abbrev": "~1.1.1",
"ansicolors": "~0.3.2",
"ansistyles": "~0.1.3",
@@ -525,15 +525,16 @@
}
},
"node_modules/@npmcli/run-script": {
- "version": "1.7.5",
- "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.7.5.tgz",
- "integrity": "sha512-G8taZCc0HExiLadB3Nv0/h1TWjQ9HhkqW/waaH9MNqMX26rPRyNORst1LlNXOO8QnxQF4tNdJfc/Z3TuEtiyww==",
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.0.tgz",
+ "integrity": "sha512-ljPLRbQM5byhqacWl9kIjt/yPMee0heaTskaMBFaFvYzOXNJ64h27xV96Sr+LnjJpqR0qJejG36QzJkXILvghQ==",
"inBundle": true,
"dependencies": {
"@npmcli/node-gyp": "^1.0.0",
"@npmcli/promise-spawn": "^1.3.0",
"infer-owner": "^1.0.4",
"node-gyp": "^7.1.0",
+ "puka": "^1.0.1",
"read-package-json-fast": "^1.1.3"
}
},
@@ -5075,6 +5076,15 @@
"inBundle": true,
"license": "MIT"
},
+ "node_modules/puka": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/puka/-/puka-1.0.1.tgz",
+ "integrity": "sha512-ssjRZxBd7BT3dte1RR3VoeT2cT/ODH8x+h0rUF1rMqB0srHYf48stSDWfiYakTp5UBZMxroZhB2+ExLDHm7W3g==",
+ "inBundle": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -9201,14 +9211,15 @@
}
},
"@npmcli/run-script": {
- "version": "1.7.5",
- "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.7.5.tgz",
- "integrity": "sha512-G8taZCc0HExiLadB3Nv0/h1TWjQ9HhkqW/waaH9MNqMX26rPRyNORst1LlNXOO8QnxQF4tNdJfc/Z3TuEtiyww==",
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.0.tgz",
+ "integrity": "sha512-ljPLRbQM5byhqacWl9kIjt/yPMee0heaTskaMBFaFvYzOXNJ64h27xV96Sr+LnjJpqR0qJejG36QzJkXILvghQ==",
"requires": {
"@npmcli/node-gyp": "^1.0.0",
"@npmcli/promise-spawn": "^1.3.0",
"infer-owner": "^1.0.4",
"node-gyp": "^7.1.0",
+ "puka": "^1.0.1",
"read-package-json-fast": "^1.1.3"
}
},
@@ -12414,6 +12425,11 @@
"psl": {
"version": "1.8.0"
},
+ "puka": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/puka/-/puka-1.0.1.tgz",
+ "integrity": "sha512-ssjRZxBd7BT3dte1RR3VoeT2cT/ODH8x+h0rUF1rMqB0srHYf48stSDWfiYakTp5UBZMxroZhB2+ExLDHm7W3g=="
+ },
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
diff --git a/package.json b/package.json
index ded215307..5a7757cfe 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
"@npmcli/arborist": "^1.0.11",
"@npmcli/ci-detect": "^1.2.0",
"@npmcli/config": "^1.2.1",
- "@npmcli/run-script": "^1.7.5",
+ "@npmcli/run-script": "^1.8.0",
"abbrev": "~1.1.1",
"ansicolors": "~0.3.2",
"ansistyles": "~0.1.3",