From b74c05d88dc48fabef031ea66ffaa4e548845655 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Tue, 17 Nov 2020 14:56:14 -0500 Subject: @npmcli/run-script@1.8.0 --- .../@npmcli/run-script/lib/make-spawn-args.js | 20 +- .../@npmcli/run-script/lib/run-script-pkg.js | 3 +- node_modules/@npmcli/run-script/package.json | 3 +- node_modules/puka/CHANGELOG.md | 31 + node_modules/puka/LICENSE.txt | 18 + node_modules/puka/README.md | 411 +++++++++++ node_modules/puka/index.js | 804 +++++++++++++++++++++ node_modules/puka/package.json | 38 + package-lock.json | 30 +- package.json | 2 +- 10 files changed, 1349 insertions(+), 11 deletions(-) create mode 100644 node_modules/puka/CHANGELOG.md create mode 100644 node_modules/puka/LICENSE.txt create mode 100644 node_modules/puka/README.md create mode 100644 node_modules/puka/index.js create mode 100644 node_modules/puka/package.json 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 (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 + +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 ", + "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", -- cgit v1.2.3