Comparing version 7.2.2 to 7.2.3-dev.7e728f6
407
build/cli.js
#!/usr/bin/env node | ||
// 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 fs from 'fs-extra'; | ||
import minimist from 'minimist'; | ||
import { createRequire } from 'node:module'; | ||
import { basename, dirname, extname, join, resolve } from 'node:path'; | ||
import url from 'node:url'; | ||
import { updateArgv } from './goods.js'; | ||
import { $, chalk, fetch, ProcessOutput } from './index.js'; | ||
import { startRepl } from './repl.js'; | ||
import { randomId } from './util.js'; | ||
import { installDeps, parseDeps } from './deps.js'; | ||
// src/cli.ts | ||
import { createRequire } from "node:module"; | ||
import { basename, dirname, extname, join, resolve } from "node:path"; | ||
import url from "node:url"; | ||
import { | ||
$, | ||
ProcessOutput, | ||
updateArgv, | ||
fetch, | ||
chalk, | ||
minimist, | ||
fs | ||
} from "./index.js"; | ||
import { randomId } from "./util.js"; | ||
import { installDeps, parseDeps } from "./deps.js"; | ||
function printUsage() { | ||
// language=txt | ||
console.log(` | ||
${chalk.bold('zx ' + getVersion())} | ||
console.log(` | ||
${chalk.bold("zx " + getVersion())} | ||
A tool for writing better scripts | ||
${chalk.bold('Usage')} | ||
${chalk.bold("Usage")} | ||
zx [options] <script> | ||
${chalk.bold('Options')} | ||
${chalk.bold("Options")} | ||
--quiet don't echo commands | ||
@@ -46,203 +38,196 @@ --shell=<path> custom shell binary | ||
} | ||
const argv = minimist(process.argv.slice(2), { | ||
string: ['shell', 'prefix', 'eval'], | ||
boolean: ['version', 'help', 'quiet', 'install', 'repl', 'experimental'], | ||
alias: { e: 'eval', i: 'install', v: 'version', h: 'help' }, | ||
stopEarly: true, | ||
var argv = minimist(process.argv.slice(2), { | ||
string: ["shell", "prefix", "eval"], | ||
boolean: ["version", "help", "quiet", "install", "repl", "experimental"], | ||
alias: { e: "eval", i: "install", v: "version", h: "help" }, | ||
stopEarly: true | ||
}); | ||
await (async function main() { | ||
const globals = './globals.js'; | ||
await import(globals); | ||
if (argv.quiet) | ||
$.verbose = false; | ||
if (argv.shell) | ||
$.shell = argv.shell; | ||
if (argv.prefix) | ||
$.prefix = argv.prefix; | ||
if (argv.experimental) { | ||
Object.assign(global, await import('./experimental.js')); | ||
} | ||
if (argv.version) { | ||
console.log(getVersion()); | ||
return; | ||
} | ||
if (argv.help) { | ||
printUsage(); | ||
return; | ||
} | ||
if (argv.repl) { | ||
startRepl(); | ||
return; | ||
} | ||
if (argv.eval) { | ||
await runScript(argv.eval); | ||
return; | ||
} | ||
const firstArg = argv._[0]; | ||
updateArgv(argv._.slice(firstArg === undefined ? 0 : 1)); | ||
if (!firstArg || firstArg === '-') { | ||
const success = await scriptFromStdin(); | ||
if (!success) | ||
printUsage(); | ||
return; | ||
} | ||
if (/^https?:/.test(firstArg)) { | ||
await scriptFromHttp(firstArg); | ||
return; | ||
} | ||
const filepath = firstArg.startsWith('file:///') | ||
? url.fileURLToPath(firstArg) | ||
: resolve(firstArg); | ||
await importPath(filepath); | ||
})().catch((err) => { | ||
if (err instanceof ProcessOutput) { | ||
console.error('Error:', err.message); | ||
} | ||
else { | ||
console.error(err); | ||
} | ||
process.exitCode = 1; | ||
await async function main() { | ||
const globals = "./globals.js"; | ||
await import(globals); | ||
if (argv.quiet) | ||
$.verbose = false; | ||
if (argv.shell) | ||
$.shell = argv.shell; | ||
if (argv.prefix) | ||
$.prefix = argv.prefix; | ||
if (argv.experimental) { | ||
Object.assign(global, await import("./experimental.js")); | ||
} | ||
if (argv.version) { | ||
console.log(getVersion()); | ||
return; | ||
} | ||
if (argv.help) { | ||
printUsage(); | ||
return; | ||
} | ||
if (argv.repl) { | ||
await (await import("./repl.js")).startRepl(); | ||
return; | ||
} | ||
if (argv.eval) { | ||
await runScript(argv.eval); | ||
return; | ||
} | ||
const firstArg = argv._[0]; | ||
updateArgv(argv._.slice(firstArg === void 0 ? 0 : 1)); | ||
if (!firstArg || firstArg === "-") { | ||
const success = await scriptFromStdin(); | ||
if (!success) | ||
printUsage(); | ||
return; | ||
} | ||
if (/^https?:/.test(firstArg)) { | ||
await scriptFromHttp(firstArg); | ||
return; | ||
} | ||
const filepath = firstArg.startsWith("file:///") ? url.fileURLToPath(firstArg) : resolve(firstArg); | ||
await importPath(filepath); | ||
}().catch((err) => { | ||
if (err instanceof ProcessOutput) { | ||
console.error("Error:", err.message); | ||
} else { | ||
console.error(err); | ||
} | ||
process.exitCode = 1; | ||
}); | ||
async function runScript(script) { | ||
const filepath = join(process.cwd(), `zx-${randomId()}.mjs`); | ||
await writeAndImport(script, filepath); | ||
const filepath = join(process.cwd(), `zx-${randomId()}.mjs`); | ||
await writeAndImport(script, filepath); | ||
} | ||
async function scriptFromStdin() { | ||
let script = ''; | ||
if (!process.stdin.isTTY) { | ||
process.stdin.setEncoding('utf8'); | ||
for await (const chunk of process.stdin) { | ||
script += chunk; | ||
} | ||
if (script.length > 0) { | ||
await runScript(script); | ||
return true; | ||
} | ||
let script = ""; | ||
if (!process.stdin.isTTY) { | ||
process.stdin.setEncoding("utf8"); | ||
for await (const chunk of process.stdin) { | ||
script += chunk; | ||
} | ||
return false; | ||
if (script.length > 0) { | ||
await runScript(script); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
async function scriptFromHttp(remote) { | ||
const res = await fetch(remote); | ||
if (!res.ok) { | ||
console.error(`Error: Can't get ${remote}`); | ||
process.exit(1); | ||
} | ||
const script = await res.text(); | ||
const pathname = new URL(remote).pathname; | ||
const name = basename(pathname); | ||
const ext = extname(pathname) || '.mjs'; | ||
const filepath = join(process.cwd(), `${name}-${randomId()}${ext}`); | ||
await writeAndImport(script, filepath); | ||
const res = await fetch(remote); | ||
if (!res.ok) { | ||
console.error(`Error: Can't get ${remote}`); | ||
process.exit(1); | ||
} | ||
const script = await res.text(); | ||
const pathname = new URL(remote).pathname; | ||
const name = basename(pathname); | ||
const ext = extname(pathname) || ".mjs"; | ||
const filepath = join(process.cwd(), `${name}-${randomId()}${ext}`); | ||
await writeAndImport(script, filepath); | ||
} | ||
async function writeAndImport(script, filepath, origin = filepath) { | ||
await fs.writeFile(filepath, script.toString()); | ||
try { | ||
await importPath(filepath, origin); | ||
} | ||
finally { | ||
await fs.rm(filepath); | ||
} | ||
await fs.writeFile(filepath, script.toString()); | ||
try { | ||
await importPath(filepath, origin); | ||
} finally { | ||
await fs.rm(filepath); | ||
} | ||
} | ||
async function importPath(filepath, origin = filepath) { | ||
const ext = extname(filepath); | ||
if (ext === '') { | ||
const tmpFilename = fs.existsSync(`${filepath}.mjs`) | ||
? `${basename(filepath)}-${randomId()}.mjs` | ||
: `${basename(filepath)}.mjs`; | ||
return writeAndImport(await fs.readFile(filepath), join(dirname(filepath), tmpFilename), origin); | ||
} | ||
if (ext === '.md') { | ||
return writeAndImport(transformMarkdown(await fs.readFile(filepath)), join(dirname(filepath), basename(filepath) + '.mjs'), origin); | ||
} | ||
if (argv.install) { | ||
const deps = parseDeps(await fs.readFile(filepath)); | ||
await installDeps(deps, dirname(filepath)); | ||
} | ||
const __filename = resolve(origin); | ||
const __dirname = dirname(__filename); | ||
const require = createRequire(origin); | ||
Object.assign(global, { __filename, __dirname, require }); | ||
await import(url.pathToFileURL(filepath).toString()); | ||
const ext = extname(filepath); | ||
if (ext === "") { | ||
const tmpFilename = fs.existsSync(`${filepath}.mjs`) ? `${basename(filepath)}-${randomId()}.mjs` : `${basename(filepath)}.mjs`; | ||
return writeAndImport( | ||
await fs.readFile(filepath), | ||
join(dirname(filepath), tmpFilename), | ||
origin | ||
); | ||
} | ||
if (ext === ".md") { | ||
return writeAndImport( | ||
transformMarkdown(await fs.readFile(filepath)), | ||
join(dirname(filepath), basename(filepath) + ".mjs"), | ||
origin | ||
); | ||
} | ||
if (argv.install) { | ||
const deps = parseDeps(await fs.readFile(filepath)); | ||
await installDeps(deps, dirname(filepath)); | ||
} | ||
const __filename = resolve(origin); | ||
const __dirname = dirname(__filename); | ||
const require2 = createRequire(origin); | ||
Object.assign(global, { __filename, __dirname, require: require2 }); | ||
await import(url.pathToFileURL(filepath).toString()); | ||
} | ||
function transformMarkdown(buf) { | ||
const source = buf.toString(); | ||
const output = []; | ||
let state = 'root'; | ||
let codeBlockEnd = ''; | ||
let prevLineIsEmpty = true; | ||
const jsCodeBlock = /^(```+|~~~+)(js|javascript)$/; | ||
const shCodeBlock = /^(```+|~~~+)(sh|bash)$/; | ||
const otherCodeBlock = /^(```+|~~~+)(.*)$/; | ||
for (let line of source.split('\n')) { | ||
switch (state) { | ||
case 'root': | ||
if (/^( {4}|\t)/.test(line) && prevLineIsEmpty) { | ||
output.push(line); | ||
state = 'tab'; | ||
} | ||
else if (jsCodeBlock.test(line)) { | ||
output.push(''); | ||
state = 'js'; | ||
codeBlockEnd = line.match(jsCodeBlock)[1]; | ||
} | ||
else if (shCodeBlock.test(line)) { | ||
output.push('await $`'); | ||
state = 'bash'; | ||
codeBlockEnd = line.match(shCodeBlock)[1]; | ||
} | ||
else if (otherCodeBlock.test(line)) { | ||
output.push(''); | ||
state = 'other'; | ||
codeBlockEnd = line.match(otherCodeBlock)[1]; | ||
} | ||
else { | ||
prevLineIsEmpty = line === ''; | ||
output.push('// ' + line); | ||
} | ||
break; | ||
case 'tab': | ||
if (/^( +|\t)/.test(line)) { | ||
output.push(line); | ||
} | ||
else if (line === '') { | ||
output.push(''); | ||
} | ||
else { | ||
output.push('// ' + line); | ||
state = 'root'; | ||
} | ||
break; | ||
case 'js': | ||
if (line === codeBlockEnd) { | ||
output.push(''); | ||
state = 'root'; | ||
} | ||
else { | ||
output.push(line); | ||
} | ||
break; | ||
case 'bash': | ||
if (line === codeBlockEnd) { | ||
output.push('`'); | ||
state = 'root'; | ||
} | ||
else { | ||
output.push(line); | ||
} | ||
break; | ||
case 'other': | ||
if (line === codeBlockEnd) { | ||
output.push(''); | ||
state = 'root'; | ||
} | ||
else { | ||
output.push('// ' + line); | ||
} | ||
break; | ||
const source = buf.toString(); | ||
const output = []; | ||
let state = "root"; | ||
let codeBlockEnd = ""; | ||
let prevLineIsEmpty = true; | ||
const jsCodeBlock = /^(```+|~~~+)(js|javascript)$/; | ||
const shCodeBlock = /^(```+|~~~+)(sh|bash)$/; | ||
const otherCodeBlock = /^(```+|~~~+)(.*)$/; | ||
for (let line of source.split("\n")) { | ||
switch (state) { | ||
case "root": | ||
if (/^( {4}|\t)/.test(line) && prevLineIsEmpty) { | ||
output.push(line); | ||
state = "tab"; | ||
} else if (jsCodeBlock.test(line)) { | ||
output.push(""); | ||
state = "js"; | ||
codeBlockEnd = line.match(jsCodeBlock)[1]; | ||
} else if (shCodeBlock.test(line)) { | ||
output.push("await $`"); | ||
state = "bash"; | ||
codeBlockEnd = line.match(shCodeBlock)[1]; | ||
} else if (otherCodeBlock.test(line)) { | ||
output.push(""); | ||
state = "other"; | ||
codeBlockEnd = line.match(otherCodeBlock)[1]; | ||
} else { | ||
prevLineIsEmpty = line === ""; | ||
output.push("// " + line); | ||
} | ||
break; | ||
case "tab": | ||
if (/^( +|\t)/.test(line)) { | ||
output.push(line); | ||
} else if (line === "") { | ||
output.push(""); | ||
} else { | ||
output.push("// " + line); | ||
state = "root"; | ||
} | ||
break; | ||
case "js": | ||
if (line === codeBlockEnd) { | ||
output.push(""); | ||
state = "root"; | ||
} else { | ||
output.push(line); | ||
} | ||
break; | ||
case "bash": | ||
if (line === codeBlockEnd) { | ||
output.push("`"); | ||
state = "root"; | ||
} else { | ||
output.push(line); | ||
} | ||
break; | ||
case "other": | ||
if (line === codeBlockEnd) { | ||
output.push(""); | ||
state = "root"; | ||
} else { | ||
output.push("// " + line); | ||
} | ||
break; | ||
} | ||
return output.join('\n'); | ||
} | ||
return output.join("\n"); | ||
} | ||
function getVersion() { | ||
return createRequire(import.meta.url)('../package.json').version; | ||
return createRequire(import.meta.url)("../package.json").version; | ||
} |
@@ -1,14 +0,9 @@ | ||
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
import { ChildProcess, spawn, StdioNull, StdioPipe } from 'node:child_process'; | ||
import { Readable, Writable } from 'node:stream'; | ||
import { inspect } from 'node:util'; | ||
import { RequestInfo, RequestInit } from 'node-fetch'; | ||
import { RequestInfo, RequestInit } from './vendor.js'; | ||
import { Duration, noop, quote } from './util.js'; | ||
export type Shell = (pieces: TemplateStringsArray, ...args: any[]) => ProcessPromise; | ||
declare const processCwd: unique symbol; | ||
export type Options = { | ||
export interface Options { | ||
[processCwd]: string; | ||
@@ -23,3 +18,3 @@ cwd?: string; | ||
log: typeof log; | ||
}; | ||
} | ||
export declare const defaults: Options; | ||
@@ -78,3 +73,3 @@ export declare const $: Shell & Options; | ||
export declare function within<R>(callback: () => R): R; | ||
export declare function cd(dir: string): void; | ||
export declare function cd(dir: string | ProcessOutput): void; | ||
export type LogEntry = { | ||
@@ -81,0 +76,0 @@ kind: 'cmd'; |
@@ -1,361 +0,393 @@ | ||
// 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 assert from 'node:assert'; | ||
import { spawn } from 'node:child_process'; | ||
import { AsyncLocalStorage, createHook } from 'node:async_hooks'; | ||
import { inspect } from 'node:util'; | ||
import chalk from 'chalk'; | ||
import which from 'which'; | ||
import { errnoMessage, exitCodeInfo, formatCmd, noop, parseDuration, psTree, quote, quotePowerShell, } from './util.js'; | ||
const processCwd = Symbol('processCwd'); | ||
const storage = new AsyncLocalStorage(); | ||
const hook = createHook({ | ||
init: syncCwd, | ||
before: syncCwd, | ||
promiseResolve: syncCwd, | ||
after: syncCwd, | ||
destroy: syncCwd, | ||
// src/core.ts | ||
import assert from "node:assert"; | ||
import { spawn } from "node:child_process"; | ||
import { AsyncLocalStorage, createHook } from "node:async_hooks"; | ||
import { inspect } from "node:util"; | ||
import { | ||
chalk, | ||
which | ||
} from "./vendor.js"; | ||
import { | ||
errnoMessage, | ||
exitCodeInfo, | ||
formatCmd, | ||
noop, | ||
parseDuration, | ||
psTree, | ||
quote, | ||
quotePowerShell | ||
} from "./util.js"; | ||
var processCwd = Symbol("processCwd"); | ||
var storage = new AsyncLocalStorage(); | ||
var hook = createHook({ | ||
init: syncCwd, | ||
before: syncCwd, | ||
promiseResolve: syncCwd, | ||
after: syncCwd, | ||
destroy: syncCwd | ||
}); | ||
hook.enable(); | ||
export const defaults = { | ||
[processCwd]: process.cwd(), | ||
verbose: true, | ||
env: process.env, | ||
shell: true, | ||
prefix: '', | ||
quote: () => { | ||
throw new Error('No quote function is defined: https://ï.at/no-quote-func'); | ||
}, | ||
spawn, | ||
log, | ||
var defaults = { | ||
[processCwd]: process.cwd(), | ||
verbose: true, | ||
env: process.env, | ||
shell: true, | ||
prefix: "", | ||
quote: () => { | ||
throw new Error("No quote function is defined: https://\xEF.at/no-quote-func"); | ||
}, | ||
spawn, | ||
log | ||
}; | ||
try { | ||
defaults.shell = which.sync('bash'); | ||
defaults.prefix = 'set -euo pipefail;'; | ||
defaults.quote = quote; | ||
} | ||
catch (err) { | ||
if (process.platform == 'win32') { | ||
defaults.shell = which.sync('powershell.exe'); | ||
defaults.quote = quotePowerShell; | ||
defaults.shell = which.sync("bash"); | ||
defaults.prefix = "set -euo pipefail;"; | ||
defaults.quote = quote; | ||
} catch (err) { | ||
if (process.platform == "win32") { | ||
try { | ||
defaults.shell = which.sync("powershell.exe"); | ||
defaults.quote = quotePowerShell; | ||
} catch (err2) { | ||
} | ||
} | ||
} | ||
function getStore() { | ||
return storage.getStore() || defaults; | ||
return storage.getStore() || defaults; | ||
} | ||
export const $ = new Proxy(function (pieces, ...args) { | ||
var $ = new Proxy( | ||
function(pieces, ...args) { | ||
const from = new Error().stack.split(/^\s*at\s/m)[2].trim(); | ||
if (pieces.some((p) => p == undefined)) { | ||
throw new Error(`Malformed command at ${from}`); | ||
if (pieces.some((p) => p == void 0)) { | ||
throw new Error(`Malformed command at ${from}`); | ||
} | ||
let resolve, reject; | ||
const promise = new ProcessPromise((...args) => ([resolve, reject] = args)); | ||
const promise = new ProcessPromise((...args2) => [resolve, reject] = args2); | ||
let cmd = pieces[0], i = 0; | ||
while (i < args.length) { | ||
let s; | ||
if (Array.isArray(args[i])) { | ||
s = args[i].map((x) => $.quote(substitute(x))).join(' '); | ||
} | ||
else { | ||
s = $.quote(substitute(args[i])); | ||
} | ||
cmd += s + pieces[++i]; | ||
let s; | ||
if (Array.isArray(args[i])) { | ||
s = args[i].map((x) => $.quote(substitute(x))).join(" "); | ||
} else { | ||
s = $.quote(substitute(args[i])); | ||
} | ||
cmd += s + pieces[++i]; | ||
} | ||
promise._bind(cmd, from, resolve, reject, getStore()); | ||
// Postpone run to allow promise configuration. | ||
setImmediate(() => promise.isHalted || promise.run()); | ||
return promise; | ||
}, { | ||
}, | ||
{ | ||
set(_, key, value) { | ||
const target = key in Function.prototype ? _ : getStore(); | ||
Reflect.set(target, key, value); | ||
return true; | ||
const target = key in Function.prototype ? _ : getStore(); | ||
Reflect.set(target, key, value); | ||
return true; | ||
}, | ||
get(_, key) { | ||
const target = key in Function.prototype ? _ : getStore(); | ||
return Reflect.get(target, key); | ||
}, | ||
}); | ||
const target = key in Function.prototype ? _ : getStore(); | ||
return Reflect.get(target, key); | ||
} | ||
} | ||
); | ||
function substitute(arg) { | ||
if (arg?.stdout) { | ||
return arg.stdout.replace(/\n$/, ''); | ||
} | ||
return `${arg}`; | ||
if (arg?.stdout) { | ||
return arg.stdout.replace(/\n$/, ""); | ||
} | ||
return `${arg}`; | ||
} | ||
export class ProcessPromise extends Promise { | ||
constructor() { | ||
super(...arguments); | ||
this._command = ''; | ||
this._from = ''; | ||
this._resolve = noop; | ||
this._reject = noop; | ||
this._snapshot = getStore(); | ||
this._stdio = ['inherit', 'pipe', 'pipe']; | ||
this._nothrow = false; | ||
this._quiet = false; | ||
this._resolved = false; | ||
this._halted = false; | ||
this._piped = false; | ||
this._prerun = noop; | ||
this._postrun = noop; | ||
} | ||
_bind(cmd, from, resolve, reject, options) { | ||
this._command = cmd; | ||
this._from = from; | ||
this._resolve = resolve; | ||
this._reject = reject; | ||
this._snapshot = { ...options }; | ||
} | ||
run() { | ||
const $ = this._snapshot; | ||
if (this.child) | ||
return this; // The _run() can be called from a few places. | ||
this._prerun(); // In case $1.pipe($2), the $2 returned, and on $2._run() invoke $1._run(). | ||
$.log({ | ||
kind: 'cmd', | ||
cmd: this._command, | ||
verbose: $.verbose && !this._quiet, | ||
}); | ||
this.child = $.spawn($.prefix + this._command, { | ||
cwd: $.cwd ?? $[processCwd], | ||
shell: typeof $.shell === 'string' ? $.shell : true, | ||
stdio: this._stdio, | ||
windowsHide: true, | ||
env: $.env, | ||
}); | ||
this.child.on('close', (code, signal) => { | ||
let message = `exit code: ${code}`; | ||
if (code != 0 || signal != null) { | ||
message = `${stderr || '\n'} at ${this._from}`; | ||
message += `\n exit code: ${code}${exitCodeInfo(code) ? ' (' + exitCodeInfo(code) + ')' : ''}`; | ||
if (signal != null) { | ||
message += `\n signal: ${signal}`; | ||
} | ||
} | ||
let output = new ProcessOutput(code, signal, stdout, stderr, combined, message); | ||
if (code === 0 || this._nothrow) { | ||
this._resolve(output); | ||
} | ||
else { | ||
this._reject(output); | ||
} | ||
this._resolved = true; | ||
}); | ||
this.child.on('error', (err) => { | ||
const message = `${err.message}\n` + | ||
` errno: ${err.errno} (${errnoMessage(err.errno)})\n` + | ||
` code: ${err.code}\n` + | ||
` at ${this._from}`; | ||
this._reject(new ProcessOutput(null, null, stdout, stderr, combined, message)); | ||
this._resolved = true; | ||
}); | ||
let stdout = '', stderr = '', combined = ''; | ||
let onStdout = (data) => { | ||
$.log({ kind: 'stdout', data, verbose: $.verbose && !this._quiet }); | ||
stdout += data; | ||
combined += data; | ||
}; | ||
let onStderr = (data) => { | ||
$.log({ kind: 'stderr', data, verbose: $.verbose && !this._quiet }); | ||
stderr += data; | ||
combined += data; | ||
}; | ||
if (!this._piped) | ||
this.child.stdout?.on('data', onStdout); // If process is piped, don't collect or print output. | ||
this.child.stderr?.on('data', onStderr); // Stderr should be printed regardless of piping. | ||
this._postrun(); // In case $1.pipe($2), after both subprocesses are running, we can pipe $1.stdout to $2.stdin. | ||
if (this._timeout && this._timeoutSignal) { | ||
const t = setTimeout(() => this.kill(this._timeoutSignal), this._timeout); | ||
this.finally(() => clearTimeout(t)).catch(noop); | ||
var ProcessPromise = class _ProcessPromise extends Promise { | ||
constructor() { | ||
super(...arguments); | ||
this._command = ""; | ||
this._from = ""; | ||
this._resolve = noop; | ||
this._reject = noop; | ||
this._snapshot = getStore(); | ||
this._stdio = ["inherit", "pipe", "pipe"]; | ||
this._nothrow = false; | ||
this._quiet = false; | ||
this._resolved = false; | ||
this._halted = false; | ||
this._piped = false; | ||
this._prerun = noop; | ||
this._postrun = noop; | ||
} | ||
_bind(cmd, from, resolve, reject, options) { | ||
this._command = cmd; | ||
this._from = from; | ||
this._resolve = resolve; | ||
this._reject = reject; | ||
this._snapshot = { ...options }; | ||
} | ||
run() { | ||
const $2 = this._snapshot; | ||
if (this.child) | ||
return this; | ||
this._prerun(); | ||
$2.log({ | ||
kind: "cmd", | ||
cmd: this._command, | ||
verbose: $2.verbose && !this._quiet | ||
}); | ||
this.child = $2.spawn($2.prefix + this._command, { | ||
cwd: $2.cwd ?? $2[processCwd], | ||
shell: typeof $2.shell === "string" ? $2.shell : true, | ||
stdio: this._stdio, | ||
windowsHide: true, | ||
env: $2.env | ||
}); | ||
this.child.on("close", (code, signal) => { | ||
let message = `exit code: ${code}`; | ||
if (code != 0 || signal != null) { | ||
message = `${stderr || "\n"} at ${this._from}`; | ||
message += ` | ||
exit code: ${code}${exitCodeInfo(code) ? " (" + exitCodeInfo(code) + ")" : ""}`; | ||
if (signal != null) { | ||
message += ` | ||
signal: ${signal}`; | ||
} | ||
return this; | ||
} | ||
let output = new ProcessOutput( | ||
code, | ||
signal, | ||
stdout, | ||
stderr, | ||
combined, | ||
message | ||
); | ||
if (code === 0 || this._nothrow) { | ||
this._resolve(output); | ||
} else { | ||
this._reject(output); | ||
} | ||
this._resolved = true; | ||
}); | ||
this.child.on("error", (err) => { | ||
const message = `${err.message} | ||
errno: ${err.errno} (${errnoMessage(err.errno)}) | ||
code: ${err.code} | ||
at ${this._from}`; | ||
this._reject( | ||
new ProcessOutput(null, null, stdout, stderr, combined, message) | ||
); | ||
this._resolved = true; | ||
}); | ||
let stdout = "", stderr = "", combined = ""; | ||
let onStdout = (data) => { | ||
$2.log({ kind: "stdout", data, verbose: $2.verbose && !this._quiet }); | ||
stdout += data; | ||
combined += data; | ||
}; | ||
let onStderr = (data) => { | ||
$2.log({ kind: "stderr", data, verbose: $2.verbose && !this._quiet }); | ||
stderr += data; | ||
combined += data; | ||
}; | ||
if (!this._piped) | ||
this.child.stdout?.on("data", onStdout); | ||
this.child.stderr?.on("data", onStderr); | ||
this._postrun(); | ||
if (this._timeout && this._timeoutSignal) { | ||
const t = setTimeout(() => this.kill(this._timeoutSignal), this._timeout); | ||
this.finally(() => clearTimeout(t)).catch(noop); | ||
} | ||
get stdin() { | ||
this.stdio('pipe'); | ||
this.run(); | ||
assert(this.child); | ||
if (this.child.stdin == null) | ||
throw new Error('The stdin of subprocess is null.'); | ||
return this.child.stdin; | ||
return this; | ||
} | ||
get stdin() { | ||
this.stdio("pipe"); | ||
this.run(); | ||
assert(this.child); | ||
if (this.child.stdin == null) | ||
throw new Error("The stdin of subprocess is null."); | ||
return this.child.stdin; | ||
} | ||
get stdout() { | ||
this.run(); | ||
assert(this.child); | ||
if (this.child.stdout == null) | ||
throw new Error("The stdout of subprocess is null."); | ||
return this.child.stdout; | ||
} | ||
get stderr() { | ||
this.run(); | ||
assert(this.child); | ||
if (this.child.stderr == null) | ||
throw new Error("The stderr of subprocess is null."); | ||
return this.child.stderr; | ||
} | ||
get exitCode() { | ||
return this.then( | ||
(p) => p.exitCode, | ||
(p) => p.exitCode | ||
); | ||
} | ||
then(onfulfilled, onrejected) { | ||
if (this.isHalted && !this.child) { | ||
throw new Error("The process is halted!"); | ||
} | ||
get stdout() { | ||
this.run(); | ||
assert(this.child); | ||
if (this.child.stdout == null) | ||
throw new Error('The stdout of subprocess is null.'); | ||
return this.child.stdout; | ||
return super.then(onfulfilled, onrejected); | ||
} | ||
catch(onrejected) { | ||
return super.catch(onrejected); | ||
} | ||
pipe(dest) { | ||
if (typeof dest == "string") | ||
throw new Error("The pipe() method does not take strings. Forgot $?"); | ||
if (this._resolved) { | ||
if (dest instanceof _ProcessPromise) | ||
dest.stdin.end(); | ||
throw new Error( | ||
"The pipe() method shouldn't be called after promise is already resolved!" | ||
); | ||
} | ||
get stderr() { | ||
this.run(); | ||
assert(this.child); | ||
if (this.child.stderr == null) | ||
throw new Error('The stderr of subprocess is null.'); | ||
return this.child.stderr; | ||
this._piped = true; | ||
if (dest instanceof _ProcessPromise) { | ||
dest.stdio("pipe"); | ||
dest._prerun = this.run.bind(this); | ||
dest._postrun = () => { | ||
if (!dest.child) | ||
throw new Error( | ||
"Access to stdin of pipe destination without creation a subprocess." | ||
); | ||
this.stdout.pipe(dest.stdin); | ||
}; | ||
return dest; | ||
} else { | ||
this._postrun = () => this.stdout.pipe(dest); | ||
return this; | ||
} | ||
get exitCode() { | ||
return this.then((p) => p.exitCode, (p) => p.exitCode); | ||
} | ||
async kill(signal = "SIGTERM") { | ||
if (!this.child) | ||
throw new Error("Trying to kill a process without creating one."); | ||
if (!this.child.pid) | ||
throw new Error("The process pid is undefined."); | ||
let children = await psTree(this.child.pid); | ||
for (const p of children) { | ||
try { | ||
process.kill(+p.PID, signal); | ||
} catch (e) { | ||
} | ||
} | ||
then(onfulfilled, onrejected) { | ||
if (this.isHalted && !this.child) { | ||
throw new Error('The process is halted!'); | ||
} | ||
return super.then(onfulfilled, onrejected); | ||
try { | ||
process.kill(this.child.pid, signal); | ||
} catch (e) { | ||
} | ||
catch(onrejected) { | ||
return super.catch(onrejected); | ||
} | ||
pipe(dest) { | ||
if (typeof dest == 'string') | ||
throw new Error('The pipe() method does not take strings. Forgot $?'); | ||
if (this._resolved) { | ||
if (dest instanceof ProcessPromise) | ||
dest.stdin.end(); // In case of piped stdin, we may want to close stdin of dest as well. | ||
throw new Error("The pipe() method shouldn't be called after promise is already resolved!"); | ||
} | ||
this._piped = true; | ||
if (dest instanceof ProcessPromise) { | ||
dest.stdio('pipe'); | ||
dest._prerun = this.run.bind(this); | ||
dest._postrun = () => { | ||
if (!dest.child) | ||
throw new Error('Access to stdin of pipe destination without creation a subprocess.'); | ||
this.stdout.pipe(dest.stdin); | ||
}; | ||
return dest; | ||
} | ||
else { | ||
this._postrun = () => this.stdout.pipe(dest); | ||
return this; | ||
} | ||
} | ||
async kill(signal = 'SIGTERM') { | ||
if (!this.child) | ||
throw new Error('Trying to kill a process without creating one.'); | ||
if (!this.child.pid) | ||
throw new Error('The process pid is undefined.'); | ||
let children = await psTree(this.child.pid); | ||
for (const p of children) { | ||
try { | ||
process.kill(+p.PID, signal); | ||
} | ||
catch (e) { } | ||
} | ||
try { | ||
process.kill(this.child.pid, signal); | ||
} | ||
catch (e) { } | ||
} | ||
stdio(stdin, stdout = 'pipe', stderr = 'pipe') { | ||
this._stdio = [stdin, stdout, stderr]; | ||
return this; | ||
} | ||
nothrow() { | ||
this._nothrow = true; | ||
return this; | ||
} | ||
quiet() { | ||
this._quiet = true; | ||
return this; | ||
} | ||
timeout(d, signal = 'SIGTERM') { | ||
this._timeout = parseDuration(d); | ||
this._timeoutSignal = signal; | ||
return this; | ||
} | ||
halt() { | ||
this._halted = true; | ||
return this; | ||
} | ||
get isHalted() { | ||
return this._halted; | ||
} | ||
} | ||
export class ProcessOutput extends Error { | ||
constructor(code, signal, stdout, stderr, combined, message) { | ||
super(message); | ||
this._code = code; | ||
this._signal = signal; | ||
this._stdout = stdout; | ||
this._stderr = stderr; | ||
this._combined = combined; | ||
} | ||
toString() { | ||
return this._combined; | ||
} | ||
get stdout() { | ||
return this._stdout; | ||
} | ||
get stderr() { | ||
return this._stderr; | ||
} | ||
get exitCode() { | ||
return this._code; | ||
} | ||
get signal() { | ||
return this._signal; | ||
} | ||
[inspect.custom]() { | ||
let stringify = (s, c) => s.length === 0 ? "''" : c(inspect(s)); | ||
return `ProcessOutput { | ||
} | ||
stdio(stdin, stdout = "pipe", stderr = "pipe") { | ||
this._stdio = [stdin, stdout, stderr]; | ||
return this; | ||
} | ||
nothrow() { | ||
this._nothrow = true; | ||
return this; | ||
} | ||
quiet() { | ||
this._quiet = true; | ||
return this; | ||
} | ||
timeout(d, signal = "SIGTERM") { | ||
this._timeout = parseDuration(d); | ||
this._timeoutSignal = signal; | ||
return this; | ||
} | ||
halt() { | ||
this._halted = true; | ||
return this; | ||
} | ||
get isHalted() { | ||
return this._halted; | ||
} | ||
}; | ||
var ProcessOutput = class extends Error { | ||
constructor(code, signal, stdout, stderr, combined, message) { | ||
super(message); | ||
this._code = code; | ||
this._signal = signal; | ||
this._stdout = stdout; | ||
this._stderr = stderr; | ||
this._combined = combined; | ||
} | ||
toString() { | ||
return this._combined; | ||
} | ||
get stdout() { | ||
return this._stdout; | ||
} | ||
get stderr() { | ||
return this._stderr; | ||
} | ||
get exitCode() { | ||
return this._code; | ||
} | ||
get signal() { | ||
return this._signal; | ||
} | ||
[inspect.custom]() { | ||
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) + ")") : ""} | ||
}`; | ||
} | ||
} | ||
}; | ||
function within(callback) { | ||
return storage.run({ ...getStore() }, callback); | ||
} | ||
export function within(callback) { | ||
return storage.run({ ...getStore() }, callback); | ||
} | ||
function syncCwd() { | ||
if ($[processCwd] != process.cwd()) | ||
process.chdir($[processCwd]); | ||
if ($[processCwd] != process.cwd()) | ||
process.chdir($[processCwd]); | ||
} | ||
export function cd(dir) { | ||
$.log({ kind: 'cd', dir }); | ||
process.chdir(dir); | ||
$[processCwd] = process.cwd(); | ||
function cd(dir) { | ||
if (dir instanceof ProcessOutput) { | ||
dir = dir.toString().replace(/\n+$/, ""); | ||
} | ||
$.log({ kind: "cd", dir }); | ||
process.chdir(dir); | ||
$[processCwd] = process.cwd(); | ||
} | ||
export function log(entry) { | ||
switch (entry.kind) { | ||
case 'cmd': | ||
if (!entry.verbose) | ||
return; | ||
process.stderr.write(formatCmd(entry.cmd)); | ||
break; | ||
case 'stdout': | ||
case 'stderr': | ||
if (!entry.verbose) | ||
return; | ||
process.stderr.write(entry.data); | ||
break; | ||
case 'cd': | ||
if (!$.verbose) | ||
return; | ||
process.stderr.write('$ ' + chalk.greenBright('cd') + ` ${entry.dir}\n`); | ||
break; | ||
case 'fetch': | ||
if (!$.verbose) | ||
return; | ||
const init = entry.init ? ' ' + inspect(entry.init) : ''; | ||
process.stderr.write('$ ' + chalk.greenBright('fetch') + ` ${entry.url}${init}\n`); | ||
break; | ||
case 'retry': | ||
if (!$.verbose) | ||
return; | ||
process.stderr.write(entry.error + '\n'); | ||
} | ||
function log(entry) { | ||
switch (entry.kind) { | ||
case "cmd": | ||
if (!entry.verbose) | ||
return; | ||
process.stderr.write(formatCmd(entry.cmd)); | ||
break; | ||
case "stdout": | ||
case "stderr": | ||
if (!entry.verbose) | ||
return; | ||
process.stderr.write(entry.data); | ||
break; | ||
case "cd": | ||
if (!$.verbose) | ||
return; | ||
process.stderr.write("$ " + chalk.greenBright("cd") + ` ${entry.dir} | ||
`); | ||
break; | ||
case "fetch": | ||
if (!$.verbose) | ||
return; | ||
const init = entry.init ? " " + inspect(entry.init) : ""; | ||
process.stderr.write( | ||
"$ " + chalk.greenBright("fetch") + ` ${entry.url}${init} | ||
` | ||
); | ||
break; | ||
case "retry": | ||
if (!$.verbose) | ||
return; | ||
process.stderr.write(entry.error + "\n"); | ||
} | ||
} | ||
export { | ||
$, | ||
ProcessOutput, | ||
ProcessPromise, | ||
cd, | ||
defaults, | ||
log, | ||
within | ||
}; |
@@ -1,3 +0,2 @@ | ||
/// <reference types="node" resolution-mode="require"/> | ||
export declare function installDeps(dependencies: Record<string, string>, prefix?: string): Promise<void>; | ||
export declare function parseDeps(content: Buffer): Record<string, string>; |
@@ -1,119 +0,116 @@ | ||
// 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 { $ } from './core.js'; | ||
import { spinner } from './experimental.js'; | ||
export async function installDeps(dependencies, prefix) { | ||
const packages = Object.entries(dependencies).map(([name, version]) => `${name}@${version}`); | ||
const flags = prefix ? `--prefix=${prefix}` : ''; | ||
if (packages.length === 0) { | ||
return; | ||
} | ||
await spinner(`npm i ${packages.join(' ')}`, () => $ `npm install --no-save --no-audit --no-fund ${flags} ${packages}`.nothrow()); | ||
// src/deps.ts | ||
import { $ } from "./core.js"; | ||
import { spinner } from "./experimental.js"; | ||
async function installDeps(dependencies, prefix) { | ||
const packages = Object.entries(dependencies).map( | ||
([name, version]) => `${name}@${version}` | ||
); | ||
const flags = prefix ? `--prefix=${prefix}` : ""; | ||
if (packages.length === 0) { | ||
return; | ||
} | ||
await spinner( | ||
`npm i ${packages.join(" ")}`, | ||
() => $`npm install --no-save --no-audit --no-fund ${flags} ${packages}`.nothrow() | ||
); | ||
} | ||
const builtins = new Set([ | ||
'_http_agent', | ||
'_http_client', | ||
'_http_common', | ||
'_http_incoming', | ||
'_http_outgoing', | ||
'_http_server', | ||
'_stream_duplex', | ||
'_stream_passthrough', | ||
'_stream_readable', | ||
'_stream_transform', | ||
'_stream_wrap', | ||
'_stream_writable', | ||
'_tls_common', | ||
'_tls_wrap', | ||
'assert', | ||
'async_hooks', | ||
'buffer', | ||
'child_process', | ||
'cluster', | ||
'console', | ||
'constants', | ||
'crypto', | ||
'dgram', | ||
'dns', | ||
'domain', | ||
'events', | ||
'fs', | ||
'http', | ||
'http2', | ||
'https', | ||
'inspector', | ||
'module', | ||
'net', | ||
'os', | ||
'path', | ||
'perf_hooks', | ||
'process', | ||
'punycode', | ||
'querystring', | ||
'readline', | ||
'repl', | ||
'stream', | ||
'string_decoder', | ||
'sys', | ||
'timers', | ||
'tls', | ||
'trace_events', | ||
'tty', | ||
'url', | ||
'util', | ||
'v8', | ||
'vm', | ||
'wasi', | ||
'worker_threads', | ||
'zlib', | ||
var builtins = /* @__PURE__ */ new Set([ | ||
"_http_agent", | ||
"_http_client", | ||
"_http_common", | ||
"_http_incoming", | ||
"_http_outgoing", | ||
"_http_server", | ||
"_stream_duplex", | ||
"_stream_passthrough", | ||
"_stream_readable", | ||
"_stream_transform", | ||
"_stream_wrap", | ||
"_stream_writable", | ||
"_tls_common", | ||
"_tls_wrap", | ||
"assert", | ||
"async_hooks", | ||
"buffer", | ||
"child_process", | ||
"cluster", | ||
"console", | ||
"constants", | ||
"crypto", | ||
"dgram", | ||
"dns", | ||
"domain", | ||
"events", | ||
"fs", | ||
"http", | ||
"http2", | ||
"https", | ||
"inspector", | ||
"module", | ||
"net", | ||
"os", | ||
"path", | ||
"perf_hooks", | ||
"process", | ||
"punycode", | ||
"querystring", | ||
"readline", | ||
"repl", | ||
"stream", | ||
"string_decoder", | ||
"sys", | ||
"timers", | ||
"tls", | ||
"trace_events", | ||
"tty", | ||
"url", | ||
"util", | ||
"v8", | ||
"vm", | ||
"wasi", | ||
"worker_threads", | ||
"zlib" | ||
]); | ||
const importRe = [ | ||
/\bimport\s+['"](?<path>[^'"]+)['"]/, | ||
/\bimport\(['"](?<path>[^'"]+)['"]\)/, | ||
/\brequire\(['"](?<path>[^'"]+)['"]\)/, | ||
/\bfrom\s+['"](?<path>[^'"]+)['"]/, | ||
var importRe = [ | ||
/\bimport\s+['"](?<path>[^'"]+)['"]/, | ||
/\bimport\(['"](?<path>[^'"]+)['"]\)/, | ||
/\brequire\(['"](?<path>[^'"]+)['"]\)/, | ||
/\bfrom\s+['"](?<path>[^'"]+)['"]/ | ||
]; | ||
const nameRe = /^(?<name>(@[a-z0-9-]+\/)?[a-z0-9-.]+)\/?.*$/i; | ||
const versionRe = /(\/\/|\/\*)\s*@(?<version>[~^]?([\dvx*]+([-.][\dx*]+)*))/i; | ||
export function parseDeps(content) { | ||
const deps = {}; | ||
const lines = content.toString().split('\n'); | ||
for (let line of lines) { | ||
const tuple = parseImports(line); | ||
if (tuple) { | ||
deps[tuple.name] = tuple.version; | ||
} | ||
var nameRe = /^(?<name>(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*)\/?.*$/i; | ||
var versionRe = /(\/\/|\/\*)\s*@(?<version>[~^]?(v?[\dx*]+([-.][\d*a-z-]+)*))/i; | ||
function parseDeps(content) { | ||
const deps = {}; | ||
const lines = content.toString().split("\n"); | ||
for (let line of lines) { | ||
const tuple = parseImports(line); | ||
if (tuple) { | ||
deps[tuple.name] = tuple.version; | ||
} | ||
return deps; | ||
} | ||
return deps; | ||
} | ||
function parseImports(line) { | ||
for (let re of importRe) { | ||
const name = parsePackageName(re.exec(line)?.groups?.path); | ||
const version = parseVersion(line); | ||
if (name) { | ||
return { name, version }; | ||
} | ||
for (let re of importRe) { | ||
const name = parsePackageName(re.exec(line)?.groups?.path); | ||
const version = parseVersion(line); | ||
if (name) { | ||
return { name, version }; | ||
} | ||
} | ||
} | ||
function parsePackageName(path) { | ||
if (!path) | ||
return; | ||
const name = nameRe.exec(path)?.groups?.name; | ||
if (name && !builtins.has(name)) { | ||
return name; | ||
} | ||
if (!path) | ||
return; | ||
const name = nameRe.exec(path)?.groups?.name; | ||
if (name && !builtins.has(name)) { | ||
return name; | ||
} | ||
} | ||
function parseVersion(line) { | ||
return versionRe.exec(line)?.groups?.version || 'latest'; | ||
return versionRe.exec(line)?.groups?.version || "latest"; | ||
} | ||
export { | ||
installDeps, | ||
parseDeps | ||
}; |
@@ -1,15 +0,8 @@ | ||
// 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. | ||
// TODO(antonmedv): Remove this export in next v8 release. | ||
export { spinner, retry, expBackoff, echo } from './goods.js'; | ||
// src/experimental.ts | ||
import { spinner, retry, expBackoff, echo } from "./goods.js"; | ||
export { | ||
echo, | ||
expBackoff, | ||
retry, | ||
spinner | ||
}; |
@@ -1,4 +0,1 @@ | ||
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="which" /> | ||
import * as _ from './index.js'; | ||
@@ -20,2 +17,3 @@ declare global { | ||
var globby: typeof _.globby; | ||
var minimist: typeof _.minimist; | ||
var nothrow: typeof _.nothrow; | ||
@@ -22,0 +20,0 @@ var os: typeof _.os; |
@@ -1,15 +0,3 @@ | ||
// 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 * as _ from './index.js'; | ||
// src/globals.ts | ||
import * as _ from "./index.js"; | ||
Object.assign(global, _); |
@@ -1,18 +0,29 @@ | ||
import * as globbyModule from 'globby'; | ||
import minimist from 'minimist'; | ||
import { RequestInfo, RequestInit } from 'node-fetch'; | ||
import { Duration } from './util.js'; | ||
export { default as chalk } from 'chalk'; | ||
export { default as fs } from 'fs-extra'; | ||
export { default as which } from 'which'; | ||
export { default as YAML } from 'yaml'; | ||
import { type Duration } from './util.js'; | ||
import { minimist, RequestInfo, RequestInit } from './vendor.js'; | ||
export { default as path } from 'node:path'; | ||
export { default as os } from 'node:os'; | ||
export { ssh } from 'webpod'; | ||
export * as os from 'node:os'; | ||
export declare let argv: minimist.ParsedArgs; | ||
export declare function updateArgv(args: string[]): void; | ||
export declare const globby: ((patterns: string | readonly string[], options?: globbyModule.Options) => Promise<string[]>) & typeof globbyModule; | ||
export declare const glob: ((patterns: string | readonly string[], options?: globbyModule.Options) => Promise<string[]>) & typeof globbyModule; | ||
export declare const globby: typeof import("globby").globby & { | ||
globby: typeof import("globby").globby; | ||
globbySync: typeof import("globby").globbySync; | ||
globbyStream: typeof import("globby").globbyStream; | ||
generateGlobTasksSync: typeof import("globby").generateGlobTasksSync; | ||
generateGlobTasks: typeof import("globby").generateGlobTasks; | ||
isGitIgnoredSync: typeof import("globby").isGitIgnoredSync; | ||
isGitIgnored: typeof import("globby").isGitIgnored; | ||
isDynamicPattern: typeof import("globby").isDynamicPattern; | ||
}; | ||
export declare const glob: typeof import("globby").globby & { | ||
globby: typeof import("globby").globby; | ||
globbySync: typeof import("globby").globbySync; | ||
globbyStream: typeof import("globby").globbyStream; | ||
generateGlobTasksSync: typeof import("globby").generateGlobTasksSync; | ||
generateGlobTasks: typeof import("globby").generateGlobTasks; | ||
isGitIgnoredSync: typeof import("globby").isGitIgnoredSync; | ||
isGitIgnored: typeof import("globby").isGitIgnored; | ||
isDynamicPattern: typeof import("globby").isDynamicPattern; | ||
}; | ||
export declare function sleep(duration: Duration): Promise<unknown>; | ||
export declare function fetch(url: RequestInfo, init?: RequestInit): Promise<import("node-fetch").Response>; | ||
export declare function fetch(url: RequestInfo, init?: RequestInit): Promise<Response>; | ||
export declare function echo(...args: any[]): void; | ||
@@ -19,0 +30,0 @@ export declare function question(query?: string, options?: { |
@@ -1,171 +0,166 @@ | ||
// 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 assert from 'node:assert'; | ||
import * as globbyModule from 'globby'; | ||
import minimist from 'minimist'; | ||
import nodeFetch from 'node-fetch'; | ||
import { createInterface } from 'node:readline'; | ||
import { $, within, ProcessOutput } from './core.js'; | ||
import { isString, parseDuration } from './util.js'; | ||
import chalk from 'chalk'; | ||
export { default as chalk } from 'chalk'; | ||
export { default as fs } from 'fs-extra'; | ||
export { default as which } from 'which'; | ||
export { default as YAML } from 'yaml'; | ||
export { default as path } from 'node:path'; | ||
export { default as os } from 'node:os'; | ||
export { ssh } from 'webpod'; | ||
export let argv = minimist(process.argv.slice(2)); | ||
export function updateArgv(args) { | ||
argv = minimist(args); | ||
global.argv = argv; | ||
// src/goods.ts | ||
import assert from "node:assert"; | ||
import { createInterface } from "node:readline"; | ||
import { $, within, ProcessOutput } from "./core.js"; | ||
import { isString, parseDuration } from "./util.js"; | ||
import { | ||
chalk, | ||
minimist, | ||
globbyModule, | ||
nodeFetch | ||
} from "./vendor.js"; | ||
import { default as default2 } from "node:path"; | ||
import * as os from "node:os"; | ||
var argv = minimist(process.argv.slice(2)); | ||
function updateArgv(args) { | ||
argv = minimist(args); | ||
global.argv = argv; | ||
} | ||
export const globby = Object.assign(function globby(patterns, options) { | ||
var globby = Object.assign( | ||
function globby2(patterns, options) { | ||
return globbyModule.globby(patterns, options); | ||
}, globbyModule); | ||
export const glob = globby; | ||
export function sleep(duration) { | ||
return new Promise((resolve) => { | ||
setTimeout(resolve, parseDuration(duration)); | ||
}); | ||
}, | ||
globbyModule | ||
); | ||
var glob = globby; | ||
function sleep(duration) { | ||
return new Promise((resolve) => { | ||
setTimeout(resolve, parseDuration(duration)); | ||
}); | ||
} | ||
export async function fetch(url, init) { | ||
$.log({ kind: 'fetch', url, init }); | ||
return nodeFetch(url, init); | ||
async function fetch(url, init) { | ||
$.log({ kind: "fetch", url, init }); | ||
return nodeFetch(url, init); | ||
} | ||
export function echo(pieces, ...args) { | ||
let msg; | ||
const lastIdx = pieces.length - 1; | ||
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(' '); | ||
} | ||
console.log(msg); | ||
function echo(pieces, ...args) { | ||
let msg; | ||
const lastIdx = pieces.length - 1; | ||
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(" "); | ||
} | ||
console.log(msg); | ||
} | ||
function stringify(arg) { | ||
if (arg instanceof ProcessOutput) { | ||
return arg.toString().replace(/\n$/, ''); | ||
} | ||
return `${arg}`; | ||
if (arg instanceof ProcessOutput) { | ||
return arg.toString().replace(/\n$/, ""); | ||
} | ||
return `${arg}`; | ||
} | ||
export async function question(query, options) { | ||
let completer = undefined; | ||
if (options && Array.isArray(options.choices)) { | ||
/* c8 ignore next 5 */ | ||
completer = function completer(line) { | ||
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); | ||
})); | ||
async function question(query, options) { | ||
let completer = void 0; | ||
if (options && Array.isArray(options.choices)) { | ||
completer = function completer2(line) { | ||
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); | ||
}) | ||
); | ||
} | ||
export async function stdin() { | ||
let buf = ''; | ||
process.stdin.setEncoding('utf8'); | ||
for await (const chunk of process.stdin) { | ||
buf += chunk; | ||
} | ||
return buf; | ||
async function stdin() { | ||
let buf = ""; | ||
process.stdin.setEncoding("utf8"); | ||
for await (const chunk of process.stdin) { | ||
buf += chunk; | ||
} | ||
return buf; | ||
} | ||
export async function retry(count, a, b) { | ||
const total = count; | ||
let callback; | ||
let delayStatic = 0; | ||
let delayGen; | ||
if (typeof a == 'function') { | ||
callback = a; | ||
async function retry(count, a, b) { | ||
const total = count; | ||
let callback; | ||
let delayStatic = 0; | ||
let delayGen; | ||
if (typeof a == "function") { | ||
callback = a; | ||
} else { | ||
if (typeof a == "object") { | ||
delayGen = a; | ||
} else { | ||
delayStatic = parseDuration(a); | ||
} | ||
else { | ||
if (typeof a == 'object') { | ||
delayGen = a; | ||
} | ||
else { | ||
delayStatic = parseDuration(a); | ||
} | ||
assert(b); | ||
callback = b; | ||
assert(b); | ||
callback = b; | ||
} | ||
let lastErr; | ||
let attempt = 0; | ||
while (count-- > 0) { | ||
attempt++; | ||
try { | ||
return await callback(); | ||
} catch (err) { | ||
let delay = 0; | ||
if (delayStatic > 0) | ||
delay = delayStatic; | ||
if (delayGen) | ||
delay = delayGen.next().value; | ||
$.log({ | ||
kind: "retry", | ||
error: chalk.bgRed.white(" FAIL ") + ` Attempt: ${attempt}${total == Infinity ? "" : `/${total}`}` + (delay > 0 ? `; next in ${delay}ms` : "") | ||
}); | ||
lastErr = err; | ||
if (count == 0) | ||
break; | ||
if (delay) | ||
await sleep(delay); | ||
} | ||
let lastErr; | ||
let attempt = 0; | ||
while (count-- > 0) { | ||
attempt++; | ||
try { | ||
return await callback(); | ||
} | ||
catch (err) { | ||
let delay = 0; | ||
if (delayStatic > 0) | ||
delay = delayStatic; | ||
if (delayGen) | ||
delay = delayGen.next().value; | ||
$.log({ | ||
kind: 'retry', | ||
error: chalk.bgRed.white(' FAIL ') + | ||
` Attempt: ${attempt}${total == Infinity ? '' : `/${total}`}` + | ||
(delay > 0 ? `; next in ${delay}ms` : ''), | ||
}); | ||
lastErr = err; | ||
if (count == 0) | ||
break; | ||
if (delay) | ||
await sleep(delay); | ||
} | ||
} | ||
throw lastErr; | ||
} | ||
throw lastErr; | ||
} | ||
export function* expBackoff(max = '60s', rand = '100ms') { | ||
const maxMs = parseDuration(max); | ||
const randMs = parseDuration(rand); | ||
let n = 1; | ||
while (true) { | ||
const ms = Math.floor(Math.random() * randMs); | ||
yield Math.min(2 ** n++, maxMs) + ms; | ||
} | ||
function* expBackoff(max = "60s", rand = "100ms") { | ||
const maxMs = parseDuration(max); | ||
const randMs = parseDuration(rand); | ||
let n = 1; | ||
while (true) { | ||
const ms = Math.floor(Math.random() * randMs); | ||
yield Math.min(2 ** n++, maxMs) + ms; | ||
} | ||
} | ||
export async function spinner(title, callback) { | ||
if (typeof title == 'function') { | ||
callback = title; | ||
title = ''; | ||
async function spinner(title, callback) { | ||
if (typeof title == "function") { | ||
callback = title; | ||
title = ""; | ||
} | ||
let i = 0; | ||
const spin = () => process.stderr.write(` ${"\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F"[i++ % 10]} ${title}\r`); | ||
return within(async () => { | ||
$.verbose = false; | ||
const id = setInterval(spin, 100); | ||
let result; | ||
try { | ||
result = await callback(); | ||
} finally { | ||
clearInterval(id); | ||
process.stderr.write(" ".repeat(process.stdout.columns - 1) + "\r"); | ||
} | ||
let i = 0; | ||
const spin = () => process.stderr.write(` ${'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'[i++ % 10]} ${title}\r`); | ||
return within(async () => { | ||
$.verbose = false; | ||
const id = setInterval(spin, 100); | ||
let result; | ||
try { | ||
result = await callback(); | ||
} | ||
finally { | ||
clearInterval(id); | ||
process.stderr.write(' '.repeat(process.stdout.columns - 1) + '\r'); | ||
} | ||
return result; | ||
}); | ||
return result; | ||
}); | ||
} | ||
export { | ||
argv, | ||
echo, | ||
expBackoff, | ||
fetch, | ||
glob, | ||
globby, | ||
os, | ||
default2 as path, | ||
question, | ||
retry, | ||
sleep, | ||
spinner, | ||
stdin, | ||
updateArgv | ||
}; |
import { ProcessPromise } from './core.js'; | ||
export * from './core.js'; | ||
export * from './goods.js'; | ||
export { Duration, quote, quotePowerShell } from './util.js'; | ||
export { minimist, chalk, fs, which, YAML, ssh } from './vendor.js'; | ||
export { type Duration, quote, quotePowerShell } from './util.js'; | ||
/** | ||
@@ -6,0 +7,0 @@ * @deprecated Use $.nothrow() instead. |
@@ -1,28 +0,23 @@ | ||
// 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. | ||
export * from './core.js'; | ||
export * from './goods.js'; | ||
export { quote, quotePowerShell } from './util.js'; | ||
/** | ||
* @deprecated Use $.nothrow() instead. | ||
*/ | ||
export function nothrow(promise) { | ||
return promise.nothrow(); | ||
// src/index.ts | ||
export * from "./core.js"; | ||
export * from "./goods.js"; | ||
import { minimist, chalk, fs, which, YAML, ssh } from "./vendor.js"; | ||
import { quote, quotePowerShell } from "./util.js"; | ||
function nothrow(promise) { | ||
return promise.nothrow(); | ||
} | ||
/** | ||
* @deprecated Use $.quiet() instead. | ||
*/ | ||
export function quiet(promise) { | ||
return promise.quiet(); | ||
function quiet(promise) { | ||
return promise.quiet(); | ||
} | ||
export { | ||
YAML, | ||
chalk, | ||
fs, | ||
minimist, | ||
nothrow, | ||
quiet, | ||
quote, | ||
quotePowerShell, | ||
ssh, | ||
which | ||
}; |
@@ -1,1 +0,1 @@ | ||
export declare function startRepl(): void; | ||
export declare function startRepl(): Promise<void>; |
@@ -1,34 +0,25 @@ | ||
// 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 chalk from 'chalk'; | ||
import os from 'node:os'; | ||
import path from 'node:path'; | ||
import repl from 'node:repl'; | ||
import { inspect } from 'node:util'; | ||
import { ProcessOutput, defaults } from './core.js'; | ||
export function startRepl() { | ||
defaults.verbose = false; | ||
const r = repl.start({ | ||
prompt: chalk.greenBright.bold('❯ '), | ||
useGlobal: true, | ||
preview: false, | ||
writer(output) { | ||
if (output instanceof ProcessOutput) { | ||
return output.toString().replace(/\n$/, ''); | ||
} | ||
return inspect(output, { colors: true }); | ||
}, | ||
}); | ||
r.setupHistory(path.join(os.homedir(), '.zx_repl_history'), () => { }); | ||
// src/repl.ts | ||
import os from "node:os"; | ||
import path from "node:path"; | ||
import { inspect } from "node:util"; | ||
import { ProcessOutput, defaults } from "./core.js"; | ||
import { chalk } from "./vendor.js"; | ||
async function startRepl() { | ||
defaults.verbose = false; | ||
const r = (await import("node:repl")).start({ | ||
prompt: chalk.greenBright.bold("\u276F "), | ||
useGlobal: true, | ||
preview: false, | ||
writer(output) { | ||
if (output instanceof ProcessOutput) { | ||
return output.toString().replace(/\n$/, ""); | ||
} | ||
return inspect(output, { colors: true }); | ||
} | ||
}); | ||
r.setupHistory(path.join(os.homedir(), ".zx_repl_history"), () => { | ||
}); | ||
} | ||
export { | ||
startRepl | ||
}; |
@@ -1,2 +0,2 @@ | ||
import psTreeModule from 'ps-tree'; | ||
import { psTreeModule } from './vendor.js'; | ||
export declare const psTree: (arg1: number) => Promise<readonly psTreeModule.PS[]>; | ||
@@ -3,0 +3,0 @@ export declare function noop(): void; |
@@ -1,346 +0,334 @@ | ||
// 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 chalk from 'chalk'; | ||
import { promisify } from 'node:util'; | ||
import psTreeModule from 'ps-tree'; | ||
export const psTree = promisify(psTreeModule); | ||
export function noop() { } | ||
export function randomId() { | ||
return Math.random().toString(36).slice(2); | ||
// src/util.ts | ||
import { promisify } from "node:util"; | ||
import { chalk, psTreeModule } from "./vendor.js"; | ||
var psTree = promisify(psTreeModule); | ||
function noop() { | ||
} | ||
export function isString(obj) { | ||
return typeof obj === 'string'; | ||
function randomId() { | ||
return Math.random().toString(36).slice(2); | ||
} | ||
export function quote(arg) { | ||
if (/^[a-z0-9/_.\-@:=]+$/i.test(arg) || arg === '') { | ||
return arg; | ||
} | ||
return (`$'` + | ||
arg | ||
.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') + | ||
`'`); | ||
function isString(obj) { | ||
return typeof obj === "string"; | ||
} | ||
export function quotePowerShell(arg) { | ||
if (/^[a-z0-9/_.\-]+$/i.test(arg) || arg === '') { | ||
return arg; | ||
} | ||
return `'` + arg.replace(/'/g, "''") + `'`; | ||
function quote(arg) { | ||
if (/^[a-z0-9/_.\-@:=]+$/i.test(arg) || arg === "") { | ||
return arg; | ||
} | ||
return `$'` + arg.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") + `'`; | ||
} | ||
export function exitCodeInfo(exitCode) { | ||
return { | ||
2: 'Misuse of shell builtins', | ||
126: 'Invoked command cannot execute', | ||
127: 'Command not found', | ||
128: 'Invalid exit argument', | ||
129: 'Hangup', | ||
130: 'Interrupt', | ||
131: 'Quit and dump core', | ||
132: 'Illegal instruction', | ||
133: 'Trace/breakpoint trap', | ||
134: 'Process aborted', | ||
135: 'Bus error: "access to undefined portion of memory object"', | ||
136: 'Floating point exception: "erroneous arithmetic operation"', | ||
137: 'Kill (terminate immediately)', | ||
138: 'User-defined 1', | ||
139: 'Segmentation violation', | ||
140: 'User-defined 2', | ||
141: 'Write to pipe with no one reading', | ||
142: 'Signal raised by alarm', | ||
143: 'Termination (request to terminate)', | ||
145: 'Child process terminated, stopped (or continued*)', | ||
146: 'Continue if stopped', | ||
147: 'Stop executing temporarily', | ||
148: 'Terminal stop signal', | ||
149: 'Background process attempting to read from tty ("in")', | ||
150: 'Background process attempting to write to tty ("out")', | ||
151: 'Urgent data available on socket', | ||
152: 'CPU time limit exceeded', | ||
153: 'File size limit exceeded', | ||
154: 'Signal raised by timer counting virtual time: "virtual timer expired"', | ||
155: 'Profiling timer expired', | ||
157: 'Pollable event', | ||
159: 'Bad syscall', | ||
}[exitCode || -1]; | ||
function quotePowerShell(arg) { | ||
if (/^[a-z0-9/_.\-]+$/i.test(arg) || arg === "") { | ||
return arg; | ||
} | ||
return `'` + arg.replace(/'/g, "''") + `'`; | ||
} | ||
export function errnoMessage(errno) { | ||
if (errno === undefined) { | ||
return 'Unknown error'; | ||
} | ||
return ({ | ||
0: 'Success', | ||
1: 'Not super-user', | ||
2: 'No such file or directory', | ||
3: 'No such process', | ||
4: 'Interrupted system call', | ||
5: 'I/O error', | ||
6: 'No such device or address', | ||
7: 'Arg list too long', | ||
8: 'Exec format error', | ||
9: 'Bad file number', | ||
10: 'No children', | ||
11: 'No more processes', | ||
12: 'Not enough core', | ||
13: 'Permission denied', | ||
14: 'Bad address', | ||
15: 'Block device required', | ||
16: 'Mount device busy', | ||
17: 'File exists', | ||
18: 'Cross-device link', | ||
19: 'No such device', | ||
20: 'Not a directory', | ||
21: 'Is a directory', | ||
22: 'Invalid argument', | ||
23: 'Too many open files in system', | ||
24: 'Too many open files', | ||
25: 'Not a typewriter', | ||
26: 'Text file busy', | ||
27: 'File too large', | ||
28: 'No space left on device', | ||
29: 'Illegal seek', | ||
30: 'Read only file system', | ||
31: 'Too many links', | ||
32: 'Broken pipe', | ||
33: 'Math arg out of domain of func', | ||
34: 'Math result not representable', | ||
35: 'File locking deadlock error', | ||
36: 'File or path name too long', | ||
37: 'No record locks available', | ||
38: 'Function not implemented', | ||
39: 'Directory not empty', | ||
40: 'Too many symbolic links', | ||
42: 'No message of desired type', | ||
43: 'Identifier removed', | ||
44: 'Channel number out of range', | ||
45: 'Level 2 not synchronized', | ||
46: 'Level 3 halted', | ||
47: 'Level 3 reset', | ||
48: 'Link number out of range', | ||
49: 'Protocol driver not attached', | ||
50: 'No CSI structure available', | ||
51: 'Level 2 halted', | ||
52: 'Invalid exchange', | ||
53: 'Invalid request descriptor', | ||
54: 'Exchange full', | ||
55: 'No anode', | ||
56: 'Invalid request code', | ||
57: 'Invalid slot', | ||
59: 'Bad font file fmt', | ||
60: 'Device not a stream', | ||
61: 'No data (for no delay io)', | ||
62: 'Timer expired', | ||
63: 'Out of streams resources', | ||
64: 'Machine is not on the network', | ||
65: 'Package not installed', | ||
66: 'The object is remote', | ||
67: 'The link has been severed', | ||
68: 'Advertise error', | ||
69: 'Srmount error', | ||
70: 'Communication error on send', | ||
71: 'Protocol error', | ||
72: 'Multihop attempted', | ||
73: 'Cross mount point (not really error)', | ||
74: 'Trying to read unreadable message', | ||
75: 'Value too large for defined data type', | ||
76: 'Given log. name not unique', | ||
77: 'f.d. invalid for this operation', | ||
78: 'Remote address changed', | ||
79: 'Can access a needed shared lib', | ||
80: 'Accessing a corrupted shared lib', | ||
81: '.lib section in a.out corrupted', | ||
82: 'Attempting to link in too many libs', | ||
83: 'Attempting to exec a shared library', | ||
84: 'Illegal byte sequence', | ||
86: 'Streams pipe error', | ||
87: 'Too many users', | ||
88: 'Socket operation on non-socket', | ||
89: 'Destination address required', | ||
90: 'Message too long', | ||
91: 'Protocol wrong type for socket', | ||
92: 'Protocol not available', | ||
93: 'Unknown protocol', | ||
94: 'Socket type not supported', | ||
95: 'Not supported', | ||
96: 'Protocol family not supported', | ||
97: 'Address family not supported by protocol family', | ||
98: 'Address already in use', | ||
99: 'Address not available', | ||
100: 'Network interface is not configured', | ||
101: 'Network is unreachable', | ||
102: 'Connection reset by network', | ||
103: 'Connection aborted', | ||
104: 'Connection reset by peer', | ||
105: 'No buffer space available', | ||
106: 'Socket is already connected', | ||
107: 'Socket is not connected', | ||
108: "Can't send after socket shutdown", | ||
109: 'Too many references', | ||
110: 'Connection timed out', | ||
111: 'Connection refused', | ||
112: 'Host is down', | ||
113: 'Host is unreachable', | ||
114: 'Socket already connected', | ||
115: 'Connection already in progress', | ||
116: 'Stale file handle', | ||
122: 'Quota exceeded', | ||
123: 'No medium (in tape drive)', | ||
125: 'Operation canceled', | ||
130: 'Previous owner died', | ||
131: 'State not recoverable', | ||
}[-errno] || 'Unknown error'); | ||
function exitCodeInfo(exitCode) { | ||
return { | ||
2: "Misuse of shell builtins", | ||
126: "Invoked command cannot execute", | ||
127: "Command not found", | ||
128: "Invalid exit argument", | ||
129: "Hangup", | ||
130: "Interrupt", | ||
131: "Quit and dump core", | ||
132: "Illegal instruction", | ||
133: "Trace/breakpoint trap", | ||
134: "Process aborted", | ||
135: 'Bus error: "access to undefined portion of memory object"', | ||
136: 'Floating point exception: "erroneous arithmetic operation"', | ||
137: "Kill (terminate immediately)", | ||
138: "User-defined 1", | ||
139: "Segmentation violation", | ||
140: "User-defined 2", | ||
141: "Write to pipe with no one reading", | ||
142: "Signal raised by alarm", | ||
143: "Termination (request to terminate)", | ||
145: "Child process terminated, stopped (or continued*)", | ||
146: "Continue if stopped", | ||
147: "Stop executing temporarily", | ||
148: "Terminal stop signal", | ||
149: 'Background process attempting to read from tty ("in")', | ||
150: 'Background process attempting to write to tty ("out")', | ||
151: "Urgent data available on socket", | ||
152: "CPU time limit exceeded", | ||
153: "File size limit exceeded", | ||
154: 'Signal raised by timer counting virtual time: "virtual timer expired"', | ||
155: "Profiling timer expired", | ||
157: "Pollable event", | ||
159: "Bad syscall" | ||
}[exitCode || -1]; | ||
} | ||
export function parseDuration(d) { | ||
if (typeof d == 'number') { | ||
if (isNaN(d) || d < 0) | ||
throw new Error(`Invalid duration: "${d}".`); | ||
return d; | ||
} | ||
else if (/\d+s/.test(d)) { | ||
return +d.slice(0, -1) * 1000; | ||
} | ||
else if (/\d+ms/.test(d)) { | ||
return +d.slice(0, -2); | ||
} | ||
throw new Error(`Unknown duration: "${d}".`); | ||
function errnoMessage(errno) { | ||
if (errno === void 0) { | ||
return "Unknown error"; | ||
} | ||
return { | ||
0: "Success", | ||
1: "Not super-user", | ||
2: "No such file or directory", | ||
3: "No such process", | ||
4: "Interrupted system call", | ||
5: "I/O error", | ||
6: "No such device or address", | ||
7: "Arg list too long", | ||
8: "Exec format error", | ||
9: "Bad file number", | ||
10: "No children", | ||
11: "No more processes", | ||
12: "Not enough core", | ||
13: "Permission denied", | ||
14: "Bad address", | ||
15: "Block device required", | ||
16: "Mount device busy", | ||
17: "File exists", | ||
18: "Cross-device link", | ||
19: "No such device", | ||
20: "Not a directory", | ||
21: "Is a directory", | ||
22: "Invalid argument", | ||
23: "Too many open files in system", | ||
24: "Too many open files", | ||
25: "Not a typewriter", | ||
26: "Text file busy", | ||
27: "File too large", | ||
28: "No space left on device", | ||
29: "Illegal seek", | ||
30: "Read only file system", | ||
31: "Too many links", | ||
32: "Broken pipe", | ||
33: "Math arg out of domain of func", | ||
34: "Math result not representable", | ||
35: "File locking deadlock error", | ||
36: "File or path name too long", | ||
37: "No record locks available", | ||
38: "Function not implemented", | ||
39: "Directory not empty", | ||
40: "Too many symbolic links", | ||
42: "No message of desired type", | ||
43: "Identifier removed", | ||
44: "Channel number out of range", | ||
45: "Level 2 not synchronized", | ||
46: "Level 3 halted", | ||
47: "Level 3 reset", | ||
48: "Link number out of range", | ||
49: "Protocol driver not attached", | ||
50: "No CSI structure available", | ||
51: "Level 2 halted", | ||
52: "Invalid exchange", | ||
53: "Invalid request descriptor", | ||
54: "Exchange full", | ||
55: "No anode", | ||
56: "Invalid request code", | ||
57: "Invalid slot", | ||
59: "Bad font file fmt", | ||
60: "Device not a stream", | ||
61: "No data (for no delay io)", | ||
62: "Timer expired", | ||
63: "Out of streams resources", | ||
64: "Machine is not on the network", | ||
65: "Package not installed", | ||
66: "The object is remote", | ||
67: "The link has been severed", | ||
68: "Advertise error", | ||
69: "Srmount error", | ||
70: "Communication error on send", | ||
71: "Protocol error", | ||
72: "Multihop attempted", | ||
73: "Cross mount point (not really error)", | ||
74: "Trying to read unreadable message", | ||
75: "Value too large for defined data type", | ||
76: "Given log. name not unique", | ||
77: "f.d. invalid for this operation", | ||
78: "Remote address changed", | ||
79: "Can access a needed shared lib", | ||
80: "Accessing a corrupted shared lib", | ||
81: ".lib section in a.out corrupted", | ||
82: "Attempting to link in too many libs", | ||
83: "Attempting to exec a shared library", | ||
84: "Illegal byte sequence", | ||
86: "Streams pipe error", | ||
87: "Too many users", | ||
88: "Socket operation on non-socket", | ||
89: "Destination address required", | ||
90: "Message too long", | ||
91: "Protocol wrong type for socket", | ||
92: "Protocol not available", | ||
93: "Unknown protocol", | ||
94: "Socket type not supported", | ||
95: "Not supported", | ||
96: "Protocol family not supported", | ||
97: "Address family not supported by protocol family", | ||
98: "Address already in use", | ||
99: "Address not available", | ||
100: "Network interface is not configured", | ||
101: "Network is unreachable", | ||
102: "Connection reset by network", | ||
103: "Connection aborted", | ||
104: "Connection reset by peer", | ||
105: "No buffer space available", | ||
106: "Socket is already connected", | ||
107: "Socket is not connected", | ||
108: "Can't send after socket shutdown", | ||
109: "Too many references", | ||
110: "Connection timed out", | ||
111: "Connection refused", | ||
112: "Host is down", | ||
113: "Host is unreachable", | ||
114: "Socket already connected", | ||
115: "Connection already in progress", | ||
116: "Stale file handle", | ||
122: "Quota exceeded", | ||
123: "No medium (in tape drive)", | ||
125: "Operation canceled", | ||
130: "Previous owner died", | ||
131: "State not recoverable" | ||
}[-errno] || "Unknown error"; | ||
} | ||
export function formatCmd(cmd) { | ||
if (cmd == undefined) | ||
return chalk.grey('undefined'); | ||
const chars = [...cmd]; | ||
let out = '$ '; | ||
let buf = ''; | ||
let ch; | ||
let state = root; | ||
let wordCount = 0; | ||
while (state) { | ||
ch = chars.shift() || 'EOF'; | ||
if (ch == '\n') { | ||
out += style(state, buf) + '\n> '; | ||
buf = ''; | ||
continue; | ||
} | ||
const next = ch == 'EOF' ? undefined : state(); | ||
if (next != state) { | ||
out += style(state, buf); | ||
buf = ''; | ||
} | ||
state = next == root ? next() : next; | ||
buf += ch; | ||
function parseDuration(d) { | ||
if (typeof d == "number") { | ||
if (isNaN(d) || d < 0) | ||
throw new Error(`Invalid duration: "${d}".`); | ||
return d; | ||
} else if (/\d+s/.test(d)) { | ||
return +d.slice(0, -1) * 1e3; | ||
} else if (/\d+ms/.test(d)) { | ||
return +d.slice(0, -2); | ||
} | ||
throw new Error(`Unknown duration: "${d}".`); | ||
} | ||
function formatCmd(cmd) { | ||
if (cmd == void 0) | ||
return chalk.grey("undefined"); | ||
const chars = [...cmd]; | ||
let out = "$ "; | ||
let buf = ""; | ||
let ch; | ||
let state = root; | ||
let wordCount = 0; | ||
while (state) { | ||
ch = chars.shift() || "EOF"; | ||
if (ch == "\n") { | ||
out += style(state, buf) + "\n> "; | ||
buf = ""; | ||
continue; | ||
} | ||
function style(state, s) { | ||
if (s == '') | ||
return ''; | ||
if (reservedWords.includes(s)) { | ||
return chalk.cyanBright(s); | ||
} | ||
if (state == word && wordCount == 0) { | ||
wordCount++; | ||
return chalk.greenBright(s); | ||
} | ||
if (state == syntax) { | ||
wordCount = 0; | ||
return chalk.cyanBright(s); | ||
} | ||
if (state == dollar) | ||
return chalk.yellowBright(s); | ||
if (state?.name.startsWith('str')) | ||
return chalk.yellowBright(s); | ||
return s; | ||
const next = ch == "EOF" ? void 0 : state(); | ||
if (next != state) { | ||
out += style(state, buf); | ||
buf = ""; | ||
} | ||
function isSyntax(ch) { | ||
return '()[]{}<>;:+|&='.includes(ch); | ||
state = next == root ? next() : next; | ||
buf += ch; | ||
} | ||
function style(state2, s) { | ||
if (s == "") | ||
return ""; | ||
if (reservedWords.includes(s)) { | ||
return chalk.cyanBright(s); | ||
} | ||
function root() { | ||
if (/\s/.test(ch)) | ||
return space; | ||
if (isSyntax(ch)) | ||
return syntax; | ||
if (/[$]/.test(ch)) | ||
return dollar; | ||
if (/["]/.test(ch)) | ||
return strDouble; | ||
if (/[']/.test(ch)) | ||
return strSingle; | ||
return word; | ||
if (state2 == word && wordCount == 0) { | ||
wordCount++; | ||
return chalk.greenBright(s); | ||
} | ||
function space() { | ||
if (/\s/.test(ch)) | ||
return space; | ||
return root; | ||
if (state2 == syntax) { | ||
wordCount = 0; | ||
return chalk.cyanBright(s); | ||
} | ||
function word() { | ||
if (/[0-9a-z/_.]/i.test(ch)) | ||
return word; | ||
return root; | ||
} | ||
function syntax() { | ||
if (isSyntax(ch)) | ||
return syntax; | ||
return root; | ||
} | ||
function dollar() { | ||
if (/[']/.test(ch)) | ||
return str; | ||
return root; | ||
} | ||
function str() { | ||
if (/[']/.test(ch)) | ||
return strEnd; | ||
if (/[\\]/.test(ch)) | ||
return strBackslash; | ||
return str; | ||
} | ||
function strBackslash() { | ||
return strEscape; | ||
} | ||
function strEscape() { | ||
return str; | ||
} | ||
function strDouble() { | ||
if (/["]/.test(ch)) | ||
return strEnd; | ||
return strDouble; | ||
} | ||
function strSingle() { | ||
if (/[']/.test(ch)) | ||
return strEnd; | ||
return strSingle; | ||
} | ||
function strEnd() { | ||
return root; | ||
} | ||
return out + '\n'; | ||
if (state2 == dollar) | ||
return chalk.yellowBright(s); | ||
if (state2?.name.startsWith("str")) | ||
return chalk.yellowBright(s); | ||
return s; | ||
} | ||
function isSyntax(ch2) { | ||
return "()[]{}<>;:+|&=".includes(ch2); | ||
} | ||
function root() { | ||
if (/\s/.test(ch)) | ||
return space; | ||
if (isSyntax(ch)) | ||
return syntax; | ||
if (/[$]/.test(ch)) | ||
return dollar; | ||
if (/["]/.test(ch)) | ||
return strDouble; | ||
if (/[']/.test(ch)) | ||
return strSingle; | ||
return word; | ||
} | ||
function space() { | ||
if (/\s/.test(ch)) | ||
return space; | ||
return root; | ||
} | ||
function word() { | ||
if (/[0-9a-z/_.]/i.test(ch)) | ||
return word; | ||
return root; | ||
} | ||
function syntax() { | ||
if (isSyntax(ch)) | ||
return syntax; | ||
return root; | ||
} | ||
function dollar() { | ||
if (/[']/.test(ch)) | ||
return str; | ||
return root; | ||
} | ||
function str() { | ||
if (/[']/.test(ch)) | ||
return strEnd; | ||
if (/[\\]/.test(ch)) | ||
return strBackslash; | ||
return str; | ||
} | ||
function strBackslash() { | ||
return strEscape; | ||
} | ||
function strEscape() { | ||
return str; | ||
} | ||
function strDouble() { | ||
if (/["]/.test(ch)) | ||
return strEnd; | ||
return strDouble; | ||
} | ||
function strSingle() { | ||
if (/[']/.test(ch)) | ||
return strEnd; | ||
return strSingle; | ||
} | ||
function strEnd() { | ||
return root; | ||
} | ||
return out + "\n"; | ||
} | ||
const reservedWords = [ | ||
'if', | ||
'then', | ||
'else', | ||
'elif', | ||
'fi', | ||
'case', | ||
'esac', | ||
'for', | ||
'select', | ||
'while', | ||
'until', | ||
'do', | ||
'done', | ||
'in', | ||
var reservedWords = [ | ||
"if", | ||
"then", | ||
"else", | ||
"elif", | ||
"fi", | ||
"case", | ||
"esac", | ||
"for", | ||
"select", | ||
"while", | ||
"until", | ||
"do", | ||
"done", | ||
"in" | ||
]; | ||
export { | ||
errnoMessage, | ||
exitCodeInfo, | ||
formatCmd, | ||
isString, | ||
noop, | ||
parseDuration, | ||
psTree, | ||
quote, | ||
quotePowerShell, | ||
randomId | ||
}; |
{ | ||
"name": "zx", | ||
"version": "7.2.2", | ||
"description": "A tool for writing better scripts.", | ||
"version": "7.2.3-dev.7e728f6", | ||
"description": "A tool for writing better scripts", | ||
"type": "module", | ||
@@ -44,7 +44,10 @@ "main": "./build/index.js", | ||
"fmt:check": "prettier --check .", | ||
"build": "tsc --project tsconfig.prod.json", | ||
"build": "npm run build:js && npm run build:dts", | ||
"build:check": "tsc", | ||
"test": "npm run build && uvu test -i fixtures", | ||
"build:js": "node scripts/build-js.mjs --format=esm --entry=src/*.ts && npm run build:vendor", | ||
"build:vendor": "node scripts/build-js.mjs --format=esm --entry=src/vendor.ts --bundle=all --banner", | ||
"build:dts": "tsc --project tsconfig.prod.json && node scripts/build-dts.mjs", | ||
"test": "npm run build && node ./test/all.test.js", | ||
"test:types": "tsd", | ||
"coverage": "c8 --check-coverage npm test -- -i package", | ||
"coverage": "c8 -x build/vendor.js -x 'test/**' -x scripts --check-coverage npm test", | ||
"mutation": "stryker run", | ||
@@ -54,27 +57,32 @@ "circular": "madge --circular src/*", | ||
}, | ||
"dependencies": { | ||
"@types/fs-extra": "^11.0.1", | ||
"@types/minimist": "^1.2.2", | ||
"@types/node": "^18.16.3", | ||
"@types/ps-tree": "^1.1.2", | ||
"@types/which": "^3.0.0", | ||
"chalk": "^5.2.0", | ||
"fs-extra": "^11.1.1", | ||
"fx": "*", | ||
"globby": "^13.1.4", | ||
"minimist": "^1.2.8", | ||
"node-fetch": "3.3.1", | ||
"ps-tree": "^1.2.0", | ||
"webpod": "^0", | ||
"which": "^3.0.0", | ||
"yaml": "^2.2.2" | ||
"optionalDependencies": { | ||
"@types/fs-extra": "^11.0.4", | ||
"@types/node": ">=20.11.19" | ||
}, | ||
"devDependencies": { | ||
"@stryker-mutator/core": "^6.4.2", | ||
"@types/fs-extra": "^11.0.4", | ||
"@types/minimist": "^1.2.5", | ||
"@types/node": ">=20.11.19", | ||
"@types/ps-tree": "^1.1.6", | ||
"@types/which": "^3.0.3", | ||
"c8": "^7.13.0", | ||
"madge": "^6.0.0", | ||
"chalk": "^5.3.0", | ||
"dts-bundle-generator": "^9.3.1", | ||
"esbuild": "^0.20.1", | ||
"esbuild-node-externals": "^1.13.0", | ||
"esbuild-plugin-entry-chunks": "^0.1.8", | ||
"fs-extra": "^11.2.0", | ||
"fx": "*", | ||
"globby": "^14.0.1", | ||
"madge": "^6.1.0", | ||
"minimist": "^1.2.8", | ||
"node-fetch-native": "^1.6.2", | ||
"prettier": "^2.8.8", | ||
"ps-tree": "^1.2.0", | ||
"tsd": "^0.28.1", | ||
"typescript": "^5.0.4", | ||
"uvu": "^0.5.6" | ||
"webpod": "^0", | ||
"which": "^3.0.0", | ||
"yaml": "^2.3.4" | ||
}, | ||
@@ -81,0 +89,0 @@ "publishConfig": { |
567
README.md
@@ -1,2 +0,2 @@ | ||
# 🐚 zx | ||
<h1><img src="https://google.github.io/zx/img/logo.svg" alt="Zx logo" height="32" valign="middle"> zx</h1> | ||
@@ -31,570 +31,9 @@ ```js | ||
```bash | ||
npm i -g zx | ||
npm install zx | ||
``` | ||
**Requirement**: Node version >= 16.0.0 | ||
## Goods | ||
[$](#command-) · [cd()](#cd) · [fetch()](#fetch) · [question()](#question) · [sleep()](#sleep) · [echo()](#echo) · [stdin()](#stdin) · [within()](#within) · [retry()](#retry) · [spinner()](#spinner) · | ||
[chalk](#chalk-package) · [fs](#fs-package) · [os](#os-package) · [path](#path-package) · [glob](#globby-package) · [yaml](#yaml-package) · [minimist](#minimist-package) · [which](#which-package) · | ||
[__filename](#__filename--__dirname) · [__dirname](#__filename--__dirname) · [require()](#require) | ||
For running commands on remote hosts, | ||
see [webpod](https://github.com/webpod/webpod). | ||
## Documentation | ||
Write your scripts in a file with an `.mjs` extension in order to | ||
use `await` at the top level. If you prefer the `.js` extension, | ||
wrap your scripts in something like `void async function () {...}()`. | ||
Read documentation on [google.github.io/zx](https://google.github.io/zx/). | ||
Add the following shebang to the beginning of your `zx` scripts: | ||
```bash | ||
#!/usr/bin/env zx | ||
``` | ||
Now you will be able to run your script like so: | ||
```bash | ||
chmod +x ./script.mjs | ||
./script.mjs | ||
``` | ||
Or via the `zx` executable: | ||
```bash | ||
zx ./script.mjs | ||
``` | ||
All functions (`$`, `cd`, `fetch`, etc) are available straight away | ||
without any imports. | ||
Or import globals explicitly (for better autocomplete in VS Code). | ||
```js | ||
import 'zx/globals' | ||
``` | ||
### ``$`command` `` | ||
Executes a given command using the `spawn` func | ||
and returns [`ProcessPromise`](#processpromise). | ||
Everything passed through `${...}` will be automatically escaped and quoted. | ||
```js | ||
let name = 'foo & bar' | ||
await $`mkdir ${name}` | ||
``` | ||
**There is no need to add extra quotes.** Read more about it in | ||
[quotes](docs/quotes.md). | ||
You can pass an array of arguments if needed: | ||
```js | ||
let flags = [ | ||
'--oneline', | ||
'--decorate', | ||
'--color', | ||
] | ||
await $`git log ${flags}` | ||
``` | ||
If the executed program returns a non-zero exit code, | ||
[`ProcessOutput`](#processoutput) will be thrown. | ||
```js | ||
try { | ||
await $`exit 1` | ||
} catch (p) { | ||
console.log(`Exit code: ${p.exitCode}`) | ||
console.log(`Error: ${p.stderr}`) | ||
} | ||
``` | ||
### `ProcessPromise` | ||
```ts | ||
class ProcessPromise extends Promise<ProcessOutput> { | ||
stdin: Writable | ||
stdout: Readable | ||
stderr: Readable | ||
exitCode: Promise<number> | ||
pipe(dest): ProcessPromise | ||
kill(): Promise<void> | ||
nothrow(): this | ||
quiet(): this | ||
} | ||
``` | ||
Read more about the [ProcessPromise](docs/process-promise.md). | ||
### `ProcessOutput` | ||
```ts | ||
class ProcessOutput { | ||
readonly stdout: string | ||
readonly stderr: string | ||
readonly signal: string | ||
readonly exitCode: number | ||
toString(): string // Combined stdout & stderr. | ||
} | ||
``` | ||
The output of the process is captured as-is. Usually, programs print a new | ||
line `\n` at the end. | ||
If `ProcessOutput` is used as an argument to some other `$` process, | ||
**zx** will use stdout and trim the new line. | ||
```js | ||
let date = await $`date` | ||
await $`echo Current date is ${date}.` | ||
``` | ||
## Functions | ||
### `cd()` | ||
Changes the current working directory. | ||
```js | ||
cd('/tmp') | ||
await $`pwd` // => /tmp | ||
``` | ||
### `fetch()` | ||
A wrapper around the [node-fetch](https://www.npmjs.com/package/node-fetch) | ||
package. | ||
```js | ||
let resp = await fetch('https://medv.io') | ||
``` | ||
### `question()` | ||
A wrapper around the [readline](https://nodejs.org/api/readline.html) package. | ||
```js | ||
let bear = await question('What kind of bear is best? ') | ||
``` | ||
### `sleep()` | ||
A wrapper around the `setTimeout` function. | ||
```js | ||
await sleep(1000) | ||
``` | ||
### `echo()` | ||
A `console.log()` alternative which can take [ProcessOutput](#processoutput). | ||
```js | ||
let branch = await $`git branch --show-current` | ||
echo`Current branch is ${branch}.` | ||
// or | ||
echo('Current branch is', branch) | ||
``` | ||
### `stdin()` | ||
Returns the stdin as a string. | ||
```js | ||
let content = JSON.parse(await stdin()) | ||
``` | ||
### `within()` | ||
Creates a new async context. | ||
```js | ||
await $`pwd` // => /home/path | ||
within(async () => { | ||
cd('/tmp') | ||
setTimeout(async () => { | ||
await $`pwd` // => /tmp | ||
}, 1000) | ||
}) | ||
await $`pwd` // => /home/path | ||
``` | ||
```js | ||
let version = await within(async () => { | ||
$.prefix += 'export NVM_DIR=$HOME/.nvm; source $NVM_DIR/nvm.sh; ' | ||
await $`nvm use 16` | ||
return $`node -v` | ||
}) | ||
``` | ||
### `retry()` | ||
Retries a callback for a few times. Will return after the first | ||
successful attempt, or will throw after specifies attempts count. | ||
```js | ||
let p = await retry(10, () => $`curl https://medv.io`) | ||
// With a specified delay between attempts. | ||
let p = await retry(20, '1s', () => $`curl https://medv.io`) | ||
// With an exponential backoff. | ||
let p = await retry(30, expBackoff(), () => $`curl https://medv.io`) | ||
``` | ||
### `spinner()` | ||
Starts a simple CLI spinner. | ||
```js | ||
await spinner(() => $`long-running command`) | ||
// With a message. | ||
await spinner('working...', () => $`sleep 99`) | ||
``` | ||
## Packages | ||
The following packages are available without importing inside scripts. | ||
### `chalk` package | ||
The [chalk](https://www.npmjs.com/package/chalk) package. | ||
```js | ||
console.log(chalk.blue('Hello world!')) | ||
``` | ||
### `fs` package | ||
The [fs-extra](https://www.npmjs.com/package/fs-extra) package. | ||
```js | ||
let {version} = await fs.readJson('./package.json') | ||
``` | ||
### `os` package | ||
The [os](https://nodejs.org/api/os.html) package. | ||
```js | ||
await $`cd ${os.homedir()} && mkdir example` | ||
``` | ||
### `path` package | ||
The [path](https://nodejs.org/api/path.html) package. | ||
```js | ||
await $`mkdir ${path.join(basedir, 'output')}` | ||
``` | ||
### `globby` package | ||
The [globby](https://github.com/sindresorhus/globby) package. | ||
```js | ||
let packages = await glob(['package.json', 'packages/*/package.json']) | ||
``` | ||
### `yaml` package | ||
The [yaml](https://www.npmjs.com/package/yaml) package. | ||
```js | ||
console.log(YAML.parse('foo: bar').foo) | ||
``` | ||
### `minimist` package | ||
The [minimist](https://www.npmjs.com/package/minimist) package available | ||
as global const `argv`. | ||
```js | ||
if (argv.someFlag) { | ||
echo('yes') | ||
} | ||
``` | ||
### `which` package | ||
The [which](https://github.com/npm/node-which) package. | ||
```js | ||
let node = await which('node') | ||
``` | ||
## Configuration | ||
### `$.shell` | ||
Specifies what shell is used. Default is `which bash`. | ||
```js | ||
$.shell = '/usr/bin/bash' | ||
``` | ||
Or use a CLI argument: `--shell=/bin/bash` | ||
### `$.spawn` | ||
Specifies a `spawn` api. Defaults to `require('child_process').spawn`. | ||
### `$.prefix` | ||
Specifies the command that will be prefixed to all commands run. | ||
Default is `set -euo pipefail;`. | ||
Or use a CLI argument: `--prefix='set -e;'` | ||
### `$.quote` | ||
Specifies a function for escaping special characters during | ||
command substitution. | ||
### `$.verbose` | ||
Specifies verbosity. Default is `true`. | ||
In verbose mode, `zx` prints all executed commands alongside with their | ||
outputs. | ||
Or use the CLI argument `--quiet` to set `$.verbose = false`. | ||
### `$.env` | ||
Specifies an environment variables map. | ||
Defaults to `process.env`. | ||
### `$.cwd` | ||
Specifies a current working directory of all processes created with the `$`. | ||
The [cd()](#cd) func changes only `process.cwd()` and if no `$.cwd` specified, | ||
all `$` processes use `process.cwd()` by default (same as `spawn` behavior). | ||
### `$.log` | ||
Specifies a [logging function](src/core.ts). | ||
```ts | ||
import { LogEntry, log } from 'zx/core' | ||
$.log = (entry: LogEntry) => { | ||
switch (entry.kind) { | ||
case 'cmd': | ||
// for example, apply custom data masker for cmd printing | ||
process.stderr.write(masker(entry.cmd)) | ||
break | ||
default: | ||
log(entry) | ||
} | ||
} | ||
``` | ||
## Polyfills | ||
### `__filename` & `__dirname` | ||
In [ESM](https://nodejs.org/api/esm.html) modules, Node.js does not provide | ||
`__filename` and `__dirname` globals. As such globals are really handy in | ||
scripts, | ||
`zx` provides these for use in `.mjs` files (when using the `zx` executable). | ||
### `require()` | ||
In [ESM](https://nodejs.org/api/modules.html#modules_module_createrequire_filename) | ||
modules, the `require()` function is not defined. | ||
The `zx` provides `require()` function, so it can be used with imports in `.mjs` | ||
files (when using `zx` executable). | ||
```js | ||
let {version} = require('./package.json') | ||
``` | ||
## FAQ | ||
### Passing env variables | ||
```js | ||
process.env.FOO = 'bar' | ||
await $`echo $FOO` | ||
``` | ||
### Passing array of values | ||
When passing an array of values as an argument to `$`, items of the array will | ||
be escaped | ||
individually and concatenated via space. | ||
Example: | ||
```js | ||
let files = [...] | ||
await $`tar cz ${files}` | ||
``` | ||
### Importing into other scripts | ||
It is possible to make use of `$` and other functions via explicit imports: | ||
```js | ||
#!/usr/bin/env node | ||
import { $ } from 'zx' | ||
await $`date` | ||
``` | ||
### Scripts without extensions | ||
If script does not have a file extension (like `.git/hooks/pre-commit`), zx | ||
assumes that it is | ||
an [ESM](https://nodejs.org/api/modules.html#modules_module_createrequire_filename) | ||
module. | ||
### Markdown scripts | ||
The `zx` can execute [scripts written as markdown](docs/markdown.md): | ||
```bash | ||
zx docs/markdown.md | ||
``` | ||
### TypeScript scripts | ||
```ts | ||
import { $ } from 'zx' | ||
// Or | ||
import 'zx/globals' | ||
void async function () { | ||
await $`ls -la` | ||
}() | ||
``` | ||
Set [`"type": "module"`](https://nodejs.org/api/packages.html#packages_type) | ||
in **package.json** | ||
and [`"module": "ESNext"`](https://www.typescriptlang.org/tsconfig/#module) | ||
in **tsconfig.json**. | ||
### Executing remote scripts | ||
If the argument to the `zx` executable starts with `https://`, the file will be | ||
downloaded and executed. | ||
```bash | ||
zx https://medv.io/game-of-life.js | ||
``` | ||
### Executing scripts from stdin | ||
The `zx` supports executing scripts from stdin. | ||
```js | ||
zx << 'EOF' | ||
await $`pwd` | ||
EOF | ||
``` | ||
### Executing scripts via --eval | ||
Evaluate the following argument as a script. | ||
```bash | ||
cat package.json | zx --eval 'let v = JSON.parse(await stdin()).version; echo(v)' | ||
``` | ||
### Installing dependencies via --install | ||
```js | ||
// script.mjs: | ||
import sh from 'tinysh' | ||
sh.say('Hello, world!') | ||
``` | ||
Add `--install` flag to the `zx` command to install missing dependencies | ||
automatically. | ||
```bash | ||
zx --install script.mjs | ||
``` | ||
You can also specify needed version by adding comment with `@` after | ||
the import. | ||
```js | ||
import sh from 'tinysh' // @^1 | ||
``` | ||
### Executing commands on remote hosts | ||
The `zx` uses [webpod](https://github.com/webpod/webpod) to execute commands on | ||
remote hosts. | ||
```js | ||
import { ssh } from 'zx' | ||
await ssh('user@host')`echo Hello, world!` | ||
``` | ||
### Attaching a profile | ||
By default `child_process` does not include aliases and bash functions. | ||
But you are still able to do it by hand. Just attach necessary directives | ||
to the `$.prefix`. | ||
```js | ||
$.prefix += 'export NVM_DIR=$HOME/.nvm; source $NVM_DIR/nvm.sh; ' | ||
await $`nvm -v` | ||
``` | ||
### Using GitHub Actions | ||
The default GitHub Action runner comes with `npx` installed. | ||
```yaml | ||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Build | ||
env: | ||
FORCE_COLOR: 3 | ||
run: | | ||
npx zx <<'EOF' | ||
await $`...` | ||
EOF | ||
``` | ||
### Canary / Beta / RC builds | ||
Impatient early adopters can try the experimental zx versions. | ||
But keep in mind: these builds are ⚠️️__beta__ in every sense. | ||
```bash | ||
npm i zx@dev | ||
npx zx@dev --install --quiet <<< 'import _ from "lodash" /* 4.17.15 */; console.log(_.VERSION)' | ||
``` | ||
## License | ||
@@ -601,0 +40,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 8 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
846931
2
23
23593
0
5
25
2
43
17
2
+ Added@types/node@22.10.7(transitive)
+ Addedundici-types@6.20.0(transitive)
- Removed@types/fs-extra@^11.0.1
- Removed@types/minimist@^1.2.2
- Removed@types/node@^18.16.3
- Removed@types/ps-tree@^1.1.2
- Removed@types/which@^3.0.0
- Removedchalk@^5.2.0
- Removedfs-extra@^11.1.1
- Removedfx@*
- Removedglobby@^13.1.4
- Removedminimist@^1.2.8
- Removednode-fetch@3.3.1
- Removedps-tree@^1.2.0
- Removedwebpod@^0
- Removedwhich@^3.0.0
- Removedyaml@^2.2.2
- Removed@nodelib/fs.scandir@2.1.5(transitive)
- Removed@nodelib/fs.stat@2.0.5(transitive)
- Removed@nodelib/fs.walk@1.2.8(transitive)
- Removed@types/minimist@1.2.5(transitive)
- Removed@types/node@18.19.74(transitive)
- Removed@types/ps-tree@1.1.6(transitive)
- Removed@types/which@3.0.4(transitive)
- Removedbraces@3.0.3(transitive)
- Removedchalk@5.4.1(transitive)
- Removeddata-uri-to-buffer@4.0.1(transitive)
- Removeddir-glob@3.0.1(transitive)
- Removedfast-glob@3.3.3(transitive)
- Removedfastq@1.18.0(transitive)
- Removedfetch-blob@3.2.0(transitive)
- Removedfill-range@7.1.1(transitive)
- Removedformdata-polyfill@4.0.10(transitive)
- Removedfs-extra@11.3.0(transitive)
- Removedfx@35.0.0(transitive)
- Removedglob-parent@5.1.2(transitive)
- Removedglobby@13.2.2(transitive)
- Removedgraceful-fs@4.2.11(transitive)
- Removedignore@5.3.2(transitive)
- Removedis-extglob@2.1.1(transitive)
- Removedis-glob@4.0.3(transitive)
- Removedis-number@7.0.0(transitive)
- Removedisexe@2.0.0(transitive)
- Removedjsonfile@6.1.0(transitive)
- Removedmerge2@1.4.1(transitive)
- Removedmicromatch@4.0.8(transitive)
- Removedminimist@1.2.8(transitive)
- Removednode-domexception@1.0.0(transitive)
- Removednode-fetch@3.3.1(transitive)
- Removedpath-type@4.0.0(transitive)
- Removedpicomatch@2.3.1(transitive)
- Removedps-tree@1.2.0(transitive)
- Removedqueue-microtask@1.2.3(transitive)
- Removedreusify@1.0.4(transitive)
- Removedrun-parallel@1.2.0(transitive)
- Removedslash@4.0.0(transitive)
- Removedto-regex-range@5.0.1(transitive)
- Removedundici-types@5.26.5(transitive)
- Removeduniversalify@2.0.1(transitive)
- Removedweb-streams-polyfill@3.3.3(transitive)
- Removedwebpod@0.0.2(transitive)
- Removedwhich@3.0.1(transitive)
- Removedyaml@2.7.0(transitive)