Commit 499d1e4
Changed files (6)
test
src/core.ts
@@ -12,17 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { StdioNull, StdioPipe } from 'child_process'
-import { ChildProcess } from 'node:child_process'
+import { ChalkInstance } from 'chalk'
+import { RequestInit } from 'node-fetch'
+import assert from 'node:assert'
import { AsyncLocalStorage } from 'node:async_hooks'
+import { ChildProcess, spawn, StdioNull, StdioPipe } from 'node:child_process'
import { Readable, Writable } from 'node:stream'
import { inspect } from 'node:util'
-import { spawn } from 'node:child_process'
-import assert from 'node:assert'
-import { ChalkInstance } from 'chalk'
import { chalk, which } from './goods.js'
-import { printCmd } from './print.js'
-import { noop, quote, substitute, psTree, exitCodeInfo } from './util.js'
+import {
+ colorize,
+ exitCodeInfo,
+ noop,
+ psTree,
+ quote,
+ substitute,
+} from './util.js'
type Shell = (pieces: TemplateStringsArray, ...args: any[]) => ProcessPromise
@@ -34,6 +39,7 @@ type Options = {
prefix: string
quote: typeof quote
spawn: typeof spawn
+ log: typeof log
}
const storage = new AsyncLocalStorage<Options>()
@@ -47,6 +53,7 @@ function initStore(): Options {
prefix: '',
quote,
spawn,
+ log,
}
storage.enterWith(context)
if (process.env.ZX_VERBOSE) $.verbose = process.env.ZX_VERBOSE == 'true'
@@ -130,9 +137,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
_run() {
if (this.child) return // The _run() called from two places: then() and setImmediate().
this._prerun() // In case $1.pipe($2), the $2 returned, and on $2._run() invoke $1._run().
- if ($.verbose && !this._quiet) {
- printCmd(this._command)
- }
+ $.log('cmd', this._command, { source: this })
this.child = spawn($.prefix + this._command, {
cwd: this._cwd,
shell: typeof $.shell === 'string' ? $.shell : true,
@@ -170,12 +175,12 @@ export class ProcessPromise extends Promise<ProcessOutput> {
stderr = '',
combined = ''
let onStdout = (data: any) => {
- if ($.verbose && !this._quiet) process.stderr.write(data)
+ $.log('stdout', data, { source: this })
stdout += data
combined += data
}
let onStderr = (data: any) => {
- if ($.verbose && !this._quiet) process.stderr.write(data)
+ $.log('stderr', data, { source: this })
stderr += data
combined += data
}
@@ -273,6 +278,10 @@ export class ProcessPromise extends Promise<ProcessOutput> {
this._quiet = true
return this
}
+
+ isQuiet() {
+ return this._quiet
+ }
}
export class ProcessOutput extends Error {
@@ -338,16 +347,46 @@ export function within<R>(callback: () => R): R {
return storage.run({ ...getStore() }, callback)
}
-/**
- * @deprecated Use $.nothrow() instead.
- */
-export function nothrow(promise: ProcessPromise) {
- return promise.nothrow()
+export type LogKind = 'cmd' | 'stdout' | 'stderr' | 'cd' | 'fetch'
+export type LogExtra = {
+ source?: ProcessPromise
+ init?: RequestInit
}
-/**
- * @deprecated Use $.quiet() instead.
- */
-export function quiet(promise: ProcessPromise) {
- return promise.quiet()
+export function log(kind: LogKind, data: string, extra: LogExtra = {}) {
+ if (extra.source?.isQuiet()) return
+ if ($.verbose) {
+ switch (kind) {
+ case 'cmd':
+ process.stderr.write(formatCmd(data))
+ break
+ case 'stdout':
+ case 'stderr':
+ process.stderr.write(data)
+ break
+ case 'cd':
+ process.stderr.write('$ ' + colorize(`cd ${data}`))
+ break
+ case 'fetch':
+ process.stderr.write(
+ '$ ' + colorize(`fetch ${data} `) + inspect(extra.init)
+ )
+ break
+ default:
+ throw new Error(`Unknown log kind "${kind}".`)
+ }
+ }
+}
+
+export function formatCmd(cmd: string) {
+ if (/\n/.test(cmd)) {
+ return (
+ cmd
+ .split('\n')
+ .map((line, i) => `${i == 0 ? '$' : '>'} ${colorize(line)}`)
+ .join('\n') + '\n'
+ )
+ } else {
+ return `$ ${colorize(cmd)}\n`
+ }
}
src/goods.ts
@@ -14,9 +14,10 @@
import * as globbyModule from 'globby'
import minimist from 'minimist'
-import { setTimeout as sleep } from 'node:timers/promises'
import nodeFetch, { RequestInfo, RequestInit } from 'node-fetch'
-import { colorize, isString, stringify } from './util.js'
+import { createInterface } from 'node:readline'
+import { setTimeout as sleep } from 'node:timers/promises'
+import { isString, stringify } from './util.js'
export { default as chalk } from 'chalk'
export { default as fs } from 'fs-extra'
@@ -38,18 +39,12 @@ globbyModule)
export const glob = globby
export async function fetch(url: RequestInfo, init?: RequestInit) {
- if ($.verbose) {
- if (typeof init !== 'undefined') {
- console.log('$', colorize(`fetch ${url}`), init)
- } else {
- console.log('$', colorize(`fetch ${url}`))
- }
- }
+ $.log('fetch', url.toString(), { init })
return nodeFetch(url, init)
}
export function cd(dir: string) {
- if ($.verbose) console.log('$', colorize(`cd ${dir}`))
+ $.log('cd', dir)
$.cwd = path.resolve($.cwd, dir)
}
@@ -70,6 +65,33 @@ export function echo(pieces: TemplateStringsArray, ...args: any[]) {
console.log(msg)
}
+export async function question(
+ query?: string,
+ options?: { choices: string[] }
+): Promise<string> {
+ let completer = undefined
+ if (options && Array.isArray(options.choices)) {
+ completer = function completer(line: string) {
+ const completions = options.choices
+ const hits = completions.filter((c) => c.startsWith(line))
+ return [hits.length ? hits : completions, line]
+ }
+ }
+ const rl = createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ terminal: true,
+ completer,
+ })
+
+ return new Promise((resolve) =>
+ rl.question(query ?? '', (answer) => {
+ rl.close()
+ resolve(answer)
+ })
+ )
+}
+
/**
* Starts a simple CLI spinner.
* @param title Spinner's title.
src/index.ts
@@ -12,36 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {
- argv,
- cd,
- chalk,
- echo,
- fetch,
- fs,
- glob,
- globby,
- path,
- sleep,
- startSpinner,
- which,
- YAML,
- os,
-} from './goods.js'
-import { question } from './question.js'
-import {
+import { ProcessPromise } from './core.js'
+
+export {
$,
ProcessPromise,
ProcessOutput,
- nothrow,
- quiet,
within,
+ log,
+ formatCmd,
+ LogKind,
+ LogExtra,
} from './core.js'
export {
- $,
- ProcessPromise,
- ProcessOutput,
argv,
cd,
chalk,
@@ -50,14 +34,25 @@ export {
fs,
glob,
globby,
- nothrow,
os,
path,
question,
- quiet,
sleep,
startSpinner,
which,
- within,
YAML,
+} from './goods.js'
+
+/**
+ * @deprecated Use $.nothrow() instead.
+ */
+export function nothrow(promise: ProcessPromise) {
+ return promise.nothrow()
+}
+
+/**
+ * @deprecated Use $.quiet() instead.
+ */
+export function quiet(promise: ProcessPromise) {
+ return promise.quiet()
}
src/print.ts
@@ -1,28 +0,0 @@
-// Copyright 2022 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 { colorize } from './util.js'
-
-export function printCmd(cmd: string) {
- if (/\n/.test(cmd)) {
- process.stderr.write(
- cmd
- .split('\n')
- .map((line, i) => `${i == 0 ? '$' : '>'} ${colorize(line)}`)
- .join('\n') + '\n'
- )
- } else {
- process.stderr.write(`$ ${colorize(cmd)}\n`)
- }
-}
src/question.ts
@@ -11,32 +11,3 @@
// 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 { createInterface } from 'node:readline'
-
-export async function question(
- query?: string,
- options?: { choices: string[] }
-): Promise<string> {
- let completer = undefined
- if (options && Array.isArray(options.choices)) {
- completer = function completer(line: string) {
- const completions = options.choices
- const hits = completions.filter((c) => c.startsWith(line))
- return [hits.length ? hits : completions, line]
- }
- }
- const rl = createInterface({
- input: process.stdin,
- output: process.stdout,
- terminal: true,
- completer,
- })
-
- return new Promise((resolve) =>
- rl.question(query ?? '', (answer) => {
- rl.close()
- resolve(answer)
- })
- )
-}
test/index.test.js
@@ -84,7 +84,7 @@ test('can use array as an argument', async () => {
assert.is((await $`echo ${args}`).toString(), 'foo')
})
-test('quiet mode is working', async () => {
+test('quiet() mode is working', async () => {
let stdout = ''
let log = console.log
console.log = (...args) => {
@@ -93,6 +93,17 @@ test('quiet mode is working', async () => {
await $`echo 'test'`.quiet()
console.log = log
assert.is(stdout, '')
+ {
+ // Deprecated.
+ let stdout = ''
+ let log = console.log
+ console.log = (...args) => {
+ stdout += args.join(' ')
+ }
+ await quiet($`echo 'test'`)
+ console.log = log
+ assert.is(stdout, '')
+ }
})
test('pipes are working', async () => {
@@ -211,6 +222,11 @@ test('await $`cmd`.exitCode does not throw', async () => {
test('nothrow() do not throw', async () => {
let { exitCode } = await $`exit 42`.nothrow()
assert.is(exitCode, 42)
+ {
+ // Deprecated.
+ let { exitCode } = await nothrow($`exit 42`)
+ assert.is(exitCode, 42)
+ }
})
test('globby available', async () => {