From 2fec11c1289fddcc40e21b3ec2ce85d5515d013d Mon Sep 17 00:00:00 2001 From: Eric Cornelissen Date: Sat, 19 Aug 2023 22:14:11 +0200 Subject: [PATCH] Offer a "oneshot" module that doesn't require initializing a class --- .eslintrc.yml | 2 + .gitignore | 2 + .npmignore | 4 ++ index.d.ts | 6 +-- oneshot.d.ts | 108 ++++++++++++++++++++++++++++++++++++++++++ oneshot.js | 119 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 12 ++++- rollup.config.js | 8 ++++ script/clean.js | 9 +++- 9 files changed, 265 insertions(+), 5 deletions(-) create mode 100644 oneshot.d.ts create mode 100644 oneshot.js diff --git a/.eslintrc.yml b/.eslintrc.yml index 91439a797..163693d72 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -741,5 +741,7 @@ ignorePatterns: - node_modules/ - index.cjs - index.d.cts + - oneshot.cjs + - oneshot.d.cts - testing.cjs - testing.d.cts diff --git a/.gitignore b/.gitignore index 8bcdd33e5..4adfa186a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ _reports/ crash-* index.cjs index.d.cts +oneshot.cjs +oneshot.d.cts testing.cjs testing.d.cts diff --git a/.npmignore b/.npmignore index 93251b098..8feed6cba 100644 --- a/.npmignore +++ b/.npmignore @@ -9,6 +9,10 @@ !index.d.cts !index.d.ts !index.js +!oneshot.cjs +!oneshot.d.cts +!oneshot.d.ts +!oneshot.js !package.json !LICENSE !README.md diff --git a/index.d.ts b/index.d.ts index 11a15ae6a..fe2305502 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,7 +8,7 @@ * * @since 2.0.0 */ -interface ShescapeOptions { +export interface ShescapeOptions { /** * Whether or not to protect against flag and option (such as `--verbose`) * injection @@ -67,7 +67,7 @@ interface ShescapeOptions { * spawnOptions * ); */ -interface Shescape { +export class Shescape { /** * Create a new {@link Shescape} instance. * @@ -77,7 +77,7 @@ interface Shescape { * @throws {Error} The shell is not supported. * @since 2.0.0 */ - new (options: ShescapeOptions): Shescape; + new(options: ShescapeOptions): Shescape; /** * Take a single value, the argument, and escape any dangerous characters. diff --git a/oneshot.d.ts b/oneshot.d.ts new file mode 100644 index 000000000..fd60d0f69 --- /dev/null +++ b/oneshot.d.ts @@ -0,0 +1,108 @@ +/** + * @overview Contains TypeScript type definitions for Shescape's oneshot + * alternative. + * @license MPL-2.0 + */ + +import type { ShescapeOptions } from "shescape"; + +/** + * Take a single value, the argument, and escape any dangerous characters. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * spawn( + * "echo", + * ["Hello", shescape.escape(userInput, { shell: false })], + * null // `options.shell` MUST be falsy + * ); + * @param {string} arg The argument to escape. + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @returns {string} The escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} The shell is not supported. + * @since 0.1.0 + */ +export function escape(arg: string, options?: ShescapeOptions): string; + +/** + * Take an array of values, the arguments, and escape any dangerous characters + * in every argument. + * + * Non-array inputs will be converted to one-value arrays and non-string values + * will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * spawn( + * "echo", + * shescape.escapeAll(["Hello", userInput], { shell: false }), + * null // `options.shell` MUST be falsy + * ); + * @param {string[]} args The arguments to escape. + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @returns {string[]} The escaped arguments. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} The shell is not supported. + * @since 1.1.0 + */ +export function escapeAll(args: string[], options?: ShescapeOptions): string[]; + +/** + * Take a single value, the argument, put shell-specific quotes around it and + * escape any dangerous characters. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * const spawnOptions = { shell: true }; // `options.shell` SHOULD be truthy + * spawn( + * "echo", + * ["Hello", shescape.quote(userInput, { shell: spawnOptions.shell })], + * spawnOptions + * ); + * @param {string} arg The argument to quote and escape. + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @returns {string} The quoted and escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} The shell is not supported. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 0.3.0 + */ +export function quote(arg: string, options?: ShescapeOptions): string; + +/** + * Take an array of values, the arguments, put shell-specific quotes around + * every argument and escape any dangerous characters in every argument. + * + * Non-array inputs will be converted to one-value arrays and non-string + * values will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * const spawnOptions = { shell: true }; // `options.shell` SHOULD be truthy + * spawn( + * "echo", + * shescape.quoteAll(["Hello", userInput], { shell: spawnOptions.shell }), + * spawnOptions + * ); + * @param {string[]} args The arguments to quote and escape. + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @returns {string[]} The quoted and escaped arguments. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} The shell is not supported. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 0.4.0 + */ +export function quoteAll(args: string[], options?: ShescapeOptions): string[]; diff --git a/oneshot.js b/oneshot.js new file mode 100644 index 000000000..45505891f --- /dev/null +++ b/oneshot.js @@ -0,0 +1,119 @@ +/** + * @overview Alternative entrypoint for the library requiring no setup. + * @license MPL-2.0 + */ + +import { Shescape } from "./index.js"; + +/** + * Take a single value, the argument, and escape any dangerous characters. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * spawn( + * "echo", + * ["Hello", shescape.escape(userInput, { shell: false })], + * null // `options.shell` MUST be falsy + * ); + * @param {string} arg The argument to escape. + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @returns {string} The escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} The shell is not supported. + * @since 0.1.0 + */ +export function escape(arg, options) { + const shescape = new Shescape(options); + return shescape.escape(arg); +} + +/** + * Take an array of values, the arguments, and escape any dangerous characters + * in every argument. + * + * Non-array inputs will be converted to one-value arrays and non-string values + * will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * spawn( + * "echo", + * shescape.escapeAll(["Hello", userInput], { shell: false }), + * null // `options.shell` MUST be falsy + * ); + * @param {string[]} args The arguments to escape. + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @returns {string[]} The escaped arguments. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} The shell is not supported. + * @since 1.1.0 + */ +export function escapeAll(args, options) { + const shescape = new Shescape(options); + return shescape.escapeAll(args); +} + +/** + * Take a single value, the argument, put shell-specific quotes around it and + * escape any dangerous characters. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * const spawnOptions = { shell: true }; // `options.shell` SHOULD be truthy + * spawn( + * "echo", + * ["Hello", shescape.quote(userInput, { shell: spawnOptions.shell })], + * spawnOptions + * ); + * @param {string} arg The argument to quote and escape. + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @returns {string} The quoted and escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} The shell is not supported. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 0.3.0 + */ +export function quote(arg, options) { + const shescape = new Shescape(options); + return shescape.quote(arg); +} + +/** + * Take an array of values, the arguments, put shell-specific quotes around + * every argument and escape any dangerous characters in every argument. + * + * Non-array inputs will be converted to one-value arrays and non-string + * values will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * const spawnOptions = { shell: true }; // `options.shell` SHOULD be truthy + * spawn( + * "echo", + * shescape.quoteAll(["Hello", userInput], { shell: spawnOptions.shell }), + * spawnOptions + * ); + * @param {string[]} args The arguments to quote and escape. + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @returns {string[]} The quoted and escaped arguments. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} The shell is not supported. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 0.4.0 + */ +export function quoteAll(args, options) { + const shescape = new Shescape(options); + return shescape.quoteAll(args); +} diff --git a/package.json b/package.json index 4170743df..63f6d1d00 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,16 @@ "default": "./index.cjs" } }, + "./oneshot": { + "import": { + "types": "./oneshot.d.ts", + "default": "./oneshot.js" + }, + "require": { + "types": "./oneshot.d.cts", + "default": "./oneshot.cjs" + } + }, "./testing": { "import": { "types": "./testing.d.ts", @@ -127,7 +137,7 @@ "test:e2e": "ava test/e2e/**/*.test.js --timeout 1m", "test:integration": "ava test/integration/**/*.test.js --timeout 1m", "test:unit": "ava test/unit/**/*.test.js", - "transpile": "rollup --config rollup.config.js && cp index.d.ts index.d.cts && cp testing.d.ts testing.d.cts", + "transpile": "rollup --config rollup.config.js && cp index.d.ts index.d.cts && cp oneshot.d.ts oneshot.d.cts && cp testing.d.ts testing.d.cts", "verify": "npm run format:check && npm run license-check && npm run lint && npm run coverage && npm run vet", "vet": "npm run vet:deps", "vet:deps": "knip --config .knip.jsonc" diff --git a/rollup.config.js b/rollup.config.js index 02604709d..227be413f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -18,6 +18,14 @@ export default [ }, external, }, + { + input: "oneshot.js", + output: { + file: "oneshot.cjs", + format: "cjs", + }, + external, + }, { input: "testing.js", output: { diff --git a/script/clean.js b/script/clean.js index 74f45b169..0d23af7f0 100644 --- a/script/clean.js +++ b/script/clean.js @@ -7,7 +7,14 @@ import * as fs from "node:fs"; import * as path from "node:path"; -const files = ["index.cjs", "index.d.cts", "testing.cjs", "testing.d.cts"]; +const files = [ + "index.cjs", + "index.d.cts", + "oneshot.cjs", + "oneshot.d.cts", + "testing.cjs", + "testing.d.cts", +]; const folders = ["./.corpus", "./.nyc_output", "./.temp", "./_reports"]; for (const file of files) {