diff --git a/package.json b/package.json index ba38ef45b6..1c73ead427 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "test:cov": "c8 --reporter=html npm run test", "test": "node zx.mjs test/full.test.mjs", "test:zx": "npm run test zx", - "test:index": "npm run test index" + "test:index": "npm run test index", + "hooks-upd": "simple-git-hooks" }, "dependencies": { "@types/fs-extra": "^9.0.13", @@ -39,7 +40,11 @@ "yaml": "^2.0.1" }, "devDependencies": { - "c8": "^7.11.2" + "c8": "^7.11.2", + "simple-git-hooks": "^2.7.0" + }, + "simple-git-hooks": { + "pre-commit": "npx prettier --single-quote --no-semi --write src test" }, "publishConfig": { "registry": "https://wombat-dressing-room.appspot.com" diff --git a/src/als.mjs b/src/als.mjs index fc39bb6ecf..48bd959151 100644 --- a/src/als.mjs +++ b/src/als.mjs @@ -12,13 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {AsyncLocalStorage} from 'node:async_hooks' +import { AsyncLocalStorage } from 'node:async_hooks' let root -export const als = new AsyncLocalStorage() -export const boundCtxKey = Symbol('AsyncLocalStorage bound ctx') -export const getCtx = () => als.getStore() -export const setRootCtx = (ctx) => { als.enterWith(ctx); root = ctx } -export const getRootCtx = () => root -export const runInCtx = (ctx, cb) => als.run(ctx, cb) +const als = new AsyncLocalStorage() + +export function getCtx() { + return als.getStore() +} +export function setRootCtx(ctx) { + als.enterWith(ctx) + root = ctx +} +export function getRootCtx() { + return root +} +export function runInCtx(ctx, cb) { + return als.run(ctx, cb) +} diff --git a/src/core.mjs b/src/core.mjs index 79e9438a00..9df55fe5be 100644 --- a/src/core.mjs +++ b/src/core.mjs @@ -12,27 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {inspect} from 'node:util' -import {spawn} from 'node:child_process' -import {chalk, psTree, which} from './goods.mjs' -import {boundCtxKey, getCtx, runInCtx, setRootCtx} from './als.mjs' -import {randId} from './util.mjs' -import {printStd, printCmd} from './print.mjs' -import {formatCmd, quote} from './guards.mjs' +import { inspect } from 'node:util' +import { spawn } from 'node:child_process' +import { chalk, psTree, which } from './goods.mjs' +import { getCtx, runInCtx, setRootCtx } from './als.mjs' +import { printStd, printCmd } from './print.mjs' +import { formatCmd, quote } from './guards.mjs' -export { getCtx, runInCtx, boundCtxKey } +export { getCtx, runInCtx } export function $(...args) { let resolve, reject - let promise = new ProcessPromise((...args) => [resolve, reject] = args) + let promise = new ProcessPromise((...args) => ([resolve, reject] = args)) - promise[boundCtxKey] = { + promise.ctx = { ...getCtx(), - id: randId(), - cmd: formatCmd(...args), - __from: (new Error().stack.split(/^\s*at\s/m)[2]).trim(), + cmd: formatCmd(...args), + __from: new Error().stack.split(/^\s*at\s/m)[2].trim(), resolve, - reject + reject, } setImmediate(() => promise._run()) // Make sure all subprocesses are started, if not explicitly by await or then(). @@ -52,8 +50,7 @@ $.prefix = '' // Bash not found, no prefix. try { $.shell = which.sync('bash') $.prefix = 'set -euo pipefail;' -} catch (e) { -} +} catch (e) {} export class ProcessPromise extends Promise { child = undefined @@ -82,8 +79,10 @@ export class ProcessPromise extends Promise { } get exitCode() { - return this - .then(p => p.exitCode, p => p.exitCode) + return this.then( + (p) => p.exitCode, + (p) => p.exitCode + ) } pipe(dest) { @@ -91,7 +90,9 @@ export class ProcessPromise extends Promise { throw new Error('The pipe() method does not take strings. Forgot $?') } if (this._resolved === true) { - throw new Error('The pipe() method shouldn\'t be called after promise is already resolved!') + throw new Error( + "The pipe() method shouldn't be called after promise is already resolved!" + ) } this._piped = true if (dest instanceof ProcessPromise) { @@ -106,25 +107,23 @@ export class ProcessPromise extends Promise { } async kill(signal = 'SIGTERM') { - this.catch(_ => _) + this.catch((_) => _) let children = await psTree(this.child.pid) for (const p of children) { try { process.kill(p.PID, signal) - } catch (e) { - } + } catch (e) {} } try { process.kill(this.child.pid, signal) - } catch (e) { - } + } catch (e) {} } _run() { if (this.child) return // The _run() called from two places: then() and setTimeout(). if (this._prerun) this._prerun() // In case $1.pipe($2), the $2 returned, and on $2._run() invoke $1._run(). - const ctx = this[boundCtxKey] + const ctx = this.ctx runInCtx(ctx, () => { const { nothrow, @@ -136,7 +135,7 @@ export class ProcessPromise extends Promise { maxBuffer, __from, resolve, - reject + reject, } = ctx printCmd(cmd) @@ -147,13 +146,14 @@ export class ProcessPromise extends Promise { stdio: [this._inheritStdin ? 'inherit' : 'pipe', 'pipe', 'pipe'], windowsHide: true, maxBuffer, - env + env, }) child.on('close', (code, signal) => { - let message = `${stderr || '\n'} at ${__from}` - message += `\n exit code: ${code}${exitCodeInfo(code) ? ' (' + exitCodeInfo(code) + ')' : ''}` + message += `\n exit code: ${code}${ + exitCodeInfo(code) ? ' (' + exitCodeInfo(code) + ')' : '' + }` if (signal !== null) { message += `\n signal: ${signal}` } @@ -164,18 +164,20 @@ export class ProcessPromise extends Promise { stderr, combined, message, - }); - (code === 0 || nothrow ? resolve : reject)(output) + }) + ;(code === 0 || nothrow ? resolve : reject)(output) this._resolved = true }) - let stdout = '', stderr = '', combined = '' - let onStdout = data => { + let stdout = '', + stderr = '', + combined = '' + let onStdout = (data) => { printStd(data) stdout += data combined += data } - let onStderr = data => { + let onStderr = (data) => { printStd(null, data) stderr += data combined += data @@ -195,7 +197,7 @@ export class ProcessOutput extends Error { #stderr = '' #combined = '' - constructor({code, signal, stdout, stderr, combined, message}) { + constructor({ code, signal, stdout, stderr, combined, message }) { super(message) this.#code = code this.#signal = signal @@ -225,12 +227,16 @@ export class ProcessOutput extends Error { } [inspect.custom]() { - let stringify = (s, c) => s.length === 0 ? '\'\'' : c(inspect(s)) + let stringify = (s, c) => (s.length === 0 ? "''" : c(inspect(s))) return `ProcessOutput { stdout: ${stringify(this.stdout, chalk.green)}, stderr: ${stringify(this.stderr, chalk.red)}, signal: ${inspect(this.signal)}, - exitCode: ${(this.exitCode === 0 ? chalk.green : chalk.red)(this.exitCode)}${(exitCodeInfo(this.exitCode) ? chalk.grey(' (' + exitCodeInfo(this.exitCode) + ')') : '')} + exitCode: ${(this.exitCode === 0 ? chalk.green : chalk.red)(this.exitCode)}${ + exitCodeInfo(this.exitCode) + ? chalk.grey(' (' + exitCodeInfo(this.exitCode) + ')') + : '' + } }` } } diff --git a/src/experimental.mjs b/src/experimental.mjs index 595391de3c..1b74aabf9f 100644 --- a/src/experimental.mjs +++ b/src/experimental.mjs @@ -12,37 +12,47 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {ProcessOutput, $} from './core.mjs' -import {sleep} from './goods.mjs' -import {isStr} from './util.mjs' +import { ProcessOutput, $ } from './core.mjs' +import { sleep } from './goods.mjs' +import { isString } from './util.mjs' // Retries a command a few times. Will return after the first // successful attempt, or will throw after specifies attempts count. -export const retry = (count = 5, delay = 0) => async (cmd, ...args) => { - while (count --> 0) try { - return await $(cmd, ...args) - } catch (p) { - if (count === 0) throw p - if (delay) await sleep(delay) +export function retry(count = 5, delay = 0) { + return async function (cmd, ...args) { + while (count-- > 0) + try { + return await $(cmd, ...args) + } catch (p) { + if (count === 0) throw p + if (delay) await sleep(delay) + } } } // Runs and sets a timeout for a cmd -export const withTimeout = (timeout, signal) => async (cmd, ...args) => { - let p = $(cmd, ...args) - if (!timeout) return p +export function withTimeout(timeout, signal) { + return async function (cmd, ...args) { + let p = $(cmd, ...args) + if (!timeout) return p - let timer = setTimeout(() => p.kill(signal), timeout) + let timer = setTimeout(() => p.kill(signal), timeout) - return p.finally(() => clearTimeout(timer)) + return p.finally(() => clearTimeout(timer)) + } } // A console.log() alternative which can take ProcessOutput. export function echo(pieces, ...args) { let msg let lastIdx = pieces.length - 1 - if (Array.isArray(pieces) && pieces.every(isStr) && lastIdx === args.length) { - msg = args.map((a, i) => pieces[i] + stringify(a)).join('') + pieces[lastIdx] + if ( + Array.isArray(pieces) && + pieces.every(isString) && + lastIdx === args.length + ) { + msg = + args.map((a, i) => pieces[i] + stringify(a)).join('') + pieces[lastIdx] } else { msg = [pieces, ...args].map(stringify).join(' ') } @@ -58,6 +68,10 @@ function stringify(arg) { // Starts a simple CLI spinner, and returns stop() func. export function startSpinner(title = '') { - let i = 0, spin = () => process.stdout.write(` ${'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'[i++ % 10]} ${title}\r`) - return (id => () => clearInterval(id))(setInterval(spin, 100)) + let i = 0, + spin = () => process.stdout.write(` ${'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'[i++ % 10]} ${title}\r`) + return ( + (id) => () => + clearInterval(id) + )(setInterval(spin, 100)) } diff --git a/src/globals.mjs b/src/globals.mjs index 7dfc55513e..0cf0d8a86c 100644 --- a/src/globals.mjs +++ b/src/globals.mjs @@ -1,3 +1,3 @@ -import {registerGlobals} from './index.mjs' +import { registerGlobals } from './index.mjs' registerGlobals() diff --git a/src/goods.mjs b/src/goods.mjs index fbaa5ab1be..f32aa69c9b 100644 --- a/src/goods.mjs +++ b/src/goods.mjs @@ -14,12 +14,12 @@ import * as globbyModule from 'globby' import minimist from 'minimist' -import {setTimeout as sleep} from 'node:timers/promises' +import { setTimeout as sleep } from 'node:timers/promises' import { promisify } from 'node:util' import psTreeModule from 'ps-tree' import nodeFetch from 'node-fetch' -import {getCtx, getRootCtx} from './als.mjs' -import {colorize} from './print.mjs' +import { getCtx, getRootCtx } from './als.mjs' +import { colorize } from './print.mjs' export { default as chalk } from 'chalk' export { default as fs } from 'fs-extra' diff --git a/src/guards.mjs b/src/guards.mjs index 36a862741d..1c024d12a1 100644 --- a/src/guards.mjs +++ b/src/guards.mjs @@ -12,32 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {getCtx} from './als.mjs' +import { getCtx } from './als.mjs' export function quote(arg) { if (/^[a-z0-9/_.-]+$/i.test(arg) || arg === '') { return arg } - return `$'` - + arg + return ( + `$'` + + arg .replace(/\\/g, '\\\\') - .replace(/'/g, '\\\'') + .replace(/'/g, "\\'") .replace(/\f/g, '\\f') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t') .replace(/\v/g, '\\v') - .replace(/\0/g, '\\0') - + `'` + .replace(/\0/g, '\\0') + + `'` + ) } -export const formatCmd = (pieces, ...args) => { - let cmd = pieces[0], i = 0 +export function formatCmd(pieces, ...args) { + let cmd = pieces[0], + i = 0 let quote = getCtx().quote while (i < args.length) { let s if (Array.isArray(args[i])) { - s = args[i].map(x => quote(substitute(x))).join(' ') + s = args[i].map((x) => quote(substitute(x))).join(' ') } else { s = quote(substitute(args[i])) } diff --git a/src/hooks.mjs b/src/hooks.mjs index 3de128ef6a..e9ca21e65d 100644 --- a/src/hooks.mjs +++ b/src/hooks.mjs @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {boundCtxKey} from './als.mjs' - export function nothrow(promise) { - promise[boundCtxKey].nothrow = true + promise.ctx.nothrow = true return promise } export function quiet(promise) { - promise[boundCtxKey].verbose = false + promise.ctx.verbose = false return promise } diff --git a/src/index.d.ts b/src/index.d.ts index aaaeba7584..f0805bf047 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,11 +1,11 @@ // Copyright 2021 Google LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // https://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -13,20 +13,23 @@ // limitations under the License. declare module 'zx' { - import {ChildProcess, spawn} from 'node:child_process' - import {Readable, Writable} from 'node:stream' + import { ChildProcess, spawn } from 'node:child_process' + import { Readable, Writable } from 'node:stream' import * as _fs from 'fs-extra' import * as _globby from 'globby' import * as _os from 'node:os' import * as _path from 'node:path' - import {ChalkInstance} from 'chalk' + import { ChalkInstance } from 'chalk' import * as _yaml from 'yaml' import _fetch from 'node-fetch' - import {ParsedArgs} from 'minimist' + import { ParsedArgs } from 'minimist' import * as _which from 'which' export interface ZxTemplate { - (pieces: TemplateStringsArray, ...args: any[]): ProcessPromise + ( + pieces: TemplateStringsArray, + ...args: any[] + ): ProcessPromise } interface $ extends ZxTemplate { @@ -45,7 +48,9 @@ declare module 'zx' { readonly stderr: Readable readonly exitCode: Promise - pipe(dest: ProcessPromise | Writable): ProcessPromise + pipe( + dest: ProcessPromise | Writable + ): ProcessPromise kill(signal?: string | number): Promise } @@ -62,10 +67,14 @@ declare module 'zx' { export type QuestionOptions = { choices: string[] } type cd = (path: string) => void - type nothrow = (p: ProcessPromise) => ProcessPromise + type nothrow = ( + p: ProcessPromise + ) => ProcessPromise type question = (query?: string, options?: QuestionOptions) => Promise type sleep = (ms: number) => Promise - type quiet = (p: ProcessPromise) => ProcessPromise + type quiet = ( + p: ProcessPromise + ) => ProcessPromise export const $: $ export const argv: ParsedArgs @@ -122,7 +131,7 @@ declare module 'zx/globals' { } declare module 'zx/experimental' { - import {ZxTemplate} from 'zx' + import { ZxTemplate } from 'zx' interface Echo { (pieces: TemplateStringsArray, ...args: any[]): void @@ -132,9 +141,11 @@ declare module 'zx/experimental' { export const retry: (count?: number, delay?: number) => ZxTemplate - export const withTimeout: (delay?: number, signal?: string | number) => ZxTemplate + export const withTimeout: ( + delay?: number, + signal?: string | number + ) => ZxTemplate type StopSpinner = () => void export function startSpinner(title: string): StopSpinner } - diff --git a/src/index.mjs b/src/index.mjs index c56e9ec3be..f1f060e7d1 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -12,10 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {argv, cd, chalk, fetch, fs, glob, globby, path, sleep, which, YAML, os} from './goods.mjs' -import {nothrow, quiet} from './hooks.mjs' -import {question} from './question.mjs' -import {$, ProcessPromise, ProcessOutput} from './core.mjs' +import { + argv, + cd, + chalk, + fetch, + fs, + glob, + globby, + path, + sleep, + which, + YAML, + os, +} from './goods.mjs' +import { nothrow, quiet } from './hooks.mjs' +import { question } from './question.mjs' +import { $, ProcessPromise, ProcessOutput } from './core.mjs' export { $, diff --git a/src/print.mjs b/src/print.mjs index cd097e90ea..c777929982 100644 --- a/src/print.mjs +++ b/src/print.mjs @@ -12,16 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {getCtx} from './als.mjs' -import {chalk} from './goods.mjs' +import { getCtx } from './als.mjs' +import { chalk } from './goods.mjs' export function printCmd(cmd) { if (!getCtx()?.verbose) return if (/\n/.test(cmd)) { - console.log(cmd - .split('\n') - .map((line, i) => (i === 0 ? '$' : '>') + ' ' + colorize(line)) - .join('\n')) + console.log( + cmd + .split('\n') + .map((line, i) => (i === 0 ? '$' : '>') + ' ' + colorize(line)) + .join('\n') + ) } else { console.log('$', colorize(cmd)) } @@ -34,7 +36,7 @@ export function printStd(data, err) { } export function colorize(cmd) { - return cmd.replace(/^[\w_.-]+(\s|$)/, substr => { + return cmd.replace(/^[\w_.-]+(\s|$)/, (substr) => { return chalk.greenBright(substr) }) } diff --git a/src/question.mjs b/src/question.mjs index 985a90e80f..f7b1df9f87 100644 --- a/src/question.mjs +++ b/src/question.mjs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {createInterface} from 'node:readline' +import { createInterface } from 'node:readline' export async function question(query, options) { let completer = undefined @@ -30,8 +30,10 @@ export async function question(query, options) { completer, }) - return new Promise((resolve) => rl.question(query ?? '', (answer) => { - rl.close() - resolve(answer) - })) + return new Promise((resolve) => + rl.question(query ?? '', (answer) => { + rl.close() + resolve(answer) + }) + ) } diff --git a/src/util.mjs b/src/util.mjs index efbf173aec..c0dcfc27e3 100644 --- a/src/util.mjs +++ b/src/util.mjs @@ -11,7 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +import { Buffer } from 'node:buffer' +import { randomFillSync } from 'node:crypto' -export const randId = () => Math.random().toString(36).slice(2) +export const randomId = (function () { + const buffer = Buffer.alloc(10) + return function () { + return randomFillSync(buffer).toString('hex') + } +})() -export const isStr = (obj) => typeof obj === 'string' +export function isString(obj) { + return typeof obj === 'string' +} diff --git a/test/experimental.test.mjs b/test/experimental.test.mjs index 8262ceb6ef..2c684ddec4 100644 --- a/test/experimental.test.mjs +++ b/test/experimental.test.mjs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {echo, retry, startSpinner, withTimeout} from '../src/experimental.mjs' -import {assert, testFactory} from './test-utils.mjs' +import { echo, retry, startSpinner, withTimeout } from '../src/experimental.mjs' +import { assert, testFactory } from './test-utils.mjs' import chalk from 'chalk' const test = testFactory('experimental', import.meta) @@ -49,7 +49,11 @@ test('withTimeout works', async () => { test('echo works', async () => { echo(chalk.red('foo'), chalk.green('bar'), chalk.bold('baz')) echo`${chalk.red('foo')} ${chalk.green('bar')} ${chalk.bold('baz')}` - echo(await $`echo ${chalk.red('foo')}`, await $`echo ${chalk.green('bar')}`, await $`echo ${chalk.bold('baz')}`) + echo( + await $`echo ${chalk.red('foo')}`, + await $`echo ${chalk.green('bar')}`, + await $`echo ${chalk.bold('baz')}` + ) }) test('spinner works', async () => { diff --git a/test/fixtures/interactive.mjs b/test/fixtures/interactive.mjs index 138b2f62b4..a9484b3247 100755 --- a/test/fixtures/interactive.mjs +++ b/test/fixtures/interactive.mjs @@ -1,13 +1,13 @@ #!/usr/bin/env zx // Copyright 2021 Google LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // https://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/test/index.test.mjs b/test/index.test.mjs index 38a518088e..b6d3ee4bc7 100755 --- a/test/index.test.mjs +++ b/test/index.test.mjs @@ -1,24 +1,24 @@ // Copyright 2021 Google LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // https://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import {inspect} from 'node:util' +import { inspect } from 'node:util' import chalk from 'chalk' -import {Writable} from 'node:stream' -import {Socket} from 'node:net' +import { Writable } from 'node:stream' +import { Socket } from 'node:net' -import {assert, testFactory} from './test-utils.mjs' -import {ProcessPromise} from '../src/index.mjs' +import { assert, testFactory } from './test-utils.mjs' +import { ProcessPromise } from '../src/index.mjs' const test = testFactory('index', import.meta) @@ -100,19 +100,17 @@ test('Quiet mode is working', async () => { }) test('Pipes are working', async () => { - let {stdout} = await $`echo "hello"` + let { stdout } = await $`echo "hello"` .pipe($`awk '{print $1" world"}'`) .pipe($`tr '[a-z]' '[A-Z]'`) assert(stdout === 'HELLO WORLD\n') try { - await $`echo foo` - .pipe(fs.createWriteStream('/tmp/output.txt')) + await $`echo foo`.pipe(fs.createWriteStream('/tmp/output.txt')) assert((await fs.readFile('/tmp/output.txt')).toString() === 'foo\n') let r = $`cat` - fs.createReadStream('/tmp/output.txt') - .pipe(r.stdin) + fs.createReadStream('/tmp/output.txt').pipe(r.stdin) assert((await r).stdout === 'foo\n') } finally { await fs.rm('/tmp/output.txt') @@ -120,7 +118,7 @@ test('Pipes are working', async () => { }) test('question', async () => { - let p = question('foo or bar? ', {choices: ['foo', 'bar']}) + let p = question('foo or bar? ', { choices: ['foo', 'bar'] }) setImmediate(() => { process.stdin.emit('data', 'fo') @@ -137,7 +135,7 @@ test('ProcessPromise', async () => { write: function (chunk, encoding, next) { contents += chunk.toString() next() - } + }, }) let p = $`echo 'test'`.pipe(stream) await p @@ -151,15 +149,18 @@ test('ProcessPromise', async () => { } catch (p) { err = p } - assert.equal(err.message, 'The pipe() method does not take strings. Forgot $?') + assert.equal( + err.message, + 'The pipe() method does not take strings. Forgot $?' + ) }) test('ProcessPromise: inherits native Promise', async () => { const p1 = $`echo 1` - const p2 = p1.then(v => v) - const p3 = p2.then(v => v) - const p4 = p3.catch(v => v) - const p5 = p1.finally(v => v) + const p2 = p1.then((v) => v) + const p3 = p2.then((v) => v) + const p4 = p3.catch((v) => v) + const p5 = p1.finally((v) => v) assert.ok(p1 instanceof Promise) assert.ok(p1 instanceof ProcessPromise) @@ -186,12 +187,16 @@ test('ProcessOutput thrown as error', async () => { }) test('The pipe() throws if already resolved', async () => { - let out, p = $`echo "Hello"` + let out, + p = $`echo "Hello"` await p try { out = await p.pipe($`less`) } catch (err) { - assert.equal(err.message, `The pipe() method shouldn't be called after promise is already resolved!`) + assert.equal( + err.message, + `The pipe() method shouldn't be called after promise is already resolved!` + ) } if (out) { assert.fail('Expected failure!') @@ -199,12 +204,12 @@ test('The pipe() throws if already resolved', async () => { }) test('ProcessOutput::exitCode do not throw', async () => { - assert(await $`grep qwerty README.md`.exitCode !== 0) - assert(await $`[[ -f ${__filename} ]]`.exitCode === 0) + assert((await $`grep qwerty README.md`.exitCode) !== 0) + assert((await $`[[ -f ${__filename} ]]`.exitCode) === 0) }) test('The nothrow() do not throw', async () => { - let {exitCode} = await nothrow($`exit 42`) + let { exitCode } = await nothrow($`exit 42`) assert(exitCode === 42) }) @@ -222,14 +227,14 @@ test('globby available', async () => { assert(await globby('test/fixtures/*'), [ 'test/fixtures/interactive.mjs', 'test/fixtures/no-extension', - 'test/fixtures/no-extension.mjs' + 'test/fixtures/no-extension.mjs', ]) }) test('fetch', async () => { assert( await fetch('https://example.com'), - await fetch('https://example.com', {method: 'GET'}) + await fetch('https://example.com', { method: 'GET' }) ) }) @@ -240,16 +245,16 @@ test('Executes a script from $PATH', async () => { const envPathSeparator = isWindows ? ';' : ':' process.env.PATH += envPathSeparator + path.resolve('/tmp/') - const toPOSIXPath = (_path) => - _path.split(path.sep).join(path.posix.sep) + const toPOSIXPath = (_path) => _path.split(path.sep).join(path.posix.sep) const zxPath = path.resolve('./zx.mjs') const zxLocation = isWindows ? toPOSIXPath(zxPath) : zxPath const scriptCode = `#!/usr/bin/env ${zxLocation}\nconsole.log('The script from path runs.')` try { - await $`echo ${scriptCode}` - .pipe(fs.createWriteStream('/tmp/script-from-path', {mode: 0o744})) + await $`echo ${scriptCode}`.pipe( + fs.createWriteStream('/tmp/script-from-path', { mode: 0o744 }) + ) await $`script-from-path` } finally { process.env.PATH = oldPath @@ -269,13 +274,14 @@ test('The cd() works with relative paths', async () => { cd('..') let p3 = $`pwd` - let results = (await Promise.all([p1, p2, p3])) - .map(p => path.basename(p.stdout.trim())) + let results = (await Promise.all([p1, p2, p3])).map((p) => + path.basename(p.stdout.trim()) + ) assert.ok($.cwd.endsWith('/tmp/zx-cd-test')) assert.deepEqual(results, ['two', 'one', 'zx-cd-test']) } finally { - fs.rmSync('/tmp/zx-cd-test', {recursive: true}) + fs.rmSync('/tmp/zx-cd-test', { recursive: true }) cd(cwd) assert.equal($.cwd, cwd) } @@ -302,7 +308,7 @@ test('The signal is passed with kill() method', async () => { }) test('YAML works', async () => { - assert.deepEqual(YAML.parse(YAML.stringify({foo: 'bar'})), {foo: 'bar'}) + assert.deepEqual(YAML.parse(YAML.stringify({ foo: 'bar' })), { foo: 'bar' }) console.log(chalk.greenBright('YAML works')) }) diff --git a/test/test-utils.mjs b/test/test-utils.mjs index 614200eaa5..d0fb6e9ee4 100644 --- a/test/test-utils.mjs +++ b/test/test-utils.mjs @@ -13,11 +13,11 @@ // limitations under the License. import chalk from 'chalk' -import {fileURLToPath} from 'node:url' -import {relative} from 'node:path' -import {setTimeout as sleep} from 'node:timers/promises' +import { fileURLToPath } from 'node:url' +import { relative } from 'node:path' +import { setTimeout as sleep } from 'node:timers/promises' -export {strict as assert} from 'assert' +export { strict as assert } from 'assert' let queued = 0 let passed = 0 @@ -29,7 +29,7 @@ let focused = 0 const singleThread = (fn) => { let p = Promise.resolve() return async function (...args) { - return (p = p.catch(_ => _).then(() => fn.call(this, ...args))) + return (p = p.catch((_) => _).then(() => fn.call(this, ...args))) } } @@ -42,12 +42,17 @@ const log = (name, group, err, file = '') => { console.log(err) console.log(file) } - console.log('\n' + chalk[err ? 'bgRedBright' : 'bgGreenBright'].black(`${chalk.inverse(' ' + group + ' ')} ${name} `)) + console.log( + '\n' + + chalk[err ? 'bgRedBright' : 'bgGreenBright'].black( + `${chalk.inverse(' ' + group + ' ')} ${name} ` + ) + ) } export const test = async function (name, cb, ms, focus, skip) { const filter = RegExp(process.argv[3] || '.') - const {group, meta} = this + const { group, meta } = this const file = meta ? relative(process.cwd(), fileURLToPath(meta.url)) : '' if (filter.test(name) || filter.test(group) || filter.test(file)) { @@ -84,21 +89,25 @@ export const skip = async function (name, cb, ms) { return test.call(this, name, cb, ms, false, true) } -export const testFactory = (group, meta) => Object.assign( - test.bind({group, meta}), { +export const testFactory = (group, meta) => + Object.assign(test.bind({ group, meta }), { test, skip, only, group, - meta + meta, }) export const printTestDigest = () => { - console.log('\n' + - chalk.black.bgYellowBright(` zx version is ${require('../package.json').version} `) + '\n' + - chalk.greenBright(` 🍺 tests passed: ${passed} `) + - (skipped ? chalk.yellowBright(`\n 🚧 skipped: ${skipped} `) : '') + - (failed ? chalk.redBright(`\n ❌ failed: ${failed} `) : '') + console.log( + '\n' + + chalk.black.bgYellowBright( + ` zx version is ${require('../package.json').version} ` + ) + + '\n' + + chalk.greenBright(` 🍺 tests passed: ${passed} `) + + (skipped ? chalk.yellowBright(`\n 🚧 skipped: ${skipped} `) : '') + + (failed ? chalk.redBright(`\n ❌ failed: ${failed} `) : '') ) failed && process.exit(1) } diff --git a/test/zx.test.mjs b/test/zx.test.mjs index f56c4b4f8f..cf297fb31a 100644 --- a/test/zx.test.mjs +++ b/test/zx.test.mjs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {assert, testFactory} from './test-utils.mjs' +import { assert, testFactory } from './test-utils.mjs' const test = testFactory('zx', import.meta) @@ -71,7 +71,10 @@ test('Eval script from https ref', async () => { test('Scripts with no extension', async () => { await $`node zx.mjs test/fixtures/no-extension` - assert.match((await fs.readFile('test/fixtures/no-extension.mjs')).toString(), /Test file to verify no-extension didn't overwrite similarly name .mjs file./) + assert.match( + (await fs.readFile('test/fixtures/no-extension.mjs')).toString(), + /Test file to verify no-extension didn't overwrite similarly name .mjs file./ + ) }) test('The require() is working from stdin', async () => { diff --git a/zx.mjs b/zx.mjs index e41ba528e6..5f5cac98ef 100755 --- a/zx.mjs +++ b/zx.mjs @@ -21,7 +21,7 @@ import {basename, dirname, extname, join, resolve} from 'node:path' import url from 'node:url' import {$, argv, fetch, ProcessOutput, registerGlobals} from './src/index.mjs' -import {randId} from './src/util.mjs' +import {randomId} from './src/util.mjs' await async function main() { registerGlobals() @@ -81,7 +81,7 @@ async function scriptFromStdin() { if (script.length > 0) { let filepath = join( tmpdir(), - randId() + '.mjs' + randomId() + '.mjs' ) await fs.mkdtemp(filepath) await writeAndImport(script, filepath, join(process.cwd(), 'stdin.mjs')) @@ -116,7 +116,7 @@ async function importPath(filepath, origin = filepath) { if (ext === '') { let tmpFilename = fs.existsSync(`${filepath}.mjs`) ? - `${basename(filepath)}-${randId()}.mjs` : + `${basename(filepath)}-${randomId()}.mjs` : `${basename(filepath)}.mjs` return await writeAndImport(